kas_widgets/
scroll_bar.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! `ScrollBar` control
7
8use super::{GripMsg, GripPart, ScrollRegion};
9use kas::event::{Scroll, TimerHandle};
10use kas::prelude::*;
11use kas::theme::Feature;
12use std::fmt::Debug;
13
14/// Scroll bar mode
15///
16/// The default value is [`ScrollBarMode::Auto`].
17#[kas_macros::impl_default(ScrollBarMode::Auto)]
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
19pub enum ScrollBarMode {
20    /// Automatically enable/disable scroll bars as required when resized.
21    ///
22    /// This has the side-effect of reserving enough space for scroll bars even
23    /// when not required.
24    Auto,
25    /// Each scroll bar has fixed visibility.
26    ///
27    /// Parameters: `(horiz_is_visible, vert_is_visible)`.
28    Fixed(bool, bool),
29    /// Enabled scroll bars float over content and are only drawn on mouse over.
30    /// Disabled scroll bars are fully hidden.
31    ///
32    /// Parameters: `(horiz_is_enabled, vert_is_enabled)`.
33    Invisible(bool, bool),
34}
35
36/// Message from a [`ScrollBar`]
37#[derive(Copy, Clone, Debug)]
38pub struct ScrollMsg(pub i32);
39
40const TIMER_HIDE: TimerHandle = TimerHandle::new(0, false);
41
42#[impl_self]
43mod ScrollBar {
44    /// A scroll bar
45    ///
46    /// Scroll bars allow user-input of a value between 0 and a defined maximum,
47    /// and allow the size of the grip to be specified.
48    ///
49    /// # Messages
50    ///
51    /// On value change, pushes a value of type [`ScrollMsg`].
52    ///
53    /// # Layout
54    ///
55    /// It is safe to not call `size_rules` before `set_rect` for this type.
56    #[derive(Clone, Debug, Default)]
57    #[widget]
58    pub struct ScrollBar<D: Directional = Direction> {
59        core: widget_core!(),
60        direction: D,
61        // Terminology assumes vertical orientation:
62        min_grip_len: i32, // units: px
63        grip_len: i32,     // units: px
64        // grip_size, max_value and value are all in arbitrary (user-provided) units:
65        grip_size: i32, // contract: > 0; relative to max_value
66        max_value: i32,
67        value: i32,
68        invisible: bool,
69        is_under_mouse: bool,
70        force_visible: bool,
71        #[widget]
72        grip: GripPart,
73    }
74
75    impl Self
76    where
77        D: Default,
78    {
79        /// Construct a scroll bar
80        ///
81        /// Default values are assumed for all parameters.
82        #[inline]
83        pub fn new() -> Self {
84            ScrollBar::new_dir(D::default())
85        }
86    }
87    impl ScrollBar<kas::dir::Down> {
88        /// Construct a scroll bar (vertical)
89        ///
90        /// Default values are assumed for all parameters.
91        pub fn down() -> Self {
92            ScrollBar::new()
93        }
94    }
95    impl ScrollBar<kas::dir::Right> {
96        /// Construct a scroll bar (horizontal)
97        ///
98        /// Default values are assumed for all parameters.
99        pub fn right() -> Self {
100            ScrollBar::new()
101        }
102    }
103
104    impl Self {
105        /// Construct a scroll bar with the given direction
106        ///
107        /// Default values are assumed for all parameters.
108        #[inline]
109        pub fn new_dir(direction: D) -> Self {
110            ScrollBar {
111                core: Default::default(),
112                direction,
113                min_grip_len: 0,
114                grip_len: 0,
115                grip_size: 1,
116                max_value: 0,
117                value: 0,
118                invisible: false,
119                is_under_mouse: false,
120                force_visible: false,
121                grip: GripPart::new(),
122            }
123        }
124
125        /// Get the scroll bar's direction
126        #[inline]
127        pub fn direction(&self) -> Direction {
128            self.direction.as_direction()
129        }
130
131        /// Set invisible property
132        ///
133        /// An "invisible" scroll bar is only drawn on mouse-over
134        #[inline]
135        pub fn set_invisible(&mut self, invisible: bool) {
136            self.invisible = invisible;
137        }
138
139        /// Set invisible property (inline)
140        ///
141        /// An "invisible" scroll bar is only drawn on mouse-over
142        #[inline]
143        pub fn with_invisible(mut self, invisible: bool) -> Self {
144            self.invisible = invisible;
145            self
146        }
147
148        /// Set the initial page length
149        ///
150        /// See [`ScrollBar::set_limits`].
151        #[inline]
152        #[must_use]
153        pub fn with_limits(mut self, max_value: i32, grip_size: i32) -> Self {
154            // We should gracefully handle zero, though appearance may be wrong.
155            self.grip_size = grip_size.max(1);
156
157            self.max_value = max_value.max(0);
158            self.value = self.value.clamp(0, self.max_value);
159            self
160        }
161
162        /// Set the initial value
163        #[inline]
164        #[must_use]
165        pub fn with_value(mut self, value: i32) -> Self {
166            self.value = value.clamp(0, self.max_value);
167            self
168        }
169
170        /// Set the page limits
171        ///
172        /// The `max_value` parameter specifies the maximum possible value.
173        /// (The minimum is always 0.) For a scroll region, this should correspond
174        /// to the maximum possible offset.
175        ///
176        /// The `grip_size` parameter specifies the size of the grip relative to
177        /// the maximum value: the grip size relative to the length of the scroll
178        /// bar is `grip_size / (max_value + grip_size)`. For a scroll region,
179        /// this should correspond to the size of the visible region.
180        /// The minimum value is 1.
181        ///
182        /// The choice of units is not important (e.g. can be pixels or lines),
183        /// so long as both parameters use the same units.
184        ///
185        /// Returns [`Action::REDRAW`] if a redraw is required.
186        pub fn set_limits(&mut self, cx: &mut EventState, max_value: i32, grip_size: i32) {
187            // We should gracefully handle zero, though appearance may be wrong.
188            self.grip_size = grip_size.max(1);
189
190            self.max_value = max_value.max(0);
191            self.value = self.value.clamp(0, self.max_value);
192            self.update_widgets(cx);
193        }
194
195        /// Read the current max value
196        ///
197        /// See also the [`ScrollBar::set_limits`] documentation.
198        #[inline]
199        pub fn max_value(&self) -> i32 {
200            self.max_value
201        }
202
203        /// Read the current grip value
204        ///
205        /// See also the [`ScrollBar::set_limits`] documentation.
206        #[inline]
207        pub fn grip_size(&self) -> i32 {
208            self.grip_size
209        }
210
211        /// Get the current value
212        #[inline]
213        pub fn value(&self) -> i32 {
214            self.value
215        }
216
217        /// Set the value
218        ///
219        /// Returns true if the value changes.
220        pub fn set_value(&mut self, cx: &mut EventState, value: i32) -> bool {
221            let value = value.clamp(0, self.max_value);
222            let changed = value != self.value;
223            if changed {
224                self.value = value;
225                self.grip.set_offset(cx, self.offset());
226            }
227            if !self.is_under_mouse {
228                self.force_visible = true;
229                let delay = cx.config().event().touch_select_delay();
230                cx.request_timer(self.id(), TIMER_HIDE, delay);
231            }
232            changed
233        }
234
235        #[inline]
236        fn bar_len(&self) -> i32 {
237            match self.direction.is_vertical() {
238                false => self.rect().size.0,
239                true => self.rect().size.1,
240            }
241        }
242
243        fn update_widgets(&mut self, cx: &mut EventState) {
244            let len = self.bar_len();
245            let total = 1i64.max(i64::from(self.max_value) + i64::from(self.grip_size));
246            let grip_len = i64::from(self.grip_size) * i64::conv(len) / total;
247            self.grip_len = i32::conv(grip_len).max(self.min_grip_len).min(len);
248            let mut size = self.rect().size;
249            size.set_component(self.direction, self.grip_len);
250            self.grip.set_size(size);
251            self.grip.set_offset(cx, self.offset());
252        }
253
254        // translate value to offset in local coordinates
255        fn offset(&self) -> Offset {
256            let len = self.bar_len() - self.grip_len;
257            let lhs = i64::from(self.value) * i64::conv(len);
258            let rhs = i64::from(self.max_value);
259            let mut pos = if rhs == 0 {
260                0
261            } else {
262                i32::conv((lhs + (rhs / 2)) / rhs).min(len)
263            };
264            if self.direction.is_reversed() {
265                pos = len - pos;
266            }
267            match self.direction.is_vertical() {
268                false => Offset(pos, 0),
269                true => Offset(0, pos),
270            }
271        }
272
273        // true if not equal to old value
274        fn apply_grip_offset(&mut self, cx: &mut EventCx, offset: Offset) {
275            let offset = self.grip.set_offset(cx, offset);
276
277            let len = self.bar_len() - self.grip_len;
278            let mut offset = match self.direction.is_vertical() {
279                false => offset.0,
280                true => offset.1,
281            };
282            if self.direction.is_reversed() {
283                offset = len - offset;
284            }
285
286            let lhs = i64::from(offset) * i64::from(self.max_value);
287            let rhs = i64::conv(len);
288            if rhs == 0 {
289                debug_assert_eq!(self.value, 0);
290                return;
291            }
292            let value = i32::conv((lhs + (rhs / 2)) / rhs);
293            if self.set_value(cx, value) {
294                cx.push(ScrollMsg(value));
295            }
296        }
297
298        /// Get whether the scroll bar is currently visible
299        ///
300        /// This property may change frequently. The method is intended only to
301        /// allow omitting draw calls while the scroll bar is not visible, since
302        /// these draw calls may require use of an additional draw pass to allow
303        /// an "invisible" scroll bar to be drawn over content.
304        #[inline]
305        pub fn currently_visible(&self, ev_state: &EventState) -> bool {
306            !self.invisible
307                || (self.max_value != 0 && self.force_visible)
308                || ev_state.is_depressed(self.grip.id_ref())
309        }
310    }
311
312    impl Layout for Self {
313        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
314            let _ = self.grip.size_rules(sizer.re(), axis);
315            sizer.feature(Feature::ScrollBar(self.direction()), axis)
316        }
317
318        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
319            let align = match self.direction.is_vertical() {
320                false => AlignPair::new(Align::Stretch, hints.vert.unwrap_or(Align::Center)),
321                true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch),
322            };
323            let rect = cx.align_feature(Feature::ScrollBar(self.direction()), rect, align);
324            widget_set_rect!(rect);
325            self.grip.set_track(rect);
326
327            // We call grip.set_rect only for compliance with the widget model:
328            self.grip.set_rect(cx, Rect::ZERO, AlignHints::NONE);
329
330            self.min_grip_len = cx.size_cx().grip_len();
331            self.update_widgets(cx);
332        }
333
334        #[inline]
335        fn draw(&self, mut draw: DrawCx) {
336            if self.currently_visible(draw.ev_state()) {
337                let dir = self.direction.as_direction();
338                draw.scroll_bar(self.rect(), &self.grip, dir);
339            }
340        }
341    }
342
343    impl Tile for Self {
344        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
345            Role::ScrollBar {
346                direction: self.direction.as_direction(),
347                value: self.value,
348                max_value: self.max_value,
349            }
350        }
351
352        fn probe(&self, coord: Coord) -> Id {
353            if self.invisible && self.max_value == 0 {
354                return self.id();
355            }
356            self.grip.try_probe(coord).unwrap_or_else(|| self.id())
357        }
358    }
359
360    impl Events for Self {
361        const REDRAW_ON_MOUSE_OVER: bool = true;
362
363        type Data = ();
364
365        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
366            match event {
367                Event::Timer(TIMER_HIDE) => {
368                    if !self.is_under_mouse {
369                        self.force_visible = false;
370                        cx.redraw(self);
371                    }
372                    Used
373                }
374                Event::PressStart(press) => {
375                    let offset = self.grip.handle_press_on_track(cx, &press);
376                    self.apply_grip_offset(cx, offset);
377                    Used
378                }
379                Event::MouseOver(true) => {
380                    self.is_under_mouse = true;
381                    self.force_visible = true;
382                    cx.redraw(self);
383                    Used
384                }
385                Event::MouseOver(false) => {
386                    self.is_under_mouse = false;
387                    let delay = cx.config().event().touch_select_delay();
388                    cx.request_timer(self.id(), TIMER_HIDE, delay);
389                    Used
390                }
391                _ => Unused,
392            }
393        }
394
395        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
396            if let Some(GripMsg::PressMove(offset)) = cx.try_pop() {
397                self.apply_grip_offset(cx, offset);
398            }
399        }
400    }
401}
402
403#[impl_self]
404mod ScrollBars {
405    /// Scroll bar controls
406    ///
407    /// This is a wrapper adding scroll bar controls around a child. Note that this
408    /// widget does not enable scrolling; see [`ScrollBarRegion`] for that.
409    ///
410    /// Scroll bar positioning does not respect the inner widget's margins, since
411    /// the result looks poor when content is scrolled. Instead the content should
412    /// force internal margins by wrapping contents with a (zero-sized) frame.
413    /// [`ScrollRegion`] already does this.
414    #[derive(Clone, Debug, Default)]
415    #[widget]
416    pub struct ScrollBars<W: Scrollable + Widget> {
417        core: widget_core!(),
418        mode: ScrollBarMode,
419        show_bars: (bool, bool), // set by user (or set_rect when mode == Auto)
420        #[widget(&())]
421        horiz_bar: ScrollBar<kas::dir::Right>,
422        #[widget(&())]
423        vert_bar: ScrollBar<kas::dir::Down>,
424        #[widget]
425        inner: W,
426    }
427
428    impl Self {
429        /// Construct
430        ///
431        /// By default scroll bars are automatically enabled based on requirements.
432        #[inline]
433        pub fn new(inner: W) -> Self {
434            ScrollBars {
435                core: Default::default(),
436                mode: ScrollBarMode::Auto,
437                show_bars: (false, false),
438                horiz_bar: ScrollBar::new(),
439                vert_bar: ScrollBar::new(),
440                inner,
441            }
442        }
443
444        /// Set fixed visibility of scroll bars (inline)
445        #[inline]
446        pub fn with_fixed_bars(mut self, horiz: bool, vert: bool) -> Self
447        where
448            Self: Sized,
449        {
450            self.mode = ScrollBarMode::Fixed(horiz, vert);
451            self.horiz_bar.set_invisible(false);
452            self.vert_bar.set_invisible(false);
453            self.show_bars = (horiz, vert);
454            self
455        }
456
457        /// Set fixed, invisible bars (inline)
458        ///
459        /// In this mode scroll bars are either enabled but invisible until
460        /// mouse over or disabled completely.
461        #[inline]
462        pub fn with_invisible_bars(mut self, horiz: bool, vert: bool) -> Self
463        where
464            Self: Sized,
465        {
466            self.mode = ScrollBarMode::Invisible(horiz, vert);
467            self.horiz_bar.set_invisible(true);
468            self.vert_bar.set_invisible(true);
469            self.show_bars = (horiz, vert);
470            self
471        }
472
473        /// Get current mode of scroll bars
474        #[inline]
475        pub fn scroll_bar_mode(&self) -> ScrollBarMode {
476            self.mode
477        }
478
479        /// Set scroll bar mode
480        pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) {
481            if mode != self.mode {
482                self.mode = mode;
483                let (invis_horiz, invis_vert) = match mode {
484                    ScrollBarMode::Auto => (false, false),
485                    ScrollBarMode::Fixed(horiz, vert) => {
486                        self.show_bars = (horiz, vert);
487                        (false, false)
488                    }
489                    ScrollBarMode::Invisible(horiz, vert) => {
490                        self.show_bars = (horiz, vert);
491                        (horiz, vert)
492                    }
493                };
494                self.horiz_bar.set_invisible(invis_horiz);
495                self.vert_bar.set_invisible(invis_vert);
496                cx.resize(self);
497            }
498        }
499
500        /// Access inner widget directly
501        #[inline]
502        pub fn inner(&self) -> &W {
503            &self.inner
504        }
505
506        /// Access inner widget directly
507        #[inline]
508        pub fn inner_mut(&mut self) -> &mut W {
509            &mut self.inner
510        }
511    }
512
513    impl Scrollable for Self {
514        #[inline]
515        fn content_size(&self) -> Size {
516            self.inner.content_size()
517        }
518        #[inline]
519        fn max_scroll_offset(&self) -> Offset {
520            self.inner.max_scroll_offset()
521        }
522        #[inline]
523        fn scroll_offset(&self) -> Offset {
524            self.inner.scroll_offset()
525        }
526        fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
527            let offset = self.inner.set_scroll_offset(cx, offset);
528            self.horiz_bar.set_value(cx, offset.0);
529            self.vert_bar.set_value(cx, offset.1);
530            offset
531        }
532    }
533
534    impl Layout for Self {
535        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
536            let mut rules = self.inner.size_rules(sizer.re(), axis);
537            let vert_rules = self.vert_bar.size_rules(sizer.re(), axis);
538            let horiz_rules = self.horiz_bar.size_rules(sizer.re(), axis);
539            let (use_horiz, use_vert) = match self.mode {
540                ScrollBarMode::Fixed(horiz, vert) => (horiz, vert),
541                ScrollBarMode::Auto => (true, true),
542                ScrollBarMode::Invisible(_, _) => (false, false),
543            };
544            if axis.is_horizontal() && use_horiz {
545                rules.append(vert_rules);
546            } else if axis.is_vertical() && use_vert {
547                rules.append(horiz_rules);
548            }
549            rules
550        }
551
552        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
553            widget_set_rect!(rect);
554            let pos = rect.pos;
555            let mut child_size = rect.size;
556
557            let bar_width = cx.size_cx().scroll_bar_width();
558            if self.mode == ScrollBarMode::Auto {
559                let max_offset = self.inner.max_scroll_offset();
560                self.show_bars = (max_offset.0 > 0, max_offset.1 > 0);
561            }
562            if self.show_bars.0 && !self.horiz_bar.invisible {
563                child_size.1 -= bar_width;
564            }
565            if self.show_bars.1 && !self.vert_bar.invisible {
566                child_size.0 -= bar_width;
567            }
568
569            let child_rect = Rect::new(pos, child_size);
570            self.inner.set_rect(cx, child_rect, hints);
571            let max_scroll_offset = self.inner.max_scroll_offset();
572
573            if self.show_bars.0 {
574                let pos = Coord(pos.0, rect.pos2().1 - bar_width);
575                let size = Size::new(child_size.0, bar_width);
576                self.horiz_bar
577                    .set_rect(cx, Rect { pos, size }, AlignHints::NONE);
578                self.horiz_bar
579                    .set_limits(cx, max_scroll_offset.0, rect.size.0);
580            } else {
581                self.horiz_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE);
582            }
583
584            if self.show_bars.1 {
585                let pos = Coord(rect.pos2().0 - bar_width, pos.1);
586                let size = Size::new(bar_width, self.rect().size.1);
587                self.vert_bar
588                    .set_rect(cx, Rect { pos, size }, AlignHints::NONE);
589                self.vert_bar
590                    .set_limits(cx, max_scroll_offset.1, rect.size.1);
591            } else {
592                self.vert_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE);
593            }
594        }
595
596        fn draw(&self, mut draw: DrawCx) {
597            self.inner.draw(draw.re());
598            if self.show_bars == (false, false) {
599                return;
600            }
601
602            // We use a new pass to draw scroll bars over inner content, but
603            // only when required to minimize cost:
604            let ev_state = draw.ev_state();
605            if matches!(self.mode, ScrollBarMode::Invisible(_, _))
606                && (self.horiz_bar.currently_visible(ev_state)
607                    || self.vert_bar.currently_visible(ev_state))
608            {
609                draw.with_pass(|mut draw| {
610                    if self.show_bars.0 {
611                        self.horiz_bar.draw(draw.re());
612                    }
613                    if self.show_bars.1 {
614                        self.vert_bar.draw(draw.re());
615                    }
616                });
617            } else {
618                if self.show_bars.0 {
619                    self.horiz_bar.draw(draw.re());
620                }
621                if self.show_bars.1 {
622                    self.vert_bar.draw(draw.re());
623                }
624            }
625        }
626    }
627
628    impl Tile for Self {
629        fn probe(&self, coord: Coord) -> Id {
630            self.vert_bar
631                .try_probe(coord)
632                .or_else(|| self.horiz_bar.try_probe(coord))
633                .or_else(|| self.inner.try_probe(coord))
634                .unwrap_or_else(|| self.id())
635        }
636    }
637
638    impl Events for Self {
639        type Data = W::Data;
640
641        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
642            let index = cx.last_child();
643            if index == Some(widget_index![self.horiz_bar]) {
644                if let Some(ScrollMsg(x)) = cx.try_pop() {
645                    let offset = Offset(x, self.inner.scroll_offset().1);
646                    self.inner.set_scroll_offset(cx, offset);
647                }
648            } else if index == Some(widget_index![self.vert_bar]) {
649                if let Some(ScrollMsg(y)) = cx.try_pop() {
650                    let offset = Offset(self.inner.scroll_offset().0, y);
651                    self.inner.set_scroll_offset(cx, offset);
652                }
653            }
654        }
655
656        fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, _: Scroll) {
657            // We assume the inner already updated its positions; this is just to set bars
658            let offset = self.inner.scroll_offset();
659            self.horiz_bar.set_value(cx, offset.0);
660            self.vert_bar.set_value(cx, offset.1);
661        }
662    }
663}
664
665#[impl_self]
666mod ScrollBarRegion {
667    /// A scrollable region with bars
668    ///
669    /// This is essentially a `ScrollBars<ScrollRegion<W>>`:
670    /// [`ScrollRegion`] handles the actual scrolling and wheel/touch events,
671    /// while [`ScrollBars`] adds scroll bar controls.
672    #[autoimpl(Deref, DerefMut, Scrollable using self.0)]
673    #[derive(Clone, Debug, Default)]
674    #[derive_widget]
675    pub struct ScrollBarRegion<W: Widget>(#[widget] ScrollBars<ScrollRegion<W>>);
676
677    impl Self {
678        /// Construct a `ScrollBarRegion<W>`
679        #[inline]
680        pub fn new(inner: W) -> Self {
681            ScrollBarRegion(ScrollBars::new(ScrollRegion::new(inner)))
682        }
683
684        /// Set fixed visibility of scroll bars (inline)
685        #[inline]
686        pub fn with_fixed_bars(self, horiz: bool, vert: bool) -> Self
687        where
688            Self: Sized,
689        {
690            ScrollBarRegion(self.0.with_fixed_bars(horiz, vert))
691        }
692
693        /// Set fixed, invisible bars (inline)
694        ///
695        /// In this mode scroll bars are either enabled but invisible until
696        /// mouse over or disabled completely.
697        #[inline]
698        pub fn with_invisible_bars(self, horiz: bool, vert: bool) -> Self
699        where
700            Self: Sized,
701        {
702            ScrollBarRegion(self.0.with_invisible_bars(horiz, vert))
703        }
704
705        /// Get current mode of scroll bars
706        #[inline]
707        pub fn scroll_bar_mode(&self) -> ScrollBarMode {
708            self.0.scroll_bar_mode()
709        }
710
711        /// Set scroll bar mode
712        #[inline]
713        pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) {
714            self.0.set_scroll_bar_mode(cx, mode);
715        }
716
717        /// Access inner widget directly
718        #[inline]
719        pub fn inner(&self) -> &W {
720            self.0.inner.inner()
721        }
722
723        /// Access inner widget directly
724        #[inline]
725        pub fn inner_mut(&mut self) -> &mut W {
726            self.0.inner.inner_mut()
727        }
728    }
729}