This commit is contained in:
2026-06-19 03:09:53 +08:00
Unverified
commit d32882443f
49 changed files with 19399 additions and 0 deletions

499
ui/sidebar.slint Normal file
View File

@@ -0,0 +1,499 @@
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;
}
}
}