Add ability to reorder workspaces (#464)

This commit is contained in:
Adam Bieńkowski 2019-07-16 02:01:12 +02:00 committed by Daniel Foré
parent a790d2d0f8
commit da85223ff8
8 changed files with 185 additions and 30 deletions

View File

@ -19,15 +19,17 @@ using Clutter;
namespace Gala
{
[Flags]
public enum DragDropActionType
{
SOURCE = 0,
SOURCE,
DESTINATION
}
public class DragDropAction : Clutter.Action
{
static Gee.HashMap<string,Gee.LinkedList<Actor>>? sources = null;
static Gee.HashMap<string,Gee.LinkedList<Actor>>? 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<string,Gee.LinkedList<Actor>> ();
if (destinations == null)
destinations = new Gee.HashMap<string,Gee.LinkedList<Actor>> ();
}
~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<Actor> ();
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;

View File

@ -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);
}
}
}
}

View File

@ -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);
}
/**

View File

@ -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 ();

View File

@ -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 ();

View File

@ -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;

View File

@ -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);

View File

@ -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)]