From afb56df55cc1889b5181df2dea4014ec85a45810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 25 Feb 2021 16:04:30 +0100 Subject: [PATCH] windowPreview: Subclass a C actor Move the get_preferred_width/height() and allocate() vfunc implementations of WindowPreview to C, subclassing the C GObject from JS. This gets us another significant performance gain, allocating a workspace with 20 windows now only takes 1.2 ms. Part-of: --- js/ui/windowPreview.js | 85 +++++++----------- src/meson.build | 2 + src/shell-window-preview.c | 175 +++++++++++++++++++++++++++++++++++++ src/shell-window-preview.h | 14 +++ 4 files changed, 221 insertions(+), 55 deletions(-) create mode 100644 src/shell-window-preview.c create mode 100644 src/shell-window-preview.h diff --git a/js/ui/windowPreview.js b/js/ui/windowPreview.js index 3bd213959..84d80fe3e 100644 --- a/js/ui/windowPreview.js +++ b/js/ui/windowPreview.js @@ -37,7 +37,7 @@ var WindowPreview = GObject.registerClass({ 'show-chrome': {}, 'size-changed': {}, }, -}, class WindowPreview extends St.Widget { +}, class WindowPreview extends Shell.WindowPreview { _init(metaWindow, workspace, overviewAdjustment) { this.metaWindow = metaWindow; this.metaWindow._delegate = this; @@ -52,17 +52,19 @@ var WindowPreview = GObject.registerClass({ offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY, }); - this._windowContainer = new Clutter.Actor({ + const windowContainer = new Clutter.Actor({ pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), }); - this._windowContainer.connect('notify::scale-x', + this.window_container = windowContainer; + + 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 // container - this._windowContainer.layout_manager = new Shell.WindowPreviewLayout(); - this.add_child(this._windowContainer); + windowContainer.layout_manager = new Shell.WindowPreviewLayout(); + this.add_child(windowContainer); this._addWindow(metaWindow); @@ -71,13 +73,13 @@ var WindowPreview = GObject.registerClass({ this._stackAbove = null; this._cachedBoundingBox = { - x: this._windowContainer.layout_manager.bounding_box.x1, - y: this._windowContainer.layout_manager.bounding_box.y1, - width: this._windowContainer.layout_manager.bounding_box.get_width(), - height: this._windowContainer.layout_manager.bounding_box.get_height(), + x: windowContainer.layout_manager.bounding_box.x1, + y: windowContainer.layout_manager.bounding_box.y1, + width: windowContainer.layout_manager.bounding_box.get_width(), + height: windowContainer.layout_manager.bounding_box.get_height(), }; - this._windowContainer.layout_manager.connect( + windowContainer.layout_manager.connect( 'notify::bounding-box', layout => { this._cachedBoundingBox = { x: layout.bounding_box.x1, @@ -127,16 +129,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._windowContainer, + source: windowContainer, coordinate: Clutter.BindCoordinate.POSITION, })); this._icon.add_constraint(new Clutter.AlignConstraint({ - source: this._windowContainer, + source: windowContainer, align_axis: Clutter.AlignAxis.X_AXIS, factor: 0.5, })); this._icon.add_constraint(new Clutter.AlignConstraint({ - source: this._windowContainer, + source: windowContainer, align_axis: Clutter.AlignAxis.Y_AXIS, pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }), factor: 1, @@ -150,22 +152,22 @@ var WindowPreview = GObject.registerClass({ reactive: true, }); this._title.add_constraint(new Clutter.BindConstraint({ - source: this._windowContainer, + source: windowContainer, coordinate: Clutter.BindCoordinate.X, })); const iconBottomOverlap = ICON_SIZE * (1 - ICON_OVERLAP); this._title.add_constraint(new Clutter.BindConstraint({ - source: this._windowContainer, + source: windowContainer, coordinate: Clutter.BindCoordinate.Y, offset: scaleFactor * (iconBottomOverlap + ICON_TITLE_SPACING), })); this._title.add_constraint(new Clutter.AlignConstraint({ - source: this._windowContainer, + source: windowContainer, align_axis: Clutter.AlignAxis.X_AXIS, factor: 0.5, })); this._title.add_constraint(new Clutter.AlignConstraint({ - source: this._windowContainer, + source: windowContainer, align_axis: Clutter.AlignAxis.Y_AXIS, pivot_point: new Graphene.Point({ x: -1, y: 0 }), factor: 1, @@ -187,17 +189,17 @@ var WindowPreview = GObject.registerClass({ child: new St.Icon({ icon_name: 'preview-close-symbolic' }), }); this._closeButton.add_constraint(new Clutter.BindConstraint({ - source: this._windowContainer, + source: windowContainer, coordinate: Clutter.BindCoordinate.POSITION, })); this._closeButton.add_constraint(new Clutter.AlignConstraint({ - source: this._windowContainer, + source: 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._windowContainer, + source: windowContainer, align_axis: Clutter.AlignAxis.Y_AXIS, pivot_point: new Graphene.Point({ x: -1, y: 0.5 }), factor: 0, @@ -223,33 +225,6 @@ var WindowPreview = GObject.registerClass({ }); } - vfunc_get_preferred_width(forHeight) { - const themeNode = this.get_theme_node(); - - // Only include window previews in size request, not chrome - const [minWidth, natWidth] = - this._windowContainer.get_preferred_width( - themeNode.adjust_for_height(forHeight)); - - return themeNode.adjust_preferred_width(minWidth, natWidth); - } - - vfunc_get_preferred_height(forWidth) { - const themeNode = this.get_theme_node(); - const [minHeight, natHeight] = - this._windowContainer.get_preferred_height( - themeNode.adjust_for_width(forWidth)); - - return themeNode.adjust_preferred_height(minHeight, natHeight); - } - - vfunc_allocate(box) { - this.set_allocation(box); - - for (const child of this) - child.allocate_available_size(0, 0, box.get_width(), box.get_height()); - } - _updateIconScale() { const { ControlsState } = OverviewControls; const { currentState, initialState, finalState } = @@ -354,13 +329,13 @@ var WindowPreview = GObject.registerClass({ }); }); - const [width, height] = this._windowContainer.get_size(); + const [width, height] = this.window_container.get_size(); const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor; const origSize = Math.max(width, height); const scale = (origSize + activeExtraSize) / origSize; - this._windowContainer.ease({ + this.window_container.ease({ scale_x: scale, scale_y: scale, duration: animate ? WINDOW_SCALE_TIME : 0, @@ -395,7 +370,7 @@ var WindowPreview = GObject.registerClass({ }); }); - this._windowContainer.ease({ + this.window_container.ease({ scale_x: 1, scale_y: 1, duration: animate ? WINDOW_SCALE_TIME : 0, @@ -407,9 +382,9 @@ var WindowPreview = GObject.registerClass({ // 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 previewScale = this.window_container.scale_x; const [previewWidth, previewHeight] = - this._windowContainer.allocation.get_size(); + this.window_container.allocation.get_size(); const heightIncrease = Math.floor(previewHeight * (previewScale - 1) / 2); @@ -427,7 +402,7 @@ var WindowPreview = GObject.registerClass({ } _addWindow(metaWindow) { - const clone = this._windowContainer.layout_manager.add_window(metaWindow); + const clone = this.window_container.layout_manager.add_window(metaWindow); if (!clone) return; @@ -446,7 +421,7 @@ var WindowPreview = GObject.registerClass({ } _deleteAll() { - const windows = this._windowContainer.layout_manager.get_windows(); + const windows = this.window_container.layout_manager.get_windows(); // Delete all windows, starting from the bottom-most (most-modal) one for (const window of windows.reverse()) @@ -471,7 +446,7 @@ var WindowPreview = GObject.registerClass({ } _hasAttachedDialogs() { - return this._windowContainer.layout_manager.get_windows().length > 1; + return this.window_container.layout_manager.get_windows().length > 1; } _updateAttachedDialogs() { diff --git a/src/meson.build b/src/meson.build index d20134fd2..b06f7cab1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -109,6 +109,7 @@ libshell_public_headers = [ 'shell-tray-icon.h', 'shell-tray-manager.h', 'shell-util.h', + 'shell-window-preview.h', 'shell-window-preview-layout.h', 'shell-window-tracker.h', 'shell-wm.h' @@ -152,6 +153,7 @@ libshell_sources = [ 'shell-tray-icon.c', 'shell-tray-manager.c', 'shell-util.c', + 'shell-window-preview.c', 'shell-window-preview-layout.c', 'shell-window-tracker.c', 'shell-wm.c' diff --git a/src/shell-window-preview.c b/src/shell-window-preview.c new file mode 100644 index 000000000..2a4c6967c --- /dev/null +++ b/src/shell-window-preview.c @@ -0,0 +1,175 @@ +#include "config.h" + +#include "shell-window-preview.h" + +enum +{ + PROP_0, + + PROP_WINDOW_CONTAINER, + + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST] = { NULL, }; + +struct _ShellWindowPreview +{ + /*< private >*/ + StWidget parent_instance; + + ClutterActor *window_container; +}; + +G_DEFINE_TYPE (ShellWindowPreview, shell_window_preview, ST_TYPE_WIDGET); + +static void +shell_window_preview_get_property (GObject *gobject, + unsigned int property_id, + GValue *value, + GParamSpec *pspec) +{ + ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject); + + switch (property_id) + { + case PROP_WINDOW_CONTAINER: + g_value_set_object (value, self->window_container); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); + } +} + +static void +shell_window_preview_set_property (GObject *gobject, + unsigned int property_id, + const GValue *value, + GParamSpec *pspec) +{ + ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject); + + switch (property_id) + { + case PROP_WINDOW_CONTAINER: + g_set_object (&self->window_container, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); + } +} + +static void +shell_window_preview_get_preferred_width (ClutterActor *actor, + float for_height, + float *min_width_p, + float *natural_width_p) +{ + ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (actor); + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); + float min_width, nat_width; + + st_theme_node_adjust_for_height (theme_node, &for_height); + + clutter_actor_get_preferred_width (self->window_container, for_height, + &min_width, &nat_width); + + st_theme_node_adjust_preferred_width (theme_node, &min_width, &nat_width); + + if (min_width_p) + *min_width_p = min_width; + + if (natural_width_p) + *natural_width_p = nat_width; +} + +static void +shell_window_preview_get_preferred_height (ClutterActor *actor, + float for_width, + float *min_height_p, + float *natural_height_p) +{ + ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (actor); + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); + float min_height, nat_height; + + st_theme_node_adjust_for_width (theme_node, &for_width); + + clutter_actor_get_preferred_height (self->window_container, for_width, + &min_height, &nat_height); + + st_theme_node_adjust_preferred_height (theme_node, &min_height, &nat_height); + + if (min_height_p) + *min_height_p = min_height; + + if (natural_height_p) + *natural_height_p = nat_height; +} + +static void +shell_window_preview_allocate (ClutterActor *actor, + const ClutterActorBox *box) +{ + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); + ClutterActorBox content_box; + float x, y, max_width, max_height; + ClutterActorIter iter; + ClutterActor *child; + + clutter_actor_set_allocation (actor, box); + + st_theme_node_get_content_box (theme_node, box, &content_box); + + clutter_actor_box_get_origin (&content_box, &x, &y); + clutter_actor_box_get_size (&content_box, &max_width, &max_height); + + clutter_actor_iter_init (&iter, actor); + while (clutter_actor_iter_next (&iter, &child)) + clutter_actor_allocate_available_size (child, x, y, max_width, max_height); +} + +static void +shell_window_preview_dispose (GObject *gobject) +{ + ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject); + + g_clear_object (&self->window_container); + + G_OBJECT_CLASS (shell_window_preview_parent_class)->dispose (gobject); +} + +static void +shell_window_preview_init (ShellWindowPreview *self) +{ +} + +static void +shell_window_preview_class_init (ShellWindowPreviewClass *klass) +{ + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + actor_class->get_preferred_width = shell_window_preview_get_preferred_width; + actor_class->get_preferred_height = shell_window_preview_get_preferred_height; + actor_class->allocate = shell_window_preview_allocate; + + gobject_class->dispose = shell_window_preview_dispose; + gobject_class->get_property = shell_window_preview_get_property; + gobject_class->set_property = shell_window_preview_set_property; + + /** + * ShellWindowPreview:window-container: + */ + obj_props[PROP_WINDOW_CONTAINER] = + g_param_spec_object ("window-container", + "window-container", + "window-container", + CLUTTER_TYPE_ACTOR, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); +} diff --git a/src/shell-window-preview.h b/src/shell-window-preview.h new file mode 100644 index 000000000..e50380018 --- /dev/null +++ b/src/shell-window-preview.h @@ -0,0 +1,14 @@ +#ifndef __SHELL_WINDOW_PREVIEW_H__ +#define __SHELL_WINDOW_PREVIEW_H__ + +#include + +G_BEGIN_DECLS + +#define SHELL_TYPE_WINDOW_PREVIEW (shell_window_preview_get_type ()) +G_DECLARE_FINAL_TYPE (ShellWindowPreview, shell_window_preview, + SHELL, WINDOW_PREVIEW, StWidget) + +G_END_DECLS + +#endif /* __SHELL_WINDOW_PREVIEW_H__ */