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;
4
5use cosmic_text::{Buffer, LayoutRun};
6use taffy::TraversePartialTree;
7
8use crate::geometry::{Point, Rect, Size};
9use crate::style::{Color, Style};
10use crate::text::TextNode;
11use crate::tree::{Node, NodeId, NodeKind, Tree, icon_rect};
12use crate::widgets::IconSource;
13
14/// The color text uses when no ancestor sets one.
15const DEFAULT_TEXT_COLOR: Color = Color::rgb(0, 0, 0);
16
17/// A window's laid-out tree, borrowed for reading. Get one from
18/// [`Window::view`](crate::Window::view).
19pub struct WindowView<'w> {
20  tree: Ref<'w, Tree>,
21}
22
23impl<'w> WindowView<'w> {
24  pub(crate) fn new(tree: Ref<'w, Tree>) -> WindowView<'w> {
25    WindowView { tree }
26  }
27
28  /// The window's size in logical pixels.
29  pub fn size(&self) -> Size {
30    self.tree.size
31  }
32
33  /// The window's root node, which has the window's style and its child.
34  pub fn root(&self) -> NodeView<'_> {
35    NodeView::new(&self.tree, self.tree.root)
36      .expect("invariant: the root node exists while the tree does")
37  }
38}
39
40/// One node of a laid-out window: a widget, or the window itself at the
41/// root. Draw it, then its children in order, so later ones are on top.
42#[derive(Clone, Copy)]
43pub struct NodeView<'a> {
44  tree: &'a Tree,
45  id: NodeId,
46  node: &'a Node,
47}
48
49impl<'a> NodeView<'a> {
50  fn new(tree: &'a Tree, id: NodeId) -> Option<NodeView<'a>> {
51    let node = tree.node(id)?;
52    Some(NodeView { tree, id, node })
53  }
54
55  /// The widget type, such as `"Row"`, or `"PanelWindow"` at the root.
56  pub fn name(&self) -> &'static str {
57    self.node.name
58  }
59
60  /// The widget path, such as `PanelWindow[DP-1]/Row/Button[2]`.
61  pub fn path(&self) -> &'a str {
62    &self.node.path
63  }
64
65  /// The rectangle from the last layout, in window coordinates. Empty
66  /// before the first layout.
67  pub fn rect(&self) -> Rect {
68    self.node.rect.unwrap_or_default()
69  }
70
71  /// The area the node and everything inside it painted at the last
72  /// layout: its rectangle, text and icons that overflow it, and its
73  /// descendants' bounds. Nothing outside it needs drawing for this node.
74  pub fn bounds(&self) -> Rect {
75    self.node.bounds
76  }
77
78  /// The style in effect, with a button's hover and pressed styles laid
79  /// over its own. Inherited fields aren't filled in;
80  /// [`TextView::color`] has the color text is drawn in.
81  pub fn style(&self) -> Style {
82    self.node.effective_style()
83  }
84
85  /// The children, in drawing order.
86  pub fn children(&self) -> impl Iterator<Item = NodeView<'a>> + 'a {
87    let tree = self.tree;
88    tree
89      .taffy
90      .child_ids(self.id)
91      .filter_map(move |id| NodeView::new(tree, id))
92  }
93
94  /// What the node draws besides its background and border.
95  pub fn content(&self) -> Content<'a> {
96    match &self.node.kind {
97      NodeKind::Text(text) => Content::Text(TextView {
98        text,
99        color: self.inherited_color(),
100      }),
101      NodeKind::Icon(icon) => match &icon.source {
102        Some(source) => Content::Icon(IconView {
103          source,
104          rect: icon_rect(self.node.content, icon.size),
105        }),
106        None => Content::None,
107      },
108      _ => Content::None,
109    }
110  }
111
112  /// The color of the nearest node, starting with this one, whose style
113  /// sets one.
114  fn inherited_color(&self) -> Color {
115    let mut id = Some(self.id);
116    while let Some(current) = id {
117      if let Some(color) = self
118        .tree
119        .node(current)
120        .and_then(|node| node.effective_style().color)
121      {
122        return color;
123      }
124      id = self.tree.taffy.parent(current);
125    }
126    DEFAULT_TEXT_COLOR
127  }
128}
129
130/// What a node draws besides its background and border.
131pub enum Content<'a> {
132  /// Nothing.
133  None,
134  /// Text.
135  Text(TextView<'a>),
136  /// An icon.
137  Icon(IconView<'a>),
138}
139
140/// Laid-out text, ready to draw. Lines can stick out of the node's
141/// rectangle when it is too small for them; draw them unclipped, as CSS
142/// does. The window's damage covers them.
143pub struct TextView<'a> {
144  text: &'a TextNode,
145  color: Color,
146}
147
148impl<'a> TextView<'a> {
149  /// The text itself.
150  pub fn content(&self) -> &'a str {
151    &self.text.content
152  }
153
154  /// The cosmic-text buffer layout measured, for drawing glyphs from.
155  pub fn buffer(&self) -> &'a Buffer {
156    &self.text.buffer
157  }
158
159  /// The lines to draw: the buffer's lines, at most `max_lines`. Glyph
160  /// positions are relative to [`origin`](Self::origin).
161  pub fn runs(&self) -> impl Iterator<Item = LayoutRun<'a>> + 'a {
162    self.text.runs()
163  }
164
165  /// The top-left corner of the first line, in window coordinates.
166  pub fn origin(&self) -> Point {
167    self.text.origin
168  }
169
170  /// The color to draw in, inherited if the text's own style doesn't set
171  /// one.
172  pub fn color(&self) -> Color {
173    self.color
174  }
175}
176
177/// An icon, ready to draw.
178pub struct IconView<'a> {
179  /// What to show.
180  pub source: &'a IconSource,
181  /// Where: a square of the icon's size, centered in the node.
182  pub rect: Rect,
183}