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}