ui: improve monitor layout snapping

parent ffcc0360
...@@ -4,7 +4,7 @@ namespace TunerDisplays { ...@@ -4,7 +4,7 @@ namespace TunerDisplays {
public class MonitorLayout : Gtk.DrawingArea { public class MonitorLayout : Gtk.DrawingArea {
private const double EDGE_PADDING = 6; private const double EDGE_PADDING = 6;
private const double MARGIN_MON = 0.66; private const double MARGIN_MON = 0.66;
private const double CONNECTED_MARGIN_MON = 1.20; private const double CONNECTED_MARGIN_MON = 0.66;
private const double SNAP_DISTANCE = 25; private const double SNAP_DISTANCE = 25;
private const double MIN_OVERLAP = 25; private const double MIN_OVERLAP = 25;
private const double MIN_ZOOM = 0.04; private const double MIN_ZOOM = 0.04;
...@@ -211,13 +211,18 @@ namespace TunerDisplays { ...@@ -211,13 +211,18 @@ namespace TunerDisplays {
return; return;
} }
snap_free_position(monitor, ref x, ref y);
}
private void snap_free_position(MonitorConfig monitor, ref int x, ref int y) {
var width = monitor.logical_width; var width = monitor.logical_width;
var height = monitor.logical_height; var height = monitor.logical_height;
var snap_units = SNAP_DISTANCE / zoom; var snap_units = SNAP_DISTANCE / zoom;
double best_x_distance = snap_units + 1; double best_distance = snap_units + 1;
double best_y_distance = snap_units + 1; int best_x = x;
int snapped_x = x; int best_y = y;
int snapped_y = y; var right = x + width;
var bottom = y + height;
foreach (var other in monitors) { foreach (var other in monitors) {
if (other == monitor) if (other == monitor)
...@@ -225,24 +230,79 @@ namespace TunerDisplays { ...@@ -225,24 +230,79 @@ namespace TunerDisplays {
var other_width = other.logical_width; var other_width = other.logical_width;
var other_height = other.logical_height; var other_height = other.logical_height;
var right = x + width;
var bottom = y + height;
var other_right = other.x + other_width; var other_right = other.x + other_width;
var other_bottom = other.y + other_height; var other_bottom = other.y + other_height;
consider_x_snap(x, other.x, ref best_x_distance, ref snapped_x, snap_units); if (ranges_overlap_with_margin(x, right, other.x, other_right, MIN_OVERLAP)) {
consider_x_snap(x, (int) Math.round(other_right), ref best_x_distance, ref snapped_x, snap_units); var aligned_x = snap_parallel_position(x, width, other.x, other_right, snap_units);
consider_x_snap((int) Math.round(right), other.x, ref best_x_distance, ref snapped_x, snap_units, (int) Math.round(-width)); consider_position(x, y, aligned_x, before_edge(other.y, height), snap_units, ref best_distance, ref best_x, ref best_y);
consider_x_snap((int) Math.round(right), (int) Math.round(other_right), ref best_x_distance, ref snapped_x, snap_units, (int) Math.round(-width)); consider_position(x, y, aligned_x, after_edge(other_bottom), snap_units, ref best_distance, ref best_x, ref best_y);
}
if (ranges_overlap_with_margin(y, bottom, other.y, other_bottom, MIN_OVERLAP)) {
var aligned_y = snap_parallel_position(y, height, other.y, other_bottom, snap_units);
consider_position(x, y, before_edge(other.x, width), aligned_y, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, after_edge(other_right), aligned_y, snap_units, ref best_distance, ref best_x, ref best_y);
}
consider_diagonal_snap(x, y, before_edge(other.x, width), before_edge(other.y, height), snap_units, ref best_distance, ref best_x, ref best_y);
consider_diagonal_snap(x, y, before_edge(other.x, width), after_edge(other_bottom), snap_units, ref best_distance, ref best_x, ref best_y);
consider_diagonal_snap(x, y, after_edge(other_right), before_edge(other.y, height), snap_units, ref best_distance, ref best_x, ref best_y);
consider_diagonal_snap(x, y, after_edge(other_right), after_edge(other_bottom), snap_units, ref best_distance, ref best_x, ref best_y);
}
x = best_x;
y = best_y;
}
private static bool ranges_overlap_with_margin(double a1, double a2, double b1, double b2, double margin) {
return double.min(a2, b2) - double.max(a1, b1) >= margin;
}
private static int snap_parallel_position(int position, double size, double other_start, double other_end, double threshold) {
double best_distance = threshold + 1;
int best = position;
consider_y_snap(y, other.y, ref best_y_distance, ref snapped_y, snap_units); consider_parallel_snap(position, (int) Math.round(other_start), ref best_distance, ref best, threshold);
consider_y_snap(y, (int) Math.round(other_bottom), ref best_y_distance, ref snapped_y, snap_units); consider_parallel_snap(
consider_y_snap((int) Math.round(bottom), other.y, ref best_y_distance, ref snapped_y, snap_units, (int) Math.round(-height)); (int) Math.round(position + size),
consider_y_snap((int) Math.round(bottom), (int) Math.round(other_bottom), ref best_y_distance, ref snapped_y, snap_units, (int) Math.round(-height)); (int) Math.round(other_end),
ref best_distance,
ref best,
threshold,
-(int) Math.round(size)
);
consider_parallel_snap(
(int) Math.round(position + size / 2),
(int) Math.round((other_start + other_end) / 2),
ref best_distance,
ref best,
threshold,
-(int) Math.round(size / 2)
);
return best;
}
private static void consider_parallel_snap(int current_edge, int target_edge, ref double best_distance, ref int best, double threshold, int offset = 0) {
var distance = Math.fabs(current_edge - target_edge);
if (distance <= threshold && distance < best_distance) {
best_distance = distance;
best = target_edge + offset;
}
} }
x = snapped_x; private static void consider_diagonal_snap(int x, int y, int candidate_x, int candidate_y, double threshold, ref double best_distance, ref int best_x, ref int best_y) {
y = snapped_y; var dx = Math.fabs(x - candidate_x);
var dy = Math.fabs(y - candidate_y);
if (dx > threshold || dy > threshold)
return;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < best_distance) {
best_distance = distance;
best_x = candidate_x;
best_y = candidate_y;
}
} }
private void snap_connected_position(MonitorConfig monitor, ref int x, ref int y) { private void snap_connected_position(MonitorConfig monitor, ref int x, ref int y) {
...@@ -263,19 +323,21 @@ namespace TunerDisplays { ...@@ -263,19 +323,21 @@ namespace TunerDisplays {
var other_height = other.logical_height; var other_height = other.logical_height;
var other_right = other.x + other_width; var other_right = other.x + other_width;
var other_bottom = other.y + other_height; var other_bottom = other.y + other_height;
var top = (int) Math.round(other.y - height); var top = before_edge(other.y, height);
var bottom = (int) Math.round(other.y + other_height); var bottom = after_edge(other_bottom);
var left = (int) Math.round(other.x - width); var left = before_edge(other.x, width);
var right = (int) Math.round(other.x + other_width); var right = after_edge(other_right);
if (ranges_overlap(x, current_right, other.x, other_right)) { 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); var aligned_x = snap_parallel_position(x, width, other.x, other_right, snap_units);
consider_position(x, y, x, bottom, snap_units, ref best_distance, ref best_x, ref best_y); consider_position(x, y, aligned_x, top, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, aligned_x, bottom, snap_units, ref best_distance, ref best_x, ref best_y);
} }
if (ranges_overlap(y, current_bottom, other.y, other_bottom)) { 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); var aligned_y = snap_parallel_position(y, height, other.y, other_bottom, snap_units);
consider_position(x, y, right, y, snap_units, ref best_distance, ref best_x, ref best_y); consider_position(x, y, left, aligned_y, snap_units, ref best_distance, ref best_x, ref best_y);
consider_position(x, y, right, aligned_y, snap_units, ref best_distance, ref best_x, ref best_y);
} }
if (enabled_count() <= 2) { if (enabled_count() <= 2) {
...@@ -298,6 +360,14 @@ namespace TunerDisplays { ...@@ -298,6 +360,14 @@ namespace TunerDisplays {
return a1 <= b2 && b1 <= a2; return a1 <= b2 && b1 <= a2;
} }
private static int before_edge(double edge, double size) {
return (int) Math.floor(edge - size);
}
private static int after_edge(double edge) {
return (int) Math.ceil(edge);
}
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) { 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 dx = Math.fabs(x - candidate_x);
var dy = Math.fabs(y - candidate_y); var dy = Math.fabs(y - candidate_y);
...@@ -309,32 +379,16 @@ namespace TunerDisplays { ...@@ -309,32 +379,16 @@ namespace TunerDisplays {
} }
} }
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) {
best_distance = distance;
snapped_x = target_edge + offset;
}
}
private static void consider_y_snap(int current_edge, int target_edge, ref double best_distance, ref int snapped_y, double threshold, int offset = 0) {
var distance = Math.fabs(current_edge - target_edge);
if (distance <= threshold && distance < best_distance) {
best_distance = distance;
snapped_y = target_edge + offset;
}
}
private void clamp_position(MonitorConfig monitor, ref int x, ref int y) { private void clamp_position(MonitorConfig monitor, ref int x, ref int y) {
if (view_width <= 0 || view_height <= 0 || zoom <= 0) if (view_width <= 0 || view_height <= 0 || zoom <= 0)
return; return;
var logical_width = monitor.logical_width; var logical_width = monitor.logical_width;
var logical_height = monitor.logical_height; var logical_height = monitor.logical_height;
var min_x = (EDGE_PADDING - offset_x) / zoom; var min_x = (EDGE_PADDING - offset_x) / zoom - logical_width + MIN_OVERLAP;
var min_y = (EDGE_PADDING - offset_y) / zoom; var min_y = (EDGE_PADDING - offset_y) / zoom - logical_height + MIN_OVERLAP;
var max_x = (view_width - EDGE_PADDING - offset_x) / zoom - logical_width; var max_x = (view_width - EDGE_PADDING - offset_x) / zoom - MIN_OVERLAP;
var max_y = (view_height - EDGE_PADDING - offset_y) / zoom - logical_height; var max_y = (view_height - EDGE_PADDING - offset_y) / zoom - MIN_OVERLAP;
if (max_x < min_x) if (max_x < min_x)
max_x = min_x; max_x = min_x;
......
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