Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 178 additions & 21 deletions src/components/panel/panel.vala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Gtk;
using GtkLayerShell;
using Gee;
using Singularity.Widgets;

namespace Singularity {

Expand All @@ -21,10 +22,28 @@ namespace Singularity {
// 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
// display.
private const int TWO_COLUMN_MIN_WIDTH = 1280;
private const int DETAIL_COLUMN_WIDTH = 340;
private const int DETAIL_COLUMN_SPACING = 18;
private const int DETAIL_SCREEN_MARGIN = 96;

private MenuButton button;
private Label summary_label;
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_target;
private ScrolledWindow detail_scroller;
private bool use_two_columns = false;
private int left_row_count = 0;
private int right_row_count = 0;
private static Gtk.CssProvider? compact_rows_provider = null;
private SensorMonitor monitor;
private bool show_frequency = true;
private bool show_utilization = true;
Expand Down Expand Up @@ -74,29 +93,51 @@ namespace Singularity {
button.child = summary_label;
append(button);

// Real Adw rows, with the original compact panel density rather
// than preferences-page group chrome and separators.
if (compact_rows_provider == null) {
compact_rows_provider = new Gtk.CssProvider();
compact_rows_provider.load_from_string(
"row.sensors-compact-row { min-height: 0; padding: 0; " +
"background: transparent; border: 0; box-shadow: none; }");
Gtk.StyleContext.add_provider_for_display(get_display(),
compact_rows_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}
detail_box = new Box(Orientation.VERTICAL, 4);
detail_box.margin_top = 10;
detail_box.margin_bottom = 10;
detail_box.margin_start = 12;
detail_box.margin_end = 12;

// The control spans the popover. Variable-height sensor sections
// then flow into independent vertical columns; unlike a FlowBox
// row, a tall CPU section does not leave a matching blank hole
// beneath a short GPU section in the opposite column.
detail_toggle_box = new Box(Orientation.VERTICAL, 4);
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_box.append(detail_toggle_box);
detail_box.append(detail_columns_box);
detail_target = detail_left;

Popover popover = new Popover();
// Bound the WHOLE popover, not just each group.
//
// The per-group cap (MAX_ROWS_PER_GROUP) limits any single
// section, but nine sensor kinds plus headings plus the clock
// section still add up: on the 55-sensor Qualcomm topology this
// change explicitly targets, the aggregate reaches roughly 70
// rows and runs off the bottom of the screen, making the lower
// groups unreachable -- capped or not. propagate_natural_height
// keeps small machines rendering exactly as before (the popover
// shrinks to fit two or three groups); only once the content
// genuinely exceeds max_content_height does it start scrolling.
var detail_scroller = new ScrolledWindow();
// Natural size is the ordinary presentation. Automatic vertical
// scrolling remains only as a safety net for unusually many rows
// on a short display; configure_detail_layout() derives the cap
// from the monitor every time the popover opens.
detail_scroller = new ScrolledWindow();
detail_scroller.child = detail_box;
detail_scroller.propagate_natural_height = true;
detail_scroller.propagate_natural_width = true;
detail_scroller.max_content_height = 600;
detail_scroller.hscrollbar_policy = PolicyType.NEVER;
detail_scroller.vscrollbar_policy = PolicyType.AUTOMATIC;
popover.child = detail_scroller;
button.popover = popover;

Expand All @@ -118,6 +159,7 @@ namespace Singularity {
// before.
popover.notify["visible"].connect(() => {
if (popover.visible) {
configure_detail_layout();
monitor.refresh();
}
});
Expand Down Expand Up @@ -228,6 +270,53 @@ namespace Singularity {
: "%d MHz".printf(khz / 1000);
}

/** Find the monitor that owns the panel surface. */
private Gdk.Monitor? panel_monitor() {
var window = get_root() as Gtk.Window;
if (window != null) {
var surface = window.get_surface();
if (surface != null) {
var display = surface.get_display();
var at_surface = display.get_monitor_at_surface(surface);
if (at_surface != null) return at_surface;
}
}

// The widget can briefly have no root during construction. The
// first display monitor is the same fallback used elsewhere in
// the shell for pre-map sizing.
var display = Gdk.Display.get_default();
if (display != null && display.get_monitors().get_n_items() > 0) {
return display.get_monitors().get_item(0) as Gdk.Monitor;
}
return null;
}

/** Re-evaluate width and overflow bounds on every popover opening. */
private void configure_detail_layout() {
int screen_width = 1024;
int screen_height = 768;
var target_monitor = panel_monitor();
if (target_monitor != null) {
var geometry = target_monitor.get_geometry();
screen_width = geometry.width;
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
? DETAIL_COLUMN_SPACING : 0;

int content_width = use_two_columns
? DETAIL_COLUMN_WIDTH * 2 + DETAIL_COLUMN_SPACING
: DETAIL_COLUMN_WIDTH;
detail_scroller.min_content_width = content_width;
detail_scroller.max_content_width = content_width;
detail_scroller.max_content_height = int.max(320,
screen_height - DETAIL_SCREEN_MARGIN);
}

/**
* True when utilisation has something real to show.
*
Expand Down Expand Up @@ -443,12 +532,58 @@ namespace Singularity {
}
}

// Singularity.Widgets.PreferencesRow is the native row container.
// Custom compact content preserves the name/heat/value layout and
// label width cap.
private void append_compact_row(Gtk.Widget content) {
var row = new PreferencesRow();
row.activatable = false;
row.selectable = false;
row.add_css_class("sensors-compact-row");
row.set_child(content);
detail_target.append(row);
}

private static void clear_box(Box box) {
Gtk.Widget? child = box.get_first_child();
while (child != null) {
box.remove(child);
child = box.get_first_child();
}
}

private Box begin_detail_section() {
var section = new Box(Orientation.VERTICAL, 4);
detail_target = section;
return section;
}

/** Keep every heading with its rows and balance whole sections. */
private void finish_detail_section(Box section) {
if (section.get_first_child() == null) return;

int rows = 0;
for (Gtk.Widget? child = section.get_first_child(); child != null;
child = child.get_next_sibling()) {
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;
}
detail_target = detail_left;
}

private void add_heading(string title) {
Label heading = new Label(title);
heading.add_css_class("heading");
heading.halign = Align.START;
heading.margin_top = 4;
detail_box.append(heading);
append_compact_row(heading);
}

/**
Expand Down Expand Up @@ -486,7 +621,7 @@ namespace Singularity {
});
row.append(toggle);

detail_box.append(row);
append_compact_row(row);
}

/**
Expand Down Expand Up @@ -624,7 +759,7 @@ namespace Singularity {
value_label.add_css_class(css);
}
row.append(value_label);
detail_box.append(row);
append_compact_row(row);
}

/**
Expand Down Expand Up @@ -951,15 +1086,16 @@ namespace Singularity {

/** Built only while the popover is open. */
private void rebuild_details() {
Gtk.Widget? child = detail_box.get_first_child();
while (child != null) {
detail_box.remove(child);
child = detail_box.get_first_child();
}
clear_box(detail_toggle_box);
clear_box(detail_left);
clear_box(detail_right);
left_row_count = 0;
right_row_count = 0;

// One control for the whole popover, at the top so its scope is
// obvious before any section renders: it decides whether every
// group below (CPU/GPU/NPU/... and Clocks) shows its heading.
detail_target = detail_toggle_box;
add_sensors_toggle();

// Every kind the backend can name, hottest-silicon first and the
Expand All @@ -970,17 +1106,38 @@ namespace Singularity {
// kinds were classified and then silently dropped -- on Sky1 that
// hid eleven of nineteen readings, including the NVMe that was the
// only one worth looking at.
Box section = begin_detail_section();
add_cpu_section();
finish_detail_section(section);

section = begin_detail_section();
add_group(SensorKind.GPU, _("GPU"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.NPU, _("NPU"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.VPU, _("VPU"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.MEMORY, _("Memory"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.STORAGE, _("Storage"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.NETWORK, _("Network"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.BOARD, _("Board"));
finish_detail_section(section);
section = begin_detail_section();
add_group(SensorKind.SYSTEM, _("System"));
finish_detail_section(section);

section = begin_detail_section();
add_utilization_details();
finish_detail_section(section);
}
}

Expand Down