500 lines
17 KiB
Plaintext
500 lines
17 KiB
Plaintext
import { Theme } from "theme.slint";
|
|
import { Sparkline } from "widgets.slint";
|
|
|
|
// One filesystem row for the disk-usage panel.
|
|
export struct DiskInfo {
|
|
path: string,
|
|
detail: string, // "available/total"
|
|
percent: float, // used fraction 0.0..1.0
|
|
}
|
|
|
|
export struct ProcessInfo {
|
|
mem: string,
|
|
cpu: string,
|
|
name: string,
|
|
}
|
|
|
|
// --- StatRow ---------------------------------------------------------------
|
|
// A single label / percentage / progress-bar row used for CPU / memory / swap.
|
|
component StatRow inherits Rectangle {
|
|
in property <string> label;
|
|
in property <float> percent; // 0.0 .. 1.0
|
|
in property <string> detail;
|
|
in property <brush> bar-color: Theme.accent;
|
|
|
|
height: 30px;
|
|
border-radius: Theme.radius-sm;
|
|
background: Theme.bg-panel-alt.with-alpha(0.38);
|
|
|
|
HorizontalLayout {
|
|
height: parent.height;
|
|
padding-left: 6px;
|
|
padding-right: 6px;
|
|
spacing: 7px;
|
|
|
|
Text {
|
|
width: 32px;
|
|
height: parent.height;
|
|
text: label;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
|
|
Rectangle {
|
|
y: (parent.height - self.height) / 2;
|
|
height: 20px;
|
|
horizontal-stretch: 1;
|
|
background: Theme.bg-root.with-alpha(0.56);
|
|
border-radius: Theme.radius-sm;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle.with-alpha(0.58);
|
|
clip: true;
|
|
|
|
Rectangle {
|
|
x: 0;
|
|
height: parent.height;
|
|
width: parent.width * clamp(percent, 0.0, 1.0);
|
|
background: bar-color.with-alpha(0.52);
|
|
border-radius: Theme.radius-sm;
|
|
animate width { duration: 500ms; easing: ease-out; }
|
|
}
|
|
|
|
HorizontalLayout {
|
|
height: parent.height;
|
|
padding-left: 7px;
|
|
padding-right: 7px;
|
|
Text {
|
|
height: parent.height;
|
|
text: Math.round(percent * 100) + "%";
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-xs;
|
|
font-weight: 600;
|
|
vertical-alignment: center;
|
|
}
|
|
Rectangle { horizontal-stretch: 1; }
|
|
Text {
|
|
height: parent.height;
|
|
text: detail;
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-xs;
|
|
horizontal-alignment: right;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component ProcessTable inherits VerticalLayout {
|
|
in property <[ProcessInfo]> processes;
|
|
|
|
vertical-stretch: 0;
|
|
spacing: 1px;
|
|
|
|
HorizontalLayout {
|
|
height: 18px;
|
|
padding-left: 6px;
|
|
padding-right: 6px;
|
|
spacing: 4px;
|
|
Text {
|
|
width: 42px;
|
|
text: @tr("Memory");
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
width: 32px;
|
|
text: "CPU";
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: @tr("Program");
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
horizontal-stretch: 1;
|
|
}
|
|
}
|
|
|
|
for p in root.processes : Rectangle {
|
|
height: 18px;
|
|
border-radius: 2px;
|
|
background: row-ta.has-hover ? Theme.bg-hover.with-alpha(0.78) : Theme.bg-panel-alt.with-alpha(0.22);
|
|
row-ta := TouchArea {}
|
|
HorizontalLayout {
|
|
height: parent.height;
|
|
padding-left: 6px;
|
|
padding-right: 6px;
|
|
spacing: 4px;
|
|
Text {
|
|
width: 42px;
|
|
text: p.mem;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
Text {
|
|
width: 32px;
|
|
text: p.cpu;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
Text {
|
|
text: p.name;
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
horizontal-stretch: 1;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- NetGraph --------------------------------------------------------------
|
|
// One network throughput graph: a header row (↑ tx / ↓ rx, plus an optional
|
|
// NIC selector dropdown) over an auto-scaled sparkline.
|
|
component NetGraph inherits VerticalLayout {
|
|
in property <string> up-text;
|
|
in property <string> down-text;
|
|
in property <[float]> history;
|
|
in property <bool> show-selector: false;
|
|
in property <[string]> ifaces;
|
|
in property <string> selected;
|
|
callback iface-selected(string);
|
|
|
|
// Stay at preferred height; without this the inherited VerticalLayout grabs
|
|
// a stretch share of the sidebar and balloons the sparkline into blank space.
|
|
vertical-stretch: 0;
|
|
spacing: 3px;
|
|
|
|
HorizontalLayout {
|
|
height: 18px;
|
|
spacing: 3px;
|
|
Text { // ↑ upload (green)
|
|
width: 12px;
|
|
height: parent.height;
|
|
text: "\u{E5D8}";
|
|
font-family: "Material Icons";
|
|
color: Theme.success;
|
|
font-size: Theme.fs-xs;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
height: parent.height;
|
|
text: root.up-text;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
}
|
|
Text { // ↓ download (blue)
|
|
width: 12px;
|
|
height: parent.height;
|
|
text: "\u{E5DB}";
|
|
font-family: "Material Icons";
|
|
color: Theme.accent;
|
|
font-size: Theme.fs-xs;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
height: parent.height;
|
|
text: root.down-text;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
}
|
|
Rectangle { horizontal-stretch: 1; } // spacer pushes selector to the right
|
|
|
|
if root.show-selector : Rectangle {
|
|
width: 64px;
|
|
height: 18px;
|
|
border-radius: 3px;
|
|
background: sel-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated;
|
|
|
|
HorizontalLayout {
|
|
height: parent.height;
|
|
padding-left: 5px; padding-right: 4px; spacing: 2px;
|
|
Text {
|
|
height: parent.height;
|
|
text: root.selected;
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
horizontal-stretch: 1;
|
|
overflow: elide;
|
|
}
|
|
Text {
|
|
height: parent.height;
|
|
text: "\u{E5CF}";
|
|
font-family: "Material Icons";
|
|
color: Theme.text-muted;
|
|
font-size: 8px;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
|
|
sel-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => { popup.show(); }
|
|
}
|
|
|
|
popup := PopupWindow {
|
|
x: 0; y: parent.height + 2px;
|
|
width: 96px;
|
|
Rectangle {
|
|
background: Theme.bg-panel;
|
|
border-radius: 4px;
|
|
border-width: 1px;
|
|
border-color: Theme.border-strong;
|
|
drop-shadow-blur: 10px;
|
|
drop-shadow-color: #00000050;
|
|
VerticalLayout {
|
|
padding: 4px;
|
|
spacing: 1px;
|
|
for iface in root.ifaces : Rectangle {
|
|
height: 22px;
|
|
border-radius: 3px;
|
|
background: row-ta.has-hover ? Theme.bg-hover : transparent;
|
|
row-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => { root.iface-selected(iface); }
|
|
}
|
|
Text {
|
|
x: 6px;
|
|
text: iface;
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
height: parent.height;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Sparkline {
|
|
height: 48px;
|
|
values: root.history;
|
|
line-color: Theme.accent;
|
|
}
|
|
}
|
|
|
|
// --- Sidebar ---------------------------------------------------------------
|
|
// Left-hand panel showing local machine health, mimicking FinalShell's
|
|
// @tr("Status") column but kept minimal for the v0.1 baseline.
|
|
export component Sidebar inherits Rectangle {
|
|
in property <bool> app-background-active: false;
|
|
in property <float> ui-opacity: 0.72;
|
|
in property <string> connection-state: @tr("Not connected");
|
|
in property <int> conn-state; // 0 gray / 1 green / 2 yellow
|
|
in property <string> resource-title: @tr("Local resources");
|
|
in property <float> cpu-percent;
|
|
in property <float> mem-percent;
|
|
in property <float> swap-percent;
|
|
in property <string> cpu-detail;
|
|
in property <string> mem-detail;
|
|
in property <string> swap-detail;
|
|
in property <string> net-top-up;
|
|
in property <string> net-top-down;
|
|
in property <[float]> net-top-history;
|
|
in property <[string]> net-ifaces;
|
|
in property <string> net-selected;
|
|
in property <bool> net-show-selector;
|
|
in property <[DiskInfo]> disks;
|
|
in property <[ProcessInfo]> processes;
|
|
callback select-net-iface(string);
|
|
|
|
width: 220px;
|
|
background: root.app-background-active ? Theme.bg-panel.with-alpha(root.ui-opacity) : Theme.bg-panel;
|
|
border-width: 0;
|
|
|
|
VerticalLayout {
|
|
padding: 10px;
|
|
spacing: 10px;
|
|
|
|
// Status header
|
|
VerticalLayout {
|
|
spacing: 3px;
|
|
Text {
|
|
text: @tr("Running status");
|
|
color: Theme.text-primary;
|
|
font-size: Theme.fs-md;
|
|
font-weight: 600;
|
|
vertical-alignment: center;
|
|
}
|
|
Rectangle {
|
|
height: 24px;
|
|
border-radius: Theme.radius-sm;
|
|
background: root.conn-state == 1 ? Theme.success.with-alpha(0.16)
|
|
: root.conn-state == 2 ? Theme.warning.with-alpha(0.16)
|
|
: Theme.bg-root;
|
|
Text {
|
|
x: 8px;
|
|
width: parent.width - 16px;
|
|
height: parent.height;
|
|
text: connection-state;
|
|
color: root.conn-state == 1 ? Theme.success
|
|
: root.conn-state == 2 ? Theme.warning
|
|
: Theme.text-secondary;
|
|
font-size: Theme.fs-sm;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 1px; background: Theme.border-subtle; }
|
|
|
|
// System stats — local machine on the welcome tab, the remote server
|
|
// when a connected session tab is active.
|
|
Text {
|
|
text: root.resource-title;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-sm;
|
|
}
|
|
|
|
VerticalLayout {
|
|
spacing: 6px;
|
|
StatRow {
|
|
label: "CPU";
|
|
percent: root.cpu-percent;
|
|
detail: root.cpu-detail;
|
|
bar-color: Theme.accent;
|
|
}
|
|
StatRow {
|
|
label: @tr("Memory");
|
|
percent: root.mem-percent;
|
|
detail: root.mem-detail;
|
|
bar-color: Theme.success;
|
|
}
|
|
StatRow {
|
|
label: @tr("Swap");
|
|
percent: root.swap-percent;
|
|
detail: root.swap-detail;
|
|
bar-color: Theme.warning;
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 1px; background: Theme.border-subtle; }
|
|
|
|
ProcessTable {
|
|
processes: root.processes;
|
|
}
|
|
|
|
if root.net-top-up != "" || root.net-top-down != "" || root.net-show-selector : Rectangle {
|
|
height: 1px;
|
|
background: Theme.border-subtle;
|
|
}
|
|
|
|
// Network — follows the active live session tab.
|
|
if root.net-top-up != "" || root.net-top-down != "" || root.net-show-selector : VerticalLayout {
|
|
vertical-stretch: 0;
|
|
spacing: 6px;
|
|
NetGraph {
|
|
up-text: root.net-top-up;
|
|
down-text: root.net-top-down;
|
|
history: root.net-top-history;
|
|
show-selector: root.net-show-selector;
|
|
ifaces: root.net-ifaces;
|
|
selected: root.net-selected;
|
|
iface-selected(i) => { root.select-net-iface(i); }
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 1px; background: Theme.border-subtle; }
|
|
|
|
// Disk-usage panel (active tab's filesystems; local on the welcome tab).
|
|
disk-section := VerticalLayout {
|
|
vertical-stretch: 1;
|
|
spacing: 3px;
|
|
|
|
HorizontalLayout {
|
|
padding-left: 6px;
|
|
padding-right: 6px;
|
|
Text {
|
|
text: @tr("Path");
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
horizontal-stretch: 1;
|
|
}
|
|
Text {
|
|
text: @tr("Free/Total");
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
}
|
|
}
|
|
|
|
disk-flick := Flickable {
|
|
vertical-stretch: 1;
|
|
viewport-width: self.width;
|
|
// Grow only when the list overflows; otherwise match the visible
|
|
// height so `alignment: start` keeps rows pinned to the top.
|
|
viewport-height: max(self.height, disk-col.preferred-height);
|
|
disk-col := VerticalLayout {
|
|
alignment: start;
|
|
spacing: 1px;
|
|
for d[i] in root.disks : Rectangle {
|
|
height: 20px;
|
|
border-radius: 2px;
|
|
clip: true;
|
|
|
|
// Usage fill: green-ish normally, amber/red when full.
|
|
Rectangle {
|
|
x: 0;
|
|
y: 0;
|
|
height: parent.height;
|
|
width: parent.width * clamp(d.percent, 0.0, 1.0);
|
|
background: d.percent > 0.9 ? Theme.danger.with-alpha(0.32)
|
|
: d.percent > 0.75 ? Theme.warning.with-alpha(0.30)
|
|
: Theme.accent.with-alpha(0.22);
|
|
}
|
|
HorizontalLayout {
|
|
height: parent.height;
|
|
padding-left: 6px;
|
|
padding-right: 6px;
|
|
spacing: 4px;
|
|
Text {
|
|
height: parent.height;
|
|
text: d.path;
|
|
color: Theme.text-secondary;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
horizontal-stretch: 1;
|
|
overflow: elide;
|
|
}
|
|
Text {
|
|
height: parent.height;
|
|
text: d.detail;
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
vertical-alignment: center;
|
|
horizontal-alignment: right;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: "meatshell v0.2.7";
|
|
color: Theme.text-muted;
|
|
font-size: Theme.fs-xs;
|
|
horizontal-alignment: center;
|
|
}
|
|
}
|
|
}
|