diff --git a/src/DragDropAction.vala b/src/DragDropAction.vala index 91fadbd4..43de6120 100644 --- a/src/DragDropAction.vala +++ b/src/DragDropAction.vala @@ -19,15 +19,17 @@ using Clutter; namespace Gala { + [Flags] public enum DragDropActionType { - SOURCE = 0, + SOURCE, DESTINATION } public class DragDropAction : Clutter.Action { static Gee.HashMap>? sources = null; + static Gee.HashMap>? destinations = null; /** * A drag has been started. You have to connect to this signal and @@ -55,9 +57,10 @@ namespace Gala /** * The destination has been crossed * + * @param target the target actor that is crossing the destination * @param hovered indicates whether the actor is now hovered or not */ - public signal void crossed (bool hovered); + public signal void crossed (Actor? target, bool hovered); /** * Emitted on the source when a destination is crossed. @@ -97,7 +100,8 @@ namespace Gala */ public bool allow_bubbling { get; set; default = true; } - Actor? hovered = null; + public Actor? hovered { private get; set; default = null; } + bool clicked = false; float last_x; float last_y; @@ -116,6 +120,10 @@ namespace Gala if (sources == null) sources = new Gee.HashMap> (); + + if (destinations == null) + destinations = new Gee.HashMap> (); + } ~DragDropAction () @@ -139,17 +147,22 @@ namespace Gala void release_actor (Actor actor) { - if (drag_type == DragDropActionType.SOURCE) { + if (DragDropActionType.SOURCE in drag_type) { actor.button_press_event.disconnect (source_clicked); var source_list = sources.@get (drag_id); source_list.remove (actor); + } + + if (DragDropActionType.DESTINATION in drag_type) { + var dest_list = destinations[drag_id]; + dest_list.remove (actor); } } void connect_actor (Actor actor) { - if (drag_type == DragDropActionType.SOURCE) { + if (DragDropActionType.SOURCE in drag_type) { actor.button_press_event.connect (source_clicked); var source_list = sources.@get (drag_id); @@ -160,12 +173,22 @@ namespace Gala source_list.add (actor); } + + if (DragDropActionType.DESTINATION in drag_type) { + var dest_list = destinations[drag_id]; + if (dest_list == null) { + dest_list = new Gee.LinkedList (); + destinations[drag_id] = dest_list; + } + + dest_list.add (actor); + } } - void emit_crossed (Actor destination, bool hovered) + void emit_crossed (Actor destination, bool is_hovered) { - get_drag_drop_action (destination).crossed (hovered); - destination_crossed (destination, hovered); + get_drag_drop_action (destination).crossed (actor, is_hovered); + destination_crossed (destination, is_hovered); } bool source_clicked (ButtonEvent event) @@ -208,7 +231,13 @@ namespace Gala var source_list = sources.@get (drag_id); if (source_list != null) { + var dest_list = destinations[drag_id]; foreach (var actor in source_list) { + // Do not unset reactivity on destinations + if (actor in dest_list) { + continue; + } + actor.reactive = false; } } @@ -310,7 +339,7 @@ namespace Gala foreach (var action in actor.get_actions ()) { drop_action = action as DragDropAction; if (drop_action == null - || drop_action.drag_type != DragDropActionType.DESTINATION + || !(DragDropActionType.DESTINATION in drop_action.drag_type) || drop_action.drag_id != drag_id) continue; diff --git a/src/Widgets/IconGroup.vala b/src/Widgets/IconGroup.vala index 29abe310..41fa5414 100644 --- a/src/Widgets/IconGroup.vala +++ b/src/Widgets/IconGroup.vala @@ -81,8 +81,11 @@ namespace Gala } } + DragDropAction drag_action; + public Workspace workspace { get; construct; } + Actor? prev_parent = null; Actor close_button; Actor icon_container; Cogl.Material dummy_material; @@ -110,15 +113,13 @@ namespace Gala dummy_material = new Cogl.Material (); - var click = new ClickAction (); - click.clicked.connect (() => selected ()); - // when the actor is pressed, the ClickAction grabs all events, so we won't be - // notified when the cursor leaves the actor, which makes our close button stay - // forever. To fix this we hide the button for as long as the actor is pressed. - click.notify["pressed"].connect (() => { - toggle_close_button (!click.pressed && get_has_pointer ()); - }); - add_action (click); + drag_action = new DragDropAction (DragDropActionType.SOURCE | DragDropActionType.DESTINATION, "multitaskingview-window"); + drag_action.actor_clicked.connect (() => selected ()); + drag_action.drag_begin.connect (drag_begin); + drag_action.drag_end.connect (drag_end); + drag_action.drag_canceled.connect (drag_canceled); + drag_action.notify["dragging"].connect (redraw); + add_action (drag_action); icon_container = new Actor (); icon_container.width = width; @@ -213,7 +214,7 @@ namespace Gala */ public override void paint () { - if (backdrop_opacity < 1) { + if (backdrop_opacity < 1 || drag_action.dragging) { base.paint (); return; } @@ -309,6 +310,14 @@ namespace Gala } } + /** + * Sets a hovered actor for the drag action. + */ + public void set_hovered_actor (Actor actor) + { + drag_action.hovered = actor; + } + /** * Trigger a redraw */ @@ -365,7 +374,13 @@ namespace Gala 5 * scale ); - cr.set_source_rgba (0, 0, 0, 0.1); + if (drag_action.dragging) { + const double BG_COLOR = 53.0 / 255.0; + cr.set_source_rgba (BG_COLOR, BG_COLOR, BG_COLOR, 0.7); + } else { + cr.set_source_rgba (0, 0, 0, 0.1); + } + cr.fill_preserve (); cr.set_line_width (1 * scale); @@ -489,5 +504,69 @@ namespace Gala return false; } + + Actor drag_begin (float click_x, float click_y) + { + unowned Screen screen = workspace.get_screen (); + if (icon_container.get_n_children () < 1 && + Prefs.get_dynamic_workspaces () && + workspace.index () == screen.get_n_workspaces () - 1) { + return null; + } + + float abs_x, abs_y; + float prev_parent_x, prev_parent_y; + + prev_parent = get_parent (); + prev_parent.get_transformed_position (out prev_parent_x, out prev_parent_y); + + var stage = get_stage (); + var container = prev_parent as IconGroupContainer; + if (container != null) { + container.remove_group_in_place (this); + container.reset_thumbs (0); + } else { + prev_parent.remove_child (this); + } + + stage.add_child (this); + + get_transformed_position (out abs_x, out abs_y); + set_position (abs_x + prev_parent_x, abs_y + prev_parent_y); + + close_button.opacity = 0; + + return this; + } + + void drag_end (Actor destination) + { + if (destination is WorkspaceInsertThumb) { + get_parent ().remove_child (this); + + unowned WorkspaceInsertThumb inserter = (WorkspaceInsertThumb) destination; + workspace.get_screen ().reorder_workspace (workspace, inserter.workspace_index); + + restore_group (); + } else { + drag_canceled (); + } + } + + void drag_canceled () + { + get_parent ().remove_child (this); + restore_group (); + } + + void restore_group () + { + var container = prev_parent as IconGroupContainer; + if (container != null) { + container.add_group (this); + container.request_reposition (false); + container.reset_thumbs (WorkspaceInsertThumb.EXPAND_DELAY); + } + } } } diff --git a/src/Widgets/IconGroupContainer.vala b/src/Widgets/IconGroupContainer.vala index 6f5f7c25..77413270 100644 --- a/src/Widgets/IconGroupContainer.vala +++ b/src/Widgets/IconGroupContainer.vala @@ -30,7 +30,7 @@ namespace Gala public const int SPACING = 48; public const int GROUP_WIDTH = 64; - public signal void request_reposition (); + public signal void request_reposition (bool animate); public Screen screen { get; construct; } @@ -65,9 +65,50 @@ namespace Gala update_inserter_indices (); } + /** + * Removes an icon group "in place". + * When initially dragging an icon group we remove + * it and it's previous WorkspaceInsertThumb. This would make + * the container immediately reallocate and fill the empty space + * with right-most IconGroups. + * + * We don't want that until the IconGroup + * leaves the expanded WorkspaceInsertThumb. + */ + public void remove_group_in_place (IconGroup group) + { + var deleted_thumb = (WorkspaceInsertThumb) group.get_previous_sibling (); + var deleted_placeholder_thumb = (WorkspaceInsertThumb) group.get_next_sibling (); + + remove_group (group); + + /** + * We will account for that empty space + * by manually expanding the next WorkspaceInsertThumb with the + * width we deleted. Because the IconGroup is still hovering over + * the expanded thumb, we will also update the drag & drop action + * of IconGroup on that. + */ + float deleted_width = deleted_thumb.get_width () + group.get_width (); + deleted_placeholder_thumb.expanded = true; + deleted_placeholder_thumb.width += deleted_width; + group.set_hovered_actor (deleted_placeholder_thumb); + } + + public void reset_thumbs (int delay) + { + foreach (var child in get_children ()) { + unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb; + if (thumb != null) { + thumb.delay = delay; + thumb.destroy_all_children (); + } + } + } + void expanded_changed (ParamSpec param) { - request_reposition (); + request_reposition (true); } /** diff --git a/src/Widgets/MultitaskingView.vala b/src/Widgets/MultitaskingView.vala index 0c6b0b01..a15f0e98 100644 --- a/src/Widgets/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView.vala @@ -64,7 +64,7 @@ namespace Gala workspaces.set_easing_mode (AnimationMode.EASE_OUT_QUAD); icon_groups = new IconGroupContainer (screen); - icon_groups.request_reposition.connect (() => reposition_icon_groups (true)); + icon_groups.request_reposition.connect ((animate) => reposition_icon_groups (animate)); dock_clones = new Actor (); @@ -77,6 +77,7 @@ namespace Gala screen.workspace_added.connect (add_workspace); screen.workspace_removed.connect (remove_workspace); + screen.workspaces_reordered.connect (() => update_positions (false)); screen.workspace_switched.connect_after ((from, to, direction) => { update_positions (opened); }); @@ -311,7 +312,9 @@ namespace Gala workspace.window_selected.disconnect (window_selected); workspace.selected.disconnect (activate_workspace); - icon_groups.remove_group (workspace.icon_group); + if (icon_groups.contains (workspace.icon_group)) { + icon_groups.remove_group (workspace.icon_group); + } workspace.destroy (); diff --git a/src/Widgets/WindowClone.vala b/src/Widgets/WindowClone.vala index d0268c70..72d61b83 100644 --- a/src/Widgets/WindowClone.vala +++ b/src/Widgets/WindowClone.vala @@ -590,7 +590,7 @@ namespace Gala var scale = hovered ? 0.4 : 1.0; var opacity = hovered ? 0 : 255; - var duration = hovered && insert_thumb != null ? WorkspaceInsertThumb.EXPAND_DELAY : 100; + var duration = hovered && insert_thumb != null ? insert_thumb.delay : 100; window_icon.save_easing_state (); diff --git a/src/Widgets/WorkspaceClone.vala b/src/Widgets/WorkspaceClone.vala index 4a53e87a..1a22adb5 100644 --- a/src/Widgets/WorkspaceClone.vala +++ b/src/Widgets/WorkspaceClone.vala @@ -165,7 +165,7 @@ namespace Gala var background_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); background.add_action (background_drop_action); - background_drop_action.crossed.connect ((hovered) => { + background_drop_action.crossed.connect ((target, hovered) => { if (!hovered && hover_activate_timeout != 0) { Source.remove (hover_activate_timeout); hover_activate_timeout = 0; diff --git a/src/Widgets/WorkspaceInsertThumb.vala b/src/Widgets/WorkspaceInsertThumb.vala index 88afa3f0..dcd2b274 100644 --- a/src/Widgets/WorkspaceInsertThumb.vala +++ b/src/Widgets/WorkspaceInsertThumb.vala @@ -25,7 +25,8 @@ namespace Gala public const int EXPAND_DELAY = 300; public int workspace_index { get; construct set; } - public bool expanded { get; private set; default = false; } + public bool expanded { get; set; default = false; } + public int delay { get; set; default = EXPAND_DELAY; } uint expand_timeout = 0; @@ -43,8 +44,8 @@ namespace Gala x_align = Clutter.ActorAlign.CENTER; var drop = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); - drop.crossed.connect ((hovered) => { - if (!Prefs.get_dynamic_workspaces ()) + drop.crossed.connect ((target, hovered) => { + if (!Prefs.get_dynamic_workspaces () && (target != null && target is WindowClone)) return; if (!hovered) { @@ -55,7 +56,7 @@ namespace Gala transform (false); } else - expand_timeout = Timeout.add (EXPAND_DELAY, expand); + expand_timeout = Timeout.add (delay, expand); }); add_action (drop); diff --git a/vapi/libmutter.vapi b/vapi/libmutter.vapi index 165ce1f0..7e1d0c98 100644 --- a/vapi/libmutter.vapi +++ b/vapi/libmutter.vapi @@ -573,6 +573,7 @@ namespace Meta { public unowned Meta.Workspace? append_new_workspace (bool activate, uint32 timestamp); public void focus_default_window (uint32 timestamp); public unowned Meta.Workspace get_active_workspace (); + public void reorder_workspace (Meta.Workspace workspace, int new_index); public int get_active_workspace_index (); public int get_current_monitor (); #if !HAS_MUTTER324 @@ -606,6 +607,7 @@ namespace Meta { public signal void workspace_added (int object); public signal void workspace_removed (int object); public signal void workspace_switched (int object, int p0, Meta.MotionDirection p1); + public signal void workspaces_reordered (); } #if HAS_MUTTER326 [CCode (cheader_filename = "meta/meta-settings.h", has_type_id = false)]