ui: improve GNOME monitor arrangement snapping

parent ce54942e
......@@ -33,6 +33,7 @@ namespace TunerDisplays {
layout = new MonitorLayout() {
height_request = 320,
hexpand = true,
require_connected = backend.id == "gnome",
can_focus = false,
focusable = false
};
......
......@@ -3,7 +3,9 @@ namespace TunerDisplays {
public class MonitorLayout : Gtk.DrawingArea {
private const double EDGE_PADDING = 6;
private const double MARGIN_MON = 0.66;
private const double CONNECTED_MARGIN_MON = 1.20;
private const double SNAP_DISTANCE = 25;
private const double MIN_OVERLAP = 25;
private const double MIN_ZOOM = 0.04;
private const double MAX_ZOOM = 0.18;
private Gee.ArrayList<MonitorConfig> monitors = new Gee.ArrayList<MonitorConfig>();
......@@ -17,6 +19,7 @@ namespace TunerDisplays {
private int drag_start_y = 0;
private bool drag_active;
private bool needs_recenter = true;
public bool require_connected { get; set; }
public signal void layout_changed();
......@@ -43,7 +46,8 @@ namespace TunerDisplays {
int next_x = drag_start_x + (int) Math.round(dx / zoom);
int next_y = drag_start_y + (int) Math.round(dy / zoom);
snap_position(monitor, ref next_x, ref next_y);
clamp_position(monitor, ref next_x, ref next_y);
if (!require_connected)
clamp_position(monitor, ref next_x, ref next_y);
if (overlaps_any(monitor, next_x, next_y)) {
next_x = previous_x;
next_y = previous_y;
......@@ -193,6 +197,11 @@ namespace TunerDisplays {
}
private void snap_position(MonitorConfig monitor, ref int x, ref int y) {
if (require_connected) {
snap_connected_position(monitor, ref x, ref y);
return;
}
var width = get_logical_width(monitor);
var height = get_logical_height(monitor);
var snap_units = SNAP_DISTANCE / zoom;
......@@ -227,6 +236,70 @@ namespace TunerDisplays {
y = snapped_y;
}
private void snap_connected_position(MonitorConfig monitor, ref int x, ref int y) {
var width = get_logical_width(monitor);
var height = get_logical_height(monitor);
var snap_units = enabled_count() <= 2 ? double.MAX : SNAP_DISTANCE / zoom;
double best_distance = double.MAX;
int best_x = x;
int best_y = y;
var current_right = x + width;
var current_bottom = y + height;
foreach (var other in monitors) {
if (other == monitor || !other.enabled)
continue;
var other_width = get_logical_width(other);
var other_height = get_logical_height(other);
var other_right = other.x + other_width;
var other_bottom = other.y + other_height;
var top = (int) Math.round(other.y - height);
var bottom = (int) Math.round(other.y + other_height);
var left = (int) Math.round(other.x - width);
var right = (int) Math.round(other.x + other_width);
if (ranges_overlap(x, current_right, other.x, other_right)) {
consider_position(x, y, x, top, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, x, bottom, snap_units, ref best_distance, ref best_x, ref best_y);
}
if (ranges_overlap(y, current_bottom, other.y, other_bottom)) {
consider_position(x, y, left, y, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, right, y, snap_units, ref best_distance, ref best_x, ref best_y);
}
if (enabled_count() <= 2) {
consider_position(x, y, (int) Math.round(other.x - width + MIN_OVERLAP), top, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, (int) Math.round(other.x + other_width - MIN_OVERLAP), top, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, (int) Math.round(other.x - width + MIN_OVERLAP), bottom, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, (int) Math.round(other.x + other_width - MIN_OVERLAP), bottom, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, left, (int) Math.round(other.y - height + MIN_OVERLAP), snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, left, (int) Math.round(other.y + other_height - MIN_OVERLAP), snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, right, (int) Math.round(other.y - height + MIN_OVERLAP), snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, right, (int) Math.round(other.y + other_height - MIN_OVERLAP), snap_units, ref best_distance, ref best_x, ref best_y);
}
}
x = best_x;
y = best_y;
}
private static bool ranges_overlap(double a1, double a2, double b1, double b2) {
return a1 <= b2 && b1 <= a2;
}
private static void consider_position(int x, int y, int candidate_x, int candidate_y, double threshold, ref double best_distance, ref int best_x, ref int best_y) {
var dx = Math.fabs(x - candidate_x);
var dy = Math.fabs(y - candidate_y);
var distance = double.max(dx, dy);
if (distance <= threshold && distance < best_distance) {
best_distance = distance;
best_x = candidate_x;
best_y = candidate_y;
}
}
private static void consider_x_snap(int current_edge, int target_edge, ref double best_distance, ref int snapped_x, double threshold, int offset = 0) {
var distance = Math.fabs(current_edge - target_edge);
if (distance <= threshold && distance < best_distance) {
......@@ -285,6 +358,15 @@ namespace TunerDisplays {
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
private int enabled_count() {
int count = 0;
foreach (var monitor in monitors) {
if (monitor.enabled)
count++;
}
return count;
}
private void normalize_positions() {
if (monitors.size == 0)
return;
......@@ -350,8 +432,9 @@ namespace TunerDisplays {
var layout_width = double.max(1, max_x - min_x);
var layout_height = double.max(1, max_y - min_y);
var fit_x = width / (layout_width + max_monitor_width * 2 * MARGIN_MON);
var fit_y = height / (layout_height + max_monitor_height * 2 * MARGIN_MON);
var margin = require_connected ? CONNECTED_MARGIN_MON : MARGIN_MON;
var fit_x = width / (layout_width + max_monitor_width * 2 * margin);
var fit_y = height / (layout_height + max_monitor_height * 2 * margin);
zoom = double.min(MAX_ZOOM, double.max(MIN_ZOOM, double.min(fit_x, fit_y)));
offset_x = width / 2.0 - ((min_x + max_x) / 2.0) * zoom;
offset_y = height / 2.0 - ((min_y + max_y) / 2.0) * zoom;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment