1use i_slint_core::input::FocusEventResult;
5use i_slint_core::items::ScrollBarPolicy;
6
7use super::*;
8
9#[repr(C)]
10#[derive(FieldOffsets, Default, SlintElement)]
11#[pin]
12pub struct NativeScrollView {
13 pub horizontal_max: Property<LogicalLength>,
14 pub horizontal_page_size: Property<LogicalLength>,
15 pub horizontal_value: Property<LogicalLength>,
16 pub vertical_max: Property<LogicalLength>,
17 pub vertical_page_size: Property<LogicalLength>,
18 pub vertical_value: Property<LogicalLength>,
19 pub cached_rendering_data: CachedRenderingData,
20 pub native_padding_left: Property<LogicalLength>,
21 pub native_padding_right: Property<LogicalLength>,
22 pub native_padding_top: Property<LogicalLength>,
23 pub native_padding_bottom: Property<LogicalLength>,
24 pub enabled: Property<bool>,
25 pub has_focus: Property<bool>,
26 pub vertical_scrollbar_policy: Property<ScrollBarPolicy>,
27 pub horizontal_scrollbar_policy: Property<ScrollBarPolicy>,
28 pub scrolled: Callback<VoidArg>,
29 data: Property<NativeSliderData>,
30 widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
31 animation_tracker: Property<i32>,
32 }
35
36impl Item for NativeScrollView {
37 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
38 let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
39 self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>" {
40 return make_unique_animated_widget<QWidget>(animation_tracker_property_ptr);
41 }});
42
43 let paddings = Rc::pin(Property::default());
44
45 paddings.as_ref().set_binding(move || {
46 cpp!(unsafe [] -> qttypes::QMargins as "QMargins" {
47 ensure_initialized();
48 QStyleOptionSlider option;
49 initQSliderOptions(option, false, true, 0, 0, 1000, 1000, false);
50
51 int extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr);
52 int sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
53 auto horizontal_size = qApp->style()->sizeFromContents(QStyle::CT_ScrollBar, &option, QSize(extent * 2 + sliderMin, extent), nullptr);
54 option.state ^= QStyle::State_Horizontal;
55 option.orientation = Qt::Vertical;
56 extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr);
57 sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
58 auto vertical_size = qApp->style()->sizeFromContents(QStyle::CT_ScrollBar, &option, QSize(extent, extent * 2 + sliderMin), nullptr);
59
60 QStyleOptionFrame frameOption;
61 frameOption.rect = QRect(QPoint(), QSize(1000, 1000));
62 frameOption.frameShape = QFrame::StyledPanel;
63 frameOption.lineWidth = 1;
64 frameOption.midLineWidth = 0;
65 QRect cr = qApp->style()->subElementRect(QStyle::SE_ShapedFrameContents, &frameOption, nullptr);
66 return {
67 cr.left(),
68 cr.top(),
69 (vertical_size.width() + frameOption.rect.right() - cr.right()),
70 (horizontal_size.height() + frameOption.rect.bottom() - cr.bottom())
71 };
72 })
73 });
74
75 self.native_padding_left.set_binding({
76 let paddings = paddings.clone();
77 move || LogicalLength::new(paddings.as_ref().get().left as _)
78 });
79 self.native_padding_right.set_binding({
80 let paddings = paddings.clone();
81 move || LogicalLength::new(paddings.as_ref().get().right as _)
82 });
83 self.native_padding_top.set_binding({
84 let paddings = paddings.clone();
85 move || LogicalLength::new(paddings.as_ref().get().top as _)
86 });
87 self.native_padding_bottom.set_binding({
88 let paddings = paddings;
89 move || LogicalLength::new(paddings.as_ref().get().bottom as _)
90 });
91 }
92
93 fn layout_info(
94 self: Pin<&Self>,
95 orientation: Orientation,
96 _window_adapter: &Rc<dyn WindowAdapter>,
97 _self_rc: &ItemRc,
98 ) -> LayoutInfo {
99 let min = match orientation {
100 Orientation::Horizontal => self.native_padding_left() + self.native_padding_right(),
101 Orientation::Vertical => self.native_padding_top() + self.native_padding_bottom(),
102 }
103 .get();
104 LayoutInfo { min, preferred: min, stretch: 1., ..LayoutInfo::default() }
105 }
106
107 fn input_event_filter_before_children(
108 self: Pin<&Self>,
109 _: &MouseEvent,
110 _window_adapter: &Rc<dyn WindowAdapter>,
111 _self_rc: &ItemRc,
112 ) -> InputEventFilterResult {
113 InputEventFilterResult::ForwardEvent
114 }
115
116 fn input_event(
117 self: Pin<&Self>,
118 event: &MouseEvent,
119 _window_adapter: &Rc<dyn WindowAdapter>,
120 self_rc: &i_slint_core::items::ItemRc,
121 ) -> InputEventResult {
122 let size: qttypes::QSize = get_size!(self_rc);
123 let mut data = self.data();
124 let active_controls = data.active_controls;
125 let pressed = data.pressed;
126 let left = self.native_padding_left().get();
127 let right = self.native_padding_right().get();
128 let top = self.native_padding_top().get();
129 let bottom = self.native_padding_bottom().get();
130
131 let mut handle_scrollbar = |horizontal: bool,
132 pos: qttypes::QPoint,
133 size: qttypes::QSize,
134 value_prop: Pin<&Property<LogicalLength>>,
135 page_size: i32,
136 max: i32| {
137 let pressed: bool = data.pressed != 0;
138 let value: i32 = value_prop.get().get() as i32;
139 let new_control = cpp!(unsafe [
140 pos as "QPoint",
141 value as "int",
142 page_size as "int",
143 max as "int",
144 size as "QSize",
145 active_controls as "int",
146 pressed as "bool",
147 horizontal as "bool"
148 ] -> u32 as "int" {
149 ensure_initialized();
150 QStyleOptionSlider option;
151 initQSliderOptions(option, pressed, true, active_controls, 0, max, -value, false);
152 option.pageStep = page_size;
153 if (!horizontal) {
154 option.state ^= QStyle::State_Horizontal;
155 option.orientation = Qt::Vertical;
156 }
157 auto style = qApp->style();
158 option.rect = { QPoint{}, size };
159 return style->hitTestComplexControl(QStyle::CC_ScrollBar, &option, pos, nullptr);
160 });
161
162 #[allow(non_snake_case)]
163 let SC_ScrollBarSlider =
164 cpp!(unsafe []->u32 as "int" { return QStyle::SC_ScrollBarSlider;});
165
166 let (pos, size) = if horizontal { (pos.x, size.width) } else { (pos.y, size.height) };
167
168 let result = match event {
169 MouseEvent::Pressed { .. } => {
170 data.pressed = if horizontal { 1 } else { 2 };
171 if new_control == SC_ScrollBarSlider {
172 data.pressed_x = pos as f32;
173 data.pressed_val = -value as f32;
174 data.pressed_max = max as f32;
175 }
176 data.active_controls = new_control;
177 InputEventResult::GrabMouse
178 }
179 MouseEvent::Exit => {
180 data.pressed = 0;
181 InputEventResult::EventIgnored
182 }
183 MouseEvent::Released { .. } => {
184 data.pressed = 0;
185 let new_val = cpp!(unsafe [active_controls as "int", value as "int", max as "int", page_size as "int"] -> i32 as "int" {
186 switch (active_controls) {
187 case QStyle::SC_ScrollBarAddPage:
188 return -value + page_size;
189 case QStyle::SC_ScrollBarSubPage:
190 return -value - page_size;
191 case QStyle::SC_ScrollBarAddLine:
192 return -value + 3.;
193 case QStyle::SC_ScrollBarSubLine:
194 return -value - 3.;
195 case QStyle::SC_ScrollBarFirst:
196 return 0;
197 case QStyle::SC_ScrollBarLast:
198 return max;
199 default:
200 return -value;
201 }
202 });
203 let old_val = value_prop.get();
204 let new_val = LogicalLength::new(-(new_val.min(max).max(0) as f32));
205 value_prop.set(new_val);
206 if new_val != old_val {
207 Self::FIELD_OFFSETS.scrolled.apply_pin(self).call(&());
208 }
209 InputEventResult::EventIgnored
210 }
211 MouseEvent::Moved { .. } => {
212 if data.pressed != 0 && data.active_controls == SC_ScrollBarSlider {
213 let max = max as f32;
214
215 if data.pressed_max != max {
220 data.pressed_x = pos as f32;
221 data.pressed_val = -value as f32;
222 data.pressed_max = max;
223 }
224
225 let new_val = data.pressed_val
226 + ((pos as f32) - data.pressed_x) * (max + (page_size as f32))
227 / size as f32;
228 let old_val = value_prop.get();
229 let new_val = LogicalLength::new(-new_val.min(max).max(0.));
230 value_prop.set(new_val);
231 if new_val != old_val {
232 Self::FIELD_OFFSETS.scrolled.apply_pin(self).call(&());
233 }
234 InputEventResult::GrabMouse
235 } else {
236 InputEventResult::EventAccepted
237 }
238 }
239 MouseEvent::Wheel { delta_x, delta_y, .. } => {
240 let max = max as f32;
241 let new_val;
242 if horizontal {
243 new_val = value as f32 + delta_x;
244 } else {
245 new_val = value as f32 + delta_y;
246 }
247 let old_val = value_prop.get();
248 let new_val = LogicalLength::new(new_val.min(0.).max(-max));
249 value_prop.set(new_val);
250 if new_val != old_val {
251 Self::FIELD_OFFSETS.scrolled.apply_pin(self).call(&());
252 }
253 InputEventResult::EventAccepted
254 }
255 MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
256 };
257 self.data.set(data);
258 result
259 };
260
261 let pos = event.position().unwrap_or_default();
262
263 if pressed == 2 || (pressed == 0 && pos.x > (size.width as f32 - right)) {
264 handle_scrollbar(
265 false,
266 qttypes::QPoint {
267 x: (pos.x - (size.width as f32 - right)) as _,
268 y: (pos.y - top) as _,
269 },
270 qttypes::QSize {
271 width: (right - left) as _,
272 height: (size.height as f32 - (bottom + top)) as _,
273 },
274 Self::FIELD_OFFSETS.vertical_value.apply_pin(self),
275 self.vertical_page_size().get() as i32,
276 self.vertical_max().get() as i32,
277 )
278 } else if pressed == 1 || pos.y > (size.height as f32 - bottom) {
279 handle_scrollbar(
280 true,
281 qttypes::QPoint {
282 x: (pos.x - left) as _,
283 y: (pos.y - (size.height as f32 - bottom)) as _,
284 },
285 qttypes::QSize {
286 width: (size.width as f32 - (right + left)) as _,
287 height: (bottom - top) as _,
288 },
289 Self::FIELD_OFFSETS.horizontal_value.apply_pin(self),
290 self.horizontal_page_size().get() as i32,
291 self.horizontal_max().get() as i32,
292 )
293 } else {
294 Default::default()
295 }
296 }
297
298 fn capture_key_event(
299 self: Pin<&Self>,
300 _event: &KeyEvent,
301 _window_adapter: &Rc<dyn WindowAdapter>,
302 _self_rc: &ItemRc,
303 ) -> KeyEventResult {
304 KeyEventResult::EventIgnored
305 }
306
307 fn key_event(
308 self: Pin<&Self>,
309 _: &KeyEvent,
310 _window_adapter: &Rc<dyn WindowAdapter>,
311 _self_rc: &ItemRc,
312 ) -> KeyEventResult {
313 KeyEventResult::EventIgnored
314 }
315
316 fn focus_event(
317 self: Pin<&Self>,
318 _: &FocusEvent,
319 _window_adapter: &Rc<dyn WindowAdapter>,
320 _self_rc: &ItemRc,
321 ) -> FocusEventResult {
322 FocusEventResult::FocusIgnored
323 }
324
325 fn_render! { this dpr size painter widget initial_state =>
326
327 let data = this.data();
328 let margins = qttypes::QMargins {
329 left: this.native_padding_left().get() as _,
330 top: this.native_padding_top().get() as _,
331 right: this.native_padding_right().get() as _,
332 bottom: this.native_padding_bottom().get() as _,
333 };
334 let enabled: bool = this.enabled();
335 let has_focus: bool = this.has_focus();
336 let vertical_bar_visible = (this.vertical_scrollbar_policy() == ScrollBarPolicy::AlwaysOn) || ((this.vertical_scrollbar_policy() == ScrollBarPolicy::AsNeeded) && (this.vertical_max().get() > 0.0));
337 let horizontal_bar_visible = (this.horizontal_scrollbar_policy() == ScrollBarPolicy::AlwaysOn) || ((this.horizontal_scrollbar_policy() == ScrollBarPolicy::AsNeeded) && (this.horizontal_max().get() > 0.0));
338 let scrollbar_bar_visible = vertical_bar_visible || horizontal_bar_visible;
339 let frame_around_contents = cpp!(unsafe [
340 painter as "QPainterPtr*",
341 widget as "QWidget*",
342 size as "QSize",
343 dpr as "float",
344 margins as "QMargins",
345 enabled as "bool",
346 has_focus as "bool",
347 initial_state as "int",
348 scrollbar_bar_visible as "bool"
349 ] -> bool as "bool" {
350 ensure_initialized();
351 QStyleOptionFrame frameOption;
352 frameOption.styleObject = widget;
353 frameOption.state |= QStyle::State(initial_state);
354 frameOption.frameShape = QFrame::StyledPanel;
355
356 frameOption.lineWidth = 1;
357 frameOption.midLineWidth = 0;
358 frameOption.rect = QRect(QPoint(), size / dpr);
359 frameOption.state |= QStyle::State_Sunken;
360 if (enabled) {
361 frameOption.state |= QStyle::State_Enabled;
362 } else {
363 frameOption.palette.setCurrentColorGroup(QPalette::Disabled);
364 }
365 if (has_focus)
366 frameOption.state |= QStyle::State_HasFocus;
367 bool foac = qApp->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &frameOption, widget);
369 QSize corner_size = QSize(margins.right() - margins.left(), margins.bottom() - margins.top());
371 if (foac) {
372 frameOption.rect = QRect(QPoint(), (size / dpr) - corner_size);
373 qApp->style()->drawControl(QStyle::CE_ShapedFrame, &frameOption, painter->get(), widget);
374 frameOption.rect = QRect(frameOption.rect.bottomRight() + QPoint(1, 1), corner_size);
375 if (scrollbar_bar_visible) {
376 qApp->style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &frameOption, painter->get(), widget);
377 }
378 } else {
379 qApp->style()->drawControl(QStyle::CE_ShapedFrame, &frameOption, painter->get(), widget);
380 frameOption.rect = QRect(frameOption.rect.bottomRight() + QPoint(1, 1) - QPoint(margins.right(), margins.bottom()), corner_size);
381 if (scrollbar_bar_visible) {
382 qApp->style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &frameOption, painter->get(), widget);
383 }
384 }
385 return foac;
386 });
387
388 let draw_scrollbar = |horizontal: bool,
389 rect: qttypes::QRectF,
390 value: i32,
391 page_size: i32,
392 max: i32,
393 active_controls: u32,
394 pressed: bool,
395 initial_state: i32| {
396 cpp!(unsafe [
397 painter as "QPainterPtr*",
398 widget as "QWidget*",
399 value as "int",
400 page_size as "int",
401 max as "int",
402 rect as "QRectF",
403 active_controls as "int",
404 pressed as "bool",
405 dpr as "float",
406 horizontal as "bool",
407 has_focus as "bool",
408 initial_state as "int"
409 ] {
410 QPainter *painter_ = painter->get();
411 auto r = rect.toAlignedRect();
412 if (!r.isValid())
414 return;
415 #if defined(Q_OS_MAC)
418 QImage scrollbar_image(r.size(), QImage::Format_ARGB32_Premultiplied);
419 scrollbar_image.fill(Qt::transparent);
420 {QPainter p(&scrollbar_image); QPainter *painter_ = &p;
421 #else
422 painter_->save();
423 auto cleanup = qScopeGuard([&] { painter_->restore(); });
424 painter_->translate(r.topLeft()); #endif
426 QStyleOptionSlider option;
427 option.state |= QStyle::State(initial_state);
428 option.rect = QRect(QPoint(), r.size());
429 initQSliderOptions(option, pressed, true, active_controls, 0, max / dpr, -value / dpr, false);
430 option.subControls = QStyle::SC_All;
431 option.pageStep = page_size / dpr;
432 if (has_focus)
433 option.state |= QStyle::State_HasFocus;
434
435 if (!horizontal) {
436 option.state ^= QStyle::State_Horizontal;
437 option.orientation = Qt::Vertical;
438 }
439
440 auto style = qApp->style();
441 style->drawComplexControl(QStyle::CC_ScrollBar, &option, painter_, widget);
442 #if defined(Q_OS_MAC)
443 }
444 (painter_)->drawImage(r.topLeft(), scrollbar_image);
445 #endif
446 });
447 };
448
449 let scrollbars_width = (margins.right - margins.left) as f32;
450 let scrollbars_height = (margins.bottom - margins.top) as f32;
451 if vertical_bar_visible {
452 draw_scrollbar(
453 false,
454 qttypes::QRectF {
455 x: ((size.width as f32 / dpr) - if frame_around_contents { scrollbars_width } else { margins.right as _ }) as _,
456 y: (if frame_around_contents { 0 } else { margins.top }) as _,
457 width: scrollbars_width as _,
458 height: ((size.height as f32 / dpr) - if frame_around_contents { scrollbars_height } else { (margins.bottom + margins.top) as f32 }) as _,
459 },
460 this.vertical_value().get() as i32,
461 this.vertical_page_size().get() as i32,
462 this.vertical_max().get() as i32,
463 data.active_controls,
464 data.pressed == 2,
465 initial_state
466 );
467 }
468 if horizontal_bar_visible {
469 draw_scrollbar(
470 true,
471 qttypes::QRectF {
472 x: (if frame_around_contents { 0 } else { margins.left }) as _,
473 y: ((size.height as f32 / dpr) - if frame_around_contents { scrollbars_height } else { margins.bottom as _ }) as _,
474 width: ((size.width as f32 / dpr) - if frame_around_contents { scrollbars_width } else { (margins.left + margins.right) as _ }) as _,
475 height: (scrollbars_height) as _,
476 },
477 this.horizontal_value().get() as i32,
478 this.horizontal_page_size().get() as i32,
479 this.horizontal_max().get() as i32,
480 data.active_controls,
481 data.pressed == 1,
482 initial_state
483 );
484 }
485 }
486
487 fn bounding_rect(
488 self: core::pin::Pin<&Self>,
489 _window_adapter: &Rc<dyn WindowAdapter>,
490 _self_rc: &ItemRc,
491 geometry: LogicalRect,
492 ) -> LogicalRect {
493 geometry
494 }
495
496 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
497 false
498 }
499}
500
501impl ItemConsts for NativeScrollView {
502 const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
503 Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
504}
505
506declare_item_vtable! {
507fn slint_get_NativeScrollViewVTable() -> NativeScrollViewVTable for NativeScrollView
508}