diff --git a/js/ui/windowPreview.js b/js/ui/windowPreview.js index bb97ebc63..3bd213959 100644 --- a/js/ui/windowPreview.js +++ b/js/ui/windowPreview.js @@ -22,180 +22,6 @@ const ICON_OVERLAP = 0.7; const ICON_TITLE_SPACING = 6; -var WindowPreviewLayout = GObject.registerClass({ - Properties: { - 'bounding-box': GObject.ParamSpec.boxed( - 'bounding-box', 'Bounding box', 'Bounding box', - GObject.ParamFlags.READABLE, - Clutter.ActorBox.$gtype), - }, -}, class WindowPreviewLayout extends Clutter.LayoutManager { - _init() { - super._init(); - - this._container = null; - this._boundingBox = new Clutter.ActorBox(); - this._windows = new Map(); - } - - _layoutChanged() { - let frameRect; - - for (const windowInfo of this._windows.values()) { - const frame = windowInfo.metaWindow.get_frame_rect(); - frameRect = frameRect?.union(frame) ?? frame; - } - - if (!frameRect) - frameRect = new Meta.Rectangle(); - - const oldBox = this._boundingBox.copy(); - this._boundingBox.set_origin(frameRect.x, frameRect.y); - this._boundingBox.set_size(frameRect.width, frameRect.height); - - if (!this._boundingBox.equal(oldBox)) - this.notify('bounding-box'); - - // Always call layout_changed(), a size or position change of an - // attached dialog might not affect the boundingBox - this.layout_changed(); - } - - vfunc_set_container(container) { - this._container = container; - } - - vfunc_get_preferred_height(_container, _forWidth) { - return [0, this._boundingBox.get_height()]; - } - - vfunc_get_preferred_width(_container, _forHeight) { - return [0, this._boundingBox.get_width()]; - } - - vfunc_allocate(container, box) { - // If the scale isn't 1, we weren't allocated our preferred size - // and have to scale the children allocations accordingly. - const scaleX = this._boundingBox.get_width() > 0 - ? box.get_width() / this._boundingBox.get_width() - : 1; - const scaleY = this._boundingBox.get_height() > 0 - ? box.get_height() / this._boundingBox.get_height() - : 1; - - const childBox = new Clutter.ActorBox(); - - for (const child of container) { - if (!child.visible) - continue; - - const windowInfo = this._windows.get(child); - if (windowInfo) { - const bufferRect = windowInfo.metaWindow.get_buffer_rect(); - childBox.set_origin( - bufferRect.x - this._boundingBox.x1, - bufferRect.y - this._boundingBox.y1); - - const [, , natWidth, natHeight] = child.get_preferred_size(); - childBox.set_size(natWidth, natHeight); - - childBox.x1 *= scaleX; - childBox.x2 *= scaleX; - childBox.y1 *= scaleY; - childBox.y2 *= scaleY; - - child.allocate(childBox); - } else { - child.allocate_preferred_size(0, 0); - } - } - } - - /** - * addWindow: - * @param {Meta.Window} window: the MetaWindow instance - * - * Creates a ClutterActor drawing the texture of @window and adds it - * to the container. If @window is already part of the preview, this - * function will do nothing. - * - * @returns {Clutter.Actor} The newly created actor drawing @window - */ - addWindow(window) { - const index = [...this._windows.values()].findIndex(info => - info.metaWindow === window); - - if (index !== -1) - return null; - - const windowActor = window.get_compositor_private(); - const actor = new Clutter.Clone({ source: windowActor }); - - this._windows.set(actor, { - metaWindow: window, - windowActor, - sizeChangedId: window.connect('size-changed', () => - this._layoutChanged()), - positionChangedId: window.connect('position-changed', () => - this._layoutChanged()), - windowActorDestroyId: windowActor.connect('destroy', () => - actor.destroy()), - destroyId: actor.connect('destroy', () => - this.removeWindow(window)), - }); - - this._container.add_child(actor); - - this._layoutChanged(); - - return actor; - } - - /** - * removeWindow: - * @param {Meta.Window} window: the window to remove from the preview - * - * Removes a MetaWindow @window from the preview which has been added - * previously using addWindow(). If @window is not part of preview, - * this function will do nothing. - */ - removeWindow(window) { - const entry = [...this._windows].find( - ([, i]) => i.metaWindow === window); - - if (!entry) - return; - - const [actor, windowInfo] = entry; - - windowInfo.metaWindow.disconnect(windowInfo.sizeChangedId); - windowInfo.metaWindow.disconnect(windowInfo.positionChangedId); - windowInfo.windowActor.disconnect(windowInfo.windowActorDestroyId); - actor.disconnect(windowInfo.destroyId); - - this._windows.delete(actor); - this._container.remove_child(actor); - - this._layoutChanged(); - } - - /** - * getWindows: - * - * Gets an array of all MetaWindows that were added to the layout - * using addWindow(), ordered by the insertion order. - * - * @returns {Array} An array including all windows - */ - getWindows() { - return [...this._windows.values()].map(i => i.metaWindow); - } - - get boundingBox() { - return this._boundingBox; - } -}); - var WindowPreview = GObject.registerClass({ Properties: { 'overlay-enabled': GObject.ParamSpec.boolean( @@ -235,7 +61,7 @@ var WindowPreview = GObject.registerClass({ // 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 WindowPreviewLayout(); + this._windowContainer.layout_manager = new Shell.WindowPreviewLayout(); this.add_child(this._windowContainer); this._addWindow(metaWindow); @@ -601,7 +427,7 @@ var WindowPreview = GObject.registerClass({ } _addWindow(metaWindow) { - const clone = this._windowContainer.layout_manager.addWindow(metaWindow); + const clone = this._windowContainer.layout_manager.add_window(metaWindow); if (!clone) return; @@ -620,7 +446,7 @@ var WindowPreview = GObject.registerClass({ } _deleteAll() { - const windows = this._windowContainer.layout_manager.getWindows(); + const windows = this._windowContainer.layout_manager.get_windows(); // Delete all windows, starting from the bottom-most (most-modal) one for (const window of windows.reverse()) @@ -645,7 +471,7 @@ var WindowPreview = GObject.registerClass({ } _hasAttachedDialogs() { - return this._windowContainer.layout_manager.getWindows().length > 1; + return this._windowContainer.layout_manager.get_windows().length > 1; } _updateAttachedDialogs() { diff --git a/src/meson.build b/src/meson.build index a7c56bbcf..d20134fd2 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-layout.h', 'shell-window-tracker.h', 'shell-wm.h' ] @@ -151,6 +152,7 @@ libshell_sources = [ 'shell-tray-icon.c', 'shell-tray-manager.c', 'shell-util.c', + 'shell-window-preview-layout.c', 'shell-window-tracker.c', 'shell-wm.c' ] diff --git a/src/shell-window-preview-layout.c b/src/shell-window-preview-layout.c new file mode 100644 index 000000000..a759a74f1 --- /dev/null +++ b/src/shell-window-preview-layout.c @@ -0,0 +1,472 @@ +#include "config.h" + +#include +#include +#include "shell-window-preview-layout.h" + +typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate; +struct _ShellWindowPreviewLayoutPrivate +{ + ClutterActor *container; + GHashTable *windows; + + ClutterActorBox bounding_box; +}; + +enum +{ + PROP_0, + + PROP_BOUNDING_BOX, + + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST] = { NULL, }; + +G_DEFINE_TYPE_WITH_PRIVATE (ShellWindowPreviewLayout, shell_window_preview_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +typedef struct _WindowInfo +{ + MetaWindow *window; + ClutterActor *window_actor; + + gulong size_changed_id; + gulong position_changed_id; + gulong window_actor_destroy_id; + gulong destroy_id; +} WindowInfo; + +static void +shell_window_preview_layout_get_property (GObject *object, + unsigned int property_id, + GValue *value, + GParamSpec *pspec) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (object); + ShellWindowPreviewLayoutPrivate *priv; + + priv = shell_window_preview_layout_get_instance_private (self); + + switch (property_id) + { + case PROP_BOUNDING_BOX: + g_value_set_boxed (value, &priv->bounding_box); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +shell_window_preview_layout_set_container (ClutterLayoutManager *layout, + ClutterContainer *container) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout); + ShellWindowPreviewLayoutPrivate *priv; + ClutterLayoutManagerClass *parent_class; + + priv = shell_window_preview_layout_get_instance_private (self); + + priv->container = CLUTTER_ACTOR (container); + + parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (shell_window_preview_layout_parent_class); + parent_class->set_container (layout, container); +} + +static void +shell_window_preview_layout_get_preferred_width (ClutterLayoutManager *layout, + ClutterContainer *container, + float for_height, + float *min_width_p, + float *natural_width_p) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout); + ShellWindowPreviewLayoutPrivate *priv; + + priv = shell_window_preview_layout_get_instance_private (self); + + if (min_width_p) + *min_width_p = 0; + + if (natural_width_p) + *natural_width_p = clutter_actor_box_get_width (&priv->bounding_box); +} + +static void +shell_window_preview_layout_get_preferred_height (ClutterLayoutManager *layout, + ClutterContainer *container, + float for_width, + float *min_height_p, + float *natural_height_p) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout); + ShellWindowPreviewLayoutPrivate *priv; + + priv = shell_window_preview_layout_get_instance_private (self); + + if (min_height_p) + *min_height_p = 0; + + if (natural_height_p) + *natural_height_p = clutter_actor_box_get_height (&priv->bounding_box); +} + + +static void +shell_window_preview_layout_allocate (ClutterLayoutManager *layout, + ClutterContainer *container, + const ClutterActorBox *box) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout); + ShellWindowPreviewLayoutPrivate *priv; + float scale_x, scale_y; + float bounding_box_width, bounding_box_height; + ClutterActorIter iter; + ClutterActor *child; + + priv = shell_window_preview_layout_get_instance_private (self); + + bounding_box_width = clutter_actor_box_get_width (&priv->bounding_box); + bounding_box_height = clutter_actor_box_get_height (&priv->bounding_box); + + if (bounding_box_width == 0) + scale_x = 1.f; + else + scale_x = clutter_actor_box_get_width (box) / bounding_box_width; + + if (bounding_box_height == 0) + scale_y = 1.f; + else + scale_y = clutter_actor_box_get_height (box) / bounding_box_height; + + clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container)); + while (clutter_actor_iter_next (&iter, &child)) + { + ClutterActorBox child_box = { 0, }; + WindowInfo *window_info; + + if (!clutter_actor_is_visible (child)) + continue; + + window_info = g_hash_table_lookup (priv->windows, child); + + if (window_info) + { + MetaRectangle buffer_rect; + float child_nat_width, child_nat_height; + + meta_window_get_buffer_rect (window_info->window, &buffer_rect); + + clutter_actor_box_set_origin (&child_box, + buffer_rect.x - priv->bounding_box.x1, + buffer_rect.y - priv->bounding_box.y1); + + clutter_actor_get_preferred_size (child, NULL, NULL, + &child_nat_width, &child_nat_height); + + clutter_actor_box_set_size (&child_box, child_nat_width, child_nat_height); + + child_box.x1 *= scale_x; + child_box.x2 *= scale_x; + child_box.y1 *= scale_y; + child_box.y2 *= scale_y; + + clutter_actor_allocate (child, &child_box); + } + else + { + float x, y; + + clutter_actor_get_fixed_position (child, &x, &y); + clutter_actor_allocate_preferred_size (child, x, y); + } + } +} + +static void +on_layout_changed (ShellWindowPreviewLayout *self) +{ + ShellWindowPreviewLayoutPrivate *priv; + GHashTableIter iter; + gpointer value; + gboolean first_rect = TRUE; + MetaRectangle bounding_rect = { 0, }; + ClutterActorBox old_bounding_box; + + priv = shell_window_preview_layout_get_instance_private (self); + + old_bounding_box = + (ClutterActorBox) CLUTTER_ACTOR_BOX_INIT (priv->bounding_box.x1, + priv->bounding_box.y1, + priv->bounding_box.x2, + priv->bounding_box.y2); + + g_hash_table_iter_init (&iter, priv->windows); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + WindowInfo *window_info = value; + MetaRectangle frame_rect; + + meta_window_get_frame_rect (window_info->window, &frame_rect); + + if (first_rect) + { + bounding_rect = frame_rect; + first_rect = FALSE; + continue; + } + + meta_rectangle_union (&frame_rect, &bounding_rect, &bounding_rect); + } + + clutter_actor_box_set_origin (&priv->bounding_box, + (float) bounding_rect.x, + (float) bounding_rect.y); + clutter_actor_box_set_size (&priv->bounding_box, + (float) bounding_rect.width, + (float) bounding_rect.height); + + if (!clutter_actor_box_equal (&priv->bounding_box, &old_bounding_box)) + g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_BOUNDING_BOX]); + + clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self)); +} + +static void +on_window_size_position_changed (MetaWindow *window, + ShellWindowPreviewLayout *self) +{ + on_layout_changed (self); +} + +static void +on_window_destroyed (ClutterActor *actor) +{ + clutter_actor_destroy (actor); +} + +static void +on_actor_destroyed (ClutterActor *actor, + ShellWindowPreviewLayout *self) +{ + ShellWindowPreviewLayoutPrivate *priv; + WindowInfo *window_info; + + priv = shell_window_preview_layout_get_instance_private (self); + + window_info = g_hash_table_lookup (priv->windows, actor); + g_assert (window_info != NULL); + + shell_window_preview_layout_remove_window (self, window_info->window); +} + +static void +shell_window_preview_layout_dispose (GObject *gobject) +{ + ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject); + ShellWindowPreviewLayoutPrivate *priv; + GHashTableIter iter; + gpointer key, value; + + priv = shell_window_preview_layout_get_instance_private (self); + + g_hash_table_iter_init (&iter, priv->windows); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + ClutterActor *actor = key; + WindowInfo *info = value; + + g_clear_signal_handler (&info->size_changed_id, info->window); + g_clear_signal_handler (&info->position_changed_id, info->window); + g_clear_signal_handler (&info->window_actor_destroy_id, info->window_actor); + g_clear_signal_handler (&info->destroy_id, actor); + + clutter_actor_remove_child (priv->container, actor); + } + + g_hash_table_remove_all (priv->windows); + + G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->dispose (gobject); +} + +static void +shell_window_preview_layout_init (ShellWindowPreviewLayout *self) +{ + ShellWindowPreviewLayoutPrivate *priv; + + priv = shell_window_preview_layout_get_instance_private (self); + + priv->windows = g_hash_table_new (NULL, NULL); +} + +static void +shell_window_preview_layout_class_init (ShellWindowPreviewLayoutClass *klass) +{ + ClutterLayoutManagerClass *layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layout_class->get_preferred_width = shell_window_preview_layout_get_preferred_width; + layout_class->get_preferred_height = shell_window_preview_layout_get_preferred_height; + layout_class->allocate = shell_window_preview_layout_allocate; + layout_class->set_container = shell_window_preview_layout_set_container; + + gobject_class->dispose = shell_window_preview_layout_dispose; + gobject_class->get_property = shell_window_preview_layout_get_property; + + /** + * ShellWindowPreviewLayout:bounding-box: + */ + obj_props[PROP_BOUNDING_BOX] = + g_param_spec_boxed ("bounding-box", + "Bounding Box", + "Bounding Box", + CLUTTER_TYPE_ACTOR_BOX, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); +} + +/** + * shell_window_preview_layout_add_window: + * @self: a #ShellWindowPreviewLayout + * @window: the #MetaWindow + * + * Creates a ClutterActor drawing the texture of @window and adds it + * to the container. If @window is already part of the preview, this + * function will do nothing. + * + * Returns: (transfer none): The newly created actor drawing @window + */ +ClutterActor * +shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self, + MetaWindow *window) +{ + ShellWindowPreviewLayoutPrivate *priv; + ClutterActor *window_actor, *actor; + WindowInfo *window_info; + GHashTableIter iter; + gpointer value; + + priv = shell_window_preview_layout_get_instance_private (self); + + g_hash_table_iter_init (&iter, priv->windows); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + WindowInfo *info = value; + + if (info->window == window) + return NULL; + } + + window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window)); + actor = clutter_clone_new (window_actor); + + window_info = g_new0 (WindowInfo, 1); + + window_info->window = window; + window_info->window_actor = window_actor; + window_info->size_changed_id = + g_signal_connect (window, "size-changed", + G_CALLBACK (on_window_size_position_changed), self); + window_info->position_changed_id = + g_signal_connect (window, "position-changed", + G_CALLBACK (on_window_size_position_changed), self); + window_info->window_actor_destroy_id = + g_signal_connect (window_actor, "destroy", + G_CALLBACK (on_window_destroyed), actor); + window_info->destroy_id = + g_signal_connect (actor, "destroy", + G_CALLBACK (on_actor_destroyed), self); + + g_hash_table_insert (priv->windows, actor, window_info); + + clutter_actor_add_child (priv->container, actor); + + on_layout_changed (self); + + return actor; +} + +/** + * shell_window_preview_layout_remove_window: + * @self: a #ShellWindowPreviewLayout + * @window: the #MetaWindow + * + * Removes a MetaWindow @window from the preview which has been added + * previously using shell_window_preview_layout_add_window(). + * If @window is not part of preview, this function will do nothing. + */ +void +shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self, + MetaWindow *window) +{ + ShellWindowPreviewLayoutPrivate *priv; + ClutterActor *actor; + WindowInfo *window_info = NULL; + GHashTableIter iter; + gpointer key, value; + + priv = shell_window_preview_layout_get_instance_private (self); + + g_hash_table_iter_init (&iter, priv->windows); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + WindowInfo *info = value; + + if (info->window == window) + { + actor = CLUTTER_ACTOR (key); + window_info = info; + break; + } + } + + if (window_info == NULL) + return; + + g_clear_signal_handler (&window_info->size_changed_id, window); + g_clear_signal_handler (&window_info->position_changed_id, window); + g_clear_signal_handler (&window_info->window_actor_destroy_id, window_info->window_actor); + g_clear_signal_handler (&window_info->destroy_id, actor); + + g_hash_table_remove (priv->windows, actor); + + clutter_actor_remove_child (priv->container, actor); + + on_layout_changed (self); +} + +/** + * shell_window_preview_layout_get_windows: + * @self: a #ShellWindowPreviewLayout + * + * Gets an array of all MetaWindows that were added to the layout + * using shell_window_preview_layout_add_window(), ordered by the + * insertion order. + * + * Returns: (transfer container) (element-type Meta.Window): The list of windows + */ +GList * +shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self) +{ + ShellWindowPreviewLayoutPrivate *priv; + GList *windows = NULL; + GHashTableIter iter; + gpointer value; + + priv = shell_window_preview_layout_get_instance_private (self); + + g_hash_table_iter_init (&iter, priv->windows); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + WindowInfo *window_info = value; + + windows = g_list_prepend (windows, window_info->window); + } + + return windows; +} diff --git a/src/shell-window-preview-layout.h b/src/shell-window-preview-layout.h new file mode 100644 index 000000000..9376b0da8 --- /dev/null +++ b/src/shell-window-preview-layout.h @@ -0,0 +1,33 @@ +#ifndef __SHELL_WINDOW_PREVIEW_LAYOUT_H__ +#define __SHELL_WINDOW_PREVIEW_LAYOUT_H__ + +G_BEGIN_DECLS + +#include + +#define SHELL_TYPE_WINDOW_PREVIEW_LAYOUT (shell_window_preview_layout_get_type ()) +G_DECLARE_FINAL_TYPE (ShellWindowPreviewLayout, shell_window_preview_layout, + SHELL, WINDOW_PREVIEW_LAYOUT, ClutterLayoutManager) + +typedef struct _ShellWindowPreviewLayout ShellWindowPreviewLayout; +typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate; + +struct _ShellWindowPreviewLayout +{ + /*< private >*/ + ClutterLayoutManager parent; + + ShellWindowPreviewLayoutPrivate *priv; +}; + +ClutterActor * shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self, + MetaWindow *window); + +void shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self, + MetaWindow *window); + +GList * shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self); + +G_END_DECLS + +#endif /* __SHELL_WINDOW_PREVIEW_LAYOUT_H__ */