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

223
ui/tabs.slint Normal file
View File

@@ -0,0 +1,223 @@
import { Theme } from "theme.slint";
import { IconButton } from "widgets.slint";
export struct TabInfo {
id: string,
title: string,
kind: string, // "welcome" | "terminal"
connected: bool,
}
// --- SingleTab -------------------------------------------------------------
component SingleTab inherits Rectangle {
in property <TabInfo> info;
in property <bool> active;
in property <bool> shown: true;
in property <bool> app-background-active: false;
in property <float> ui-opacity: 0.72;
callback selected();
callback closed();
height: shown ? 30px : 0px;
property <length> title-width: min(128px, max(34px, title-probe.preferred-width));
property <length> tab-width: info.kind == "welcome"
? max(72px, title-width + 34px)
: max(74px, title-width + 62px);
width: shown ? min(176px, tab-width) : 0px;
visible: shown;
horizontal-stretch: 0;
vertical-stretch: 0;
border-radius: Theme.radius-sm;
background: active ? (root.app-background-active ? Theme.bg-panel.with-alpha(root.ui-opacity) : Theme.bg-panel)
: (touch.has-hover ? Theme.bg-hover : transparent);
animate background { duration: 120ms; }
// Declared FIRST so the HorizontalLayout's children (close button) are at
// a higher z-order and receive their own click events.
touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.selected(); }
}
title-probe := Text {
text: info.title;
font-size: Theme.fs-sm;
opacity: 0;
x: -1000px;
y: -1000px;
}
HorizontalLayout {
height: parent.height;
padding-left: 10px;
padding-right: 6px;
spacing: 8px;
alignment: center;
// Status indicator
Rectangle {
width: 8px;
height: parent.height;
Rectangle {
y: (parent.height - self.height) / 2;
width: parent.width;
height: 8px;
border-radius: 4px;
background: info.kind == "welcome" ? Theme.text-muted
: (info.connected ? Theme.success : Theme.warning);
}
}
Text {
text: info.title;
height: parent.height;
color: active ? Theme.text-primary : Theme.text-secondary;
font-size: Theme.fs-sm;
vertical-alignment: center;
overflow: elide;
horizontal-stretch: 1;
}
// Only show close button for non-welcome tabs
if info.kind != "welcome" : Rectangle {
width: 20px;
height: parent.height;
IconButton {
y: (parent.height - self.height) / 2;
glyph: "×";
size: 20px;
clicked => { root.closed(); }
}
}
}
}
// --- TabBar ----------------------------------------------------------------
export component TabBar inherits Rectangle {
in property <[TabInfo]> tabs;
in property <string> active-id;
in-out property <int> visible-start: 0;
in property <bool> app-background-active: false;
in property <float> ui-opacity: 0.72;
property <int> visible-count: 5;
callback tab-selected(string);
callback tab-closed(string);
callback new-tab();
callback previous-tab();
callback next-tab();
height: 36px;
background: root.app-background-active ? Theme.bg-panel-alt.with-alpha(root.ui-opacity) : Theme.bg-panel-alt;
HorizontalLayout {
height: parent.height;
padding-left: 3px;
padding-right: 100px; // reserve the overlaid theme/download/settings buttons
padding-top: 3px;
padding-bottom: 3px;
spacing: 2px;
Rectangle {
width: 30px;
height: 30px;
border-radius: Theme.radius-sm;
background: root.active-id == "welcome"
? Theme.bg-panel
: (folder-ta.has-hover ? Theme.bg-hover : transparent);
folder-ta := TouchArea {
mouse-cursor: pointer;
clicked => { root.new-tab(); }
}
Text {
width: parent.width;
height: parent.height;
text: "📂";
color: root.active-id == "welcome" ? Theme.text-primary : Theme.text-secondary;
font-size: Theme.fs-md;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
width: 56px;
height: 30px;
vertical-stretch: 0;
HorizontalLayout {
height: parent.height;
spacing: 0;
Rectangle {
width: 28px;
height: parent.height;
border-radius: Theme.radius-sm;
background: prev-ta.has-hover ? Theme.bg-hover : transparent;
prev-ta := TouchArea {
mouse-cursor: pointer;
clicked => { root.previous-tab(); }
}
Text {
width: parent.width;
height: parent.height;
text: "[<]";
color: Theme.text-secondary;
font-size: Theme.fs-xs;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
width: 28px;
height: parent.height;
border-radius: Theme.radius-sm;
background: next-ta.has-hover ? Theme.bg-hover : transparent;
next-ta := TouchArea {
mouse-cursor: pointer;
clicked => { root.next-tab(); }
}
Text {
width: parent.width;
height: parent.height;
text: "[>]";
color: Theme.text-secondary;
font-size: Theme.fs-xs;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
Rectangle {
horizontal-stretch: 1;
height: 30px;
clip: true;
tabs-flick := Flickable {
width: parent.width;
height: parent.height;
viewport-width: max(self.width, tabs-row.preferred-width);
viewport-height: self.height;
interactive: true;
tabs-row := HorizontalLayout {
height: parent.height;
spacing: 0px;
for tab[i] in root.tabs : SingleTab {
shown: i >= root.visible-start && i < root.visible-start + root.visible-count;
info: tab;
active: tab.id == root.active-id;
app-background-active: root.app-background-active;
ui-opacity: root.ui-opacity;
selected => { root.tab-selected(tab.id); }
closed => { root.tab-closed(tab.id); }
}
}
}
}
}
}