ui: prevent monitor overlap after resize

parent 87463a2c
......@@ -43,6 +43,195 @@ namespace TunerDisplays {
monitor.y = 0;
}
private enum MonitorSide {
NONE,
LEFT,
RIGHT,
TOP,
BOTTOM
}
internal static void repair_layout_after_monitor_resize(
MonitorConfig changed,
Gee.ArrayList<MonitorConfig> monitors,
double old_width,
double old_height
) {
if (!changed.enabled || changed.mirrored)
return;
var old_x = changed.x;
var old_y = changed.y;
var old_right = old_x + old_width;
var old_bottom = old_y + old_height;
foreach (var monitor in monitors) {
if (monitor == changed || !monitor.enabled)
continue;
var side = adjacent_side(monitor, old_x, old_y, old_right, old_bottom);
if (side != MonitorSide.NONE)
place_next_to(changed, monitor, side);
}
var max_passes = monitors.size * monitors.size + 4;
for (int pass = 0; pass < max_passes; pass++) {
bool moved = false;
for (int i = 0; i < monitors.size; i++) {
var a = monitors[i];
if (!a.enabled)
continue;
for (int j = i + 1; j < monitors.size; j++) {
var b = monitors[j];
if (!b.enabled || !monitors_overlap(a, b))
continue;
if (a == changed)
move_away_from(a, b);
else if (b == changed)
move_away_from(b, a);
else
move_away_from(a, b);
moved = true;
}
}
if (!moved)
break;
}
normalize_enabled_positions(monitors);
}
private static MonitorSide adjacent_side(MonitorConfig monitor, double old_x, double old_y, double old_right, double old_bottom) {
var x = (double) monitor.x;
var y = (double) monitor.y;
var right = x + monitor.logical_width;
var bottom = y + monitor.logical_height;
if (edges_touch(x, old_right) && ranges_overlap(y, bottom, old_y, old_bottom))
return MonitorSide.RIGHT;
if (edges_touch(right, old_x) && ranges_overlap(y, bottom, old_y, old_bottom))
return MonitorSide.LEFT;
if (edges_touch(y, old_bottom) && ranges_overlap(x, right, old_x, old_right))
return MonitorSide.BOTTOM;
if (edges_touch(bottom, old_y) && ranges_overlap(x, right, old_x, old_right))
return MonitorSide.TOP;
return MonitorSide.NONE;
}
private static bool edges_touch(double a, double b) {
return Math.fabs(Math.round(a) - Math.round(b)) <= 1.0;
}
private static void place_next_to(MonitorConfig anchor, MonitorConfig monitor, MonitorSide side) {
switch (side) {
case MonitorSide.RIGHT:
monitor.x = (int) Math.ceil(anchor.x + anchor.logical_width);
break;
case MonitorSide.LEFT:
monitor.x = (int) Math.floor(anchor.x - monitor.logical_width);
break;
case MonitorSide.BOTTOM:
monitor.y = (int) Math.ceil(anchor.y + anchor.logical_height);
break;
case MonitorSide.TOP:
monitor.y = (int) Math.floor(anchor.y - monitor.logical_height);
break;
default:
break;
}
}
private static void move_away_from(MonitorConfig anchor, MonitorConfig monitor) {
var best_x = monitor.x;
var best_y = monitor.y;
var best_distance = double.MAX;
consider_non_overlapping_position(anchor, monitor, (int) Math.floor(anchor.x - monitor.logical_width), monitor.y, ref best_x, ref best_y, ref best_distance);
consider_non_overlapping_position(anchor, monitor, (int) Math.ceil(anchor.x + anchor.logical_width), monitor.y, ref best_x, ref best_y, ref best_distance);
consider_non_overlapping_position(anchor, monitor, monitor.x, (int) Math.floor(anchor.y - monitor.logical_height), ref best_x, ref best_y, ref best_distance);
consider_non_overlapping_position(anchor, monitor, monitor.x, (int) Math.ceil(anchor.y + anchor.logical_height), ref best_x, ref best_y, ref best_distance);
monitor.x = best_x;
monitor.y = best_y;
}
private static void consider_non_overlapping_position(
MonitorConfig anchor,
MonitorConfig monitor,
int x,
int y,
ref int best_x,
ref int best_y,
ref double best_distance
) {
if (rects_overlap(
anchor.x, anchor.y, anchor.logical_width, anchor.logical_height,
x, y, monitor.logical_width, monitor.logical_height
)) {
return;
}
var dx = x - monitor.x;
var dy = y - monitor.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < best_distance) {
best_distance = distance;
best_x = x;
best_y = y;
}
}
private static bool monitors_overlap(MonitorConfig a, MonitorConfig b) {
return rects_overlap(
a.x, a.y, a.logical_width, a.logical_height,
b.x, b.y, b.logical_width, b.logical_height
);
}
private static bool rects_overlap(double ax, double ay, double aw, double ah, double bx, double by, double bw, double bh) {
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
private static bool ranges_overlap(double a1, double a2, double b1, double b2) {
return a1 < b2 && b1 < a2;
}
private static void normalize_enabled_positions(Gee.ArrayList<MonitorConfig> monitors) {
int min_x = 0;
int min_y = 0;
bool first = true;
foreach (var monitor in monitors) {
if (!monitor.enabled)
continue;
if (first) {
min_x = monitor.x;
min_y = monitor.y;
first = false;
} else {
min_x = int.min(min_x, monitor.x);
min_y = int.min(min_y, monitor.y);
}
}
if (first || (min_x == 0 && min_y == 0))
return;
foreach (var monitor in monitors) {
if (!monitor.enabled)
continue;
monitor.x -= min_x;
monitor.y -= min_y;
}
}
private static string refresh_rate_label(DisplayMode mode) {
return _("%.2f Hz").printf(mode.refresh);
}
......
......@@ -99,6 +99,9 @@ namespace TunerDisplays {
public override void set_value(Value value) {
var context = MonitorSettingsState.current;
var monitor = context.monitor;
var repair_layout = affects_layout_size(field);
var old_width = monitor.logical_width;
var old_height = monitor.logical_height;
switch (field) {
case "enabled":
......@@ -151,9 +154,29 @@ namespace TunerDisplays {
break;
}
if (repair_layout && size_changed(old_width, old_height, monitor.logical_width, monitor.logical_height))
repair_layout_after_monitor_resize(monitor, context.all_monitors, old_width, old_height);
context.emit_changed();
}
private static bool affects_layout_size(string field) {
switch (field) {
case "mode":
case "resolution":
case "scale-choice":
case "scale-spin":
case "transform":
return true;
default:
return false;
}
}
private static bool size_changed(double old_width, double old_height, double width, double height) {
return Math.fabs(old_width - width) > 0.01 || Math.fabs(old_height - height) > 0.01;
}
private static string get_string_value(string field) {
var monitor = MonitorSettingsState.current.monitor;
switch (field) {
......
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