Skip to main content

kas_core/theme/
draw.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//! Widget-facing high-level draw API
7
8use winit::keyboard::Key;
9
10use super::{FrameStyle, MarkStyle, SelectionStyle, SizeCx, Text, ThemeSize};
11use crate::dir::Direction;
12use crate::draw::color::{ParseError, Rgb, Rgba};
13use crate::draw::{Draw, DrawIface, DrawRounded, DrawShared, DrawSharedImpl, ImageId, PassType};
14use crate::event::EventState;
15#[allow(unused)] use crate::event::{Command, ConfigCx};
16use crate::geom::{Coord, Offset, Rect};
17use crate::text::{Effect, TextDisplay, format::FormattableText};
18use crate::theme::ColorsLinear;
19use crate::{Id, Tile, autoimpl};
20#[allow(unused)] use crate::{Layout, theme::TextClass};
21use std::ops::Range;
22use std::time::Instant;
23
24/// Optional background colour
25#[derive(Copy, Clone, Debug, Default, PartialEq)]
26pub enum Background {
27    /// Use theme/feature's default
28    #[default]
29    Default,
30    /// Error state
31    Error,
32    /// A given color
33    Rgb(Rgb),
34}
35
36impl From<Rgb> for Background {
37    #[inline]
38    fn from(color: Rgb) -> Self {
39        Background::Rgb(color)
40    }
41}
42
43#[derive(Copy, Clone, Debug, thiserror::Error)]
44pub enum BackgroundParseError {
45    /// No `#` prefix
46    ///
47    /// NOTE: this exists to allow the possibility of supporting new exprs like
48    /// "Default" or "Error".
49    #[error("Unknown: no `#` prefix")]
50    Unknown,
51    /// Invalid hex
52    #[error("invalid hex sequence")]
53    InvalidRgb(#[from] ParseError),
54}
55
56impl std::str::FromStr for Background {
57    type Err = BackgroundParseError;
58
59    #[inline]
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        if s.starts_with("#") {
62            Rgb::from_str(s).map(|c| c.into()).map_err(|e| e.into())
63        } else {
64            Err(BackgroundParseError::Unknown)
65        }
66    }
67}
68
69/// Draw interface
70///
71/// This interface is provided to widgets in [`Layout::draw`].
72/// Lower-level interfaces may be accessed through [`Self::draw`].
73///
74/// `DrawCx` is not a `Copy` or `Clone` type; instead it may be "reborrowed"
75/// via [`Self::re`].
76///
77/// -   `draw.check_box(&*self, self.state);` — note `&*self` to convert from to
78///     `&W` from `&mut W`, since the latter would cause borrow conflicts
79#[autoimpl(Debug ignore self.h)]
80pub struct DrawCx<'a> {
81    h: &'a mut dyn ThemeDraw,
82    id: Id,
83}
84
85impl<'a> DrawCx<'a> {
86    /// Reborrow with a new lifetime
87    ///
88    /// Rust allows references like `&T` or `&mut T` to be "reborrowed" through
89    /// coercion: essentially, the pointer is copied under a new, shorter, lifetime.
90    /// Until rfcs#1403 lands, reborrows on user types require a method call.
91    #[inline(always)]
92    pub fn re<'b>(&'b mut self) -> DrawCx<'b>
93    where
94        'a: 'b,
95    {
96        DrawCx {
97            h: self.h,
98            id: self.id.clone(),
99        }
100    }
101
102    /// Construct from a [`DrawCx`] and [`EventState`]
103    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
104    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
105    pub(crate) fn new(h: &'a mut dyn ThemeDraw, id: Id) -> Self {
106        DrawCx { h, id }
107    }
108
109    /// Set the identity of the current widget
110    ///
111    /// This struct tracks the [`Id`] of the calling widget to allow evaluation
112    /// of widget state (e.g. is disabled, is under the mouse, has key focus).
113    /// Usually you don't need to worry about this since the `#[widget]` macro
114    /// injects a call to this method at the start of [`Layout::draw`].
115    pub fn set_id(&mut self, id: Id) {
116        self.id = id;
117    }
118
119    /// Access event-management state
120    pub fn ev_state(&mut self) -> &mut EventState {
121        self.h.components().2
122    }
123
124    /// Access a [`SizeCx`]
125    ///
126    /// (This also allows access to [`EventState`].)
127    pub fn size_cx(&mut self) -> SizeCx<'_> {
128        let (w, _, es) = self.h.components();
129        SizeCx::new(es, w)
130    }
131
132    /// Access theme colors
133    pub fn colors(&self) -> &ColorsLinear {
134        self.h.colors()
135    }
136
137    /// Access a [`DrawShared`]
138    pub fn draw_shared(&mut self) -> &mut dyn DrawShared {
139        self.h.components().1.shared()
140    }
141
142    /// Access the low-level draw device
143    ///
144    /// Note: this drawing API is modular, with limited functionality in the
145    /// base trait [`Draw`]. To access further functionality, it is necessary
146    /// to downcast with [`crate::draw::DrawIface::downcast_from`].
147    pub fn draw(&mut self) -> &mut dyn Draw {
148        self.h.components().1
149    }
150
151    /// Access the draw device as a [`DrawRounded`] implementation, if possible
152    ///
153    /// Warning: this does not reflect whether the underlying draw device
154    /// supports [`DrawRounded`] (which would require specialization) but
155    /// whether the theme in question requires [`DrawRounded`]. As such, this
156    /// method is only useful with a theme requiring this extension such as
157    /// [`FlatTheme`](super::FlatTheme).
158    pub fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded> {
159        self.h.draw_rounded()
160    }
161
162    /// Access the low-level draw device (implementation type)
163    ///
164    /// The implementing type must be specified. See [`DrawIface::downcast_from`].
165    pub fn draw_iface<DS: DrawSharedImpl>(&mut self) -> Option<DrawIface<'_, DS>> {
166        DrawIface::downcast_from(self.draw())
167    }
168
169    /// Draw to a new pass
170    ///
171    /// Adds a new draw pass for purposes of enforcing draw order. Content of
172    /// the new pass will be drawn after content in the parent pass.
173    ///
174    /// Warning: the number of passes used can have a substantial performance
175    /// impact, potentially more on GPU communication than CPU usage.
176    pub fn with_pass<F: FnOnce(DrawCx)>(&mut self, f: F) {
177        let clip_rect = self.h.get_clip_rect();
178        let id = self.id.clone();
179        self.h.new_pass(
180            clip_rect,
181            Offset::ZERO,
182            PassType::Clip,
183            Box::new(|h| f(DrawCx { h, id })),
184        );
185    }
186
187    /// Draw to a new pass with clipping and offset (e.g. for scrolling)
188    ///
189    /// Adds a new draw pass of type [`PassType::Clip`], with draw operations
190    /// clipped to `rect` and translated by `offset.
191    ///
192    /// Warning: the number of passes used can have a substantial performance
193    /// impact, potentially more on GPU communication than CPU usage.
194    pub fn with_clip_region<F: FnOnce(DrawCx)>(&mut self, rect: Rect, offset: Offset, f: F) {
195        let id = self.id.clone();
196        self.h.new_pass(
197            rect,
198            offset,
199            PassType::Clip,
200            Box::new(|h| f(DrawCx { h, id })),
201        );
202    }
203
204    /// Draw to a new pass as an overlay (e.g. for pop-up menus)
205    ///
206    /// Adds a new draw pass of type [`PassType::Overlay`], with draw operations
207    /// clipped to `rect`.
208    ///
209    /// The theme is permitted to enlarge the `rect` for the purpose of drawing
210    /// a frame or shadow around this overlay, thus the
211    /// [`Self::get_clip_rect`] may be larger than expected.
212    ///
213    /// Warning: the number of passes used can have a substantial performance
214    /// impact, potentially more on GPU communication than CPU usage.
215    pub fn with_overlay<F: FnOnce(DrawCx)>(&mut self, rect: Rect, offset: Offset, f: F) {
216        let id = self.id.clone();
217        self.h.new_pass(
218            rect,
219            offset,
220            PassType::Overlay,
221            Box::new(|h| f(DrawCx { h, id })),
222        );
223    }
224
225    /// Target area for drawing
226    ///
227    /// Drawing is restricted to this [`Rect`], which may be the whole window, a
228    /// [clip region](Self::with_clip_region) or an
229    /// [overlay](Self::with_overlay). This may be used to cull hidden
230    /// items from lists inside a scrollable view.
231    pub fn get_clip_rect(&mut self) -> Rect {
232        self.h.get_clip_rect()
233    }
234
235    /// Register widget `id` as handler of an access `key`
236    ///
237    /// An *access key* (also known as mnemonic) is a shortcut key able to
238    /// directly open menus, activate buttons, etc. Usually this requires that
239    /// the <kbd>Alt</kbd> is held, though
240    /// [alt-bypass mode](crate::window::Window::with_alt_bypass) is available.
241    ///
242    /// The widget `id` is bound to the given `key`, if available. When the
243    /// access key is pressed (assuming that this binding succeeds), widget `id`
244    /// will receive navigation focus (if supported; otherwise an ancestor may
245    /// receive focus) and is sent [`Command::Activate`] (likewise, an ancestor
246    /// may handle this if widget `id` does not).
247    ///
248    /// If multiple widgets attempt to register themselves as handlers of the
249    /// same `key`, then only the first succeeds.
250    ///
251    /// Returns `true` when the key should be underlined.
252    pub fn access_key(&mut self, id: &Id, key: &Key) -> bool {
253        self.ev_state().add_access_key_binding(id, key)
254    }
255
256    /// Draw a frame inside the given `rect`
257    ///
258    /// The frame dimensions are given by [`SizeCx::frame`].
259    pub fn frame(&mut self, rect: Rect, style: FrameStyle, bg: Background) {
260        self.h.frame(&self.id, rect, style, bg)
261    }
262
263    /// Draw a separator in the given `rect`
264    pub fn separator(&mut self, rect: Rect) {
265        self.h.separator(rect);
266    }
267
268    /// Draw a selection highlight / frame
269    ///
270    /// Adjusts the background color and/or draws a line around the given rect.
271    /// In the latter case, a margin of size [`SizeCx::inner_margins`] around
272    /// `rect` is expected.
273    pub fn selection(&mut self, rect: Rect, style: SelectionStyle) {
274        self.h.selection(rect, style);
275    }
276
277    /// Draw text
278    ///
279    /// Text is clipped to `rect`.
280    ///
281    /// This is a convenience method over [`Self::text_with_effects`].
282    ///
283    /// The `text` should be prepared before calling this method.
284    pub fn text<T: FormattableText>(&mut self, rect: Rect, text: &Text<T>) {
285        self.text_with_position(rect.pos, rect, text);
286    }
287
288    /// Draw text with specified color
289    ///
290    /// Text is clipped to `rect` and drawn using `color`.
291    ///
292    /// This is a convenience method over [`Self::text_with_effects`].
293    ///
294    /// The `text` should be prepared before calling this method.
295    pub fn text_with_color<T: FormattableText>(&mut self, rect: Rect, text: &Text<T>, color: Rgba) {
296        let effects = text.effect_tokens();
297        self.text_with_effects(rect.pos, rect, text, &[color], effects);
298    }
299
300    /// Draw text with effects and an offset
301    ///
302    /// Text is clipped to `rect`, drawing from `pos`; use `pos = rect.pos` if
303    /// the text is not scrolled.
304    ///
305    /// This is a convenience method over [`Self::text_with_effects`].
306    ///
307    /// The `text` should be prepared before calling this method.
308    pub fn text_with_position<T: FormattableText>(
309        &mut self,
310        pos: Coord,
311        rect: Rect,
312        text: &Text<T>,
313    ) {
314        let effects = text.effect_tokens();
315        self.text_with_effects(pos, rect, text, &[], effects);
316    }
317
318    /// Draw text with a given effect list
319    ///
320    /// Text is clipped to `rect`, drawing from `pos`; use `pos = rect.pos` if
321    /// the text is not scrolled.
322    ///
323    /// If `colors` is empty, it is replaced with a single theme-defined color.
324    /// Text is then drawn using `colors[0]` except as specified by effects.
325    ///
326    /// The list of `effects` (if not empty) controls render effects:
327    /// [`Effect::e`] is an index into `colors` while [`Effect::flags`] controls
328    /// underline and strikethrough. [`Effect::start`] is the text index at
329    /// which this effect first takes effect, and must effects must be ordered
330    /// such that the sequence of [`Effect::start`] values is strictly
331    /// increasing. [`Effect::default()`] is used if `effects` is empty or while
332    /// `index < effects.first().unwrap().start`.
333    ///
334    /// Text objects may embed their own list of effects, accessible using
335    /// [`Text::effect_tokens`]. It is always valid to disregard these
336    /// and use a custom `effects` list or empty list.
337    pub fn text_with_effects<T: FormattableText>(
338        &mut self,
339        pos: Coord,
340        rect: Rect,
341        text: &Text<T>,
342        colors: &[Rgba],
343        effects: &[Effect],
344    ) {
345        if let Ok(display) = text.display() {
346            if effects.is_empty() {
347                // Use the faster and simpler implementation when we don't have effects
348                self.h
349                    .text(&self.id, pos, rect, display, colors.first().cloned());
350            } else {
351                if cfg!(debug_assertions) {
352                    let num_colors = if colors.is_empty() { 1 } else { colors.len() };
353                    let mut i = 0;
354                    for effect in effects {
355                        assert!(effect.start >= i);
356                        i = effect.start;
357
358                        assert!(usize::from(effect.e) < num_colors);
359                    }
360                }
361
362                self.h
363                    .text_effects(&self.id, pos, rect, display, colors, effects);
364            }
365        }
366    }
367
368    /// Draw some text with a selection
369    ///
370    /// Text is drawn like [`Self::text_with_position`] except that the subset
371    /// identified by `range` is highlighted using theme-defined colors.
372    pub fn text_with_selection<T: FormattableText>(
373        &mut self,
374        pos: Coord,
375        rect: Rect,
376        text: &Text<T>,
377        range: Range<usize>,
378    ) {
379        if range.is_empty() {
380            return self.text_with_position(pos, rect, text);
381        }
382
383        let Ok(display) = text.display() else {
384            return;
385        };
386
387        self.h
388            .text_selected_range(&self.id, pos, rect, display, range);
389    }
390
391    /// Draw an edit marker at the given `byte` index on this `text`
392    ///
393    /// The text cursor is draw from `rect.pos` and clipped to `rect`.
394    ///
395    /// The `text` should be prepared before calling this method.
396    pub fn text_cursor<T: FormattableText>(
397        &mut self,
398        pos: Coord,
399        rect: Rect,
400        text: &Text<T>,
401        byte: usize,
402    ) {
403        if let Ok(text) = text.display() {
404            self.h.text_cursor(&self.id, pos, rect, text, byte);
405        }
406    }
407
408    /// Draw UI element: check box (without label)
409    ///
410    /// The check box is a small visual element, typically a distinctive square
411    /// box with or without a "check" selection mark.
412    ///
413    /// The theme may animate transitions. To achieve this, `last_change` should be
414    /// the time of the last state change caused by the user, or none when the
415    /// last state change was programmatic.
416    pub fn check_box(&mut self, rect: Rect, checked: bool, last_change: Option<Instant>) {
417        self.h.check_box(&self.id, rect, checked, last_change);
418    }
419
420    /// Draw UI element: radio box (without label)
421    ///
422    /// The radio box is a small visual element, typically a disinctive
423    /// circular box with or without a "radio" selection mark.
424    ///
425    /// The theme may animate transitions. To achieve this, `last_change` should be
426    /// the time of the last state change caused by the user, or none when the
427    /// last state change was programmatic.
428    pub fn radio_box(&mut self, rect: Rect, checked: bool, last_change: Option<Instant>) {
429        self.h.radio_box(&self.id, rect, checked, last_change);
430    }
431
432    /// Draw UI element: mark
433    ///
434    /// If `rect` is larger than required, the mark will be centered.
435    pub fn mark(&mut self, rect: Rect, style: MarkStyle) {
436        self.h.mark(&self.id, rect, style);
437    }
438
439    /// Draw UI element: scroll bar
440    pub fn scroll_bar<W: Tile>(&mut self, track_rect: Rect, grip: &W, dir: Direction) {
441        self.h
442            .scroll_bar(&self.id, grip.id_ref(), track_rect, grip.rect(), dir);
443    }
444
445    /// Draw UI element: slider
446    pub fn slider<W: Tile>(&mut self, track_rect: Rect, grip: &W, dir: Direction) {
447        self.h
448            .slider(&self.id, grip.id_ref(), track_rect, grip.rect(), dir);
449    }
450
451    /// Draw UI element: progress bar
452    ///
453    /// -   `rect`: area of whole widget
454    /// -   `dir`: direction of progress bar
455    /// -   `state`: highlighting information
456    /// -   `value`: progress value, between 0.0 and 1.0
457    pub fn progress_bar(&mut self, rect: Rect, dir: Direction, value: f32) {
458        self.h.progress_bar(&self.id, rect, dir, value);
459    }
460
461    /// Draw an image
462    pub fn image(&mut self, rect: Rect, id: ImageId) {
463        self.h.image(id, rect);
464    }
465}
466
467/// Theme drawing implementation
468///
469/// # Theme extension
470///
471/// Most themes will not want to implement *everything*, but rather derive
472/// not-explicitly-implemented methods from a base theme. This may be achieved
473/// with the [`kas::extends`](crate::extends) macro:
474/// ```ignore
475/// #[extends(ThemeDraw, base = self.base())]
476/// impl ThemeDraw {
477///     // only implement some methods here
478/// }
479/// ```
480/// Note: [`Self::components`] must be implemented
481/// explicitly since this method returns references.
482///
483/// If Rust had stable specialization + GATs + negative trait bounds we could
484/// allow theme extension without macros as follows.
485/// <details>
486///
487/// ```ignore
488/// #![feature(generic_associated_types)]
489/// #![feature(specialization)]
490/// # use kas_core::geom::Rect;
491/// # use kas_core::theme::ThemeDraw;
492/// /// Provides a default implementation of each theme method over a base theme
493/// pub trait ThemeDrawExtends: ThemeDraw {
494///     /// Type of base implementation
495///     type Base<'a>: ThemeDraw where Self: 'a;
496///
497///     /// Access the base theme
498///     fn base<'a>(&'a mut self) -> Self::Base<'a>;
499/// }
500///
501/// // Note: we may need negative trait bounds here to avoid conflict with impl for Box<H>
502/// impl<D: ThemeDrawExtends> ThemeDraw for D {
503///     default fn get_clip_rect(&mut self) -> Rect {
504///         self.base().get_clip_rect()
505///     }
506///
507///     // And so on for other methods...
508/// }
509/// ```
510/// </details>
511#[autoimpl(for<H: trait + ?Sized> Box<H>)]
512pub trait ThemeDraw {
513    /// Access components: [`ThemeSize`], [`Draw`], [`EventState`]
514    fn components(&mut self) -> (&dyn ThemeSize, &mut dyn Draw, &mut EventState);
515
516    /// Access theme colors
517    fn colors(&self) -> &ColorsLinear;
518
519    /// Access draw device over [`DrawRounded`] (if available)
520    ///
521    /// TODO(Rust): remove once Rust supports downcast to trait objects
522    fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded>;
523
524    /// Construct a new pass
525    fn new_pass<'a>(
526        &mut self,
527        rect: Rect,
528        offset: Offset,
529        class: PassType,
530        f: Box<dyn FnOnce(&mut dyn ThemeDraw) + 'a>,
531    );
532
533    /// Target area for drawing
534    ///
535    /// Drawing is restricted to this [`Rect`]. Affected by [`Self::new_pass`].
536    /// This may be used to cull hidden items from lists inside a scrollable view.
537    fn get_clip_rect(&mut self) -> Rect;
538
539    /// Draw [`EventState`] overlay
540    fn event_state_overlay(&mut self);
541
542    /// Draw a frame inside the given `rect`
543    ///
544    /// The frame dimensions are given by [`ThemeSize::frame`].
545    fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background);
546
547    /// Draw a separator in the given `rect`
548    fn separator(&mut self, rect: Rect);
549
550    /// Draw a selection highlight / frame
551    fn selection(&mut self, rect: Rect, style: SelectionStyle);
552
553    /// Draw text
554    ///
555    /// The `text` should be prepared before calling this method.
556    fn text(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, color: Option<Rgba>);
557
558    /// Draw text with effects
559    ///
560    /// [`ThemeDraw::text`] already supports *font* effects: bold,
561    /// emphasis, text size. In addition, this method supports underline and
562    /// strikethrough effects.
563    ///
564    /// If `effects` is empty or all [`Effect::flags`] are default then it is
565    /// equivalent (and faster) to call [`Self::text`] instead.
566    ///
567    /// The `text` should be prepared before calling this method.
568    fn text_effects(
569        &mut self,
570        id: &Id,
571        pos: Coord,
572        rect: Rect,
573        text: &TextDisplay,
574        colors: &[Rgba],
575        effects: &[Effect],
576    );
577
578    /// Method used to implement [`DrawCx::text_with_selection`]
579    fn text_selected_range(
580        &mut self,
581        id: &Id,
582        pos: Coord,
583        rect: Rect,
584        text: &TextDisplay,
585        range: Range<usize>,
586    );
587
588    /// Draw an edit marker at the given `byte` index on this `text`
589    ///
590    /// The `text` should be prepared before calling this method.
591    fn text_cursor(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, byte: usize);
592
593    /// Draw UI element: check box
594    ///
595    /// The check box is a small visual element, typically a distinctive square
596    /// box with or without a "check" selection mark.
597    ///
598    /// The theme may animate transitions. To achieve this, `last_change` should be
599    /// the time of the last state change caused by the user, or none when the
600    /// last state change was programmatic.
601    fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>);
602
603    /// Draw UI element: radio button
604    ///
605    /// The radio box is a small visual element, typically a disinctive
606    /// circular box with or without a "radio" selection mark.
607    ///
608    /// The theme may animate transitions. To achieve this, `last_change` should be
609    /// the time of the last state change caused by the user, or none when the
610    /// last state change was programmatic.
611    fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>);
612
613    /// Draw UI element: mark
614    fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle);
615
616    /// Draw UI element: scroll bar
617    ///
618    /// -   `id`: [`Id`] of the bar
619    /// -   `grip_id`: [`Id`] of the grip
620    /// -   `rect`: area of whole widget (slider track)
621    /// -   `grip_rect`: area of slider grip
622    /// -   `dir`: direction of bar
623    fn scroll_bar(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction);
624
625    /// Draw UI element: slider
626    ///
627    /// -   `id`: [`Id`] of the bar
628    /// -   `grip_id`: [`Id`] of the grip
629    /// -   `rect`: area of whole widget (slider track)
630    /// -   `grip_rect`: area of slider grip
631    /// -   `dir`: direction of slider (currently only LTR or TTB)
632    fn slider(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction);
633
634    /// Draw UI element: progress bar
635    ///
636    /// -   `id`: [`Id`] of the bar
637    /// -   `rect`: area of whole widget
638    /// -   `dir`: direction of progress bar
639    /// -   `value`: progress value, between 0.0 and 1.0
640    fn progress_bar(&mut self, id: &Id, rect: Rect, dir: Direction, value: f32);
641
642    /// Draw an image
643    fn image(&mut self, id: ImageId, rect: Rect);
644}
645
646#[cfg(test)]
647mod test {
648    use super::*;
649
650    fn _draw_ext(mut draw: DrawCx) {
651        // We can't call this method without constructing an actual ThemeDraw.
652        // But we don't need to: we just want to test that methods are callable.
653
654        let _scale = draw.size_cx().scale_factor();
655
656        let text = crate::theme::Text::new("sample", TextClass::Label, false);
657        draw.text_with_selection(Coord::ZERO, Rect::ZERO, &text, 0..6)
658    }
659}