From 992f1a7d1eca30eb4f0b114701dbbab4cfe01c9a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 22 Feb 2024 20:57:52 +0000 Subject: [PATCH] Move drag gesture to overlay; simplify (#376) --- src/Widgets/DisplayWidget.vala | 70 +--- src/Widgets/DisplaysOverlay.vala | 528 ++++++++++++++++--------------- 2 files changed, 296 insertions(+), 302 deletions(-) diff --git a/src/Widgets/DisplayWidget.vala b/src/Widgets/DisplayWidget.vala index 419d9f6e..5b8a5453 100644 --- a/src/Widgets/DisplayWidget.vala +++ b/src/Widgets/DisplayWidget.vala @@ -28,8 +28,6 @@ public struct Display.Resolution { public class Display.DisplayWidget : Gtk.EventBox { public signal void set_as_primary (); - public signal void move_display (double diff_x, double diff_y); - public signal void end_grab (int delta_x, int delta_y); public signal void check_position (); public signal void configuration_changed (); public signal void active_changed (); @@ -37,15 +35,10 @@ public class Display.DisplayWidget : Gtk.EventBox { public Display.VirtualMonitor virtual_monitor { get; construct; } public string bg_color { get; construct; } public string text_color { get; construct; } + public string display_name { get {return virtual_monitor.get_display_name (); }} public double window_ratio { get; private set; default = 1.0; } - public int delta_x { get; set; default = 0; } - public int delta_y { get; set; default = 0; } - public bool only_display { get; set; default = false; } - - private double start_x = 0; - private double start_y = 0; - private bool holding = false; + public bool connected { get; set; } public Gtk.Button primary_image { get; private set; } public Gtk.MenuButton toggle_settings { get; private set; } @@ -64,9 +57,6 @@ public class Display.DisplayWidget : Gtk.EventBox { private int real_width = 0; private int real_height = 0; - private Gtk.EventControllerMotion motion_event_controller; - private Gtk.GestureMultiPress click_gesture; - private enum ResolutionColumns { NAME, WIDTH, @@ -95,10 +85,6 @@ public class Display.DisplayWidget : Gtk.EventBox { } construct { - events |= Gdk.EventMask.BUTTON_PRESS_MASK; - events |= Gdk.EventMask.BUTTON_RELEASE_MASK; - events |= Gdk.EventMask.POINTER_MOTION_MASK; - virtual_monitor.get_current_mode_size (out real_width, out real_height); primary_image = new Gtk.Button.from_icon_name ("non-starred-symbolic") { @@ -327,7 +313,7 @@ public class Display.DisplayWidget : Gtk.EventBox { return; } - set_geometry (virtual_monitor.x, virtual_monitor.y, active_width, active_height); + set_virtual_monitor_geometry (virtual_monitor.x, virtual_monitor.y, active_width, active_height); var new_mode = virtual_monitor.get_mode_for_resolution (active_width, active_height); if (new_mode == null) { return; @@ -418,12 +404,6 @@ public class Display.DisplayWidget : Gtk.EventBox { configuration_changed (); check_position (); - click_gesture = new Gtk.GestureMultiPress (this); - click_gesture.pressed.connect (gesture_press_event); - click_gesture.released.connect (gesture_release_event); - - motion_event_controller = new Gtk.EventControllerMotion (this); - motion_event_controller.motion.connect (motion_event); } private void populate_refresh_rates () { @@ -531,34 +511,6 @@ public class Display.DisplayWidget : Gtk.EventBox { }); } - private void gesture_press_event (int n_press, double x, double y) { - if (only_display) { - return; - } - - start_x = x; - start_y = y; - holding = true; - } - - private void gesture_release_event (int n_press, double x, double y) { - holding = false; - if ((delta_x == 0 && delta_y == 0) || only_display) { - return; - } - - var old_delta_x = delta_x; - var old_delta_y = delta_y; - delta_x = 0; - delta_y = 0; - end_grab (old_delta_x, old_delta_y); - } - - private void motion_event (double event_x, double event_y) { - if (holding && !only_display) { - move_display (event_x - start_x, event_y - start_y); - } - } public void set_primary (bool is_primary) { if (is_primary) { @@ -582,18 +534,30 @@ public class Display.DisplayWidget : Gtk.EventBox { natural_height = minimum_height; } - public void get_geometry (out int x, out int y, out int width, out int height) { + public void get_virtual_monitor_geometry (out int x, out int y, out int width, out int height) { x = virtual_monitor.x; y = virtual_monitor.y; width = real_width; height = real_height; } - public void set_geometry (int x, int y, int width, int height) { + public void set_virtual_monitor_geometry (int x, int y, int width, int height) { virtual_monitor.x = x; virtual_monitor.y = y; real_width = width; real_height = height; + + queue_resize_no_redraw (); + } + + public void move_x (int dx) { + virtual_monitor.x += dx; + queue_resize_no_redraw (); + } + + public void move_y (int dy) { + virtual_monitor.y += dy; + queue_resize_no_redraw (); } public bool equals (DisplayWidget sibling) { diff --git a/src/Widgets/DisplaysOverlay.vala b/src/Widgets/DisplaysOverlay.vala index dd618643..de207a06 100644 --- a/src/Widgets/DisplaysOverlay.vala +++ b/src/Widgets/DisplaysOverlay.vala @@ -27,7 +27,12 @@ public class Display.DisplaysOverlay : Gtk.Overlay { public signal void configuration_changed (bool changed); private bool scanning = false; + // The ratio between the real dimensions of the virtual monitor(s) and the + // allocated size of the overlay (min). Used for scaling movement of the + // display widgets to changes in real monitor position and ensuring display widgets + // fit inside overlay after dragging. private double current_ratio = 1.0f; + private int current_allocated_width = 0; private int current_allocated_height = 0; private int default_x_margin = 0; @@ -38,6 +43,12 @@ public class Display.DisplaysOverlay : Gtk.Overlay { public int active_displays { get; set; default = 0; } private List display_widgets; + private DisplayWidget? dragging_display = null; + public bool only_display { + get { + return active_displays <= 1; + } + } private static Gtk.CssProvider display_provider; @@ -65,6 +76,8 @@ public class Display.DisplaysOverlay : Gtk.Overlay { @define-color TEXT_COLOR %s; """; + private Gtk.GestureDrag drag_gesture; + construct { var grid = new Gtk.Grid () { hexpand = true, @@ -75,6 +88,11 @@ public class Display.DisplaysOverlay : Gtk.Overlay { display_widgets = new List (); + drag_gesture = new Gtk.GestureDrag (this); + drag_gesture.drag_begin.connect (on_drag_begin); + drag_gesture.drag_update.connect (on_drag_update); + drag_gesture.drag_end.connect (on_drag_end); + monitor_manager = Display.MonitorManager.get_default (); monitor_manager.notify["virtual-monitor-number"].connect (() => rescan_displays ()); rescan_displays (); @@ -99,9 +117,52 @@ public class Display.DisplaysOverlay : Gtk.Overlay { }); } + private double prev_dx = 0; + private double prev_dy = 0; + private void on_drag_begin (double x, double y) { + if (only_display) { + return; + } + + Gdk.Rectangle start_rect = {(int) x, (int) y, 1, 1}; + Gtk.Allocation alloc; + prev_dx = 0; + prev_dy = 0; + foreach (var display_widget in display_widgets) { + get_child_position (display_widget, out alloc); + if (start_rect.intersect (alloc, null)) { + dragging_display = display_widget; + break; + } + } + + reorder_overlay (dragging_display, -1); + } + + // dx & dy are screen offsets from the start of dragging + private void on_drag_update (double dx, double dy) { + if (!only_display && dragging_display != null) { + dragging_display.move_x ((int) ((dx - prev_dx) / current_ratio)); + dragging_display.move_y ((int) ((dy - prev_dy) / current_ratio)); + prev_dx = dx; + prev_dy = dy; + } + } + + private void on_drag_end () { + if (dragging_display != null) { + verify_layout (dragging_display); + dragging_display = null; + } + } + + // Determine the position in the overlay of a display widget based on its + // virtual monitor geometry and any offsets when dragging. public override bool get_child_position (Gtk.Widget widget, out Gdk.Rectangle allocation) { allocation = Gdk.Rectangle (); - if (current_allocated_width != get_allocated_width () || current_allocated_height != get_allocated_height ()) { + if (current_allocated_width != get_allocated_width () || + current_allocated_height != get_allocated_height ()) { + calculate_ratio (); } @@ -109,9 +170,7 @@ public class Display.DisplaysOverlay : Gtk.Overlay { var display_widget = (DisplayWidget) widget; int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); - x += display_widget.delta_x; - y += display_widget.delta_y; + display_widget.get_virtual_monitor_geometry (out x, out y, out width, out height); var x_start = (int) Math.round (x * current_ratio); var y_start = (int) Math.round (y * current_ratio); var x_end = (int) Math.round ((x + width) * current_ratio); @@ -180,18 +239,57 @@ public class Display.DisplaysOverlay : Gtk.Overlay { } private void change_active_displays_sensitivity () { - foreach (unowned var widget in display_widgets) { - if (widget.virtual_monitor.is_active) { - widget.only_display = (active_displays == 1); + } + + private void check_configuration_change () { + // check if valid (connected) + var result = true; + foreach (unowned var dw in display_widgets) { + dw.connected = false; + } + + foreach (unowned var dw1 in display_widgets) { + foreach (unowned var dw2 in display_widgets) { + if (dw2 == dw1) { + warning ("Skip %s", dw2.display_name); + continue; + } else if (dw1.connected) { + warning ("%s already connected", dw1.display_name); + break; + } + + dw1.connected = is_connected (dw1, dw2); + if (dw1.connected) { + dw2.connected = true; + } + } + } + + foreach (unowned var dw in display_widgets) { + if (!dw.connected) { + result = false; + break; } } + + configuration_changed (result); } - private void check_configuration_changed () { - // TODO check if it actually has changed - configuration_changed (true); + // Determine whether two displays adjoin but do not overlap + private bool is_connected (DisplayWidget dw1, DisplayWidget dw2) { + int x1, y1, width1, height1; + dw1.get_virtual_monitor_geometry (out x1, out y1, out width1, out height1); + int x2, y2, width2, height2; + dw2.get_virtual_monitor_geometry (out x2, out y2, out width2, out height2); + Gdk.Rectangle rect1 = {x1, y1, width1, height1}; + Gdk.Rectangle rect2 = {x2 - 1, y2 - 1, width2 + 2, height2 + 2}; + Gdk.Rectangle intersection; + return rect1.intersect (rect2, out intersection) && + (intersection.width == 1 || intersection.height == 1); } + // Calculate the required scaling required to fit the current monitor + // configuration into the overlay private void calculate_ratio () { int added_width = 0; int added_height = 0; @@ -200,7 +298,7 @@ public class Display.DisplaysOverlay : Gtk.Overlay { foreach (unowned var display_widget in display_widgets) { int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); + display_widget.get_virtual_monitor_geometry (out x, out y, out width, out height); added_width += width; added_height += height; @@ -254,48 +352,20 @@ public class Display.DisplaysOverlay : Gtk.Overlay { display_widget.set_as_primary.connect (() => set_as_primary (display_widget.virtual_monitor)); display_widget.check_position.connect (() => { - check_intersects (display_widget); - close_gaps (); - verify_global_positions (); - calculate_ratio (); + verify_layout (display_widget); }); - display_widget.move_display.connect (move_display); - display_widget.configuration_changed.connect (check_configuration_changed); + display_widget.configuration_changed.connect (check_configuration_change); display_widget.active_changed.connect (() => { active_displays += virtual_monitor.is_active ? 1 : -1; change_active_displays_sensitivity (); - check_configuration_changed (); + check_configuration_change (); calculate_ratio (); }); if (!monitor_manager.is_mirrored && virtual_monitor.is_active) { show_windows (); } - - display_widget.end_grab.connect ((delta_x, delta_y) => { - if (delta_x == 0 && delta_y == 0) { - return; - } - - int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); - display_widget.set_geometry (delta_x + x, delta_y + y, width, height); - display_widget.queue_resize_no_redraw (); - check_configuration_changed (); - check_intersects (display_widget); - snap_edges (display_widget); - close_gaps (); - verify_global_positions (); - calculate_ratio (); - }); - - check_intersects (display_widget); - var old_delta_x = display_widget.delta_x; - var old_delta_y = display_widget.delta_y; - display_widget.delta_x = 0; - display_widget.delta_y = 0; - display_widget.end_grab (old_delta_x, old_delta_y); } private void set_as_primary (Display.VirtualMonitor new_primary) { @@ -310,121 +380,118 @@ public class Display.DisplaysOverlay : Gtk.Overlay { virtual_monitor.primary = virtual_monitor == new_primary; } - check_configuration_changed (); - } - - private void move_display (DisplayWidget display_widget, double diff_x, double diff_y) { - reorder_overlay (display_widget, -1); - display_widget.delta_x = (int) (diff_x / current_ratio); - display_widget.delta_y = (int) (diff_y / current_ratio); - Gdk.ModifierType state; - Gtk.get_current_event_state (out state); - if (!(Gdk.ModifierType.CONTROL_MASK in state)) { - align_edges (display_widget); - } - - display_widget.queue_resize_no_redraw (); + check_configuration_change (); } - private void align_edges (DisplayWidget display_widget) { - int aligned_delta[2] = { int.MAX, int.MAX }; - int current_delta[2] = { display_widget.delta_x, display_widget.delta_y }; - - int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); - - int widget_points[6], anchor_points[6]; - widget_points [0] = x; // x_start - widget_points [1] = x + width / 2 - 1; // x_center - widget_points [2] = x + width - 1; // x_end - widget_points [3] = y; // y_start - widget_points [4] = y + height / 2 - 1; // y_center - widget_points [5] = y + height - 1; // y_end - - foreach (unowned var widget in display_widgets) { - if (widget == display_widget) { - continue; - } - - var anchor = widget; - anchor.get_geometry (out x, out y, out width, out height); - anchor_points [0] = x; // x_start - anchor_points [1] = x + width / 2 - 1; // x_center - anchor_points [2] = x + width - 1; // x_end - anchor_points [3] = y; // y_start - anchor_points [4] = y + height / 2 - 1; // y_center - anchor_points [5] = y + height - 1; // y_end - - int threshold = int.min (width, height) / 10; - for (var u = 0; u < 2; u++) { // 0: X, 1: Y - for (var i = 0; i < 3; i++) { - for (var j = 0; j < 3; j++) { - int test_delta = anchor_points [i + 3 * u] - widget_points [j + 3 * u]; - if (threshold > (test_delta - current_delta [u]).abs ()) { - if (test_delta.abs () < aligned_delta [u].abs ()) { - aligned_delta [u] = test_delta; - if (i == 0 && j != i) { - aligned_delta [u] -= 1; - } else if (j == 0 && i != j) { - aligned_delta [u] += 1; - } - } - } - } - } - } + private void verify_layout (DisplayWidget changed_widget) { + uint iteration = 0; + // Continues iterating while at least one widget gets moved (or too many iterations) + while (iteration < 10 && + (check_intersects (changed_widget) || + align_edges (changed_widget)) + ) { + iteration++; } - if (aligned_delta [0] != int.MAX) { - display_widget.delta_x = aligned_delta [0]; - } - if (aligned_delta [1] != int.MAX) { - display_widget.delta_y = aligned_delta [1]; - } - } + set_origin_zero (); + calculate_ratio (); - private void close_gaps () { - foreach (unowned var widget in display_widgets) { - if (!is_connected (widget, display_widgets)) { - snap_edges (widget); - } - } + check_configuration_change (); } - // to check if a display_widget is connected (has no gaps) one can check if - // a 1px larger rectangle intersects with any of other display_widgets - private bool is_connected (DisplayWidget display_widget, List other_display_widgets) { + // Return true if a display moved + private bool align_edges ( + DisplayWidget changed_widget, + bool moved = false, + uint level = 0 + ) { int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); - Gdk.Rectangle rect = {x - 1, y - 1, width + 2, height + 2}; - - foreach (var other_display_widget in other_display_widgets) { - if (other_display_widget == display_widget) { + Gdk.Rectangle overlap; + foreach (unowned var other_display_widget in display_widgets) { + if (other_display_widget == changed_widget) { continue; } + changed_widget.get_virtual_monitor_geometry ( + out x, + out y, + out width, + out height + ); + Gdk.Rectangle source_rect = {x, y, width, height}; + int dx = 0, dy = 0; int other_x, other_y, other_width, other_height; - other_display_widget.get_geometry (out other_x, out other_y, out other_width, out other_height); + other_display_widget.get_virtual_monitor_geometry ( + out other_x, + out other_y, + out other_width, + out other_height + ); + + int dx_left = x - other_x; + int dx_right = (x + width) - (other_x + other_width); + int dy_top = y - other_y; + int dy_bottom = (y + height) - (other_y + other_height); + + Gdk.Rectangle rect_top = {other_x, other_y - other_height, other_width, height}; + Gdk.Rectangle rect_bottom = {other_x, other_y + other_height, other_width, height}; + Gdk.Rectangle rect_left = {other_x - width, other_y, width, other_height}; + Gdk.Rectangle rect_right = {other_x + other_width, other_y, width, other_height}; + if (source_rect.intersect (rect_top, out overlap)) { // Move down + dy = other_y - (y + height); + if (dx_left.abs () < MINIMUM_WIDGET_OFFSET) { + dx = -dx_left; + } else if (dx_right.abs () < MINIMUM_WIDGET_OFFSET) { + dx = -dx_right; + } + } else if (source_rect.intersect (rect_bottom, out overlap)) { + dy = other_y + other_height - y; + if (dx_left.abs () < MINIMUM_WIDGET_OFFSET) { + dx = -dx_left; + } else if (dx_right.abs () < MINIMUM_WIDGET_OFFSET) { + dx = -dx_right; + } + } else if (source_rect.intersect (rect_left, out overlap)) { + dx = other_x - (x + width); + if (dy_top.abs () < MINIMUM_WIDGET_OFFSET) { + dy = -dy_top; + } else if (dy_bottom.abs () < MINIMUM_WIDGET_OFFSET) { + dy = -dy_bottom; + } + } else if (source_rect.intersect (rect_right, out overlap)) { + dx = (other_x + other_width) - x; + if (dy_top.abs () < MINIMUM_WIDGET_OFFSET) { + dy = -dy_top; + } else if (dy_bottom.abs () < MINIMUM_WIDGET_OFFSET) { + dy = -dy_bottom; + } + } - Gdk.Rectangle other_rect = { other_x, other_y, other_width, other_height }; - Gdk.Rectangle intersection; - var is_connected = rect.intersect (other_rect, out intersection); - var is_diagonal = intersection.height == 1 && intersection.width == 1; - if (is_connected && !is_diagonal) { - return true; + other_display_widget.move_x (-dx); + other_display_widget.move_y (-dy); + moved = moved || dx != 0 || dy != 0; + if (dx != 0 || dy != 0) { + align_edges (other_display_widget, moved, ++level); } } - return false; + return moved; } - private void verify_global_positions () { + // Ensure real monitor coords have origin of {0, 0} + private void set_origin_zero () { int min_x = int.MAX; int min_y = int.MAX; foreach (unowned var display_widget in display_widgets) { int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); + // assert (display_widget.delta_x == 0 && display_widget.delta_y == 0); + display_widget.get_virtual_monitor_geometry ( + out x, + out y, + out width, + out height + ); min_x = int.min (min_x, x); min_y = int.min (min_y, y); } @@ -435,140 +502,103 @@ public class Display.DisplaysOverlay : Gtk.Overlay { foreach (unowned var display_widget in display_widgets) { int x, y, width, height; - display_widget.get_geometry (out x, out y, out width, out height); - display_widget.set_geometry (x - min_x, y - min_y, width, height); + display_widget.get_virtual_monitor_geometry ( + out x, + out y, + out width, + out height + ); + display_widget.set_virtual_monitor_geometry ( + x - min_x, + y - min_y, + width, + height + ); } + + return; } - // If widget is intersects with any other widgets -> move other widgets to fix intersection - public void check_intersects (DisplayWidget source_display_widget, int level = 0, int distance_x = 0, int distance_y = 0) { + // If widget is not contiguous with any other widgets -> move other widgets to fix + // Return true if a display moved + private bool check_intersects ( + DisplayWidget changed_widget, + bool moved = false, + uint level = 0 + ) { + if (only_display) { + return false; + } + if (level > 10) { - warning ("Maximum level of recursion reached! Could not fix intersects!"); - return; + warning ("Depth of recursion exceeds limit (10)"); + return moved; } - int source_x, source_y, source_width, source_height; - source_display_widget.get_geometry (out source_x, out source_y, out source_width, out source_height); - Gdk.Rectangle src_rect = { source_x, source_y, source_width, source_height }; + int x, y, width, height; + changed_widget.get_virtual_monitor_geometry ( + out x, + out y, + out width, + out height + ); + Gdk.Rectangle src_rect = { x, y, width, height }; foreach (unowned var other_display_widget in display_widgets) { - if (other_display_widget == source_display_widget) { + int distance_x = 0; + int distance_y = 0; + if (other_display_widget == changed_widget) { continue; } int other_x, other_y, other_width, other_height; - other_display_widget.get_geometry (out other_x, out other_y, out other_width, out other_height); - Gdk.Rectangle test_rect = { other_x, other_y, other_width, other_height }; - if (src_rect.intersect (test_rect, null)) { - if (level == 0) { - var distance_left = source_x - other_x - other_width; - var distance_right = source_x - other_x + source_width; - var distance_top = source_y - other_y - other_height; - var distance_bottom = source_y - other_y + source_height; - var test_distance_x = distance_right < -distance_left ? distance_right : distance_left; - var test_distance_y = distance_bottom < -distance_top ? distance_bottom : distance_top; - - // if distance to upper egde == distance lower edge, move horizontally - if (test_distance_x.abs () <= test_distance_y.abs () || distance_top == -distance_bottom) { - distance_x = test_distance_x; + other_display_widget.get_virtual_monitor_geometry ( + out other_x, + out other_y, + out other_width, + out other_height + ); + Gdk.Rectangle overlap; + Gdk.Rectangle other_rect = { other_x, other_y, other_width, other_height }; + if (src_rect.intersect (other_rect, out overlap)) { + // delta to align on left of other + var dx_left = ((x + width) - other_x).abs (); + //delta to align on right of other + var dx_right = ((other_x + other_width) - x).abs (); + // delta to align on top of other + var dy_top = ((y + height) - other_y).abs (); + //delta to align on bottom of other + int dy_bottom = ((other_y + other_height) - y).abs (); + if (x < other_x) { + if (y < other_y) { + //Align on top/left of other + distance_x = overlap.width > overlap.height ? 0 : dx_left; + distance_y = overlap.width > overlap.height ? dy_top : 0; } else { - distance_y = test_distance_y; + //Align on bottom/left of other + distance_x = overlap.width > overlap.height ? 0 : dx_left; + distance_y = overlap.width > overlap.height ? -dy_bottom : 0; } - } - - other_display_widget.set_geometry (other_x + distance_x, other_y + distance_y, other_width, other_height); - other_display_widget.queue_resize_no_redraw (); - check_intersects (other_display_widget, level + 1, distance_x, distance_y); - } - } - } - - public void snap_edges (DisplayWidget last_moved) { - if (scanning) return; - // Snap last_moved - debug ("Snapping displays"); - var anchors = new List (); - - foreach (unowned var widget in display_widgets) { - if (last_moved.equals (widget)) { - return; - } - anchors.append (widget); - } - - snap_widget (last_moved, anchors); - - /*/ FIXME: Re-Snapping with 3 or more displays is broken - // This is used to make sure all displays are connected - anchors = new List(); - get_children ().foreach ((child) => { - if (!(child is DisplayWidget)) return; - snap_widget ((DisplayWidget) child, anchors); - anchors.append ((DisplayWidget) child); - });*/ - } - - /****************************************************************************************** - * Widget snapping is done by trying to snap a widget to other widgets called Anchors. * - * It first calculates the distance between each anchor and the widget, and afterwards * - * snaps the widget to the closest edge/corner * - * * - * Cases: W = widget, A = current anchor * - * * - * 1. 2. 3. 4. 5. 6. 7. 8. * - * A W W A A W W W A A * - * W A A A W W * - * * - ******************************************************************************************/ - - private void snap_widget (Display.DisplayWidget widget, List anchors) { - if (anchors.length () == 0) { - return; - } - - int widget_x, widget_y, widget_width, widget_height; - widget.get_geometry (out widget_x, out widget_y, out widget_width, out widget_height); - widget_x += widget.delta_x; - widget_y += widget.delta_y; - - int shortest_distance = int.MAX, shortest_distance_x = 0, shortest_distance_y = 0; - foreach (var anchor in anchors) { - int anchor_x, anchor_y, anchor_width, anchor_height; - anchor.get_geometry (out anchor_x, out anchor_y, out anchor_width, out anchor_height); - - var distance_origin_x = anchor_x - widget_x; - var distance_origin_y = anchor_y - widget_y; - var distance_left = distance_origin_x + anchor_width; - var distance_right = distance_origin_x - widget_width; - var distance_top = distance_origin_y + anchor_height; - var distance_bottom = distance_origin_y - widget_height; - var distance_widget_anchor_x = distance_right > -distance_left ? distance_right : distance_left; - var distance_widget_anchor_y = distance_bottom > -distance_top ? distance_bottom : distance_top; - - // widget is between left and right edges of anchor, no horizontal movement needed - if (distance_left > 0 && distance_right < 0) { - distance_widget_anchor_x = 0; - // widget is between top and bottom edges of anchor, no vertical movement needed - } else if (distance_top > 0 && distance_bottom < 0) { - distance_widget_anchor_y = 0; - // widget is diagonal to anchor, as diagonal monitors are not allowed, offset by 50px (MINIMUM_WIDGET_OFFSET) - } else { - if (distance_widget_anchor_x.abs () >= distance_widget_anchor_y.abs ()) { - distance_widget_anchor_x += (distance_origin_x > 0 ? 1 : -1) * MINIMUM_WIDGET_OFFSET; } else { - distance_widget_anchor_y += (distance_origin_y > 0 ? 1 : -1) * MINIMUM_WIDGET_OFFSET; + if (y < other_y) { + //Align on top/right of other + distance_x = overlap.width > overlap.height ? 0 : -dx_right; + distance_y = overlap.width > overlap.height ? dy_top : 0; + } else { + //Align on bottom/right of other + distance_x = overlap.width > overlap.height ? 0 : -dx_right; + distance_y = overlap.width > overlap.height ? -dy_bottom : 0; + } } - } - var shortest_distance_candidate = distance_widget_anchor_x * distance_widget_anchor_x - + distance_widget_anchor_y * distance_widget_anchor_y; - if (shortest_distance_candidate < shortest_distance) { - shortest_distance = shortest_distance_candidate; - shortest_distance_x = distance_widget_anchor_x; - shortest_distance_y = distance_widget_anchor_y; + other_display_widget.move_x (distance_x); + other_display_widget.move_y (distance_y); + check_intersects (other_display_widget, moved, ++level); } + + moved = moved || distance_x != 0 || distance_y != 0; } - widget.set_geometry (widget_x + shortest_distance_x, widget_y + shortest_distance_y, widget_width, widget_height); + return moved; } }