diff --git a/CHANGELOG.md b/CHANGELOG.md index 0378cb14d..6da21cebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project are documented in this file. - Fixed updating model of ComboBox does not change current-value - Fixed set current-index of ComboBox to -1 does not reset current-value - Fixed issue where the text of `SpinBox` is not updated after value is changed from outside + - Added `step-size` to `SpinBox` ## [1.6.0] - 2024-05-13 diff --git a/docs/reference/src/language/widgets/spinbox.md b/docs/reference/src/language/widgets/spinbox.md index 8ec3a70a5..e23e9a344 100644 --- a/docs/reference/src/language/widgets/spinbox.md +++ b/docs/reference/src/language/widgets/spinbox.md @@ -8,6 +8,7 @@ - **`value`** (_in-out_ _int_): The value. Defaults to the minimum. - **`minimum`** (_in_ _int_): The minimum value (default: 0). - **`maximum`** (_in_ _int_): The maximum value (default: 100). +- **`step-size`** (_in_ _int_): The size that is used on increment or decrement of `value` (default: 1). ### Callbacks diff --git a/internal/compiler/widgets/common/spinbox-base.slint b/internal/compiler/widgets/common/spinbox-base.slint index 40a3679ca..20c4c87b2 100644 --- a/internal/compiler/widgets/common/spinbox-base.slint +++ b/internal/compiler/widgets/common/spinbox-base.slint @@ -11,6 +11,7 @@ export component SpinBoxBase { in property selection-background-color <=> text-input.selection-background-color; in property selection-foreground-color <=> text-input.selection-foreground-color; in property horizontal-alignment <=> text-input.horizontal-alignment; + in property step-size: 1; out property has-focus <=> text-input.has-focus; in-out property value: minimum; @@ -24,11 +25,11 @@ export component SpinBoxBase { } public function increment() { - root.update-value(root.value + 1); + root.update-value(root.value + root.step-size); } public function decrement() { - root.update-value(root.value - 1); + root.update-value(root.value - root.step-size); } private property scroll-delta: 2px; diff --git a/internal/compiler/widgets/cosmic-base/spinbox.slint b/internal/compiler/widgets/cosmic-base/spinbox.slint index ab46d7791..910f2d2dd 100644 --- a/internal/compiler/widgets/cosmic-base/spinbox.slint +++ b/internal/compiler/widgets/cosmic-base/spinbox.slint @@ -40,6 +40,7 @@ export component SpinBox { in property minimum <=> base.minimum; in property maximum <=> base.maximum; in property enabled <=> base.enabled; + in property step-size <=> base.step-size; out property has-focus <=> base.has-focus; in-out property value <=> base.value; diff --git a/internal/compiler/widgets/cupertino-base/spinbox.slint b/internal/compiler/widgets/cupertino-base/spinbox.slint index 58fd3b8fa..014d040ec 100644 --- a/internal/compiler/widgets/cupertino-base/spinbox.slint +++ b/internal/compiler/widgets/cupertino-base/spinbox.slint @@ -6,10 +6,10 @@ import { FocusBorder } from "components.slint"; import { SpinBoxBase } from "../common/spinbox-base.slint"; component SpinBoxButton { - in property enabled <=> i-touch-area.enabled; - in property icon <=> i-icon.source; + in property enabled <=> touch-area.enabled; + in property icon <=> icon.source; - callback clicked <=> i-touch-area.clicked; + callback clicked <=> touch-area.clicked; private property background: CupertinoPalette.accent-background; private property icon-color: CupertinoPalette.accent-foreground; @@ -18,11 +18,11 @@ component SpinBoxButton { horizontal-stretch: 0; states [ - disabled when !i-touch-area.enabled : { + disabled when !touch-area.enabled : { opacity: 0.5; icon-color: CupertinoPalette.foreground-secondary; } - pressed when i-touch-area.pressed : { + pressed when touch-area.pressed : { root.background: CupertinoPalette.secondary-accent-background; } ] @@ -67,7 +67,7 @@ component SpinBoxButton { } - i-icon := Image { + icon := Image { image-fit: contain; colorize: root.icon-color; width: 12px; @@ -76,38 +76,39 @@ component SpinBoxButton { } } - i-touch-area := TouchArea {} + touch-area := TouchArea {} } export component SpinBox { - in property minimum <=> i-base.minimum; - in property maximum <=> i-base.maximum; - in property enabled <=> i-base.enabled; - out property has-focus <=> i-base.has-focus; - in-out property value <=> i-base.value; + in property minimum <=> base.minimum; + in property maximum <=> base.maximum; + in property enabled <=> base.enabled; + in property step-size <=> base.step-size; + out property has-focus <=> base.has-focus; + in-out property value <=> base.value; - callback edited <=> i-base.edited; + callback edited <=> base.edited; private property background: CupertinoPalette.control-background; min-width: 128px; - min-height: max(22px, i-layout.min-height); + min-height: max(22px, layout.min-height); vertical-stretch: 0; horizontal-stretch: 1; - forward-focus: i-base; + forward-focus: base; accessible-role: spinbox; accessible-value: root.value; accessible-value-minimum: root.minimum; accessible-value-maximum: root.maximum; accessible-value-step: (root.maximum - root.minimum) / 100; - accessible-action-set-value(v) => { if v.is-float() { i-base.update-value(v.to-float()); } } - accessible-action-increment => { i-base.increment(); } - accessible-action-decrement => { i-base.decrement(); } + accessible-action-set-value(v) => { if v.is-float() { base.update-value(v.to-float()); } } + accessible-action-increment => { base.increment(); } + accessible-action-decrement => { base.decrement(); } states [ disabled when !root.enabled : { - i-base.color: CupertinoPalette.foreground-secondary; + base.color: CupertinoPalette.foreground-secondary; root.background: CupertinoPalette.tertiary-control-background; } ] @@ -140,7 +141,7 @@ export component SpinBox { } } - i-layout := HorizontalLayout { + layout := HorizontalLayout { padding-left: 7px; padding-right: 2px; spacing: 2px; @@ -149,7 +150,7 @@ export component SpinBox { clip: true; horizontal-stretch: 1; - i-base := SpinBoxBase { + base := SpinBoxBase { width: 100%; color: CupertinoPalette.foreground; font-size: CupertinoFontSettings.body.font-size; @@ -165,7 +166,7 @@ export component SpinBox { enabled: root.enabled; clicked => { - i-base.increment(); + base.increment(); } } @@ -175,7 +176,7 @@ export component SpinBox { enabled: root.enabled; clicked => { - i-base.decrement(); + base.decrement(); } } } diff --git a/internal/compiler/widgets/fluent-base/spinbox.slint b/internal/compiler/widgets/fluent-base/spinbox.slint index 688794c2a..e2a57a5fc 100644 --- a/internal/compiler/widgets/fluent-base/spinbox.slint +++ b/internal/compiler/widgets/fluent-base/spinbox.slint @@ -5,23 +5,23 @@ import { FluentPalette, FluentFontSettings, Icons } from "styling.slint"; import { SpinBoxBase } from "../common/spinbox-base.slint"; component SpinBoxButton { - callback clicked <=> i-touch-area.clicked; + callback clicked <=> touch-area.clicked; - in property icon <=> i-icon.source; + in property icon <=> icon.source; min-width: 28px; horizontal-stretch: 0; states [ - pressed when i-touch-area.pressed : { - i-background.background: FluentPalette.subtle; + pressed when touch-area.pressed : { + background.background: FluentPalette.subtle; } ] - i-background := Rectangle { + background := Rectangle { border-radius: 3px; - i-icon := Image { + icon := Image { image-fit: contain; colorize: FluentPalette.text-secondary; width: 12px; @@ -30,55 +30,55 @@ component SpinBoxButton { } } - i-touch-area := TouchArea {} + touch-area := TouchArea {} } export component SpinBox { - in property minimum <=> i-base.minimum; - in property maximum <=> i-base.maximum; - in property enabled <=> i-base.enabled; - out property has-focus <=> i-base.has-focus; - in-out property value <=> i-base.value; + in property minimum <=> base.minimum; + in property maximum <=> base.maximum; + in property enabled <=> base.enabled; + in property step-size <=> base.step-size; + out property has-focus <=> base.has-focus; + in-out property value <=> base.value; - callback edited <=> i-base.edited; + callback edited <=> base.edited; min-width: 128px; min-height: 30px; vertical-stretch: 0; horizontal-stretch: 1; - forward-focus: i-base; + forward-focus: base; accessible-role: spinbox; accessible-value: root.value; accessible-value-minimum: root.minimum; accessible-value-maximum: root.maximum; accessible-value-step: (root.maximum - root.minimum) / 100; - accessible-action-set-value(v) => { if v.is-float() { i-base.update-value(v.to-float()); } } - accessible-action-increment => { i-base.increment(); } - accessible-action-decrement => { i-base.decrement(); } - + accessible-action-set-value(v) => { if v.is-float() { base.update-value(v.to-float()); } } + accessible-action-increment => { base.increment(); } + accessible-action-decrement => { base.decrement(); } states [ disabled when !root.enabled : { - i-background.background: FluentPalette.control-disabled; - i-background.border-color: FluentPalette.border; - i-base.color: FluentPalette.text-disabled; - i-base.selection-foreground-color: FluentPalette.text-accent-foreground-disabled; + background.background: FluentPalette.control-disabled; + background.border-color: FluentPalette.border; + base.color: FluentPalette.text-disabled; + base.selection-foreground-color: FluentPalette.text-accent-foreground-disabled; } focused when root.has-focus : { - i-background.background: FluentPalette.control-input-active; - i-background.border-color: FluentPalette.border; - i-focus-border.background: FluentPalette.accent-background; + background.background: FluentPalette.control-input-active; + background.border-color: FluentPalette.border; + focus-border.background: FluentPalette.accent-background; } ] - i-background := Rectangle { + background := Rectangle { border-radius: 4px; background: FluentPalette.control-background; border-width: 1px; border-color: FluentPalette.text-control-border; - i-layout := HorizontalLayout { + layout := HorizontalLayout { padding-left: 12px; padding-right: 2px; padding-top: 4px; @@ -89,7 +89,7 @@ export component SpinBox { clip: true; horizontal-stretch: 1; - i-base := SpinBoxBase { + base := SpinBoxBase { width: 100%; color: FluentPalette.control-foreground; font-size: FluentFontSettings.body.font-size; @@ -104,7 +104,7 @@ export component SpinBox { icon: Icons.chevron-up; clicked => { - i-base.increment(); + base.increment(); } } @@ -113,12 +113,12 @@ export component SpinBox { icon: Icons.chevron-down; clicked => { - i-base.decrement(); + base.decrement(); } } } - i-focus-border := Rectangle { + focus-border := Rectangle { x: parent.border-radius; y: parent.height - self.height; width: parent.width - 2 * parent.border-radius; diff --git a/internal/compiler/widgets/material-base/spinbox.slint b/internal/compiler/widgets/material-base/spinbox.slint index 5ba96fb54..090e65c86 100644 --- a/internal/compiler/widgets/material-base/spinbox.slint +++ b/internal/compiler/widgets/material-base/spinbox.slint @@ -5,44 +5,44 @@ import { MaterialPalette, MaterialFontSettings, Icons } from "styling.slint"; import { SpinBoxBase } from "../common/spinbox-base.slint"; component SpinBoxButton inherits Rectangle { - in-out property pressed: self.enabled && i-touch-area.pressed; - in-out property enabled <=> i-touch-area.enabled; + in-out property pressed: self.enabled && touch-area.pressed; + in-out property enabled <=> touch-area.enabled; in-out property icon-opacity: 1; in-out property icon-fill: MaterialPalette.accent-foreground; - callback clicked <=> i-touch-area.clicked; + callback clicked <=> touch-area.clicked; width: root.height; states [ disabled when !root.enabled : { - i-background.background: MaterialPalette.control-foreground; - i-background.opacity: 0.12; + background.background: MaterialPalette.control-foreground; + background.opacity: 0.12; icon-opacity: 0.38; icon-fill: MaterialPalette.control-foreground; } - pressed when i-touch-area.pressed : { - i-state-layer.opacity: 0.12; + pressed when touch-area.pressed : { + state-layer.opacity: 0.12; } - hover when i-touch-area.has-hover : { - i-state-layer.opacity: 0.08; + hover when touch-area.has-hover : { + state-layer.opacity: 0.08; } ] - i-background := Rectangle { + background := Rectangle { width: 100%; height: 100%; border-radius: max(self.width, self.height) / 2; background: MaterialPalette.accent-background; } - i-state-layer := Rectangle { + state-layer := Rectangle { x: 0; y: 0; opacity: 0; - width: i-background.width; - height: i-background.height; - border-radius: i-background.border-radius; + width: background.width; + height: background.height; + border-radius: background.border-radius; background: MaterialPalette.accent-foreground; animate opacity { duration: 250ms; easing: ease; } @@ -55,53 +55,54 @@ component SpinBoxButton inherits Rectangle { @children } - i-touch-area := TouchArea { } + touch-area := TouchArea { } } // Increment and decrement a value in the given range. export component SpinBox { - in property minimum <=> i-base.minimum; - in property maximum <=> i-base.maximum; - in property enabled <=> i-base.enabled; - out property has-focus <=> i-base.has-focus; - in-out property value <=> i-base.value; + in property minimum <=> base.minimum; + in property maximum <=> base.maximum; + in property enabled <=> base.enabled; + in property step-size <=> base.step-size; + out property has-focus <=> base.has-focus; + in-out property value <=> base.value; - callback edited <=> i-base.edited; + callback edited <=> base.edited; - forward-focus: i-base; + forward-focus: base; horizontal-stretch: 1; vertical-stretch: 0; - min-width: i-layout.min-width; - min-height: max(56px, i-layout.min-height); + min-width: layout.min-width; + min-height: max(56px, layout.min-height); accessible-role: spinbox; accessible-value: root.value; accessible-value-minimum: root.minimum; accessible-value-maximum: root.maximum; accessible-value-step: (root.maximum - root.minimum) / 100; - accessible-action-set-value(v) => { if v.is-float() { i-base.update-value(v.to-float()); } } - accessible-action-increment => { i-base.increment(); } - accessible-action-decrement => { i-base.decrement(); } + accessible-action-set-value(v) => { if v.is-float() { base.update-value(v.to-float()); } } + accessible-action-increment => { base.increment(); } + accessible-action-decrement => { base.decrement(); } states [ disabled when !root.enabled : { - i-background.border-color: MaterialPalette.control-foreground; - i-background.opacity: 0.38; - i-base.opacity: 0.38; + background.border-color: MaterialPalette.control-foreground; + background.opacity: 0.38; + base.opacity: 0.38; } focused when root.has-focus : { - i-background.border-width: 2px; - i-background.border-color: MaterialPalette.accent-background; - i-base.color: MaterialPalette.accent-background; + background.border-width: 2px; + background.border-color: MaterialPalette.accent-background; + base.color: MaterialPalette.accent-background; } ] - i-background := Rectangle { + background := Rectangle { border-radius: 4px; border-width: 1px; border-color: MaterialPalette.border; - i-layout := HorizontalLayout { + layout := HorizontalLayout { padding-top: 8px; padding-bottom: 8px; padding-left: 16px; @@ -112,7 +113,7 @@ export component SpinBox { clip: true; horizontal-stretch: 1; - i-base := SpinBoxBase { + base := SpinBoxBase { width: 100%; color: MaterialPalette.control-foreground; font-size: MaterialFontSettings.body-large.font-size; @@ -135,7 +136,7 @@ export component SpinBox { } clicked => { - i-base.increment(); + base.increment(); } } @@ -151,7 +152,7 @@ export component SpinBox { } clicked => { - i-base.decrement(); + base.decrement(); } } } diff --git a/internal/compiler/widgets/qt/spinbox.slint b/internal/compiler/widgets/qt/spinbox.slint index 3d51dd954..f01e535c2 100644 --- a/internal/compiler/widgets/qt/spinbox.slint +++ b/internal/compiler/widgets/qt/spinbox.slint @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-commercial export component SpinBox inherits NativeSpinBox { + in property step-size: 1; + value: root.minimum; accessible-role: spinbox; accessible-value: root.value; @@ -10,8 +12,16 @@ export component SpinBox inherits NativeSpinBox { accessible-value-step: (root.maximum - root.minimum) / 100; accessible-action-set-value(v) => { if v.is-float() {update-value(v.to-float());} } - accessible-action-increment => { update-value(root.value + 1); } - accessible-action-decrement => { update-value(root.value - 1); } + accessible-action-increment => { root.increment(); } + accessible-action-decrement => { root.decrement(); } + + function increment() { + root.update-value(root.value + root.step-size); + } + + function decrement() { + root.update-value(root.value - root.step-size); + } function update-value(value: int) { if (value >= root.minimum && value <= root.maximum) { @@ -19,4 +29,4 @@ export component SpinBox inherits NativeSpinBox { root.edited(value); } } -} \ No newline at end of file +} diff --git a/tests/cases/widgets/spinbox_basic.slint b/tests/cases/widgets/spinbox_basic.slint index 8c8c5dbae..a2840afed 100644 --- a/tests/cases/widgets/spinbox_basic.slint +++ b/tests/cases/widgets/spinbox_basic.slint @@ -14,6 +14,7 @@ export component TestCase inherits Window { out property spinbox-focused <=> box.has_focus; callback edited <=> box.edited; in-out property value <=> box.value; + in property step-size <=> box.step-size; } /* @@ -63,6 +64,18 @@ assert_eq!(instance.get_value(), 40); assert_eq!(&*edits.borrow(), &[40]); edits.borrow_mut().clear(); +// step size +instance.set_step_size(2); + +instance.window().dispatch_event(WindowEvent::PointerScrolled { position , delta_x: 0.0, delta_y: 50.0 }); +assert_eq!(instance.get_value(), 42); +assert_eq!(&*edits.borrow(), &[42]); +edits.borrow_mut().clear(); + +instance.window().dispatch_event(WindowEvent::PointerScrolled { position , delta_x: 0.0, delta_y: -10.0 }); +assert_eq!(instance.get_value(), 40); +assert_eq!(&*edits.borrow(), &[40]); +edits.borrow_mut().clear(); instance.set_value(0); // scroll down should do nothing when the value is 0 @@ -79,7 +92,6 @@ let text_input = text_input_search.next().unwrap(); assert_eq!(text_input.accessible_value().unwrap(), "30"); edits.borrow_mut().clear(); - ```