From 2ee1ee435d0a72dc71eb19b90a793861dafa45ca Mon Sep 17 00:00:00 2001 From: notfirefox <138280734+notfirefox@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:02:17 +0000 Subject: [PATCH] Allow scrolling through tabs for all backends This allows the user to use the scroll wheel to scroll through the tabs. In order to do this I have made created a common TabBarBase and let all other implementations of TabBar* inherit from it. I also used slint-lsp to format the files (it did not like the inline comments). --- .../widgets/common/tabwidget-base.slint | 35 +++++++++++++ .../widgets/cosmic-base/tabwidget.slint | 37 ++++++++------ .../widgets/cupertino-base/tabwidget.slint | 46 +++++++++-------- .../widgets/fluent-base/tabwidget.slint | 42 ++++++++------- .../widgets/material-base/tabwidget.slint | 51 ++++++++++++------- internal/compiler/widgets/qt/tabwidget.slint | 30 ++++++----- 6 files changed, 154 insertions(+), 87 deletions(-) create mode 100644 internal/compiler/widgets/common/tabwidget-base.slint diff --git a/internal/compiler/widgets/common/tabwidget-base.slint b/internal/compiler/widgets/common/tabwidget-base.slint new file mode 100644 index 000000000..14f4b59f5 --- /dev/null +++ b/internal/compiler/widgets/common/tabwidget-base.slint @@ -0,0 +1,35 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial + +export component TabBarBase inherits TouchArea { + // injected properties: + // The currently selected tab + in-out property current; + // The total number of tabs + in-out property num-tabs; + + // Returns the index of the previous tab + protected pure function previous-tab() -> int { + return Math.max(root.current - 1, 0); + } + + // Returns the index of the next tab + protected pure function next-tab() -> int { + return Math.min(root.current + 1, root.num-tabs - 1); + } + + private property scroll-delta: 2px; + + // Allows scrolling through the tabs + scroll-event(event) => { + if (event.delta-y < -root.scroll-delta) { + root.current = next-tab(); + return accept; + } + if (event.delta-y > -root.scroll-delta) { + root.current = previous-tab(); + return accept; + } + reject + } +} diff --git a/internal/compiler/widgets/cosmic-base/tabwidget.slint b/internal/compiler/widgets/cosmic-base/tabwidget.slint index 76b601d84..eaad215dd 100644 --- a/internal/compiler/widgets/cosmic-base/tabwidget.slint +++ b/internal/compiler/widgets/cosmic-base/tabwidget.slint @@ -3,6 +3,7 @@ import { CosmicPalette, CosmicFontSettings } from "styling.slint"; import { StateLayerBase } from "components.slint"; +import { TabBarBase } from "../common/tabwidget-base.slint"; export component TabWidgetImpl inherits Rectangle { in property tabbar-preferred-height; @@ -27,13 +28,17 @@ export component TabWidgetImpl inherits Rectangle { } export component TabImpl inherits Rectangle { - in property current-focused; // The currently focused tab - in property tab-index; // The index of this tab - in property num-tabs; // The total number of tabs + // The currently focused tab + in property current-focused; + // The index of this tab + in property tab-index; + // The total number of tabs + in property num-tabs; in property title <=> text.text; in property enabled: true; out property has-focus: root.current-focused == root.tab-index; - in-out property current; // The currently selected tab + // The currently selected tab + in-out property current; private property show-left-border: root.tab-index == 0 || root.is-current; private property show-right-border: root.current != root.tab-index + 1; @@ -69,7 +74,7 @@ export component TabImpl inherits Rectangle { background: state-layer.background; } - if root.has-focus : Rectangle { + if root.has-focus: Rectangle { Rectangle { y: 0; height: root.height - root.inner-border-radius; @@ -126,11 +131,10 @@ export component TabImpl inherits Rectangle { } } -export component TabBarImpl { +export component TabBarImpl inherits TabBarBase { // injected properties: - in-out property current; // The currently selected tab - in-out property current-focused: focus-scope.has-focus ? focus-scope.focused-tab : -1; // The currently focused tab - in-out property num-tabs; // The total number of tabs + // The currently focused tab + in-out property current-focused: focus-scope.has-focus ? focus-scope.focused-tab : -1; accessible-role: tab-list; accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current; @@ -140,7 +144,7 @@ export component TabBarImpl { @children } - if focus-scope.has-focus : Rectangle { + if focus-scope.has-focus: Rectangle { y: root.height - self.height; height: 1px; background: CosmicPalette.accent-background; @@ -150,12 +154,13 @@ export component TabBarImpl { property focused-tab: 0; x: 0; - width: 0px; // Do not react on clicks + // Do not react on clicks + width: 0px; key-pressed(event) => { if (event.text == "\n") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } if (event.text == Key.LeftArrow) { self.focused-tab = Math.max(self.focused-tab - 1, 0); @@ -170,12 +175,12 @@ export component TabBarImpl { key-released(event) => { if (event.text == " ") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } return reject; } } } -export component TabWidget inherits TabWidget {} +export component TabWidget inherits TabWidget { } diff --git a/internal/compiler/widgets/cupertino-base/tabwidget.slint b/internal/compiler/widgets/cupertino-base/tabwidget.slint index d8a659d89..68391d7df 100644 --- a/internal/compiler/widgets/cupertino-base/tabwidget.slint +++ b/internal/compiler/widgets/cupertino-base/tabwidget.slint @@ -3,6 +3,7 @@ import { CupertinoPalette, CupertinoFontSettings } from "styling.slint"; import { FocusBorder } from "components.slint"; +import { TabBarBase } from "../common/tabwidget-base.slint"; export component TabWidgetImpl inherits Rectangle { in property tabbar-preferred-height; @@ -27,13 +28,17 @@ export component TabWidgetImpl inherits Rectangle { } export component TabImpl inherits Rectangle { - in property current-focused; // The currently focused tab - in property tab-index; // The index of this tab - in property num-tabs; // The total number of tabs + // The currently focused tab + in property current-focused; + // The index of this tab + in property tab-index; + // The total number of tabs + in property num-tabs; in property title <=> i-text.text; in property enabled: true; out property has-focus: root.current-focused == root.tab-index; - in-out property current; // The currently selected tab + // The currently selected tab + in-out property current; private property hide-right-border: root.tab-index == root.num-tabs - 1 || tab-index + 1 == current; private property is-current: root.tab-index == root.current; @@ -45,11 +50,10 @@ export component TabImpl inherits Rectangle { accessible-role: tab; accessible-label: root.title; - if (root.is-current || i-touch-area.pressed) : Rectangle { + if (root.is-current || i-touch-area.pressed): Rectangle { width: 100%; height: 100%; - background: i-touch-area.pressed ? CupertinoPalette.secondary-control-background : - CupertinoPalette.control-background; + background: i-touch-area.pressed ? CupertinoPalette.secondary-control-background : CupertinoPalette.control-background; border-radius: 5px; drop-shadow-blur: 0.25px; drop-shadow-color: #0000001f; @@ -57,10 +61,13 @@ export component TabImpl inherits Rectangle { border-width: 1px; border-color: CupertinoPalette.decent-border; - animate background { duration: 75ms; easing: linear; } + animate background { + duration: 75ms; + easing: linear; + } } - if (!root.is-current && root.tab-index < root.num-tabs - 1) : Rectangle { + if (!root.is-current && root.tab-index < root.num-tabs - 1): Rectangle { x: root.width - self.width; y: (parent.height - self.height) / 2; width: 1px; @@ -90,16 +97,14 @@ export component TabImpl inherits Rectangle { } } -export component TabBarImpl { +export component TabBarImpl inherits TabBarBase { // injected properties: - in-out property current; // The currently selected tab - in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; // The currently focused tab - in-out property num-tabs; // The total number of tabs + // The currently focused tab + in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; accessible-role: tab-list; accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current; - Rectangle { border-radius: 7px; border-color: CupertinoPalette.bar-border; @@ -128,12 +133,13 @@ export component TabBarImpl { property focused-tab: 0; x: 0; - width: 0px; // Do not react on clicks + // Do not react on clicks + width: 0px; key-pressed(event) => { if (event.text == "\n") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } if (event.text == Key.LeftArrow) { self.focused-tab = Math.max(self.focused-tab - 1, 0); @@ -148,12 +154,12 @@ export component TabBarImpl { key-released(event) => { if (event.text == " ") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } return reject; } } } -export component TabWidget inherits TabWidget {} +export component TabWidget inherits TabWidget { } diff --git a/internal/compiler/widgets/fluent-base/tabwidget.slint b/internal/compiler/widgets/fluent-base/tabwidget.slint index 79066afa2..af9be5f13 100644 --- a/internal/compiler/widgets/fluent-base/tabwidget.slint +++ b/internal/compiler/widgets/fluent-base/tabwidget.slint @@ -3,6 +3,7 @@ import { FluentPalette, FluentFontSettings } from "styling.slint"; import { FocusBorder } from "components.slint"; +import { TabBarBase } from "../common/tabwidget-base.slint"; export component TabWidgetImpl inherits Rectangle { in property tabbar-preferred-height; @@ -27,13 +28,17 @@ export component TabWidgetImpl inherits Rectangle { } export component TabImpl inherits Rectangle { - in property current-focused; // The currently focused tab - in property tab-index; // The index of this tab - in property num-tabs; // The total number of tabs + // The currently focused tab + in property current-focused; + // The index of this tab + in property tab-index; + // The total number of tabs + in property num-tabs; in property title <=> i-text.text; in property enabled: true; out property has-focus: root.current-focused == root.tab-index; - in-out property current; // The currently selected tab + // The currently selected tab + in-out property current; private property hide-right-border: root.tab-index == root.num-tabs - 1 || tab-index + 1 == current; private property is-current: root.tab-index == root.current; @@ -57,8 +62,7 @@ export component TabImpl inherits Rectangle { border-radius: 7px; border-width: root.is-current ? 1px : 0px; border-color: FluentPalette.card-stroke; - background: i-touch-area.pressed || root.is-current ? FluentPalette.layer-on-mica-base-alt : - i-touch-area.has-hover ? FluentPalette.layer-on-mica-base-alt-secondary : transparent; + background: i-touch-area.pressed || root.is-current ? FluentPalette.layer-on-mica-base-alt : i-touch-area.has-hover ? FluentPalette.layer-on-mica-base-alt-secondary : transparent; } } @@ -83,14 +87,14 @@ export component TabImpl inherits Rectangle { } } - if (!root.is-current && !root.hide-right-border && !i-touch-area.has-hover && !i-touch-area.pressed) : Rectangle { + if (!root.is-current && !root.hide-right-border && !i-touch-area.has-hover && !i-touch-area.pressed): Rectangle { x: (parent.width - self.width); width: 2px; height: 16px; background: FluentPalette.divider; } - if (!root.is-current && !i-touch-area.has-hover && !i-touch-area.pressed) : Rectangle { + if (!root.is-current && !i-touch-area.has-hover && !i-touch-area.pressed): Rectangle { y: (parent.height - self.height); width: 100%; height: 1px; @@ -98,16 +102,15 @@ export component TabImpl inherits Rectangle { } // focus border - if (root.has-focus && root.enabled) : FocusBorder { + if (root.has-focus && root.enabled): FocusBorder { border-radius: i-background.border-radius; } } -export component TabBarImpl { +export component TabBarImpl inherits TabBarBase { // injected properties: - in-out property current; // The currently selected tab - in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; // The currently focused tab - in-out property num-tabs; // The total number of tabs + // The currently focused tab + in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; HorizontalLayout { alignment: start; @@ -122,12 +125,13 @@ export component TabBarImpl { property focused-tab: 0; x: 0; - width: 0px; // Do not react on clicks + // Do not react on clicks + width: 0px; key-pressed(event) => { if (event.text == "\n") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } if (event.text == Key.LeftArrow) { self.focused-tab = Math.max(self.focused-tab - 1, 0); @@ -142,12 +146,12 @@ export component TabBarImpl { key-released(event) => { if (event.text == " ") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } return reject; } } } -export component TabWidget inherits TabWidget {} +export component TabWidget inherits TabWidget { } diff --git a/internal/compiler/widgets/material-base/tabwidget.slint b/internal/compiler/widgets/material-base/tabwidget.slint index 326bb9583..cd8b34969 100644 --- a/internal/compiler/widgets/material-base/tabwidget.slint +++ b/internal/compiler/widgets/material-base/tabwidget.slint @@ -3,6 +3,7 @@ import { MaterialPalette, MaterialFontSettings } from "styling.slint"; +import { TabBarBase } from "../common/tabwidget-base.slint"; export component TabWidgetImpl inherits Rectangle { in property tabbar-preferred-height; @@ -27,12 +28,16 @@ export component TabWidgetImpl inherits Rectangle { } export component TabImpl inherits Rectangle { - in property current-focused; // The currently focused tab - in property tab-index; // The index of this tab - in property num-tabs; // The total number of tabs + // The currently focused tab + in property current-focused; + // The index of this tab + in property tab-index; + // The total number of tabs + in property num-tabs; in property title <=> i-text.text; in property enabled: true; - in-out property current; // The currently selected tab + // The currently selected tab + in-out property current; private property has-focus: root.current-focused == root.tab-index; private property active: root.tab-index == root.current; @@ -52,8 +57,11 @@ export component TabImpl inherits Rectangle { border-radius: i-container.border-radius; background: MaterialPalette.accent-background; - animate opacity { duration: 250ms; easing: ease; } - } + animate opacity { + duration: 250ms; + easing: ease; + } + } i-layout := HorizontalLayout { padding-left: 16px; @@ -67,7 +75,10 @@ export component TabImpl inherits Rectangle { font-size: MaterialFontSettings.title-small.font-size; font-weight: MaterialFontSettings.title-small.font-weight; - animate color { duration: 250ms; easing: ease; } + animate color { + duration: 250ms; + easing: ease; + } } } @@ -78,7 +89,10 @@ export component TabImpl inherits Rectangle { y: parent.height - self.height; background: MaterialPalette.accent-background; - animate opacity { duration: 250ms; easing: ease; } + animate opacity { + duration: 250ms; + easing: ease; + } } i-touch-area := TouchArea { @@ -90,11 +104,10 @@ export component TabImpl inherits Rectangle { } } -export component TabBarImpl { +export component TabBarImpl inherits TabBarBase { // injected properties: - in-out property current; // The currently selected tab - in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; // The currently focused tab - in-out property num-tabs; // The total number of tabs + // The currently focused tab + in-out property current-focused: i-focus-scope.has-focus ? i-focus-scope.focused-tab : -1; accessible-role: tab-list; accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current; @@ -109,12 +122,13 @@ export component TabBarImpl { property focused-tab: 0; x: 0; - width: 0; // Do not react on clicks + // Do not react on clicks + width: 0; key-pressed(event) => { if (event.text == "\n") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } if (event.text == Key.LeftArrow) { self.focused-tab = Math.max(self.focused-tab - 1, 0); @@ -129,13 +143,12 @@ export component TabBarImpl { key-released(event) => { if (event.text == " ") { - root.current = root.current-focused; - return accept; + root.current = root.current-focused; + return accept; } return reject; } } } - -export component TabWidget inherits TabWidget {} +export component TabWidget inherits TabWidget { } diff --git a/internal/compiler/widgets/qt/tabwidget.slint b/internal/compiler/widgets/qt/tabwidget.slint index 87846f0f8..4544ed466 100644 --- a/internal/compiler/widgets/qt/tabwidget.slint +++ b/internal/compiler/widgets/qt/tabwidget.slint @@ -1,6 +1,8 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial +import { TabBarBase } from "../common/tabwidget-base.slint"; + export component TabWidgetImpl inherits NativeTabWidget { } export component TabImpl inherits NativeTab { @@ -8,41 +10,43 @@ export component TabImpl inherits NativeTab { accessible-label <=> root.title; } -export component TabBarImpl { +export component TabBarImpl inherits TabBarBase { // injected properties: - in-out property current; // The currently selected tab - in-out property current-focused: i-focus-scope.has-focus ? root.current : -1; // The currently focused tab - in-out property num-tabs; // The total number of tabs + // The currently focused tab + in-out property current-focused: i-focus-scope.has-focus ? root.current : -1; accessible-role: tab-list; accessible-delegate-focus: root.current; Rectangle { - clip: true; // The breeze style draws outside of the tab bar, which is clip by default with Qt + // The breeze style draws outside of the tab bar, which is clip by default with Qt + clip: true; HorizontalLayout { - spacing: 0px; // Qt renders Tabs next to each other and renders "spacing" as part of the tab itself + // Qt renders Tabs next to each other and renders "spacing" as part of the tab itself + spacing: 0px; alignment: NativeStyleMetrics.tab-bar-alignment; @children } } i-focus-scope := FocusScope { - x:0; - width: 0px; // Do not react on clicks + x: 0; + // Do not react on clicks + width: 0px; key-pressed(event) => { if (event.text == Key.LeftArrow) { - root.current = Math.max(root.current - 1, 0); - return accept; + root.current = root.previous-tab(); + return accept; } if (event.text == Key.RightArrow) { - root.current = Math.min(root.current + 1, root.num-tabs - 1); - return accept; + root.current = root.next-tab(); + return accept; } return reject; } } } -export component TabWidget inherits TabWidget {} +export component TabWidget inherits TabWidget { }