[gnome-shell/T29763: 27/249] hotCorner: Add Endless' Hot Corner to the right side of the bottom panel



commit 65d1b3501000c8e07b925d50a97533814bb9e81e
Author: Mario Sanchez Prada <mario endlessm com>
Date:   Wed Feb 14 11:19:51 2018 +0000

    hotCorner: Add Endless' Hot Corner to the right side of the bottom panel
    
    Clicking the hot corner will toggle the windows picker, as well
    as moving the mouse against it if enabled.
    
     * 2020-03-13: Code style updates

 data/gnome-shell-theme.gresource.xml      |   6 ++
 data/org.gnome.shell.gschema.xml.in       |  44 +++++++++
 data/theme/corner-ripple-bl.png           | Bin 0 -> 2516 bytes
 data/theme/corner-ripple-br.png           | Bin 0 -> 2392 bytes
 data/theme/corner-ripple-tl.png           | Bin 0 -> 2493 bytes
 data/theme/corner-ripple-tr.png           | Bin 0 -> 2386 bytes
 data/theme/gnome-shell-sass/_endless.scss |  25 +++++
 data/theme/hot-corner-rtl-symbolic.svg    |  58 +++++++++++
 data/theme/hot-corner-symbolic.svg        |  13 +++
 js/js-resources.gresource.xml             |   1 +
 js/ui/hotCorner.js                        |  59 ++++++++++++
 js/ui/layout.js                           | 153 +++++++++++++++++++++++-------
 js/ui/panel.js                            |   1 +
 js/ui/ripples.js                          |  46 +++++++++
 js/ui/sessionMode.js                      |   2 +-
 po/POTFILES.in                            |   1 +
 16 files changed, 372 insertions(+), 37 deletions(-)
---
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index e4ab761cd3..b239a8e2a4 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -34,7 +34,13 @@
 
     <!-- Endless-specific resources beyond this point -->
 
+    <file>corner-ripple-bl.png</file>
+    <file>corner-ripple-br.png</file>
+    <file>corner-ripple-tl.png</file>
+    <file>corner-ripple-tr.png</file>
     <file>endless-button-symbolic.svg</file>
+    <file>hot-corner-symbolic.svg</file>
+    <file>hot-corner-rtl-symbolic.svg</file>
     <file>mini-icon-active-indicator.png</file>
   </gresource>
 </gresources>
diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in
index 28dfb260c8..0f044ad3f5 100644
--- a/data/org.gnome.shell.gschema.xml.in
+++ b/data/org.gnome.shell.gschema.xml.in
@@ -112,6 +112,50 @@
 
     <!-- Endless-specific keys beyond this point -->
 
+    <key name="hot-corner-enabled" type="b">
+      <default>false</default>
+      <summary>
+        Enables the hot corner for the window picker
+      </summary>
+      <description>
+        By default, the hot corner for the window picker is enabled.
+        Set false to disable the hot corner.
+      </description>
+    </key>
+    <key name="hot-corner-on-right" type="b">
+      <default>true</default>
+      <summary>
+        Set the left/right position of the hot corner for the window picker
+      </summary>
+      <description>
+        Default position of the hot corner is in a right corner.
+        Set false to move the hot corner to a left corner.
+        The position is not affected by the RTL vs. LTR text direction.
+      </description>
+    </key>
+    <key name="hot-corner-on-bottom" type="b">
+      <default>true</default>
+      <summary>
+        Set the top/bottom position of the hot corner for the window picker
+      </summary>
+      <description>
+        Default position of the hot corner is in a bottom corner.
+        Set false to move the hot corner to a top corner.
+      </description>
+    </key>
+    <key name="hot-corner-size" type="i">
+      <default>1</default>
+      <summary>
+        Set the size of the hot corner target
+      </summary>
+      <description>
+        Number of pixels in one dimension of the square target
+        for the hot corner.
+        Default is for a single pixel in the very corner.
+        For some virtual machines, this value may need to be set
+        larger than the default.
+      </description>
+    </key>
     <key name="no-default-maximize" type="b">
       <default>false</default>
       <summary>
diff --git a/data/theme/corner-ripple-bl.png b/data/theme/corner-ripple-bl.png
new file mode 100644
index 0000000000..7daba08949
Binary files /dev/null and b/data/theme/corner-ripple-bl.png differ
diff --git a/data/theme/corner-ripple-br.png b/data/theme/corner-ripple-br.png
new file mode 100644
index 0000000000..25b44e2d7a
Binary files /dev/null and b/data/theme/corner-ripple-br.png differ
diff --git a/data/theme/corner-ripple-tl.png b/data/theme/corner-ripple-tl.png
new file mode 100644
index 0000000000..326ecaa52e
Binary files /dev/null and b/data/theme/corner-ripple-tl.png differ
diff --git a/data/theme/corner-ripple-tr.png b/data/theme/corner-ripple-tr.png
new file mode 100644
index 0000000000..26cf965f63
Binary files /dev/null and b/data/theme/corner-ripple-tr.png differ
diff --git a/data/theme/gnome-shell-sass/_endless.scss b/data/theme/gnome-shell-sass/_endless.scss
index d2c0f48b78..fe0fd940ee 100644
--- a/data/theme/gnome-shell-sass/_endless.scss
+++ b/data/theme/gnome-shell-sass/_endless.scss
@@ -246,3 +246,28 @@
     &:hover { @extend %endless-button-hover; }
     &:clicked { @extend %endless-button-clicked; }
 }
+
+// Hot Corner
+
+#panel {
+    .hot-corner {
+        -minimum-hpadding: 0px;
+        -natural-hpadding: 0px;
+        color: #777;
+        height: 40px;
+
+        .single-icon-button {
+            icon-size: 18px;
+            padding: 0px;
+        }
+
+        &:hover { color: white; }
+    }
+}
+
+.ripple-box {
+    background-image: url("corner-ripple-tl.png");
+    &:tr { background-image: url("corner-ripple-tr.png"); }
+    &:bl { background-image: url("corner-ripple-bl.png"); }
+    &:br { background-image: url("corner-ripple-br.png"); }
+}
diff --git a/data/theme/hot-corner-rtl-symbolic.svg b/data/theme/hot-corner-rtl-symbolic.svg
new file mode 100644
index 0000000000..991d9e4441
--- /dev/null
+++ b/data/theme/hot-corner-rtl-symbolic.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="20px"
+   height="20px"
+   viewBox="0 0 20 20"
+   enable-background="new 0 0 20 20"
+   xml:space="preserve"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="hot-corner-indicator-symbolic.svg"><metadata
+     id="metadata3062"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; 
/><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+     id="defs3060" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1014"
+     id="namedview3058"
+     showgrid="false"
+     inkscape:zoom="40.95"
+     inkscape:cx="10"
+     inkscape:cy="10"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="Layer_1" /><g
+     id="Captions"
+     transform="matrix(-1,0,0,1,20,0)" /><g
+     id="g3050"
+     transform="matrix(-1,0,0,1,20,0)"><path
+       d="m 20,15 c -2.8,0 -5,2.2 -5,5 h 5 v -5 z"
+       id="path3052"
+       inkscape:connector-curvature="0" /><path
+       d="M 20,4 V 3 C 10.6,3 3,10.6 3,20 H 4 C 4,11.2 11.2,4 20,4 z"
+       id="path3054"
+       inkscape:connector-curvature="0" /><path
+       d="m 20,11 v -1 c -5.5,0 -10,4.5 -10,10 h 1 c 0,-5 4,-9 9,-9 z"
+       id="path3056"
+       inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
diff --git a/data/theme/hot-corner-symbolic.svg b/data/theme/hot-corner-symbolic.svg
new file mode 100644
index 0000000000..28b7795c0a
--- /dev/null
+++ b/data/theme/hot-corner-symbolic.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; x="0px" y="0px"
+        width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" 
xml:space="preserve">
+<g id="Captions">
+</g>
+<g>
+       <path opacity="1.0" d="M20,15c-2.8,0-5,2.2-5,5h5V15z"/>
+       <path opacity="1.0" d="M20,4V3C10.6,3,3,10.6,3,20h1C4,11.2,11.2,4,20,4z"/>
+       <path opacity="1.0" d="M20,11v-1c-5.5,0-10,4.5-10,10h1C11,15,15,11,20,11z"/>
+</g>
+</svg>
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 4efd4b95e8..e460b0a174 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -145,6 +145,7 @@
     <file>ui/appIconBar.js</file>
     <file>ui/endlessButton.js</file>
     <file>ui/forceAppExitDialog.js</file>
+    <file>ui/hotCorner.js</file>
     <file>ui/workspaceMonitor.js</file>
   </gresource>
 </gresources>
diff --git a/js/ui/hotCorner.js b/js/ui/hotCorner.js
new file mode 100644
index 0000000000..60164b7787
--- /dev/null
+++ b/js/ui/hotCorner.js
@@ -0,0 +1,59 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+/* exported HotCorner */
+
+const { Clutter, Gdk, Gio, GObject } = imports.gi;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+
+var HOT_CORNER_ENABLED_KEY = 'hot-corner-enabled';
+
+var HotCorner = GObject.registerClass(
+class HotCorner extends PanelMenu.SingleIconButton {
+    _init() {
+        super._init(_('Hot Corner'), Clutter.ActorAlign.END, Clutter.ActorAlign.END);
+        this.add_style_class_name('hot-corner');
+
+        let iconFile;
+        if (this.get_text_direction() === Clutter.TextDirection.RTL)
+            iconFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/hot-corner-rtl-symbolic.svg');
+        else
+            iconFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/hot-corner-symbolic.svg');
+        this.setIcon(new Gio.FileIcon({ file: iconFile }));
+
+        this._enableMenuItem = this.menu.addAction(_('Enable Hot Corner'), () => {
+            global.settings.set_boolean(HOT_CORNER_ENABLED_KEY, true);
+        });
+
+        this._disableMenuItem = this.menu.addAction(_('Disable Hot Corner'), () => {
+            global.settings.set_boolean(HOT_CORNER_ENABLED_KEY, false);
+        });
+
+        if (global.settings.get_boolean(HOT_CORNER_ENABLED_KEY))
+            this._enableMenuItem.actor.visible = false;
+        else
+            this._disableMenuItem.actor.visible = false;
+
+        this.menu.connect('menu-closed', () => {
+            let isEnabled = global.settings.get_boolean(HOT_CORNER_ENABLED_KEY);
+            this._enableMenuItem.actor.visible = !isEnabled;
+            this._disableMenuItem.actor.visible = isEnabled;
+        });
+    }
+
+    // overrides default implementation from PanelMenu.Button
+    vfunc_event(event) {
+        if (this.menu &&
+            (event.type() === Clutter.EventType.TOUCH_BEGIN ||
+             event.type() === Clutter.EventType.BUTTON_PRESS)) {
+            let button = event.get_button();
+            if (button === Gdk.BUTTON_PRIMARY && Main.overview.shouldToggleByCornerOrButton())
+                Main.overview.toggle();
+            else if (button === Gdk.BUTTON_SECONDARY)
+                this.menu.toggle();
+        }
+
+        return Clutter.EVENT_PROPAGATE;
+    }
+});
diff --git a/js/ui/layout.js b/js/ui/layout.js
index 519e2ac3ce..e88e82aa3e 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -17,6 +17,17 @@ var STARTUP_ANIMATION_TIME = 500;
 var KEYBOARD_ANIMATION_TIME = 150;
 var BACKGROUND_FADE_ANIMATION_TIME = 1000;
 
+// Gsettings keys to determine position of hot corner
+// and whether or not it is enabled.
+const HOT_CORNER_ENABLED_KEY = 'hot-corner-enabled';
+const HOT_CORNER_ON_RIGHT_KEY = 'hot-corner-on-right';
+const HOT_CORNER_ON_BOTTOM_KEY = 'hot-corner-on-bottom';
+
+// Gsettings key for the size of the hot corner target.
+// When using a VirtualBox VM, may need to set to at least 3 pixels,
+// since the VM may "steal" two rows from the guest OS display.
+const HOT_CORNER_SIZE_KEY = 'hot-corner-size';
+
 var HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
 var HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms
 
@@ -294,6 +305,14 @@ var LayoutManager = GObject.registerClass({
         let monitorManager = Meta.MonitorManager.get();
         monitorManager.connect('monitors-changed',
                                this._monitorsChanged.bind(this));
+
+        global.settings.connect('changed::' + HOT_CORNER_ENABLED_KEY,
+                                this._updateHotCorners.bind(this));
+        global.settings.connect('changed::' + HOT_CORNER_ON_RIGHT_KEY,
+                                this._updateHotCorners.bind(this));
+        global.settings.connect('changed::' + HOT_CORNER_ON_BOTTOM_KEY,
+                                this._updateHotCorners.bind(this));
+
         this._monitorsChanged();
 
         // NVIDIA drivers don't preserve FBO contents across
@@ -377,6 +396,12 @@ var LayoutManager = GObject.registerClass({
     }
 
     _updateHotCorners() {
+        let cornerRightSetting = global.settings.get_boolean(HOT_CORNER_ON_RIGHT_KEY);
+        let textDirection = Clutter.get_default_text_direction();
+        this._cornerOnRight = (cornerRightSetting && textDirection == Clutter.TextDirection.LTR) ||
+            (!cornerRightSetting && textDirection == Clutter.TextDirection.RTL);
+        this._cornerOnBottom  = global.settings.get_boolean(HOT_CORNER_ON_BOTTOM_KEY);
+
         // destroy old hot corners
         this.hotCorners.forEach(corner => {
             if (corner)
@@ -394,19 +419,28 @@ var LayoutManager = GObject.registerClass({
         // build new hot corners
         for (let i = 0; i < this.monitors.length; i++) {
             let monitor = this.monitors[i];
-            let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
+            let cornerX = monitor.x;
             let cornerY = monitor.y;
+            if (this._cornerOnRight)
+                cornerX += monitor.width;
+
+            if (this._cornerOnBottom)
+                cornerY += monitor.height;
 
-            let haveTopLeftCorner = true;
+            let haveHotCorner = true;
 
             if (i != this.primaryIndex) {
-                // Check if we have a top left (right for RTL) corner.
-                // I.e. if there is no monitor directly above or to the left(right)
-                let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
+                // Check if we have the specified corner.
+                // I.e. if there is no monitor directly above/below
+                // or beside (to the left/right)
+                let besideX = this._cornerOnRight ? cornerX + 1 : cornerX - 1;
                 let besideY = cornerY;
-                let aboveX = cornerX;
-                let aboveY = cornerY - 1;
+                let aboveOrBelowX = cornerX;
+                let aboveOrBelowY = this._cornerOnBottom ? cornerY + 1 : cornerY - 1;
 
+                // Iterate through all other monitors, and see if any of them
+                // contain the point that is one pixel diagonally further
+                // outside the corner point of interest.
                 for (let j = 0; j < this.monitors.length; j++) {
                     if (i == j)
                         continue;
@@ -415,20 +449,20 @@ var LayoutManager = GObject.registerClass({
                         besideX < otherMonitor.x + otherMonitor.width &&
                         besideY >= otherMonitor.y &&
                         besideY < otherMonitor.y + otherMonitor.height) {
-                        haveTopLeftCorner = false;
+                        haveHotCorner = false;
                         break;
                     }
-                    if (aboveX >= otherMonitor.x &&
-                        aboveX < otherMonitor.x + otherMonitor.width &&
-                        aboveY >= otherMonitor.y &&
-                        aboveY < otherMonitor.y + otherMonitor.height) {
-                        haveTopLeftCorner = false;
+                    if (aboveOrBelowX >= otherMonitor.x &&
+                        aboveOrBelowX < otherMonitor.x + otherMonitor.width &&
+                        aboveOrBelowY >= otherMonitor.y &&
+                        aboveOrBelowY < otherMonitor.y + otherMonitor.height) {
+                        haveHotCorner = false;
                         break;
                     }
                 }
             }
 
-            if (haveTopLeftCorner) {
+            if (haveHotCorner) {
                 let corner = new HotCorner(this, monitor, cornerX, cornerY);
                 corner.setBarrierSize(size);
                 this.hotCorners.push(corner);
@@ -1127,13 +1161,23 @@ class HotCorner extends Clutter.Actor {
         this._x = x;
         this._y = y;
 
+        let cornerRightSetting = global.settings.get_boolean(HOT_CORNER_ON_RIGHT_KEY);
+        let textDirection = Clutter.get_default_text_direction();
+        this._cornerOnRight = (cornerRightSetting && textDirection == Clutter.TextDirection.LTR) ||
+            (!cornerRightSetting && textDirection == Clutter.TextDirection.RTL);
+        this._cornerOnBottom = global.settings.get_boolean(HOT_CORNER_ON_BOTTOM_KEY);
+        this._targetSize = global.settings.get_int(HOT_CORNER_SIZE_KEY);
+
         this._setupFallbackCornerIfNeeded(layoutManager);
 
         this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
                                                     HOT_CORNER_PRESSURE_TIMEOUT,
                                                     Shell.ActionMode.NORMAL |
                                                     Shell.ActionMode.OVERVIEW);
-        this._pressureBarrier.connect('trigger', this._toggleOverview.bind(this));
+        this._pressureBarrier.connect('trigger', () => {
+            if (this.isEnabled())
+                this._toggleOverview();
+        });
 
         let px = 0.0;
         let py = 0.0;
@@ -1148,6 +1192,10 @@ class HotCorner extends Clutter.Actor {
         this.connect('destroy', this._onDestroy.bind(this));
     }
 
+    isEnabled() {
+        return global.settings.get_boolean(HOT_CORNER_ENABLED_KEY);
+    }
+
     setBarrierSize(size) {
         if (this._verticalBarrier) {
             this._pressureBarrier.removeBarrier(this._verticalBarrier);
@@ -1162,22 +1210,39 @@ class HotCorner extends Clutter.Actor {
         }
 
         if (size > 0) {
-            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
-                this._verticalBarrier = new Meta.Barrier({ display: global.display,
-                                                           x1: this._x, x2: this._x, y1: this._y, y2: 
this._y + size,
-                                                           directions: Meta.BarrierDirection.NEGATIVE_X });
-                this._horizontalBarrier = new Meta.Barrier({ display: global.display,
-                                                             x1: this._x - size, x2: this._x, y1: this._y, 
y2: this._y,
-                                                             directions: Meta.BarrierDirection.POSITIVE_Y });
+            // The corner itself is at (this._x, this._y).
+            // Extend the barrier by size towards the center of the screen.
+
+            let x1, x2, y1, y2;
+            let xDir, yDir;
+
+            if (this._cornerOnRight) {
+                x1 = this._x - size;
+                x2 = this._x;
+                xDir = Meta.BarrierDirection.NEGATIVE_X;
+            } else {
+                x1 = this._x;
+                x2 = this._x + size;
+                xDir = Meta.BarrierDirection.POSITIVE_X;
+            }
+
+            if (this._cornerOnBottom) {
+                y1 = this._y - size;
+                y2 = this._y;
+                yDir = Meta.BarrierDirection.NEGATIVE_Y;
             } else {
-                this._verticalBarrier = new Meta.Barrier({ display: global.display,
-                                                           x1: this._x, x2: this._x, y1: this._y, y2: 
this._y + size,
-                                                           directions: Meta.BarrierDirection.POSITIVE_X });
-                this._horizontalBarrier = new Meta.Barrier({ display: global.display,
-                                                             x1: this._x, x2: this._x + size, y1: this._y, 
y2: this._y,
-                                                             directions: Meta.BarrierDirection.POSITIVE_Y });
+                y1 = this._y;
+                y2 = this._y + size;
+                yDir = Meta.BarrierDirection.POSITIVE_Y;
             }
 
+            this._verticalBarrier = new Meta.Barrier({ display: global.display,
+                                                       x1: this._x, x2: this._x, y1: y1, y2: y2,
+                                                       directions: xDir });
+            this._horizontalBarrier = new Meta.Barrier({ display: global.display,
+                                                         x1: x1, x2: x2, y1: this._y, y2: this._y,
+                                                         directions: yDir });
+
             this._pressureBarrier.addBarrier(this._verticalBarrier);
             this._pressureBarrier.addBarrier(this._horizontalBarrier);
         }
@@ -1195,8 +1260,8 @@ class HotCorner extends Clutter.Actor {
             });
 
             this._corner = new Clutter.Actor({ name: 'hot-corner',
-                                               width: 1,
-                                               height: 1,
+                                               width: this._targetSize,
+                                               height: this._targetSize,
                                                opacity: 0,
                                                reactive: true });
             this._corner._delegate = this;
@@ -1204,11 +1269,26 @@ class HotCorner extends Clutter.Actor {
             this.add_child(this._corner);
             layoutManager.addChrome(this);
 
-            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
-                this._corner.set_position(this.width - this._corner.width, 0);
-                this.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
-            } else {
-                this._corner.set_position(0, 0);
+            if (this._cornerOnRight) {
+                if (this._cornerOnBottom) {
+                    // Bottom-right corner
+                    this._corner.set_position(this.width - this._corner.width, this.height - 
this._corner.height);
+                    this.set_anchor_point_from_gravity(Clutter.Gravity.SOUTH_EAST);
+                } else {
+                    // Top-right corner
+                    this._corner.set_position(this.width - this._corner.width, 0);
+                    this.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
+                }
+            } else if (this._cornerOnLeft) {
+                if (this._cornerOnBottom) {
+                    // Bottom-left corner
+                    this._corner.set_position(0, this.height - this._corner.height);
+                    this.set_anchor_point_from_gravity(Clutter.Gravity.SOUTH_WEST);
+                } else {
+                    // Top-left corner
+                    this._corner.set_position(0, 0);
+                    // Default gravity is north-west
+                }
             }
 
             this._corner.connect('enter-event',
@@ -1240,7 +1320,8 @@ class HotCorner extends Clutter.Actor {
         if (source != Main.xdndHandler)
             return DND.DragMotionResult.CONTINUE;
 
-        this._toggleOverview();
+        if (this.isEnabled())
+            this._toggleOverview();
 
         return DND.DragMotionResult.CONTINUE;
     }
diff --git a/js/ui/panel.js b/js/ui/panel.js
index a390ed211b..9a1af1a25c 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -824,6 +824,7 @@ const PANEL_ITEM_IMPLEMENTATIONS = {
     'a11y': imports.ui.status.accessibility.ATIndicator,
     'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
     'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator,
+    'hotCorner': imports.ui.hotCorner.HotCorner,
     'powerMenu': PowerMenu,
 };
 
diff --git a/js/ui/ripples.js b/js/ui/ripples.js
index f38062228f..8a88c77305 100644
--- a/js/ui/ripples.js
+++ b/js/ui/ripples.js
@@ -3,6 +3,12 @@
 
 const { Clutter, St } = imports.gi;
 
+// Gsettings keys to determine position of hot corner
+// and whether or not it is enabled.
+const HOT_CORNER_ENABLED_KEY = 'hot-corner-enabled';
+const HOT_CORNER_ON_RIGHT_KEY = 'hot-corner-on-right';
+const HOT_CORNER_ON_BOTTOM_KEY = 'hot-corner-on-bottom';
+
 // Shamelessly copied from the layout "hotcorner" ripples implementation
 var Ripples = class Ripples {
     constructor(px, py, styleClass) {
@@ -32,6 +38,46 @@ var Ripples = class Ripples {
                                            reactive: false,
                                            visible: false });
         this._ripple3.set_pivot_point(px, py);
+
+        let cornerRightSetting = global.settings.get_boolean(HOT_CORNER_ON_RIGHT_KEY);
+        let textDirection = Clutter.get_default_text_direction();
+        this._cornerOnRight = (cornerRightSetting && textDirection == Clutter.TextDirection.LTR) ||
+            (!cornerRightSetting && textDirection == Clutter.TextDirection.RTL);
+        this._cornerOnBottom = global.settings.get_boolean(HOT_CORNER_ON_BOTTOM_KEY);
+
+        // Remove all existing style pseudo-classes
+        // (note: top-left is default and does not use a pseudo-class)
+        let corners = ['tr', 'bl', 'br'];
+        let ripples = [this._ripple1, this._ripple2, this._ripple3];
+        for (let corner of corners) {
+            for (let ripple of ripples)
+                ripple.remove_style_pseudo_class(corner);
+        }
+
+        // Add the style pseudo-class for the selected ripple corner
+        let addCorner = null;
+        if (this._cornerOnRight) {
+            if (this._cornerOnBottom) {
+                // Bottom-right corner
+                addCorner = 'br';
+            } else {
+                // Top-right corner
+                addCorner = 'tr';
+            }
+        } else {
+            if (this._cornerOnBottom) {
+                // Bottom-left corner
+                addCorner = 'bl';
+            } else {
+                // Top-left corner
+                // No style pseudo-class to add
+            }
+        }
+
+        if (addCorner) {
+            for (let ripple of ripples)
+                ripple.add_style_pseudo_class(addCorner);
+        }
     }
 
     destroy() {
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 5cc06af057..4cc4be895f 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -89,7 +89,7 @@ const _modes = {
         panel: {
             left: ['endlessButton', 'appIcons'],
             center: [],
-            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu', 'dateMenu'],
+            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu', 'dateMenu', 'hotCorner'],
         },
     },
 };
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 35ccbb1a15..bae933a610 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -100,3 +100,4 @@ subprojects/gvc/gvc-mixer-control.c
 js/ui/appIconBar.js
 js/ui/endlessButton.js
 js/ui/forceAppExitDialog.js
+js/ui/hotCorner.js


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]