diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss index 64cc612aa3eddac761864fcfd77a8f1e420af202..c8c53d557ff0e5d652f3c702794eff5662438307 100644 --- a/data/theme/gnome-shell-sass/_common.scss +++ b/data/theme/gnome-shell-sass/_common.scss @@ -1159,6 +1159,15 @@ StScrollBar { border: 1px solid $selected_bg_color; } +// Pointer location +.ripple-pointer-location { + width: 50px; + height: 50px; + border-radius: 25px 25px 25px 25px; // radius the size of the box give us the curve + background-color: lighten(transparentize($selected_bg_color, 0.7), 30%); + box-shadow: 0 0 2px 2px lighten($selected_bg_color, 20%); +} + // not really top bar only .popup-menu-arrow { icon-size: 1.09em; } .popup-menu-icon { icon-size: 1.09em; } diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 836d1c6746f35a55a77a82affb90abc3a8ba7c54..7807cce9db5350e2339187eedfc111051700f245 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -64,6 +64,7 @@ ui/keyboard.js ui/layout.js ui/lightbox.js + ui/locatePointer.js ui/lookingGlass.js ui/magnifier.js ui/magnifierDBus.js @@ -84,6 +85,7 @@ ui/pointerWatcher.js ui/popupMenu.js ui/remoteSearch.js + ui/ripples.js ui/runDialog.js ui/screenShield.js ui/screencast.js diff --git a/js/ui/layout.js b/js/ui/layout.js index 6f6677d642aa2e6062b63bf5860a4da2d252ef61..90385e82cb2c751b52094b138f9cc5185abccf62 100644 --- a/js/ui/layout.js +++ b/js/ui/layout.js @@ -11,6 +11,7 @@ const DND = imports.ui.dnd; const Main = imports.ui.main; const Params = imports.misc.params; const Tweener = imports.ui.tweener; +const Ripples = imports.ui.ripples; var STARTUP_ANIMATION_TIME = 0.5; var KEYBOARD_ANIMATION_TIME = 0.15; @@ -1117,14 +1118,15 @@ var HotCorner = class HotCorner { Shell.ActionMode.OVERVIEW); this._pressureBarrier.connect('trigger', this._toggleOverview.bind(this)); - // Cache the three ripples instead of dynamically creating and destroying them. - this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false }); - this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false }); - this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false }); + let px = 0.0; + let py = 0.0; + if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { + px = 1.0; + py = 0.0; + } - layoutManager.uiGroup.add_actor(this._ripple1); - layoutManager.uiGroup.add_actor(this._ripple2); - layoutManager.uiGroup.add_actor(this._ripple3); + this._ripples = new Ripples.Ripples(px, py, 'ripple-box'); + this._ripples.addTo(layoutManager.uiGroup); } setBarrierSize(size) { @@ -1206,53 +1208,12 @@ var HotCorner = class HotCorner { this.actor.destroy(); } - _animRipple(ripple, delay, time, startScale, startOpacity, finalScale) { - // We draw a ripple by using a source image and animating it scaling - // outwards and fading away. We want the ripples to move linearly - // or it looks unrealistic, but if the opacity of the ripple goes - // linearly to zero it fades away too quickly, so we use Tweener's - // 'onUpdate' to give a non-linear curve to the fade-away and make - // it more visible in the middle section. - - ripple._opacity = startOpacity; - - if (ripple.get_text_direction() == Clutter.TextDirection.RTL) - ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST); - - ripple.visible = true; - ripple.opacity = 255 * Math.sqrt(startOpacity); - ripple.scale_x = ripple.scale_y = startScale; - - ripple.x = this._x; - ripple.y = this._y; - - Tweener.addTween(ripple, { _opacity: 0, - scale_x: finalScale, - scale_y: finalScale, - delay: delay, - time: time, - transition: 'linear', - onUpdate() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); }, - onComplete() { ripple.visible = false; } }); - } - - _rippleAnimation() { - // Show three concentric ripples expanding outwards; the exact - // parameters were found by trial and error, so don't look - // for them to make perfect sense mathematically - - // delay time scale opacity => scale - this._animRipple(this._ripple1, 0.0, 0.83, 0.25, 1.0, 1.5); - this._animRipple(this._ripple2, 0.05, 1.0, 0.0, 0.7, 1.25); - this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1); - } - _toggleOverview() { if (this._monitor.inFullscreen && !Main.overview.visible) return; if (Main.overview.shouldToggleByCornerOrButton()) { - this._rippleAnimation(); + this._ripples.playAnimation(this._x, this._y); Main.overview.toggle(); } } diff --git a/js/ui/locatePointer.js b/js/ui/locatePointer.js new file mode 100644 index 0000000000000000000000000000000000000000..52a0bc48657d62c3d025789682fab4f070750c17 --- /dev/null +++ b/js/ui/locatePointer.js @@ -0,0 +1,24 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const { Clutter, Gio, GLib, St } = imports.gi; +const Ripples = imports.ui.ripples; +const Main = imports.ui.main; + +const LOCATE_POINTER_KEY = "locate-pointer"; +const LOCATE_POINTER_SCHEMA = "org.gnome.desktop.interface" + +var locatePointer = class { + constructor() { + this._settings = new Gio.Settings({schema_id: LOCATE_POINTER_SCHEMA}); + this._ripples = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location'); + this._ripples.addTo(Main.uiGroup); + } + + show() { + if (!this._settings.get_boolean("locate-pointer")) + return; + + let [x, y, mods] = global.get_pointer(); + this._ripples.playAnimation(x, y); + } +}; diff --git a/js/ui/main.js b/js/ui/main.js index d9f287cd9ded262a38ac40ca5d362340b223023a..b3cd69283a7a1b11afd832acd01ea8c555c7b811 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -37,6 +37,7 @@ const WindowManager = imports.ui.windowManager; const Magnifier = imports.ui.magnifier; const XdndHandler = imports.ui.xdndHandler; const KbdA11yDialog = imports.ui.kbdA11yDialog; +const LocatePointer = imports.ui.locatePointer; const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; const STICKY_KEYS_ENABLE = 'stickykeys-enable'; @@ -74,6 +75,7 @@ var layoutManager = null; var kbdA11yDialog = null; var inputMethod = null; var introspectService = null; +var locatePointer = null; let _startDate; let _defaultCssStylesheet = null; let _cssStylesheet = null; @@ -92,6 +94,8 @@ function _sessionUpdated() { wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); + wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL); + wm.setCustomKeybindingHandler('panel-run-dialog', Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, @@ -165,6 +169,8 @@ function _initializeUI() { kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog(); wm = new WindowManager.WindowManager(); magnifier = new Magnifier.Magnifier(); + locatePointer = new LocatePointer.locatePointer(); + if (LoginManager.canLock()) screenShield = new ScreenShield.ScreenShield(); @@ -190,6 +196,10 @@ function _initializeUI() { overview.toggle(); }); + global.connect('locate-pointer', () => { + locatePointer.show(); + }); + global.display.connect('show-restart-message', (display, message) => { showRestartMessage(message); return true; diff --git a/js/ui/ripples.js b/js/ui/ripples.js new file mode 100644 index 0000000000000000000000000000000000000000..37d7362deef788dcf1326750141164ca89f92dca --- /dev/null +++ b/js/ui/ripples.js @@ -0,0 +1,97 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const { Clutter, St } = imports.gi; +const Tweener = imports.ui.tweener; + +// Shamelessly copied from the layout "hotcorner" ripples implementation +var Ripples = class Ripples { + constructor(px, py, styleClass) { + this._x = 0; + this._y = 0; + + this._px = px; + this._py = py; + + this._ripple1 = new St.BoxLayout({ style_class: styleClass, + opacity: 0, + can_focus: false, + reactive: false, + visible: false }); + this._ripple1.set_pivot_point(px, py); + + this._ripple2 = new St.BoxLayout({ style_class: styleClass, + opacity: 0, + can_focus: false, + reactive: false, + visible: false }); + this._ripple2.set_pivot_point(px, py); + + this._ripple3 = new St.BoxLayout({ style_class: styleClass, + opacity: 0, + can_focus: false, + reactive: false, + visible: false }); + this._ripple3.set_pivot_point(px, py); + } + + _animRipple(ripple, delay, time, startScale, startOpacity, finalScale) { + // We draw a ripple by using a source image and animating it scaling + // outwards and fading away. We want the ripples to move linearly + // or it looks unrealistic, but if the opacity of the ripple goes + // linearly to zero it fades away too quickly, so we use Tweener's + // 'onUpdate' to give a non-linear curve to the fade-away and make + // it more visible in the middle section. + + ripple.x = this._x; + ripple.y = this._y; + ripple._opacity = startOpacity; + ripple.visible = true; + ripple.opacity = 255 * Math.sqrt(startOpacity); + ripple.scale_x = ripple.scale_y = startScale; + ripple.set_translation( - this._px * ripple.width, - this._py * ripple.height, 0.0); + + Tweener.addTween(ripple, { _opacity: 0, + scale_x: finalScale, + scale_y: finalScale, + delay: delay, + time: time, + transition: 'linear', + onUpdate() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); }, + onComplete() { ripple.visible = false; } }); + } + + addTo(stage) { + if (this._stage !== undefined) { + throw new Error('Ripples already added'); + return; + } + + this._stage = stage; + this._stage.add_actor(this._ripple1); + this._stage.add_actor(this._ripple2); + this._stage.add_actor(this._ripple3); + } + + playAnimation(x, y) { + if (this._stage === undefined) { + throw new Error('Ripples not added'); + return; + } + + this._x = x; + this._y = y; + + this._stage.set_child_above_sibling(this._ripple1, null); + this._stage.set_child_above_sibling(this._ripple2, this._ripple1); + this._stage.set_child_above_sibling(this._ripple3, this._ripple2); + + // Show three concentric ripples expanding outwards; the exact + // parameters were found by trial and error, so don't look + // for them to make perfect sense mathematically + + // delay time scale opacity => scale + this._animRipple(this._ripple1, 0.0, 0.83, 0.25, 1.0, 1.5); + this._animRipple(this._ripple2, 0.05, 1.0, 0.0, 0.7, 1.25); + this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1); + } +}; diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 35a9a8bcb44f567f22b6d40ba7c1fd4f50a9aba6..2e491646caa7111d493d6fca1decf2cd0ad3790d 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -346,6 +346,13 @@ gnome_shell_plugin_create_inhibit_shortcuts_dialog (MetaPlugin *plugin, return _shell_wm_create_inhibit_shortcuts_dialog (get_shell_wm (), window); } +static void +gnome_shell_plugin_locate_pointer (MetaPlugin *plugin) +{ + GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin); + _shell_global_locate_pointer (shell_plugin->global); +} + static void gnome_shell_plugin_class_init (GnomeShellPluginClass *klass) { @@ -378,6 +385,8 @@ gnome_shell_plugin_class_init (GnomeShellPluginClass *klass) plugin_class->create_close_dialog = gnome_shell_plugin_create_close_dialog; plugin_class->create_inhibit_shortcuts_dialog = gnome_shell_plugin_create_inhibit_shortcuts_dialog; + + plugin_class->locate_pointer = gnome_shell_plugin_locate_pointer; } static void diff --git a/src/shell-global-private.h b/src/shell-global-private.h index df53236aac54ea01718916891ff87aa13f793c7e..9969691cb47db09ae5c81f043cc609e80f4aa645 100644 --- a/src/shell-global-private.h +++ b/src/shell-global-private.h @@ -18,4 +18,6 @@ GjsContext *_shell_global_get_gjs_context (ShellGlobal *global); gboolean _shell_global_check_xdnd_event (ShellGlobal *global, XEvent *xev); +void _shell_global_locate_pointer (ShellGlobal *global); + #endif /* __SHELL_GLOBAL_PRIVATE_H__ */ diff --git a/src/shell-global.c b/src/shell-global.c index 293053d0b58446125b27db9848a10bf8509ede46..70120c2da834ebc0680925af873f9a84560e3472 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -115,6 +115,7 @@ enum { enum { NOTIFY_ERROR, + LOCATE_POINTER, LAST_SIGNAL }; @@ -357,6 +358,13 @@ shell_global_class_init (ShellGlobalClass *klass) G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + shell_global_signals[LOCATE_POINTER] = + g_signal_new ("locate-pointer", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); g_object_class_install_property (gobject_class, PROP_SESSION_MODE, @@ -1732,3 +1740,9 @@ shell_global_get_persistent_state (ShellGlobal *global, { return load_variant (global->userdatadir_path, property_type, property_name); } + +void +_shell_global_locate_pointer (ShellGlobal *global) +{ + g_signal_emit (global, shell_global_signals[LOCATE_POINTER], 0); +}