Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
RangeSlider.qml
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-License-Identifier: MPL-2.0
3
4pragma ComponentBehavior: Bound
5
6import QtQuick
7import QtQuick.Templates as T
8import Fluid as MD
9
25T.RangeSlider {
26 id: control
27
29 enum LabelBehavior {
30 Floating,
31 WithinBounds,
32 Visible,
33 Gone
34 }
35
37 enum TickVisibilityMode {
38 AutoLimit,
39 AutoHide,
40 Hidden
41 }
42
44 enum Size {
45 ExtraSmall,
46 Small,
47 Medium,
48 Large,
49 ExtraLarge
50 }
51
55 property int labelBehavior: RangeSlider.LabelBehavior.Floating
56
60 property int tickVisibilityMode: RangeSlider.TickVisibilityMode.AutoLimit
61
65 property int size: RangeSlider.Size.ExtraSmall
66
70 property string firstValueIndicatorText: control.first.value.toLocaleString(Qt.locale(), "f", Number.isInteger(control.first.value) ? 0 : 2)
74 property string secondValueIndicatorText: control.second.value.toLocaleString(Qt.locale(), "f", Number.isInteger(control.second.value) ? 0 : 2)
75
80 property string firstAccessibleName: control.Accessible.name.length > 0 ? qsTr("%1 minimum").arg(control.Accessible.name) : qsTr("Minimum value")
81
86 property string secondAccessibleName: control.Accessible.name.length > 0 ? qsTr("%1 maximum").arg(control.Accessible.name) : qsTr("Maximum value")
87
88 first.value: from
89 second.value: to
90 snapMode: T.RangeSlider.SnapAlways
91 hoverEnabled: true
92 focusPolicy: Qt.StrongFocus
93
94 // The template's single node cannot identify which handle its value and
95 // actions describe. Keep it out of the accessibility tree; ignored items
96 // promote accessible descendants, so the two handle nodes remain visible.
97 Accessible.ignored: true
98
99 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, first.implicitHandleWidth + leftPadding + rightPadding, second.implicitHandleWidth + leftPadding + rightPadding)
100 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, first.implicitHandleHeight + topPadding + bottomPadding, second.implicitHandleHeight + topPadding + bottomPadding)
101
102 QtObject {
103 id: metrics
104
105 readonly property real handleHeight: {
106 switch (control.size) {
107 case RangeSlider.Size.ExtraSmall:
108 return MD.Tokens.slider.activeHandleHeightExtraSmall;
109 case RangeSlider.Size.Small:
110 return MD.Tokens.slider.activeHandleHeightSmall;
111 case RangeSlider.Size.Medium:
112 return MD.Tokens.slider.activeHandleHeightMedium;
113 case RangeSlider.Size.Large:
114 return MD.Tokens.slider.activeHandleHeightLarge;
115 case RangeSlider.Size.ExtraLarge:
116 return MD.Tokens.slider.activeHandleHeightExtraLarge;
117 }
118 return MD.Tokens.slider.activeHandleHeightExtraSmall;
119 }
120
121 readonly property real trackHeight: {
122 switch (control.size) {
123 case RangeSlider.Size.ExtraSmall:
124 return MD.Tokens.slider.activeTrackHeightExtraSmall;
125 case RangeSlider.Size.Small:
126 return MD.Tokens.slider.activeTrackHeightSmall;
127 case RangeSlider.Size.Medium:
128 return MD.Tokens.slider.activeTrackHeightMedium;
129 case RangeSlider.Size.Large:
130 return MD.Tokens.slider.activeTrackHeightLarge;
131 case RangeSlider.Size.ExtraLarge:
132 return MD.Tokens.slider.activeTrackHeightExtraLarge;
133 }
134 return MD.Tokens.slider.activeTrackHeightExtraSmall;
135 }
136
137 readonly property real trackOuterCornerRadius: {
138 switch (control.size) {
139 case RangeSlider.Size.ExtraSmall:
140 return MD.Tokens.slider.activeTrackLeadingCornerRadiusExtraSmall;
141 case RangeSlider.Size.Small:
142 return MD.Tokens.slider.activeTrackLeadingCornerRadiusSmall;
143 case RangeSlider.Size.Medium:
144 return MD.Tokens.slider.activeTrackLeadingCornerRadiusMedium;
145 case RangeSlider.Size.Large:
146 return MD.Tokens.slider.activeTrackLeadingCornerRadiusLarge;
147 case RangeSlider.Size.ExtraLarge:
148 return MD.Tokens.slider.activeTrackLeadingCornerRadiusExtraLarge;
149 }
150 return MD.Tokens.slider.activeTrackLeadingCornerRadiusExtraSmall;
151 }
152
153 readonly property real effectiveHandleWidth: (control.first.pressed || control.second.pressed) ? MD.Tokens.slider.pressedHandleWidth : (control.visualFocus ? MD.Tokens.slider.focusHandleWidth : MD.Tokens.slider.handleWidth)
154 readonly property real handleContainerSize: trackOuterCornerRadius * 2
155 readonly property bool reservesLabelSpace: control.labelBehavior === RangeSlider.LabelBehavior.WithinBounds || control.labelBehavior === RangeSlider.LabelBehavior.Visible
156 readonly property real labelSpace: {
157 if (!reservesLabelSpace)
158 return 0;
159 const indicatorSize = control.horizontal ? Math.max(firstSliderHandle.valueIndicatorHeight, secondSliderHandle.valueIndicatorHeight) : Math.max(firstSliderHandle.valueIndicatorWidth, secondSliderHandle.valueIndicatorWidth);
160 return indicatorSize + MD.Tokens.slider.valueIndicatorActiveBottomSpace;
161 }
162
163 readonly property color activeTrackColor: control.enabled ? control.MD.Style.primaryColor : control.MD.Style.onSurfaceColor
164 readonly property color inactiveTrackColor: control.enabled ? control.MD.Style.secondaryContainerColor : control.MD.Style.onSurfaceColor
165 readonly property color activeTickColor: control.enabled ? control.MD.Style.onPrimaryColor : control.MD.Style.inverseOnSurfaceColor
166 readonly property color inactiveTickColor: control.enabled ? control.MD.Style.onSecondaryContainerColor : control.MD.Style.onSurfaceColor
167 readonly property color handleColor: control.enabled ? control.MD.Style.primaryColor : control.MD.Style.onSurfaceColor
168 readonly property real activeTrackOpacity: control.enabled ? MD.Tokens.slider.visibleOpacity : MD.Tokens.slider.disabledActiveTrackOpacity
169 readonly property real inactiveTrackOpacity: control.enabled ? MD.Tokens.slider.visibleOpacity : MD.Tokens.slider.disabledInactiveTrackOpacity
170 readonly property real handleOpacity: control.enabled ? MD.Tokens.slider.visibleOpacity : MD.Tokens.slider.disabledHandleOpacity
171 }
172
173 background: MD.SliderTrack {
174 horizontal: control.horizontal
175 mirrored: control.mirrored
176 centered: false
177 rangeMode: true
178 controlEnabled: control.enabled
179 visualPosition: control.first.visualPosition
180 secondVisualPosition: control.second.visualPosition
181 rangeFrom: control.from
182 rangeTo: control.to
183 stepSize: control.stepSize
184 tickVisibilityMode: control.tickVisibilityMode
185 autoLimitMode: RangeSlider.TickVisibilityMode.AutoLimit
186 autoHideMode: RangeSlider.TickVisibilityMode.AutoHide
187 hiddenMode: RangeSlider.TickVisibilityMode.Hidden
188 trackHeight: metrics.trackHeight
189 trackOuterCornerRadius: metrics.trackOuterCornerRadius
190 handleHeight: metrics.handleHeight
191 effectiveHandleWidth: metrics.effectiveHandleWidth
192 labelSpace: metrics.labelSpace
193 activeTrackColor: metrics.activeTrackColor
194 inactiveTrackColor: metrics.inactiveTrackColor
195 activeTickColor: metrics.activeTickColor
196 inactiveTickColor: metrics.inactiveTickColor
197 activeTrackOpacity: metrics.activeTrackOpacity
198 inactiveTrackOpacity: metrics.inactiveTrackOpacity
199 trackIconActiveStart: ""
200 trackIconActiveEnd: ""
201 trackIconInactiveStart: ""
202 trackIconInactiveEnd: ""
203 trackIconActiveColor: metrics.activeTickColor
204 trackIconInactiveColor: metrics.inactiveTickColor
205 trackIconSize: 0
206 trackIconPadding: 0
207 }
208
209 first.handle: MD.SliderHandle {
210 id: firstSliderHandle
211 objectName: "rangeSliderFirstHandle"
212
213 x: control.leftPadding + (control.horizontal ? control.first.visualPosition * (control.availableWidth - width) : (control.mirrored ? (control.availableWidth - metrics.labelSpace - width) / 2 : metrics.labelSpace + (control.availableWidth - metrics.labelSpace - width) / 2))
214 y: control.topPadding + (control.horizontal ? metrics.labelSpace + (control.availableHeight - metrics.labelSpace - height) / 2 : control.first.visualPosition * (control.availableHeight - height))
215
216 valueIndicatorText: control.firstValueIndicatorText
217 labelBehavior: control.labelBehavior
218 // SliderHandle uses these conventional property names to provide Qt's
219 // QAccessibleValueInterface. The first handle cannot move beyond the
220 // second, so its accessible maximum follows the second handle's value.
221 accessibilityEnabled: true
222 accessibleName: control.firstAccessibleName
223 value: control.first.value
224 minimumValue: Math.min(control.from, control.second.value)
225 maximumValue: Math.max(control.from, control.second.value)
226 stepSize: control.stepSize === 0 ? 0.1 : Math.abs(control.stepSize)
227
228 // Delegate accessibility requests to the same template node methods
229 // used by keyboard interaction, preserving snapping and range ordering.
230 accessibilityIncreaseAction: function() { control.first.increase(); }
231 accessibilityDecreaseAction: function() { control.first.decrease(); }
232
233 // Qt moves active focus between the actual handle items when Tab and
234 // Backtab are used, so report focus per handle instead of on the root.
235 handleHasFocus: activeFocus
236 handlePressed: control.first.pressed
237 handleHovered: control.first.hovered
238 controlEnabled: control.enabled
239 horizontal: control.horizontal
240 mirrored: control.mirrored
241 controlWidth: control.width
242 controlHeight: control.height
243 activeHandleWidth: metrics.effectiveHandleWidth
244 handleHeight: metrics.handleHeight
245 handleContainerSize: metrics.handleContainerSize
246 handleColor: metrics.handleColor
247 handleOpacity: metrics.handleOpacity
248 valueIndicatorContainerColor: control.MD.Style.inverseSurfaceColor
249 valueIndicatorLabelColor: control.MD.Style.inverseOnSurfaceColor
250 }
251
252 second.handle: MD.SliderHandle {
253 id: secondSliderHandle
254 objectName: "rangeSliderSecondHandle"
255
256 x: control.leftPadding + (control.horizontal ? control.second.visualPosition * (control.availableWidth - width) : (control.mirrored ? (control.availableWidth - metrics.labelSpace - width) / 2 : metrics.labelSpace + (control.availableWidth - metrics.labelSpace - width) / 2))
257 y: control.topPadding + (control.horizontal ? metrics.labelSpace + (control.availableHeight - metrics.labelSpace - height) / 2 : control.second.visualPosition * (control.availableHeight - height))
258
259 valueIndicatorText: control.secondValueIndicatorText
260 labelBehavior: control.labelBehavior
261 // Mirror the first handle's value contract, constraining the accessible
262 // minimum to the first handle so assistive actions cannot cross it.
263 accessibilityEnabled: true
264 accessibleName: control.secondAccessibleName
265 value: control.second.value
266 minimumValue: Math.min(control.first.value, control.to)
267 maximumValue: Math.max(control.first.value, control.to)
268 stepSize: control.stepSize === 0 ? 0.1 : Math.abs(control.stepSize)
269
270 // Use the native node operations for identical pointer, keyboard, and
271 // assistive-technology value changes.
272 accessibilityIncreaseAction: function() { control.second.increase(); }
273 accessibilityDecreaseAction: function() { control.second.decrease(); }
274 handleHasFocus: activeFocus
275 handlePressed: control.second.pressed
276 handleHovered: control.second.hovered
277 controlEnabled: control.enabled
278 horizontal: control.horizontal
279 mirrored: control.mirrored
280 controlWidth: control.width
281 controlHeight: control.height
282 activeHandleWidth: metrics.effectiveHandleWidth
283 handleHeight: metrics.handleHeight
284 handleContainerSize: metrics.handleContainerSize
285 handleColor: metrics.handleColor
286 handleOpacity: metrics.handleOpacity
287 valueIndicatorContainerColor: control.MD.Style.inverseSurfaceColor
288 valueIndicatorLabelColor: control.MD.Style.inverseOnSurfaceColor
289 }
290}
Material Design 3 design tokens.
Definition tokens.h:65
A Material 3 Expressive slider for selecting a value range.