import { HorizontalBox } from "std-widgets.slint"; import { AppTheme, LayoutMetrics, ThemeUtilities } from "../design-system.slint"; import { TypeScale } from "../typography.slint"; import { FocusVisibleScope } from "focus-visible-scope.slint"; // Application-owned button with deterministic geometry across compositors. export component Button inherits Rectangle { in property text; in property icon; in property has-icon: false; in property colorize-icon: true; in property icon-size: LayoutMetrics.icon-size; in property primary: false; in property destructive: false; in property selected: false; in property enabled: true; // A button that fills a fixed-width column may elide a label that does // not fit there; other buttons are given room for the whole label. in property label-overflow: clip; // The width that shows the whole label, for buttons sized to it. The // label row moves when pressed, so it does not size the button itself. out property content-width: label-row.preferred-width; out property focus-visible: keyboard.focus-visible; callback clicked; height: 32px; min-width: 64px; border-radius: LayoutMetrics.corner-radius - 2px; forward-focus: keyboard; border-width: keyboard.focus-visible ? 2px : root.primary || root.destructive ? 0px : 1px; border-color: keyboard.focus-visible || root.selected ? AppTheme.accent : AppTheme.control-border; private property fill-color: !root.enabled ? AppTheme.disabled-bg : root.destructive ? (button-area.pressed ? AppTheme.destructive-pressed : button-area.has-hover ? AppTheme.destructive-hover : AppTheme.destructive) : root.primary ? (button-area.pressed ? AppTheme.primary-pressed : button-area.has-hover ? AppTheme.primary-hover : AppTheme.primary) : root.selected ? AppTheme.accent-soft : button-area.pressed ? AppTheme.muted-control-bg : button-area.has-hover ? AppTheme.control-bg.brighter(AppTheme.dark ? 8% : 2%) : AppTheme.surface; private property foreground: root.destructive ? white : root.primary ? ThemeUtilities.contrasting-foreground(root.fill-color) : root.selected ? AppTheme.accent : AppTheme.heading; background: root.fill-color; opacity: root.enabled ? 1 : 0.5; animate background { duration: 120ms; easing: ease-out; } accessible-role: button; accessible-label: root.text; accessible-action-default => { if root.enabled { root.clicked(); } } label-row := HorizontalBox { y: button-area.pressed ? 1px : 0px; animate y { duration: 140ms; easing: spring(0); } padding-left: 12px; padding-right: 12px; spacing: 7px; alignment: center; cross-axis-alignment: center; if root.has-icon : Image { width: root.icon-size; height: root.icon-size; source: root.icon; colorize: root.colorize-icon ? (root.primary || root.destructive ? root.foreground : AppTheme.body) : transparent; image-fit: contain; accessible-role: none; } Text { text: root.text; color: root.foreground; font-size: TypeScale.body; font-weight: root.primary || root.destructive || root.selected ? 600 : 500; horizontal-alignment: center; vertical-alignment: center; overflow: root.label-overflow; } } keyboard := FocusVisibleScope { enabled: root.enabled; accessible-role: none; key-pressed(event) => { if event.text == Key.Return || event.text == Key.Space { root.clicked(); return accept; } reject } } button-area := TouchArea { enabled: root.enabled; clicked => { keyboard.focus(); keyboard.pointer-activated(); root.clicked(); } mouse-cursor: pointer; } }