Files
meatshell/ui/tabs.slint
2026-06-19 03:09:53 +08:00

224 lines
7.3 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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); }
}
}
}
}
}
}