[gnome-shell] windowPreviews: Replace border with scale effect



commit 30f27412c2274abd857aeebffc8eea66675144cb
Author: Florian Müllner <fmuellner gnome org>
Date:   Fri Jan 8 13:27:10 2021 +0100

    windowPreviews: Replace border with scale effect
    
    We currently use a thick border to indicate the hovered/focused preview. It
    works well for that purpose, but is a bit in the face. Slightly scaling up
    the preview still provides a clear indication, but in a more subtle and
    elegant way.
    
    https://gitlab.gnome.org/Teams/Design/os-mockups/-/issues/81
    
    Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1605>

 .../gnome-shell-sass/widgets/_window-picker.scss   |  16 ---
 js/ui/windowPreview.js                             | 122 +++++++++++----------
 2 files changed, 62 insertions(+), 76 deletions(-)
---
diff --git a/data/theme/gnome-shell-sass/widgets/_window-picker.scss 
b/data/theme/gnome-shell-sass/widgets/_window-picker.scss
index 4cf3e12e2f..a39af155fb 100644
--- a/data/theme/gnome-shell-sass/widgets/_window-picker.scss
+++ b/data/theme/gnome-shell-sass/widgets/_window-picker.scss
@@ -3,31 +3,15 @@
 $window_picker_spacing: $base_spacing; // 6px
 $window_picker_padding: $base_padding * 2; // 12px
 
-$window_thumbnail_border_color:transparentize($selected_fg_color, 0.65);
-
 $window_close_button_size: 24px;
 $window_close_button_padding: 3px;
 
-$window_clone_border_size: 6px;
-
 // Window picker
 .window-picker {
   // Space between window thumbnails
   spacing: $window_picker_spacing;
 }
 
-// Borders on window thumbnails
-.window-clone-border {
-  border-width: $window_clone_border_size;
-  border-style: solid;
-  border-color: $window_thumbnail_border_color;
-  border-radius: $base_border_radius + 2;
-  // For window decorations with round corners we can't match
-  // the exact shape when the window is scaled. So apply a shadow
-  // to fix that case
-  box-shadow: inset 0 0 0 1px transparentize($borders_color, 0.8);
-}
-
 // Window titles
 .window-caption {
   color: $osd_fg_color;
diff --git a/js/ui/windowPreview.js b/js/ui/windowPreview.js
index e860f06471..3c12b4708d 100644
--- a/js/ui/windowPreview.js
+++ b/js/ui/windowPreview.js
@@ -11,6 +11,9 @@ var WINDOW_DND_SIZE = 256;
 var WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
 var WINDOW_OVERLAY_FADE_TIME = 200;
 
+var WINDOW_SCALE_TIME = 200;
+var WINDOW_ACTIVE_SCALE = 1.02;
+
 var DRAGGING_WINDOW_OPACITY = 100;
 
 const ICON_SIZE = 64;
@@ -220,7 +223,11 @@ var WindowPreview = GObject.registerClass({
             offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY,
         });
 
-        this._windowContainer = new Clutter.Actor();
+        this._windowContainer = new Clutter.Actor({
+            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
+        });
+        this._windowContainer.connect('notify::scale-x',
+            () => this._adjustOverlayOffsets());
         // gjs currently can't handle setting an actors layout manager during
         // the initialization of the actor if that layout manager keeps track
         // of its container, so set the layout manager after creating the
@@ -282,34 +289,6 @@ var WindowPreview = GObject.registerClass({
         this._closeRequested = false;
         this._idleHideOverlayId = 0;
 
-        this._border = new St.Widget({
-            visible: false,
-            style_class: 'window-clone-border',
-        });
-        this._borderConstraint = new Clutter.BindConstraint({
-            source: this._windowContainer,
-            coordinate: Clutter.BindCoordinate.SIZE,
-        });
-        this._border.add_constraint(this._borderConstraint);
-        this._border.add_constraint(new Clutter.AlignConstraint({
-            source: this._windowContainer,
-            align_axis: Clutter.AlignAxis.BOTH,
-            factor: 0.5,
-        }));
-        this._borderCenter = new Clutter.Actor();
-        this._borderCenterConstraint = new Clutter.BindConstraint({
-            source: this._windowContainer,
-            coordinate: Clutter.BindCoordinate.SIZE,
-        });
-        this._borderCenter.add_constraint(this._borderCenterConstraint);
-        this._borderCenter.add_constraint(new Clutter.AlignConstraint({
-            source: this._windowContainer,
-            align_axis: Clutter.AlignAxis.BOTH,
-            factor: 0.5,
-        }));
-        this._border.connect('style-changed',
-            this._onBorderStyleChanged.bind(this));
-
         const tracker = Shell.WindowTracker.get_default();
         const app = tracker.get_window_app(this.metaWindow);
         this._icon = app.create_icon_texture(ICON_SIZE);
@@ -319,16 +298,16 @@ var WindowPreview = GObject.registerClass({
             pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
         });
         this._icon.add_constraint(new Clutter.BindConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             coordinate: Clutter.BindCoordinate.POSITION,
         }));
         this._icon.add_constraint(new Clutter.AlignConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             align_axis: Clutter.AlignAxis.X_AXIS,
             factor: 0.5,
         }));
         this._icon.add_constraint(new Clutter.AlignConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             align_axis: Clutter.AlignAxis.Y_AXIS,
             pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
             factor: 1,
@@ -378,25 +357,23 @@ var WindowPreview = GObject.registerClass({
             child: new St.Icon({ icon_name: 'window-close-symbolic' }),
         });
         this._closeButton.add_constraint(new Clutter.BindConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             coordinate: Clutter.BindCoordinate.POSITION,
         }));
         this._closeButton.add_constraint(new Clutter.AlignConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             align_axis: Clutter.AlignAxis.X_AXIS,
             pivot_point: new Graphene.Point({ x: 0.5, y: -1 }),
             factor: this._closeButtonSide === St.Side.LEFT ? 0 : 1,
         }));
         this._closeButton.add_constraint(new Clutter.AlignConstraint({
-            source: this._borderCenter,
+            source: this._windowContainer,
             align_axis: Clutter.AlignAxis.Y_AXIS,
             pivot_point: new Graphene.Point({ x: -1, y: 0.5 }),
             factor: 0,
         }));
         this._closeButton.connect('clicked', () => this._deleteAll());
 
-        this.add_child(this._borderCenter);
-        this.add_child(this._border);
         this.add_child(this._title);
         this.add_child(this._icon);
         this.add_child(this._closeButton);
@@ -405,7 +382,6 @@ var WindowPreview = GObject.registerClass({
             if (!this.realized)
                 return;
 
-            this._border.ensure_style();
             this._title.ensure_style();
             this._icon.ensure_style();
         });
@@ -438,16 +414,6 @@ var WindowPreview = GObject.registerClass({
             child.allocate_available_size(0, 0, box.get_width(), box.get_height());
     }
 
-    _onBorderStyleChanged() {
-        let borderNode = this._border.get_theme_node();
-        this._borderSize = borderNode.get_border_width(St.Side.TOP);
-
-        // Increase the size of the border actor so the border outlines
-        // the bounding box
-        this._borderConstraint.offset = this._borderSize * 2;
-        this._borderCenterConstraint.offset = this._borderSize;
-    }
-
     _windowCanClose() {
         return this.metaWindow.can_close() &&
                !this._hasAttachedDialogs();
@@ -475,9 +441,8 @@ var WindowPreview = GObject.registerClass({
         const [, closeButtonHeight] = this._closeButton.get_preferred_height(-1);
         const [, iconHeight] = this._icon.get_preferred_height(-1);
 
-        const topOversize = this._borderSize / 2 + closeButtonHeight / 2;
-        const bottomOversize =
-            this._borderSize / 2 + (1 - ICON_OVERLAP) * iconHeight;
+        const topOversize = closeButtonHeight / 2;
+        const bottomOversize = (1 - ICON_OVERLAP) * iconHeight;
 
         return [topOversize, bottomOversize];
     }
@@ -486,11 +451,11 @@ var WindowPreview = GObject.registerClass({
         const [, closeButtonWidth] = this._closeButton.get_preferred_width(-1);
 
         const leftOversize = this._closeButtonSide === St.Side.LEFT
-            ? (this._borderSize / 2) + (closeButtonWidth / 2)
-            : this._borderSize;
+            ? closeButtonWidth / 2
+            : 0;
         const rightOversize = this._closeButtonSide === St.Side.LEFT
-            ? this._borderSize
-            : (this._borderSize / 2) + (closeButtonWidth / 2);
+            ? 0
+            : closeButtonWidth / 2;
 
         return [leftOversize, rightOversize];
     }
@@ -507,15 +472,15 @@ var WindowPreview = GObject.registerClass({
 
         // If we're supposed to animate and an animation in our direction
         // is already happening, let that one continue
-        const ongoingTransition = this._border.get_transition('opacity');
+        const ongoingTransition = this._title.get_transition('opacity');
         if (animate &&
             ongoingTransition &&
             ongoingTransition.get_interval().peek_final_value() === 255)
             return;
 
         const toShow = this._windowCanClose()
-            ? [this._border, this._title, this._closeButton]
-            : [this._border, this._title];
+            ? [this._title, this._closeButton]
+            : [this._title];
 
         toShow.forEach(a => {
             a.opacity = 0;
@@ -527,6 +492,13 @@ var WindowPreview = GObject.registerClass({
             });
         });
 
+        this._windowContainer.ease({
+            scale_x: WINDOW_ACTIVE_SCALE,
+            scale_y: WINDOW_ACTIVE_SCALE,
+            duration: animate ? WINDOW_SCALE_TIME : 0,
+            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+        });
+
         this.emit('show-chrome');
     }
 
@@ -539,13 +511,13 @@ var WindowPreview = GObject.registerClass({
 
         // If we're supposed to animate and an animation in our direction
         // is already happening, let that one continue
-        const ongoingTransition = this._border.get_transition('opacity');
+        const ongoingTransition = this._title.get_transition('opacity');
         if (animate &&
             ongoingTransition &&
             ongoingTransition.get_interval().peek_final_value() === 0)
             return;
 
-        [this._border, this._title, this._closeButton].forEach(a => {
+        [this._title, this._closeButton].forEach(a => {
             a.opacity = 255;
             a.ease({
                 opacity: 0,
@@ -554,6 +526,36 @@ var WindowPreview = GObject.registerClass({
                 onComplete: () => a.hide(),
             });
         });
+
+        this._windowContainer.ease({
+            scale_x: 1,
+            scale_y: 1,
+            duration: animate ? WINDOW_SCALE_TIME : 0,
+            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+        });
+    }
+
+    _adjustOverlayOffsets() {
+        // Assume that scale-x and scale-y update always set
+        // in lock-step; that allows us to not use separate
+        // handlers for horizontal and vertical offsets
+        const previewScale = this._windowContainer.scale_x;
+        const [previewWidth, previewHeight] =
+            this._windowContainer.allocation.get_size();
+
+        const heightIncrease =
+            Math.floor(previewHeight * (previewScale - 1) / 2);
+        const widthIncrease =
+            Math.floor(previewWidth * (previewScale - 1) / 2);
+
+        const closeAlign = this._closeButtonSide === St.Side.LEFT ? -1 : 1;
+
+        this._icon.translation_y = heightIncrease;
+        this._title.translation_y = heightIncrease;
+        this._closeButton.set({
+            translation_x: closeAlign * widthIncrease,
+            translation_y: -heightIncrease,
+        });
     }
 
     _addWindow(metaWindow) {


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