diff --git a/sensors/sensors.vala b/sensors/sensors.vala index 7003778..ddf1872 100644 --- a/sensors/sensors.vala +++ b/sensors/sensors.vala @@ -50,11 +50,12 @@ class SensorsIndicator : Gtk.Box { // Sensor counts vary by two orders of magnitude across platforms, so // the detail list is capped rather than unbounded. private const int MAX_ROWS_PER_GROUP = 6; - // Gdk.Monitor geometry is expressed in logical pixels. Two 340px - // columns plus the popover margins fit comfortably from 1280px up; - // below that, keeping one column avoids a popover that dominates the + // Column count is derived from BOTH logical Gdk.Monitor geometry and + // the monitor's scale factor (see configure_detail_layout()), so a + // scaled HiDPI panel can use more columns than a same-logical-width + // native display; capped at 3 so the popover never dominates a huge // display. - private const int TWO_COLUMN_MIN_WIDTH = 1280; + private const int MAX_DETAIL_COLUMNS = 3; private const int DETAIL_COLUMN_WIDTH = 340; private const int DETAIL_COLUMN_SPACING = 18; private const int DETAIL_SCREEN_MARGIN = 96; @@ -64,13 +65,11 @@ class SensorsIndicator : Gtk.Box { private Box detail_box; private Box detail_toggle_box; private Box detail_columns_box; - private Box detail_left; - private Box detail_right; + private Box[] detail_columns; private Box detail_target; private ScrolledWindow detail_scroller; - private bool use_two_columns = false; - private int left_row_count = 0; - private int right_row_count = 0; + private int detail_column_count = 1; + private int[] detail_column_rows; private static Gtk.CssProvider? compact_rows_provider = null; private SensorMonitor monitor; private bool show_frequency = true; @@ -145,15 +144,17 @@ class SensorsIndicator : Gtk.Box { detail_columns_box = new Box(Orientation.HORIZONTAL, DETAIL_COLUMN_SPACING); detail_columns_box.homogeneous = true; - detail_left = new Box(Orientation.VERTICAL, 8); - detail_right = new Box(Orientation.VERTICAL, 8); - detail_left.hexpand = true; - detail_right.hexpand = true; - detail_columns_box.append(detail_left); - detail_columns_box.append(detail_right); + detail_columns = new Box[MAX_DETAIL_COLUMNS]; + detail_column_rows = new int[MAX_DETAIL_COLUMNS]; + for (int i = 0; i < MAX_DETAIL_COLUMNS; i++) { + detail_columns[i] = new Box(Orientation.VERTICAL, 8); + detail_columns[i].hexpand = true; + detail_columns[i].visible = i == 0; + detail_columns_box.append(detail_columns[i]); + } detail_box.append(detail_toggle_box); detail_box.append(detail_columns_box); - detail_target = detail_left; + detail_target = detail_columns[0]; Popover popover = new Popover(); // Natural size is the ordinary presentation. Automatic vertical @@ -331,14 +332,32 @@ class SensorsIndicator : Gtk.Box { screen_height = geometry.height; } - use_two_columns = screen_width >= TWO_COLUMN_MIN_WIDTH; - detail_right.visible = use_two_columns; - detail_columns_box.spacing = use_two_columns + // Base density on effective physical resolution rather than one + // logical-pixel breakpoint. A scaled HiDPI panel can therefore use + // more columns than a native low-resolution display with the same + // logical width, and a huge logical desktop on a non-scaled panel + // is capped by the logical calculation instead of over-widening. + int scale_factor = 1; + if (target_monitor != null) scale_factor = target_monitor.get_scale_factor(); + int effective_width = screen_width * int.max(1, scale_factor); + int available_width = int.max(DETAIL_COLUMN_WIDTH, effective_width - DETAIL_SCREEN_MARGIN); + int density_slot = DETAIL_COLUMN_WIDTH * 2 + DETAIL_COLUMN_SPACING; + int physical_column_count = int.min(MAX_DETAIL_COLUMNS, + int.max(1, (available_width + DETAIL_COLUMN_SPACING) / density_slot)); + int logical_width = int.max(DETAIL_COLUMN_WIDTH, screen_width - DETAIL_SCREEN_MARGIN); + int logical_column_count = int.max(1, + (logical_width + DETAIL_COLUMN_SPACING) / (DETAIL_COLUMN_WIDTH + DETAIL_COLUMN_SPACING)); + detail_column_count = int.min(physical_column_count, logical_column_count); + detail_column_count = int.min(detail_column_count, MAX_DETAIL_COLUMNS); + + for (int i = 0; i < MAX_DETAIL_COLUMNS; i++) { + detail_columns[i].visible = i < detail_column_count; + } + detail_columns_box.spacing = detail_column_count > 1 ? DETAIL_COLUMN_SPACING : 0; - int content_width = use_two_columns - ? DETAIL_COLUMN_WIDTH * 2 + DETAIL_COLUMN_SPACING - : DETAIL_COLUMN_WIDTH; + int content_width = DETAIL_COLUMN_WIDTH * detail_column_count + + DETAIL_COLUMN_SPACING * (detail_column_count - 1); detail_scroller.min_content_width = content_width; detail_scroller.max_content_width = content_width; detail_scroller.max_content_height = int.max(320, @@ -582,6 +601,9 @@ class SensorsIndicator : Gtk.Box { private Box begin_detail_section() { var section = new Box(Orientation.VERTICAL, 4); + // Bordered frame so stacked resource-pool sections in the same + // column read as distinct cards, not one continuous list. + section.add_css_class("sensors-resource-section"); detail_target = section; return section; } @@ -596,14 +618,15 @@ class SensorsIndicator : Gtk.Box { rows++; } - if (!use_two_columns || left_row_count <= right_row_count) { - detail_left.append(section); - left_row_count += rows; - } else { - detail_right.append(section); - right_row_count += rows; + int target_column = 0; + for (int i = 1; i < detail_column_count; i++) { + if (detail_column_rows[i] < detail_column_rows[target_column]) { + target_column = i; + } } - detail_target = detail_left; + detail_columns[target_column].append(section); + detail_column_rows[target_column] += rows; + detail_target = detail_columns[0]; } private void add_heading(string title) { @@ -1115,10 +1138,10 @@ class SensorsIndicator : Gtk.Box { /** Built only while the popover is open. */ private void rebuild_details() { clear_box(detail_toggle_box); - clear_box(detail_left); - clear_box(detail_right); - left_row_count = 0; - right_row_count = 0; + for (int i = 0; i < MAX_DETAIL_COLUMNS; i++) { + clear_box(detail_columns[i]); + detail_column_rows[i] = 0; + } // One control for the whole popover, at the top so its scope is // obvious before any section renders: it decides whether every