i_slint_backend_qt/qt_widgets/
slider.rs1use i_slint_core::{
5 input::{key_codes, FocusEventResult, FocusReason, KeyEventType},
6 items::PointerEventButton,
7};
8
9use super::*;
10
11#[derive(Default, Copy, Clone, Debug, PartialEq)]
12#[repr(C)]
13pub(super) struct NativeSliderData {
15 pub active_controls: u32,
16 pub pressed: u8,
18 pub pressed_x: f32,
19 pub pressed_val: f32,
20}
21
22type FloatArg = (f32,);
23
24#[repr(C)]
25#[derive(FieldOffsets, Default, SlintElement)]
26#[pin]
27pub struct NativeSlider {
28 pub orientation: Property<Orientation>,
29 pub enabled: Property<bool>,
30 pub has_focus: Property<bool>,
31 pub value: Property<f32>,
32 pub minimum: Property<f32>,
33 pub maximum: Property<f32>,
34 pub step: Property<f32>,
35 pub cached_rendering_data: CachedRenderingData,
36 data: Property<NativeSliderData>,
37 pub changed: Callback<FloatArg>,
38 pub released: Callback<FloatArg>,
39 widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
40 animation_tracker: Property<i32>,
41}
42
43cpp! {{
44void initQSliderOptions(QStyleOptionSlider &option, bool pressed, bool enabled, int active_controls, int minimum, int maximum, int value, bool vertical) {
45 option.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
46 option.activeSubControls = { active_controls };
47 if (vertical) {
48 option.orientation = Qt::Vertical;
49 } else {
50 option.orientation = Qt::Horizontal;
51 option.state |= QStyle::State_Horizontal;
52 }
53 option.maximum = maximum;
54 option.minimum = minimum;
55 option.sliderPosition = value;
56 option.sliderValue = value;
57 if (enabled) {
58 option.state |= QStyle::State_Enabled;
59 } else {
60 option.palette.setCurrentColorGroup(QPalette::Disabled);
61 }
62 if (pressed) {
63 option.state |= QStyle::State_Sunken | QStyle::State_MouseOver;
64 }
65}
66}}
67
68impl Item for NativeSlider {
69 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
70 let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
71 self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>" {
72 return make_unique_animated_widget<QSlider>(animation_tracker_property_ptr);
73 }})
74 }
75
76 fn layout_info(
77 self: Pin<&Self>,
78 orientation: Orientation,
79 _window_adapter: &Rc<dyn WindowAdapter>,
80 _self_rc: &ItemRc,
81 ) -> LayoutInfo {
82 let enabled = self.enabled();
83 let value = (self.value() * 1024.0) as i32;
86 let min = (self.minimum() * 1024.0) as i32;
87 let max = (self.maximum() * 1024.0) as i32;
88 let data = self.data();
89 let active_controls = data.active_controls;
90 let pressed = data.pressed;
91 let vertical = self.orientation() == Orientation::Vertical;
92 let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
93
94 let size = cpp!(unsafe [
95 enabled as "bool",
96 value as "int",
97 min as "int",
98 max as "int",
99 active_controls as "int",
100 pressed as "bool",
101 vertical as "bool",
102 widget as "QWidget*"
103 ] -> qttypes::QSize as "QSize" {
104 ensure_initialized();
105 QStyleOptionSlider option;
106 initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
107 auto style = qApp->style();
108 auto thick = style->pixelMetric(QStyle::PM_SliderThickness, &option, widget);
109 return style->sizeFromContents(QStyle::CT_Slider, &option, QSize(0, thick), widget);
110 });
111 let (width, height) = (size.width as f32, size.height as f32);
112 match orientation {
113 Orientation::Horizontal => {
114 if !vertical {
115 LayoutInfo {
116 min: width,
117 preferred: width,
118 stretch: 1.,
119 ..LayoutInfo::default()
120 }
121 } else {
122 LayoutInfo {
123 min: height,
124 preferred: height,
125 max: height,
126 ..LayoutInfo::default()
127 }
128 }
129 }
130 Orientation::Vertical => {
131 if !vertical {
132 LayoutInfo {
133 min: height,
134 preferred: height,
135 max: height,
136 ..LayoutInfo::default()
137 }
138 } else {
139 LayoutInfo {
140 min: width,
141 preferred: width,
142 stretch: 1.,
143 ..LayoutInfo::default()
144 }
145 }
146 }
147 }
148 }
149
150 fn input_event_filter_before_children(
151 self: Pin<&Self>,
152 _: &MouseEvent,
153 _window_adapter: &Rc<dyn WindowAdapter>,
154 _self_rc: &ItemRc,
155 ) -> InputEventFilterResult {
156 InputEventFilterResult::ForwardEvent
157 }
158
159 #[allow(clippy::unnecessary_cast)] fn input_event(
161 self: Pin<&Self>,
162 event: &MouseEvent,
163 window_adapter: &Rc<dyn WindowAdapter>,
164 self_rc: &i_slint_core::items::ItemRc,
165 ) -> InputEventResult {
166 let size: qttypes::QSize = get_size!(self_rc);
167 let enabled = self.enabled();
168 let value = (self.value() * 1024.0) as i32;
171 let min = (self.minimum() * 1024.0) as i32;
172 let max = (self.maximum() * 1024.0) as i32;
173 let mut data = self.data();
174 let active_controls = data.active_controls;
175 let pressed: bool = data.pressed != 0;
176 let vertical = self.orientation() == Orientation::Vertical;
177 let pos = event
178 .position()
179 .map(|p| qttypes::QPoint { x: p.x as _, y: p.y as _ })
180 .unwrap_or_default();
181 let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
182
183 let new_control = cpp!(unsafe [
184 pos as "QPoint",
185 size as "QSize",
186 enabled as "bool",
187 value as "int",
188 min as "int",
189 max as "int",
190 active_controls as "int",
191 pressed as "bool",
192 vertical as "bool",
193 widget as "QWidget*"
194 ] -> u32 as "int" {
195 ensure_initialized();
196 QStyleOptionSlider option;
197 initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
198 auto style = qApp->style();
199 option.rect = { QPoint{}, size };
200 return style->hitTestComplexControl(QStyle::CC_Slider, &option, pos, widget);
201 });
202 let result = match event {
203 _ if !enabled => {
204 data.pressed = 0;
205 InputEventResult::EventIgnored
206 }
207 MouseEvent::Pressed {
208 position: pos,
209 button: PointerEventButton::Left,
210 click_count: _,
211 } => {
212 if !self.has_focus() {
213 WindowInner::from_pub(window_adapter.window()).set_focus_item(
214 self_rc,
215 true,
216 FocusReason::PointerClick,
217 );
218 }
219 data.pressed_x = if vertical { pos.y as f32 } else { pos.x as f32 };
220 data.pressed = 1;
221 data.pressed_val = self.value();
222 InputEventResult::GrabMouse
223 }
224 MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
225 if data.pressed != 0 {
226 Self::FIELD_OFFSETS.released.apply_pin(self).call(&(self.value(),));
227 }
228 data.pressed = 0;
229 InputEventResult::EventAccepted
230 }
231 MouseEvent::Moved { position: pos } => {
232 let (coord, size) =
233 if vertical { (pos.y, size.height) } else { (pos.x, size.width) };
234 if data.pressed != 0 {
235 let new_val = data.pressed_val
237 + ((coord as f32) - data.pressed_x) * (self.maximum() - self.minimum())
238 / size as f32;
239 self.set_value(new_val);
240 InputEventResult::GrabMouse
241 } else {
242 InputEventResult::EventIgnored
243 }
244 }
245 MouseEvent::Wheel { delta_x, delta_y, .. } => {
246 let new_val = self.value() + delta_x + delta_y;
247 self.set_value(new_val);
248 InputEventResult::EventAccepted
249 }
250 MouseEvent::Pressed { button, .. } | MouseEvent::Released { button, .. } => {
251 debug_assert_ne!(*button, PointerEventButton::Left);
252 InputEventResult::EventIgnored
253 }
254 MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
255 };
256 data.active_controls = new_control;
257
258 self.data.set(data);
259 result
260 }
261
262 fn capture_key_event(
263 self: Pin<&Self>,
264 _event: &KeyEvent,
265 _window_adapter: &Rc<dyn WindowAdapter>,
266 _self_rc: &ItemRc,
267 ) -> KeyEventResult {
268 KeyEventResult::EventIgnored
269 }
270
271 fn key_event(
272 self: Pin<&Self>,
273 event: &KeyEvent,
274 _window_adapter: &Rc<dyn WindowAdapter>,
275 _self_rc: &ItemRc,
276 ) -> KeyEventResult {
277 if self.enabled() {
278 let Some(keycode) = event.text.chars().next() else {
279 return KeyEventResult::EventIgnored;
280 };
281 let vertical = self.orientation() == Orientation::Vertical;
282
283 if (!vertical && keycode == key_codes::RightArrow)
284 || (vertical && keycode == key_codes::DownArrow)
285 {
286 if event.event_type == KeyEventType::KeyPressed {
287 self.set_value(self.value() + self.step());
288 } else if event.event_type == KeyEventType::KeyReleased {
289 Self::FIELD_OFFSETS.released.apply_pin(self).call(&(self.value(),));
290 }
291 return KeyEventResult::EventAccepted;
292 }
293 if (!vertical && keycode == key_codes::LeftArrow)
294 || (vertical && keycode == key_codes::UpArrow)
295 {
296 if event.event_type == KeyEventType::KeyPressed {
297 self.set_value(self.value() - self.step());
298 } else if event.event_type == KeyEventType::KeyReleased {
299 Self::FIELD_OFFSETS.released.apply_pin(self).call(&(self.value(),));
300 }
301 return KeyEventResult::EventAccepted;
302 }
303 if keycode == key_codes::Home {
304 if event.event_type == KeyEventType::KeyPressed {
305 self.set_value(self.minimum());
306 } else if event.event_type == KeyEventType::KeyReleased {
307 Self::FIELD_OFFSETS.released.apply_pin(self).call(&(self.value(),));
308 }
309 return KeyEventResult::EventAccepted;
310 }
311 if keycode == key_codes::End {
312 if event.event_type == KeyEventType::KeyPressed {
313 self.set_value(self.maximum());
314 } else if event.event_type == KeyEventType::KeyReleased {
315 Self::FIELD_OFFSETS.released.apply_pin(self).call(&(self.value(),));
316 }
317 return KeyEventResult::EventAccepted;
318 }
319 }
320 KeyEventResult::EventIgnored
321 }
322
323 fn focus_event(
324 self: Pin<&Self>,
325 event: &FocusEvent,
326 _window_adapter: &Rc<dyn WindowAdapter>,
327 _self_rc: &ItemRc,
328 ) -> FocusEventResult {
329 if self.enabled() {
330 Self::FIELD_OFFSETS
331 .has_focus
332 .apply_pin(self)
333 .set(matches!(event, FocusEvent::FocusIn(_)));
334 FocusEventResult::FocusAccepted
335 } else {
336 FocusEventResult::FocusIgnored
337 }
338 }
339
340 fn_render! { this dpr size painter widget initial_state =>
341 let enabled = this.enabled();
342 let has_focus = this.has_focus();
343 let value = (this.value() * 1024.0) as i32;
346 let min = (this.minimum() * 1024.0) as i32;
347 let max = (this.maximum() * 1024.0) as i32;
348 let data = this.data();
349 let active_controls = data.active_controls;
350 let pressed = data.pressed;
351 let vertical = this.orientation() == Orientation::Vertical;
352
353 cpp!(unsafe [
354 painter as "QPainterPtr*",
355 widget as "QWidget*",
356 enabled as "bool",
357 has_focus as "bool",
358 value as "int",
359 min as "int",
360 max as "int",
361 size as "QSize",
362 active_controls as "int",
363 pressed as "bool",
364 vertical as "bool",
365 dpr as "float",
366 initial_state as "int"
367 ] {
368 QStyleOptionSlider option;
369 option.styleObject = widget;
370 option.state |= QStyle::State(initial_state);
371 if (has_focus) {
372 option.state |= QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
373 }
374 option.rect = QRect(QPoint(), size / dpr);
375 initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
376 auto style = qApp->style();
377 style->drawComplexControl(QStyle::CC_Slider, &option, painter->get(), widget);
378 });
379 }
380
381 fn bounding_rect(
382 self: core::pin::Pin<&Self>,
383 _window_adapter: &Rc<dyn WindowAdapter>,
384 _self_rc: &ItemRc,
385 geometry: LogicalRect,
386 ) -> LogicalRect {
387 geometry
388 }
389
390 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
391 false
392 }
393}
394
395impl NativeSlider {
396 fn set_value(self: Pin<&Self>, new_val: f32) {
397 let new_val = new_val.max(self.minimum()).min(self.maximum());
398 self.value.set(new_val);
399 Self::FIELD_OFFSETS.changed.apply_pin(self).call(&(new_val,));
400 }
401}
402
403impl ItemConsts for NativeSlider {
404 const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
405 Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
406}
407
408declare_item_vtable! {
409fn slint_get_NativeSliderVTable() -> NativeSliderVTable for NativeSlider
410}