Skip to main content

crabshell_ui/
view.rs

1//! A read-only view of a laid-out window, for drawing it.
2
3use std::cell::Ref;
4use std::path::Path;
5
6use cosmic_text::{Buffer, LayoutRun};
7use taffy::TraversePartialTree;
8
9use crate::geometry::{Affine, Point, Rect, Size};
10use crate::screencopy::CaptureFrame;
11use crate::style::{Color, Style};
12use crate::text::TextNode;
13use crate::tree::{Node, NodeId, NodeKind, Tree, centered, icon_rect};
14use crate::widgets::{Fit, IconSource};
15
16/// The color text uses when no ancestor sets one.
17const DEFAULT_TEXT_COLOR: Color = Color::rgb(0, 0, 0);
18
19/// A window's laid-out tree, borrowed for reading. Get one from
20/// [`Window::view`](crate::Window::view).
21pub struct WindowView<'w> {
22  tree: Ref<'w, Tree>,
23}
24
25impl<'w> WindowView<'w> {
26  pub(crate) fn new(tree: Ref<'w, Tree>) -> WindowView<'w> {
27    WindowView { tree }
28  }
29
30  /// The window's size in logical pixels.
31  pub fn size(&self) -> Size {
32    self.tree.size
33  }
34
35  /// The window's root node, which has the window's style and its child.
36  pub fn root(&self) -> NodeView<'_> {
37    NodeView::new(&self.tree, self.tree.root)
38      .expect("invariant: the root node exists while the tree does")
39  }
40}
41
42/// One node of a laid-out window: a widget, or the window itself at the
43/// root. Draw it, then its children in order, so later ones are on top.
44#[derive(Clone, Copy)]
45pub struct NodeView<'a> {
46  tree: &'a Tree,
47  id: NodeId,
48  node: &'a Node,
49}
50
51impl<'a> NodeView<'a> {
52  fn new(tree: &'a Tree, id: NodeId) -> Option<NodeView<'a>> {
53    let node = tree.node(id)?;
54    Some(NodeView { tree, id, node })
55  }
56
57  /// The widget type, such as `"Row"`, or `"PanelWindow"` at the root.
58  pub fn name(&self) -> &'static str {
59    self.node.name
60  }
61
62  /// The widget path, such as `PanelWindow[DP-1]/Row/Button[2]`.
63  pub fn path(&self) -> &'a str {
64    &self.node.path
65  }
66
67  /// The rectangle from the last layout, in window coordinates. Empty
68  /// before the first layout.
69  pub fn rect(&self) -> Rect {
70    self.node.rect.unwrap_or_default()
71  }
72
73  /// The area the node and everything inside it painted at the last
74  /// layout: its rectangle, text and icons that overflow it, and its
75  /// descendants' bounds, moved by its [`transform`](Self::transform) into
76  /// its parent's coordinates. Nothing outside it needs drawing for this
77  /// node.
78  pub fn bounds(&self) -> Rect {
79    self.node.bounds
80  }
81
82  /// [`bounds`](Self::bounds) before the node's own transform, in its own
83  /// coordinates: what a layer drawn for the transform must hold. The
84  /// same as `bounds` when it has none.
85  pub fn inner_bounds(&self) -> Rect {
86    self.node.inner_bounds
87  }
88
89  /// How the node's style moves it and everything inside it where it is
90  /// drawn, from its own coordinates, which its rectangle and its
91  /// children's are in, into its parent's. `None` when it doesn't. The
92  /// whole subtree is drawn as one layer through it.
93  pub fn transform(&self) -> Option<Affine> {
94    self.node.transform
95  }
96
97  /// The style in effect, with a button's hover and pressed styles laid
98  /// over its own. Inherited fields aren't filled in;
99  /// [`TextView::color`] has the color text is drawn in.
100  pub fn style(&self) -> Style {
101    self.node.shown_style()
102  }
103
104  /// The children, in drawing order.
105  pub fn children(&self) -> impl Iterator<Item = NodeView<'a>> + 'a {
106    let tree = self.tree;
107    tree
108      .taffy
109      .child_ids(self.id)
110      .filter_map(move |id| NodeView::new(tree, id))
111  }
112
113  /// Whether the node cuts its children to the inside of its border: a
114  /// clipping style or a scroll view.
115  pub fn clips(&self) -> bool {
116    self.node.clips()
117  }
118
119  /// What the node draws besides its background and border.
120  pub fn content(&self) -> Content<'a> {
121    match &self.node.kind {
122      NodeKind::Text(text) => Content::Text(TextView {
123        text,
124        color: self.inherited_color(),
125      }),
126      NodeKind::Icon(icon) => match &icon.source {
127        Some(source) => Content::Icon(IconView {
128          source,
129          rect: icon_rect(self.node.content, icon.size),
130        }),
131        None => Content::None,
132      },
133      NodeKind::Input(input) => {
134        let color = self.inherited_color();
135        let color = if input.showing_placeholder() {
136          Color::rgba(color.r, color.g, color.b, color.a / 2)
137        } else {
138          color
139        };
140        Content::Input(InputView {
141          text: TextView {
142            text: &input.text,
143            color,
144          },
145          // An input method may ask for no caret while it composes.
146          caret: (input.focused
147            && !matches!(&input.preedit, Some(preedit) if preedit.cursor.is_none()))
148          .then_some(input.caret),
149          selection: if input.focused { &input.selection } else { &[] },
150          area: input.area,
151          underline: &input.underline,
152        })
153      }
154      NodeKind::Image(image) => match &image.source {
155        Some(path) => Content::Image(ImageView {
156          path,
157          rect: centered(self.node.content, image.width, image.height),
158          fit: image.fit,
159        }),
160        None => Content::None,
161      },
162      NodeKind::Capture(capture) => match &capture.frame {
163        Some(frame) => Content::Capture(CaptureView {
164          id: capture.id,
165          frame,
166          rect: self.node.content,
167          fit: capture.fit,
168        }),
169        None => Content::None,
170      },
171      _ => Content::None,
172    }
173  }
174
175  /// The color of the nearest node, starting with this one, whose style
176  /// sets one.
177  fn inherited_color(&self) -> Color {
178    let mut id = Some(self.id);
179    while let Some(current) = id {
180      if let Some(color) = self
181        .tree
182        .node(current)
183        .and_then(|node| node.shown_style().color)
184      {
185        return color;
186      }
187      id = self.tree.taffy.parent(current);
188    }
189    DEFAULT_TEXT_COLOR
190  }
191}
192
193/// What a node draws besides its background and border.
194pub enum Content<'a> {
195  /// Nothing.
196  None,
197  /// Text.
198  Text(TextView<'a>),
199  /// An icon.
200  Icon(IconView<'a>),
201  /// An image.
202  Image(ImageView<'a>),
203  /// A screencopy view's frame.
204  Capture(CaptureView<'a>),
205  /// A text field.
206  Input(InputView<'a>),
207}
208
209/// A text field, ready to draw: the selection behind the text, then the
210/// text, then the caret.
211pub struct InputView<'a> {
212  /// What the field shows; a placeholder comes in half its color.
213  pub text: TextView<'a>,
214  /// The caret, while the field has the keyboard.
215  pub caret: Option<Rect>,
216  /// The selection, while the field has the keyboard: one span, or more
217  /// where right-to-left and left-to-right text mix.
218  pub selection: &'a [Rect],
219  /// The content box everything is cut to.
220  pub area: Rect,
221  /// The line under text an input method is composing.
222  pub underline: &'a [Rect],
223}
224
225/// Laid-out text, ready to draw. Lines can stick out of the node's
226/// rectangle when it is too small for them; draw them unclipped, as CSS
227/// does. The window's damage covers them.
228pub struct TextView<'a> {
229  text: &'a TextNode,
230  color: Color,
231}
232
233impl<'a> TextView<'a> {
234  /// The text itself.
235  pub fn content(&self) -> &'a str {
236    &self.text.content
237  }
238
239  /// The cosmic-text buffer layout measured, for drawing glyphs from.
240  pub fn buffer(&self) -> &'a Buffer {
241    &self.text.buffer
242  }
243
244  /// The lines to draw: the buffer's lines, at most `max_lines`. Glyph
245  /// positions are relative to [`origin`](Self::origin).
246  pub fn runs(&self) -> impl Iterator<Item = LayoutRun<'a>> + 'a {
247    self.text.runs()
248  }
249
250  /// The top-left corner of the first line, in window coordinates.
251  pub fn origin(&self) -> Point {
252    self.text.origin
253  }
254
255  /// The color to draw in, inherited if the text's own style doesn't set
256  /// one.
257  pub fn color(&self) -> Color {
258    self.color
259  }
260}
261
262/// An icon, ready to draw.
263pub struct IconView<'a> {
264  /// What to show.
265  pub source: &'a IconSource,
266  /// Where: a square of the icon's size, centered in the node.
267  pub rect: Rect,
268}
269
270/// An image, ready to draw.
271pub struct ImageView<'a> {
272  /// The file to show.
273  pub path: &'a Path,
274  /// Where: a box of the image's size, centered in the node.
275  pub rect: Rect,
276  /// How the picture fills the box.
277  pub fit: Fit,
278}
279
280/// A screencopy view's frame, ready to draw.
281pub struct CaptureView<'a> {
282  /// Names the view, so a cache can keep its frame scaled to its box.
283  pub id: u64,
284  /// The frame last captured.
285  pub frame: &'a CaptureFrame,
286  /// Where: the view's content box.
287  pub rect: Rect,
288  /// How the frame fills the box.
289  pub fit: Fit,
290}