From e6b101f9e28f749180cb129225d6bd847d1d266f Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 24 Oct 2023 16:48:06 +0100 Subject: [PATCH 1/3] Move WorkspaceDot from JS to C Since ease is unavailable from C, we take the from of an abstract type with a couple vfuncs implemented by thinned down JS type. This removes JS from the sizing/allocation of dots --- js/ui/panel.js | 80 +--------- src/meson.build | 6 +- src/shell-workspace-dot.c | 317 ++++++++++++++++++++++++++++++++++++++ src/shell-workspace-dot.h | 42 +++++ 4 files changed, 369 insertions(+), 76 deletions(-) create mode 100644 src/shell-workspace-dot.c create mode 100644 src/shell-workspace-dot.h diff --git a/js/ui/panel.js b/js/ui/panel.js index 6fa493b69..8648c783c 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -4,7 +4,6 @@ import Atk from 'gi://Atk'; import Clutter from 'gi://Clutter'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; -import Graphene from 'gi://Graphene'; import Meta from 'gi://Meta'; import Shell from 'gi://Shell'; import St from 'gi://St'; @@ -19,7 +18,6 @@ import * as PopupMenu from './popupMenu.js'; import * as PanelMenu from './panelMenu.js'; import {QuickSettingsMenu, SystemIndicator} from './quickSettings.js'; import * as Main from './main.js'; -import * as Util from '../misc/util.js'; import * as RemoteAccessStatus from './status/remoteAccess.js'; import * as PowerProfileStatus from './status/powerProfiles.js'; @@ -49,7 +47,6 @@ const BUTTON_DND_ACTIVATION_TIMEOUT = 250; const N_QUICK_SETTINGS_COLUMNS = 2; -const INACTIVE_WORKSPACE_DOT_SCALE = 0.75; /** * AppMenuButton: @@ -260,68 +257,9 @@ const AppMenuButton = GObject.registerClass({ } }); -const WorkspaceDot = GObject.registerClass({ - Properties: { - 'expansion': GObject.ParamSpec.double('expansion', '', '', - GObject.ParamFlags.READWRITE, - 0.0, 1.0, 0.0), - 'width-multiplier': GObject.ParamSpec.double( - 'width-multiplier', '', '', - GObject.ParamFlags.READWRITE, - 1.0, 10.0, 1.0), - }, -}, class WorkspaceDot extends Clutter.Actor { - constructor(params = {}) { - super({ - pivot_point: new Graphene.Point({x: 0.5, y: 0.5}), - ...params, - }); - - this._dot = new St.Widget({ - style_class: 'workspace-dot', - y_align: Clutter.ActorAlign.CENTER, - pivot_point: new Graphene.Point({x: 0.5, y: 0.5}), - request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT, - }); - this.add_child(this._dot); - - this.connect('notify::width-multiplier', () => this.queue_relayout()); - this.connect('notify::expansion', () => { - this._updateVisuals(); - this.queue_relayout(); - }); - this._updateVisuals(); - - this._destroying = false; - } - - _updateVisuals() { - const {expansion} = this; - - this._dot.set({ - opacity: Util.lerp(0.50, 1.0, expansion) * 255, - scaleX: Util.lerp(INACTIVE_WORKSPACE_DOT_SCALE, 1.0, expansion), - scaleY: Util.lerp(INACTIVE_WORKSPACE_DOT_SCALE, 1.0, expansion), - }); - } - - vfunc_get_preferred_width(forHeight) { - const factor = Util.lerp(1.0, this.widthMultiplier, this.expansion); - return this._dot.get_preferred_width(forHeight).map(v => Math.round(v * factor)); - } - - vfunc_get_preferred_height(forWidth) { - return this._dot.get_preferred_height(forWidth); - } - - vfunc_allocate(box) { - this.set_allocation(box); - - box.set_origin(0, 0); - this._dot.allocate(box); - } - - scaleIn() { +const WorkspaceDot = GObject.registerClass( +class WorkspaceDot extends Shell.WorkspaceDot { + vfunc_scale_in() { this.set({ scale_x: 0, scale_y: 0, @@ -335,9 +273,7 @@ const WorkspaceDot = GObject.registerClass({ }); } - scaleOutAndDestroy() { - this._destroying = true; - + vfunc_scale_out_and_destroy() { this.ease({ duration: 500, mode: Clutter.AnimationMode.EASE_OUT_CUBIC, @@ -346,10 +282,6 @@ const WorkspaceDot = GObject.registerClass({ onComplete: () => this.destroy(), }); } - - get destroying() { - return this._destroying; - } }); const WorkspaceIndicators = GObject.registerClass( @@ -382,10 +314,10 @@ class WorkspaceIndicators extends St.BoxLayout { if (nIndicators < targetIndicators) { const indicator = new WorkspaceDot(); this.add_child(indicator); - indicator.scaleIn(); + indicator.scale_in(); } else { const indicator = activeIndicators[nIndicators - remaining - 1]; - indicator.scaleOutAndDestroy(); + indicator.scale_out_and_destroy(); } } diff --git a/src/meson.build b/src/meson.build index 070312bd0..b7b645f21 100644 --- a/src/meson.build +++ b/src/meson.build @@ -116,7 +116,8 @@ libshell_public_headers = [ 'shell-window-preview-layout.h', 'shell-window-tracker.h', 'shell-wm.h', - 'shell-workspace-background.h' + 'shell-workspace-background.h', + 'shell-workspace-dot.h', ] if have_networkmanager @@ -161,7 +162,8 @@ libshell_sources = [ 'shell-window-preview-layout.c', 'shell-window-tracker.c', 'shell-wm.c', - 'shell-workspace-background.c' + 'shell-workspace-background.c', + 'shell-workspace-dot.c', ] if have_networkmanager diff --git a/src/shell-workspace-dot.c b/src/shell-workspace-dot.c new file mode 100644 index 000000000..c82dfb12e --- /dev/null +++ b/src/shell-workspace-dot.c @@ -0,0 +1,317 @@ +/* shell-workspace-dot.c + * + * Copyright 2023 Zander Brown + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include "shell-workspace-dot.h" + +#define INACTIVE_WORKSPACE_DOT_SCALE 0.75 + +typedef struct _ShellWorkspaceDotPrivate ShellWorkspaceDotPrivate; +struct _ShellWorkspaceDotPrivate +{ + ClutterActor *dot; + + float width_multiplier; + float expansion; + + gboolean destroying; +}; + +enum +{ + PROP_0, + + PROP_WIDTH_MULTIPLIER, + PROP_EXPANSION, + PROP_DESTROYING, + + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST] = { NULL, }; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ShellWorkspaceDot, shell_workspace_dot, CLUTTER_TYPE_ACTOR) + +static void +shell_workspace_dot_dispose (GObject *object) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (SHELL_WORKSPACE_DOT (object)); + + g_clear_object (&priv->dot); + + G_OBJECT_CLASS (shell_workspace_dot_parent_class)->dispose (object); +} + + +static inline gboolean +value_changed (float old_value, float new_value) +{ + return !G_APPROX_VALUE (old_value, new_value, FLT_EPSILON); +} + + +static gboolean +set_if_changed (GObject *object, + GParamSpec *pspec, + float *target, + const GValue *value) +{ + float new_value = g_value_get_double (value); + + if (G_LIKELY (value_changed (*target, new_value))) + { + *target = new_value; + g_object_notify_by_pspec (object, pspec); + return TRUE; + } + + return FALSE; +} + +static inline float +lerp (float start, float end, float progress) +{ + return start + progress * (end - start); +} + +static void +update_visuals (ShellWorkspaceDot *self) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (self); + float scale = lerp (INACTIVE_WORKSPACE_DOT_SCALE, 1.0, priv->expansion); + + clutter_actor_set_opacity (priv->dot, + lerp (0.50, 1.0, priv->expansion) * 255); + clutter_actor_set_scale (priv->dot, scale, scale); +} + +static void +shell_workspace_dot_set_property (GObject *object, + unsigned int property_id, + const GValue *value, + GParamSpec *pspec) +{ + ShellWorkspaceDot *self = SHELL_WORKSPACE_DOT (object); + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (self); + + switch (property_id) + { + case PROP_WIDTH_MULTIPLIER: + if (set_if_changed (object, pspec, &priv->width_multiplier, value)) + { + clutter_actor_queue_relayout (CLUTTER_ACTOR (object)); + } + break; + case PROP_EXPANSION: + if (set_if_changed (object, pspec, &priv->expansion, value)) + { + update_visuals (self); + clutter_actor_queue_relayout (CLUTTER_ACTOR (object)); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +shell_workspace_dot_get_property (GObject *object, + unsigned int property_id, + GValue *value, + GParamSpec *pspec) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (SHELL_WORKSPACE_DOT (object)); + + switch (property_id) + { + case PROP_WIDTH_MULTIPLIER: + g_value_set_double (value, priv->width_multiplier); + break; + case PROP_EXPANSION: + g_value_set_double (value, priv->expansion); + break; + case PROP_DESTROYING: + g_value_set_boolean (value, priv->destroying); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +shell_workspace_dot_get_preferred_width (ClutterActor *actor, + float for_height, + float *min_width_p, + float *natural_width_p) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (SHELL_WORKSPACE_DOT (actor)); + float factor = lerp (1.0, priv->width_multiplier, priv->expansion); + float min, nat; + + clutter_actor_get_preferred_width (priv->dot, for_height, &min, &nat); + + if (min_width_p) + *min_width_p = min * factor; + + if (natural_width_p) + *natural_width_p = nat * factor; +} + +static void +shell_workspace_dot_get_preferred_height (ClutterActor *actor, + float for_width, + float *min_height_p, + float *natural_height_p) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (SHELL_WORKSPACE_DOT (actor)); + + clutter_actor_get_preferred_height (priv->dot, + for_width, + min_height_p, + natural_height_p); +} + +static void +shell_workspace_dot_allocate (ClutterActor *actor, + const ClutterActorBox *box) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (SHELL_WORKSPACE_DOT (actor)); + ClutterActorBox dot_box = *box; + + clutter_actor_set_allocation (actor, box); + + clutter_actor_box_set_origin (&dot_box, 0, 0); + clutter_actor_allocate (priv->dot, &dot_box); +} + +static void +shell_workspace_dot_class_init (ShellWorkspaceDotClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + object_class->dispose = shell_workspace_dot_dispose; + object_class->set_property = shell_workspace_dot_set_property; + object_class->get_property = shell_workspace_dot_get_property; + + actor_class->get_preferred_width = shell_workspace_dot_get_preferred_width; + actor_class->get_preferred_height = shell_workspace_dot_get_preferred_height; + actor_class->allocate = shell_workspace_dot_allocate; + + obj_props[PROP_WIDTH_MULTIPLIER] = + g_param_spec_double ("width-multiplier", NULL, NULL, + 1.0, 10.0, 1.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + obj_props[PROP_EXPANSION] = + g_param_spec_double ("expansion", NULL, NULL, + 0.0, 1.0, 0.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ShellWorkspaceDot:destroying: (attributes org.gtk.Property.get=shell_workspace_dot_is_destroying) + */ + obj_props[PROP_DESTROYING] = + g_param_spec_boolean ("destroying", NULL, NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); +} + +static void +shell_workspace_dot_init (ShellWorkspaceDot *self) +{ + ShellWorkspaceDotPrivate *priv = + shell_workspace_dot_get_instance_private (self); + + clutter_actor_set_pivot_point (CLUTTER_ACTOR (self), 0.5, 0.5); + + priv->dot = g_object_new (ST_TYPE_WIDGET, + "style-class", "workspace-dot", + "y-align", CLUTTER_ACTOR_ALIGN_CENTER, + "pivot-point", &GRAPHENE_POINT_INIT (0.5, 0.5), + "request-mode", CLUTTER_REQUEST_WIDTH_FOR_HEIGHT, + NULL); + g_object_ref_sink (priv->dot); + clutter_actor_add_child (CLUTTER_ACTOR (self), priv->dot); + + priv->destroying = FALSE; + + update_visuals (self); +} + +gboolean +shell_workspace_dot_is_destroying (ShellWorkspaceDot *self) +{ + ShellWorkspaceDotPrivate *priv; + + g_return_val_if_fail (SHELL_IS_WORKSPACE_DOT (self), FALSE); + + priv = shell_workspace_dot_get_instance_private (self); + + return priv->destroying; +} + +void +shell_workspace_dot_scale_in (ShellWorkspaceDot *self) +{ + ShellWorkspaceDotClass *klass; + + g_return_if_fail (SHELL_IS_WORKSPACE_DOT (self)); + + klass = SHELL_WORKSPACE_DOT_GET_CLASS (self); + + if (G_LIKELY (klass->scale_in)) + klass->scale_in (self); + else + g_warning ("%s didn't override scale_in", G_OBJECT_TYPE_NAME (self)); +} + +void +shell_workspace_dot_scale_out_and_destroy (ShellWorkspaceDot *self) +{ + ShellWorkspaceDotPrivate *priv; + ShellWorkspaceDotClass *klass; + + g_return_if_fail (SHELL_IS_WORKSPACE_DOT (self)); + + priv = shell_workspace_dot_get_instance_private (self); + priv->destroying = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DESTROYING]); + + klass = SHELL_WORKSPACE_DOT_GET_CLASS (self); + + if (G_LIKELY (klass->scale_out_and_destroy)) + klass->scale_out_and_destroy (self); + else + g_warning ("%s didn't override scale_out_and_destroy", + G_OBJECT_TYPE_NAME (self)); +} diff --git a/src/shell-workspace-dot.h b/src/shell-workspace-dot.h new file mode 100644 index 000000000..c8178cc77 --- /dev/null +++ b/src/shell-workspace-dot.h @@ -0,0 +1,42 @@ +/* shell-workspace-dot.h + * + * Copyright 2023 Zander Brown + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SHELL_TYPE_WORKSPACE_DOT (shell_workspace_dot_get_type ()) +G_DECLARE_DERIVABLE_TYPE (ShellWorkspaceDot, shell_workspace_dot, SHELL, WORKSPACE_DOT, ClutterActor) + +struct _ShellWorkspaceDotClass +{ + ClutterActorClass parent_class; + + void (*scale_in) (ShellWorkspaceDot *dot); + void (*scale_out_and_destroy) (ShellWorkspaceDot *dot); +}; + +gboolean shell_workspace_dot_is_destroying (ShellWorkspaceDot *self); +void shell_workspace_dot_scale_in (ShellWorkspaceDot *self); +void shell_workspace_dot_scale_out_and_destroy (ShellWorkspaceDot *self); + +G_END_DECLS From e1763f630a9a0a30d7040121e7a6667ff550abdf Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 24 Oct 2023 19:02:32 +0100 Subject: [PATCH 2/3] Move WorkspaceIndicators from JS to C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids calling JS when switching workspace Of course when adding/removing workspaces we still hit JS for ease(), but this should keep us in C for more ‘idle’ cases --- js/ui/panel.js | 64 +------- src/meson.build | 2 + src/shell-workspace-indicators.c | 253 +++++++++++++++++++++++++++++++ src/shell-workspace-indicators.h | 37 +++++ 4 files changed, 300 insertions(+), 56 deletions(-) create mode 100644 src/shell-workspace-indicators.c create mode 100644 src/shell-workspace-indicators.h diff --git a/js/ui/panel.js b/js/ui/panel.js index 8648c783c..e29be2653 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -285,61 +285,11 @@ class WorkspaceDot extends Shell.WorkspaceDot { }); const WorkspaceIndicators = GObject.registerClass( -class WorkspaceIndicators extends St.BoxLayout { - constructor() { - super(); - - this._workspacesAdjustment = Main.createWorkspacesAdjustment(this); - this._workspacesAdjustment.connectObject( - 'notify::value', () => this._updateExpansion(), - 'notify::upper', () => this._recalculateDots(), - this); - - for (let i = 0; i < this._workspacesAdjustment.upper; i++) - this.insert_child_at_index(new WorkspaceDot(), i); - this._updateExpansion(); - } - - _getActiveIndicators() { - return [...this].filter(i => !i.destroying); - } - - _recalculateDots() { - const activeIndicators = this._getActiveIndicators(); - const nIndicators = activeIndicators.length; - const targetIndicators = this._workspacesAdjustment.upper; - - let remaining = Math.abs(nIndicators - targetIndicators); - while (remaining--) { - if (nIndicators < targetIndicators) { - const indicator = new WorkspaceDot(); - this.add_child(indicator); - indicator.scale_in(); - } else { - const indicator = activeIndicators[nIndicators - remaining - 1]; - indicator.scale_out_and_destroy(); - } - } - - this._updateExpansion(); - } - - _updateExpansion() { - const nIndicators = this._getActiveIndicators().length; - const activeWorkspace = this._workspacesAdjustment.value; - - let widthMultiplier; - if (nIndicators <= 2) - widthMultiplier = 3.625; - else if (nIndicators <= 5) - widthMultiplier = 3.25; - else - widthMultiplier = 2.75; - - this.get_children().forEach((indicator, index) => { - const distance = Math.abs(index - activeWorkspace); - indicator.expansion = Math.clamp(1 - distance, 0, 1); - indicator.widthMultiplier = widthMultiplier; +class WorkspaceIndicators extends Shell.WorkspaceIndicators { + constructor(params = {}) { + super({ + dot_type: WorkspaceDot.$gtype, + ...params, }); } }); @@ -357,7 +307,9 @@ class ActivitiesButton extends PanelMenu.Button { accessible_name: _('Activities'), }); - this.add_child(new WorkspaceIndicators()); + this.add_child(new WorkspaceIndicators({ + workspaces_adjustment: Main.createWorkspacesAdjustment(this), + })); Main.overview.connect('showing', () => { this.add_style_pseudo_class('checked'); diff --git a/src/meson.build b/src/meson.build index b7b645f21..17583772d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -118,6 +118,7 @@ libshell_public_headers = [ 'shell-wm.h', 'shell-workspace-background.h', 'shell-workspace-dot.h', + 'shell-workspace-indicators.h', ] if have_networkmanager @@ -164,6 +165,7 @@ libshell_sources = [ 'shell-wm.c', 'shell-workspace-background.c', 'shell-workspace-dot.c', + 'shell-workspace-indicators.c', ] if have_networkmanager diff --git a/src/shell-workspace-indicators.c b/src/shell-workspace-indicators.c new file mode 100644 index 000000000..271309066 --- /dev/null +++ b/src/shell-workspace-indicators.c @@ -0,0 +1,253 @@ +/* shell-workspace-dot.c + * + * Copyright 2023 Zander Brown + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "shell-workspace-dot.h" +#include "shell-workspace-indicators.h" + +typedef struct _ShellWorkspaceIndicatorsPrivate ShellWorkspaceIndicatorsPrivate; +struct _ShellWorkspaceIndicatorsPrivate +{ + GType dot_type; + StAdjustment *workspaces_adjustment; +}; + +enum +{ + PROP_0, + + PROP_DOT_TYPE, + PROP_WORKSPACES_ADJUSTMENT, + + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST] = { NULL, }; + +G_DEFINE_TYPE_WITH_PRIVATE (ShellWorkspaceIndicators, shell_workspace_indicators, ST_TYPE_BOX_LAYOUT) + +static void +shell_workspace_indicators_dispose (GObject *object) +{ + ShellWorkspaceIndicatorsPrivate *priv = + shell_workspace_indicators_get_instance_private (SHELL_WORKSPACE_INDICATORS (object)); + + g_clear_object (&priv->workspaces_adjustment); + + G_OBJECT_CLASS (shell_workspace_indicators_parent_class)->dispose (object); +} + +static GPtrArray * +get_active_dots (ShellWorkspaceIndicators *self) +{ + GPtrArray *active = g_ptr_array_new (); + ClutterActor *child; + + for (child = clutter_actor_get_first_child (CLUTTER_ACTOR (self)); + child != NULL; + child = clutter_actor_get_next_sibling (child)) + { + if (!shell_workspace_dot_is_destroying (SHELL_WORKSPACE_DOT (child))) + g_ptr_array_add (active, child); + } + + return active; +} + +static void +update_expansion (ShellWorkspaceIndicators *self) +{ + ShellWorkspaceIndicatorsPrivate *priv; + g_autoptr (GPtrArray) active_dots = NULL; + size_t n_indicators; + double active_workspace, width_multiplier; + size_t index = 0; + + g_return_if_fail (SHELL_IS_WORKSPACE_INDICATORS (self)); + + priv = shell_workspace_indicators_get_instance_private (self); + + active_dots = get_active_dots (self); + n_indicators = active_dots->len; + active_workspace = st_adjustment_get_value (priv->workspaces_adjustment); + + if (n_indicators <= 2) + width_multiplier = 3.625; + else if (n_indicators <= 5) + width_multiplier = 3.25; + else + width_multiplier = 2.75; + + for (ClutterActor *child = clutter_actor_get_first_child (CLUTTER_ACTOR (self)); + child != NULL; + index++, child = clutter_actor_get_next_sibling (child)) + { + double distance = fabs (((double) index) - active_workspace); + + g_object_set (child, + "expansion", CLAMP (1.0 - distance, 0.0, 1.0), + "width-multiplier", width_multiplier, + NULL); + } +} + +static void +recalculate_dots (ShellWorkspaceIndicators *self) +{ + ShellWorkspaceIndicatorsPrivate *priv = + shell_workspace_indicators_get_instance_private (self); + ShellWorkspaceDot *dot; + g_autoptr (GPtrArray) active_dots = get_active_dots (self); + size_t remaining; + double target; + + st_adjustment_get_values (priv->workspaces_adjustment, + NULL, + NULL, + &target, + NULL, + NULL, + NULL); + + remaining = abs (active_dots->len - target); + + while (remaining--) + if (active_dots->len < target) + { + dot = g_object_new (priv->dot_type, NULL); + clutter_actor_add_child (CLUTTER_ACTOR (self), CLUTTER_ACTOR (dot)); + shell_workspace_dot_scale_in (dot); + } + else + { + dot = g_ptr_array_index (active_dots, + active_dots->len - remaining - 1); + shell_workspace_dot_scale_out_and_destroy (dot); + } + + update_expansion (self); +} + +static void +shell_workspace_indicators_set_property (GObject *object, + unsigned int property_id, + const GValue *value, + GParamSpec *pspec) +{ + ShellWorkspaceIndicators *self = SHELL_WORKSPACE_INDICATORS (object); + ShellWorkspaceIndicatorsPrivate *priv = + shell_workspace_indicators_get_instance_private (self); + ShellWorkspaceDot *dot; + double upper; + + switch (property_id) + { + case PROP_DOT_TYPE: + priv->dot_type = g_value_get_gtype (value); + g_object_notify_by_pspec (object, pspec); + break; + case PROP_WORKSPACES_ADJUSTMENT: + priv->workspaces_adjustment = g_value_dup_object (value); + g_signal_connect_object (priv->workspaces_adjustment, + "notify::value", + G_CALLBACK (update_expansion), + object, + G_CONNECT_SWAPPED); + g_signal_connect_object (priv->workspaces_adjustment, + "notify::upper", + G_CALLBACK (recalculate_dots), + object, + G_CONNECT_SWAPPED); + + st_adjustment_get_values (priv->workspaces_adjustment, + NULL, + NULL, + &upper, + NULL, + NULL, + NULL); + + for (size_t i = 0; i < ((size_t) upper); i++) + { + dot = g_object_new (priv->dot_type, NULL); + clutter_actor_insert_child_at_index (CLUTTER_ACTOR (self), + CLUTTER_ACTOR (dot), + i); + } + update_expansion (self); + + g_object_notify_by_pspec (object, pspec); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +shell_workspace_indicators_get_property (GObject *object, + unsigned int property_id, + GValue *value, + GParamSpec *pspec) +{ + ShellWorkspaceIndicators *self = SHELL_WORKSPACE_INDICATORS (object); + ShellWorkspaceIndicatorsPrivate *priv = + shell_workspace_indicators_get_instance_private (self); + + switch (property_id) + { + case PROP_DOT_TYPE: + g_value_set_gtype (value, priv->dot_type); + break; + case PROP_WORKSPACES_ADJUSTMENT: + g_value_set_object (value, priv->workspaces_adjustment); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +shell_workspace_indicators_class_init (ShellWorkspaceIndicatorsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = shell_workspace_indicators_dispose; + object_class->set_property = shell_workspace_indicators_set_property; + object_class->get_property = shell_workspace_indicators_get_property; + + obj_props[PROP_DOT_TYPE] = + g_param_spec_gtype ("dot-type", NULL, NULL, + SHELL_TYPE_WORKSPACE_DOT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + obj_props[PROP_WORKSPACES_ADJUSTMENT] = + g_param_spec_object ("workspaces-adjustment", NULL, NULL, + ST_TYPE_ADJUSTMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); +} + +static void +shell_workspace_indicators_init (ShellWorkspaceIndicators *self) +{ +} diff --git a/src/shell-workspace-indicators.h b/src/shell-workspace-indicators.h new file mode 100644 index 000000000..307933b81 --- /dev/null +++ b/src/shell-workspace-indicators.h @@ -0,0 +1,37 @@ +/* shell-workspace-indicators.h + * + * Copyright 2023 Zander Brown + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "shell-workspace-dot.h" + +G_BEGIN_DECLS + +#define SHELL_TYPE_WORKSPACE_INDICATORS (shell_workspace_indicators_get_type ()) +G_DECLARE_DERIVABLE_TYPE (ShellWorkspaceIndicators, shell_workspace_indicators, SHELL, WORKSPACE_INDICATORS, StBoxLayout) + +struct _ShellWorkspaceIndicatorsClass +{ + StBoxLayoutClass parent_class; +}; + +G_END_DECLS From 26f15db9288862c7e5a9c778714c4c0ffa1ef728 Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 24 Oct 2023 23:35:02 +0100 Subject: [PATCH 3/3] shell/workspace-dot: Avoid excess marshalling No need for the overhead of via properties when we can just have a method to call in the tight loop in update_expansion --- src/shell-workspace-dot.c | 98 ++++++++++++++++++++------------ src/shell-workspace-dot.h | 3 + src/shell-workspace-indicators.c | 13 ++--- 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/src/shell-workspace-dot.c b/src/shell-workspace-dot.c index c82dfb12e..007416a24 100644 --- a/src/shell-workspace-dot.c +++ b/src/shell-workspace-dot.c @@ -64,32 +64,6 @@ shell_workspace_dot_dispose (GObject *object) G_OBJECT_CLASS (shell_workspace_dot_parent_class)->dispose (object); } - -static inline gboolean -value_changed (float old_value, float new_value) -{ - return !G_APPROX_VALUE (old_value, new_value, FLT_EPSILON); -} - - -static gboolean -set_if_changed (GObject *object, - GParamSpec *pspec, - float *target, - const GValue *value) -{ - float new_value = g_value_get_double (value); - - if (G_LIKELY (value_changed (*target, new_value))) - { - *target = new_value; - g_object_notify_by_pspec (object, pspec); - return TRUE; - } - - return FALSE; -} - static inline float lerp (float start, float end, float progress) { @@ -121,17 +95,14 @@ shell_workspace_dot_set_property (GObject *object, switch (property_id) { case PROP_WIDTH_MULTIPLIER: - if (set_if_changed (object, pspec, &priv->width_multiplier, value)) - { - clutter_actor_queue_relayout (CLUTTER_ACTOR (object)); - } + shell_workspace_dot_set_state (self, + priv->expansion, + g_value_get_double (value)); break; case PROP_EXPANSION: - if (set_if_changed (object, pspec, &priv->expansion, value)) - { - update_visuals (self); - clutter_actor_queue_relayout (CLUTTER_ACTOR (object)); - } + shell_workspace_dot_set_state (self, + g_value_get_double (value), + priv->width_multiplier); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -268,6 +239,63 @@ shell_workspace_dot_init (ShellWorkspaceDot *self) update_visuals (self); } +static inline gboolean +value_changed (float old_value, float new_value) +{ + return !G_APPROX_VALUE (old_value, new_value, FLT_EPSILON); +} + +static gboolean +set_if_changed (GObject *object, + GParamSpec *pspec, + float *target, + float new_value) +{ + if (G_LIKELY (value_changed (*target, new_value))) + { + *target = new_value; + g_object_notify_by_pspec (object, pspec); + return TRUE; + } + + return FALSE; +} + +void +shell_workspace_dot_set_state (ShellWorkspaceDot *self, + float expansion, + float width_multiplier) +{ + ShellWorkspaceDotPrivate *priv; + GObject *object = G_OBJECT (self); + gboolean trigger_layout = FALSE; + + g_return_if_fail (SHELL_IS_WORKSPACE_DOT (self)); + + priv = shell_workspace_dot_get_instance_private (self); + + g_object_freeze_notify (object); + + trigger_layout = set_if_changed (object, + obj_props[PROP_WIDTH_MULTIPLIER], + &priv->width_multiplier, + width_multiplier); + + if (set_if_changed (object, + obj_props[PROP_EXPANSION], + &priv->expansion, + expansion)) + { + update_visuals (self); + trigger_layout = TRUE; + } + + g_object_thaw_notify (object); + + if (G_LIKELY (trigger_layout)) + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + gboolean shell_workspace_dot_is_destroying (ShellWorkspaceDot *self) { diff --git a/src/shell-workspace-dot.h b/src/shell-workspace-dot.h index c8178cc77..73b9c8dd2 100644 --- a/src/shell-workspace-dot.h +++ b/src/shell-workspace-dot.h @@ -35,6 +35,9 @@ struct _ShellWorkspaceDotClass void (*scale_out_and_destroy) (ShellWorkspaceDot *dot); }; +void shell_workspace_dot_set_state (ShellWorkspaceDot *self, + float expansion, + float width_multiplier); gboolean shell_workspace_dot_is_destroying (ShellWorkspaceDot *self); void shell_workspace_dot_scale_in (ShellWorkspaceDot *self); void shell_workspace_dot_scale_out_and_destroy (ShellWorkspaceDot *self); diff --git a/src/shell-workspace-indicators.c b/src/shell-workspace-indicators.c index 271309066..a61a8bc9f 100644 --- a/src/shell-workspace-indicators.c +++ b/src/shell-workspace-indicators.c @@ -80,7 +80,7 @@ update_expansion (ShellWorkspaceIndicators *self) ShellWorkspaceIndicatorsPrivate *priv; g_autoptr (GPtrArray) active_dots = NULL; size_t n_indicators; - double active_workspace, width_multiplier; + float active_workspace, width_multiplier; size_t index = 0; g_return_if_fail (SHELL_IS_WORKSPACE_INDICATORS (self)); @@ -102,12 +102,11 @@ update_expansion (ShellWorkspaceIndicators *self) child != NULL; index++, child = clutter_actor_get_next_sibling (child)) { - double distance = fabs (((double) index) - active_workspace); - - g_object_set (child, - "expansion", CLAMP (1.0 - distance, 0.0, 1.0), - "width-multiplier", width_multiplier, - NULL); + float distance = fabsf (((float) index) - active_workspace); + + shell_workspace_dot_set_state (SHELL_WORKSPACE_DOT (child), + CLAMP (1.0 - distance, 0.0, 1.0), + width_multiplier); } }