Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DnD reordering #181

Merged
merged 41 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
209c651
Implement DnD reordering
leolost2605 Jul 26, 2023
4c63bac
Remove app by dragging into nothing + remember modifications
leolost2605 Jul 26, 2023
f9382c6
Formatting
leolost2605 Jul 26, 2023
25ea032
Naming
leolost2605 Jul 26, 2023
2a8dc4d
Remove leftover
leolost2605 Jul 26, 2023
46c8d8c
Work around bug
leolost2605 Jul 26, 2023
85b1503
Animate reordering fix issues
leolost2605 Jul 29, 2023
2ea006f
Make a popover to prepare for poof animation
leolost2605 Jul 29, 2023
89d8ae9
Add poof animation
leolost2605 Jul 29, 2023
d34fa98
Remove test mp4
leolost2605 Jul 29, 2023
65a61e7
Cleanup
leolost2605 Jul 29, 2023
02d3333
Fix some issues, add license, cleanup
leolost2605 Jul 29, 2023
7f185d1
Improve reordering behavior and fix lint
leolost2605 Jul 29, 2023
d2ec1e7
Add some animations - prototype
leolost2605 Jul 30, 2023
f03e1ff
Cleanup
leolost2605 Jul 30, 2023
eb21f7b
Fix small line where drop could fail
leolost2605 Jul 30, 2023
6f66f81
Apply suggestions from code review
leolost2605 Jul 30, 2023
51849de
More suggestions from code review
leolost2605 Jul 30, 2023
b888a30
Increase poof size
leolost2605 Jul 30, 2023
1808721
Add queue_resize
leolost2605 Jul 30, 2023
4936890
Fix poof alignment
leolost2605 Jul 30, 2023
c9eaeb1
Remove queue_resize
leolost2605 Jul 30, 2023
bb8123d
Remove whitespace
leolost2605 Jul 30, 2023
26d5a9d
Don't allow removing apps with windows
leolost2605 Jul 30, 2023
a9f89a8
Unpin pinned launchers when removed
leolost2605 Jul 30, 2023
861d221
Maybe fix glitches
leolost2605 Jul 30, 2023
29fa1ad
Add comment
leolost2605 Jul 30, 2023
40f5efd
Remove launcher from hashmap when removing
leolost2605 Jul 30, 2023
f4dc073
Move drag source signal handlers into their own methods
leolost2605 Jul 30, 2023
5aaf537
Cleanup
leolost2605 Jul 30, 2023
52d391d
When unpinning app with open windows move launcher to last position
leolost2605 Jul 30, 2023
1d9bbf1
Improve naming
leolost2605 Aug 1, 2023
80af33d
Merge branch 'main' into dnd-reordering
zeebok Aug 24, 2023
8d4a735
Apply suggestions from code review
leolost2605 Sep 18, 2023
a2ffaa9
Add comment
leolost2605 Sep 18, 2023
a888373
Merge branch 'main' into dnd-reordering
lenemter Oct 5, 2023
8433157
Merge branch 'main' into dnd-reordering
leolost2605 Nov 11, 2023
17f99fe
Fix crash on dnd remove
leolost2605 Nov 11, 2023
211e1cc
Add comments + cleanup
leolost2605 Nov 11, 2023
d6354aa
Cleanup
leolost2605 Nov 11, 2023
cfd15c4
Merge branch 'main' into dnd-reordering
leolost2605 Nov 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions data/Launcher.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,31 @@ launcher image {
transform: translatey(-20px);
}
}

.move-right {
animation: move-right 300ms;
animation-direction: normal;
}

@keyframes move-right {
from {
transform: translatex(-60px);
}
to {
transform: translatex(0);
}
}

.move-left {
animation: move-left 300ms;
animation-direction: normal;
}

@keyframes move-left {
from {
transform: translatex(60px);
}
to {
transform: translatex(0);
}
}
1 change: 1 addition & 0 deletions data/dock.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
<gresource prefix="/io/elementary/dock">
<file compressed="true">MainWindow.css</file>
<file compressed="true">Launcher.css</file>
<file compressed="true">poof.svg</file>
</gresource>
</gresources>
114 changes: 114 additions & 0 deletions data/poof.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
141 changes: 139 additions & 2 deletions src/Launcher.vala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@
*/

public class Dock.Launcher : Gtk.Button {
// Matches icon size and padding in Launcher.css
public const int ICON_SIZE = 48;
public const int PADDING = 6;

public GLib.DesktopAppInfo app_info { get; construct; }
public bool pinned { get; set; }

public GLib.List<AppWindow> windows { get; private owned set; }

private static Gtk.CssProvider css_provider;

private Gtk.Image image;
private int drag_offset_x = 0;
private int drag_offset_y = 0;
private string animate_css_class_name = "";
private uint animate_timeout_id = 0;

private Gtk.PopoverMenu popover;

public Launcher (GLib.DesktopAppInfo app_info) {
Expand Down Expand Up @@ -56,14 +66,34 @@ public class Dock.Launcher : Gtk.Button {
};
popover.set_parent (this);

var image = new Gtk.Image () {
image = new Gtk.Image () {
gicon = app_info.get_icon ()
};
image.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);

child = image;
// Needed to work around DnD bug where it
// would stop working once the button got clicked
var box = new Gtk.Box (VERTICAL, 0);
box.append (image);

child = box;
tooltip_text = app_info.get_display_name ();

var drag_source = new Gtk.DragSource () {
actions = MOVE
};
box.add_controller (drag_source);
drag_source.prepare.connect (on_drag_prepare);
drag_source.drag_begin.connect (on_drag_begin);
drag_source.drag_cancel.connect (on_drag_cancel);
drag_source.drag_end.connect (() => image.gicon = app_info.get_icon ());

var drop_target = new Gtk.DropTarget (typeof (Launcher), MOVE) {
preload = true
};
box.add_controller (drop_target);
drop_target.enter.connect (on_drop_enter);

notify["pinned"].connect (() => ((MainWindow) get_root ()).sync_pinned ());

var gesture_click = new Gtk.GestureClick () {
Expand Down Expand Up @@ -103,6 +133,30 @@ public class Dock.Launcher : Gtk.Button {
});
}

public void animate_move (Gtk.DirectionType dir) {
if (animate_timeout_id != 0) {
Source.remove (animate_timeout_id);
animate_timeout_id = 0;
remove_css_class (animate_css_class_name);
}

if (dir == LEFT) {
animate_css_class_name = "move-left";
} else if (dir == RIGHT) {
animate_css_class_name = "move-right";
} else {
warning ("Invalid direction type.");
return;
}

add_css_class (animate_css_class_name);
animate_timeout_id = Timeout.add (300, () => {
remove_css_class (animate_css_class_name);
animate_timeout_id = 0;
return Source.REMOVE;
});
}

public void update_windows (owned GLib.List<AppWindow>? new_windows) {
if (new_windows == null) {
windows = new GLib.List<AppWindow> ();
Expand All @@ -128,4 +182,87 @@ public class Dock.Launcher : Gtk.Button {
return null;
}
}

private Gdk.ContentProvider? on_drag_prepare (double x, double y) {
drag_offset_x = (int) x;
drag_offset_y = (int) y;

var val = Value (typeof (Launcher));
val.set_object (this);
return new Gdk.ContentProvider.for_value (val);
}

private void on_drag_begin (Gtk.DragSource drag_source, Gdk.Drag drag) {
var paintable = new Gtk.WidgetPaintable (image); //Maybe TODO How TF can I get a paintable from a gicon?!?!?
drag_source.set_icon (paintable.get_current_image (), drag_offset_x, drag_offset_y);
image.clear ();
}

private bool on_drag_cancel (Gdk.Drag drag, Gdk.DragCancelReason reason) {
if (pinned && reason == NO_TARGET) {
var popover = new PoofPopover ();

unowned var window = (MainWindow) get_root ();
popover.set_parent (window);
unowned var surface = window.get_surface ();

double x, y;
surface.get_device_position (drag.device, out x, out y, null);

var rect = Gdk.Rectangle () {
x = (int) x,
y = (int) y
};

popover.set_pointing_to (rect);
// 50 and -13 position the popover in a way that the cursor is in the top left corner.
// (TODO: I got this with trial and error and I very much doubt that will be the same everywhere
// and at different scalings so it needs testing.)
// Although the drag_offset is also measured from the top left corner it works
// the other way round (i.e it moves the cursor not the surface)
// than set_offset so we put a - in front.
popover.set_offset (
50 - (drag_offset_x * (popover.width_request / ICON_SIZE)),
-13 - (drag_offset_y * (popover.height_request / ICON_SIZE))
);
popover.popup ();
popover.start_animation ();

var box = (Gtk.Box) parent;
if (!windows.is_empty ()) {
window.move_launcher_after (this, (Launcher) box.get_last_child ());
}

pinned = false;

return true;
} else {
image.gicon = app_info.get_icon ();
return false;
}
}

private Gdk.DragAction on_drop_enter (Gtk.DropTarget drop_target, double x, double y) {
var val = drop_target.get_value ();
if (val != null) {
var obj = val.get_object ();

if (obj != null && obj is Launcher) {
Launcher source = (Launcher) obj;
Launcher target = this;

if (source != target) {
if (((x > get_allocated_width () / 2) && get_next_sibling () == source) ||
((x < get_allocated_width () / 2) && get_prev_sibling () != source)
) {
target = (Launcher) get_prev_sibling ();
}

((MainWindow) get_root ()).move_launcher_after (source, target);
}
}
}

return MOVE;
}
}
54 changes: 53 additions & 1 deletion src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
public const string ACTION_PREFIX = ACTION_GROUP_PREFIX + ".";

private static Gtk.CssProvider css_provider;
private static Settings settings;

private Gtk.Box box;
private Dock.DesktopIntegration desktop_integration;
Expand All @@ -24,6 +25,8 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
static construct {
css_provider = new Gtk.CssProvider ();
css_provider.load_from_resource ("/io/elementary/dock/MainWindow.css");

settings = new Settings ("io.elementary.dock");
}

construct {
Expand All @@ -41,7 +44,9 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
resizable = false;
set_titlebar (empty_title);

var settings = new Settings ("io.elementary.dock");
// Fixes DnD reordering of launchers failing on a very small line between two launchers
var drop_target_launcher = new Gtk.DropTarget (typeof (Launcher), MOVE);
box.add_controller (drop_target_launcher);

GLib.Bus.get_proxy.begin<Dock.DesktopIntegration> (
GLib.BusType.SESSION,
Expand Down Expand Up @@ -142,6 +147,53 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
});
}

public void move_launcher_after (Launcher source, Launcher? target) {
lenemter marked this conversation as resolved.
Show resolved Hide resolved
var before_source = source.get_prev_sibling ();

box.reorder_child_after (source, target);

/*
* should_animate toggles to true once either the launcher before the one
* that was moved is reached or once the one that was moved is reached
* and goes false again once the other one is reached. While true
* all launchers that are iterated over are animated to move in the appropriate
* direction.
*/
bool should_animate = false;
Gtk.DirectionType dir = UP; // UP is an invalid placeholder value

// source was the first launcher in the box so we start animating right away
if (before_source == null) {
should_animate = true;
dir = LEFT;
}

Launcher child = (Launcher) box.get_first_child ();
while (child != null) {
if (child == source) {
should_animate = !should_animate;
if (should_animate) {
dir = RIGHT;
}
}

if (should_animate && child != source) {
child.animate_move (dir);
}

if (child == before_source) {
should_animate = !should_animate;
if (should_animate) {
dir = LEFT;
}
}

child = (Launcher) child.get_next_sibling ();
}

sync_pinned ();
}

public void remove_launcher (Launcher launcher, bool from_map = true) {
foreach (var action in list_actions ()) {
if (action.has_prefix (launcher.app_info.get_id ())) {
Expand Down
56 changes: 56 additions & 0 deletions src/PoofPopover.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io)
*/

public class Dock.PoofPopover : Gtk.Popover {
private Gtk.Adjustment vadjustment;
private int poof_frames;

construct {
var texture = Gdk.Texture.from_resource ("/io/elementary/dock/poof.svg");

var poof_size = texture.width;
poof_frames = (int) Math.floor (texture.height / poof_size);

var picture = new Gtk.Picture.for_paintable (texture) {
width_request = poof_size,
height_request = poof_size * poof_frames,
keep_aspect_ratio = true
};

var scrolled_window = new Gtk.ScrolledWindow () {
hexpand = true,
vexpand = true,
child = picture,
vscrollbar_policy = EXTERNAL
};

vadjustment = scrolled_window.get_vadjustment ();

height_request = poof_size;
width_request = poof_size;
has_arrow = false;
remove_css_class ("background");
child = scrolled_window;
}

public void start_animation () {
var frame = 0;
Timeout.add (30, () => {
var adjustment_step = (int) vadjustment.get_upper () / poof_frames;
vadjustment.value = vadjustment.value + adjustment_step;
if (frame < poof_frames) {
frame++;
return Source.CONTINUE;
} else {
popdown ();
Idle.add (() => {
unparent ();
return Source.REMOVE;
});
return Source.REMOVE;
}
});
}
}
3 changes: 2 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ sources = [
'AppWindow.vala',
'DesktopIntegration.vala',
'Launcher.vala',
'MainWindow.vala'
'MainWindow.vala',
'PoofPopover.vala'
]

executable(
Expand Down