Skip to main content

crabshell_ui/
window.rs

1//! [`PanelWindow`], the description of a bar, and [`Window`], the live tree
2//! built from it that the platform lays out, draws and sends input to.
3
4use std::cell::RefCell;
5use std::ops::BitOr;
6use std::rc::Rc;
7
8use crabshell_reactive::{Owner, untrack};
9
10use crate::cx::{Anchor, Cx};
11use crate::geometry::{Rect, Size};
12use crate::prop::{IntoProp, Prop};
13use crate::style::Style;
14use crate::text::TextSystem;
15use crate::tree::{Tree, borrow};
16use crate::view::WindowView;
17use crate::widgets::Widget;
18
19/// A monitor, as the compositor names it.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Output {
22  name: String,
23}
24
25impl Output {
26  /// An output called `name`, such as `"DP-1"`. The platform creates
27  /// these from the compositor's announcements.
28  pub fn new(name: impl Into<String>) -> Output {
29    Output { name: name.into() }
30  }
31
32  /// The output's name, such as `"DP-1"`. It is an owned `String` so a
33  /// `move` closure can keep it.
34  pub fn name(&self) -> String {
35    self.name.clone()
36  }
37}
38
39/// An edge of the output.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum Edge {
42  /// The top edge.
43  Top,
44  /// The bottom edge.
45  Bottom,
46  /// The left edge.
47  Left,
48  /// The right edge.
49  Right,
50}
51
52impl Edge {
53  const fn bit(self) -> u8 {
54    match self {
55      Edge::Top => 1,
56      Edge::Bottom => 2,
57      Edge::Left => 4,
58      Edge::Right => 8,
59    }
60  }
61}
62
63/// The edges a window is anchored to. Combine edges with `|`:
64/// `Edge::Top | Edge::Left | Edge::Right`.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
66pub struct Anchors(u8);
67
68impl Anchors {
69  /// Anchored to no edge: the compositor centers the window.
70  pub const NONE: Anchors = Anchors(0);
71
72  /// Whether `edge` is one of the anchors.
73  pub const fn contains(self, edge: Edge) -> bool {
74    self.0 & edge.bit() != 0
75  }
76
77  /// The edge the window sits against and reserves space along: the only
78  /// anchor, or the one anchor whose two neighbors are also anchors (`Top`
79  /// for `Top | Left | Right`). `None` for a corner or no clear edge.
80  pub fn edge(self) -> Option<Edge> {
81    let [top, bottom, left, right] =
82      [Edge::Top, Edge::Bottom, Edge::Left, Edge::Right].map(|edge| self.contains(edge));
83    match (top, bottom, left, right) {
84      (true, false, false, false) | (true, false, true, true) => Some(Edge::Top),
85      (false, true, false, false) | (false, true, true, true) => Some(Edge::Bottom),
86      (false, false, true, false) | (true, true, true, false) => Some(Edge::Left),
87      (false, false, false, true) | (true, true, false, true) => Some(Edge::Right),
88      _ => None,
89    }
90  }
91}
92
93impl From<Edge> for Anchors {
94  fn from(edge: Edge) -> Anchors {
95    Anchors(edge.bit())
96  }
97}
98
99impl BitOr for Edge {
100  type Output = Anchors;
101
102  fn bitor(self, other: Edge) -> Anchors {
103    Anchors(self.bit() | other.bit())
104  }
105}
106
107impl BitOr<Edge> for Anchors {
108  type Output = Anchors;
109
110  fn bitor(self, edge: Edge) -> Anchors {
111    Anchors(self.0 | edge.bit())
112  }
113}
114
115/// The layer-shell layer a window is drawn in, from bottom to top.
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
117pub enum Layer {
118  /// Below everything, with the wallpaper.
119  Background,
120  /// Below windows.
121  Bottom,
122  /// Above windows. The default.
123  #[default]
124  Top,
125  /// Above everything, fullscreen windows included.
126  Overlay,
127}
128
129/// A bar: a layer-shell surface on one output with one child.
130///
131/// This is a description; [`Window::new`] builds it. It takes no keyboard
132/// focus. Every property takes a value or a closure.
133///
134/// ```
135/// use crabshell_ui::{Edge, Output, PanelWindow, Text};
136///
137/// let output = Output::new("DP-1");
138/// let bar = PanelWindow::new(&output)
139///     .anchors(Edge::Top | Edge::Left | Edge::Right)
140///     .height(30)
141///     .child(Text::new("hello"));
142/// ```
143pub struct PanelWindow {
144  pub(crate) output: Output,
145  pub(crate) anchors: Option<Prop<Anchors>>,
146  pub(crate) width: Option<Prop<u32>>,
147  pub(crate) height: Option<Prop<u32>>,
148  pub(crate) exclusive_zone: Option<Prop<i32>>,
149  pub(crate) layer: Option<Prop<Layer>>,
150  pub(crate) style: Option<Prop<Style>>,
151  pub(crate) child: Option<Box<dyn Widget>>,
152}
153
154impl PanelWindow {
155  /// A window on `output`, anchored to no edge, in the top layer.
156  pub fn new(output: &Output) -> PanelWindow {
157    PanelWindow {
158      output: output.clone(),
159      anchors: None,
160      width: None,
161      height: None,
162      exclusive_zone: None,
163      layer: None,
164      style: None,
165      child: None,
166    }
167  }
168
169  /// Sets the edges the window is anchored to.
170  pub fn anchors(mut self, anchors: impl IntoProp<Anchors>) -> PanelWindow {
171    self.anchors = Some(anchors.into_prop());
172    self
173  }
174
175  /// Sets the width in logical pixels. 0, the default, lets the compositor
176  /// choose, which it does when the window is anchored left and right.
177  pub fn width(mut self, width: impl IntoProp<u32>) -> PanelWindow {
178    self.width = Some(width.into_prop());
179    self
180  }
181
182  /// Sets the height in logical pixels. 0, the default, lets the
183  /// compositor choose, which it does when the window is anchored top and
184  /// bottom.
185  pub fn height(mut self, height: impl IntoProp<u32>) -> PanelWindow {
186    self.height = Some(height.into_prop());
187    self
188  }
189
190  /// Sets how much space, in logical pixels, other windows keep clear
191  /// along the anchored edge. The default is the window's height for a top
192  /// or bottom bar and its width for a left or right one.
193  pub fn exclusive_zone(mut self, zone: impl IntoProp<i32>) -> PanelWindow {
194    self.exclusive_zone = Some(zone.into_prop());
195    self
196  }
197
198  /// Sets the layer.
199  pub fn layer(mut self, layer: impl IntoProp<Layer>) -> PanelWindow {
200    self.layer = Some(layer.into_prop());
201    self
202  }
203
204  /// Sets the window's style. Its color and font are inherited by every
205  /// [`Text`](crate::Text) in the window that doesn't set its own.
206  pub fn style(mut self, style: impl IntoProp<Style>) -> PanelWindow {
207    self.style = Some(style.into_prop());
208    self
209  }
210
211  /// Sets the window's one child, which fills the window.
212  pub fn child(mut self, child: impl Widget) -> PanelWindow {
213    self.child = Some(Box::new(child));
214    self
215  }
216}
217
218/// What the platform needs to set up the window's layer surface.
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220pub struct SurfaceConfig {
221  /// The anchored edges.
222  pub anchors: Anchors,
223  /// The requested width; 0 lets the compositor choose.
224  pub width: u32,
225  /// The requested height; 0 lets the compositor choose.
226  pub height: u32,
227  /// The exclusive zone, with the default worked out.
228  pub exclusive_zone: i32,
229  /// The layer.
230  pub layer: Layer,
231}
232
233/// A live window: the tree of nodes built from a [`PanelWindow`].
234///
235/// The platform owns one per layer surface. Each event-loop iteration it
236/// runs the reactive flush, then calls [`layout`](Self::layout) if
237/// [`needs_layout`](Self::needs_layout), then draws
238/// [`take_damage`](Self::take_damage) from [`view`](Self::view). When
239/// nothing changed, there is nothing to lay out or draw.
240///
241/// Dropping the window disposes its owner, which frees every node,
242/// binding and handler in it.
243pub struct Window {
244  pub(crate) tree: Rc<RefCell<Tree>>,
245  pub(crate) owner: Owner,
246}
247
248impl Window {
249  /// Builds `panel` under a new child owner of `cx`.
250  ///
251  /// Properties set to values take effect now. Bindings are effects, so
252  /// they first run at the next [`Runtime::flush`]; run it before the
253  /// first layout.
254  ///
255  /// [`Runtime::flush`]: crabshell_reactive::Runtime::flush
256  pub fn new(cx: &Cx, mut panel: PanelWindow) -> Window {
257    // Describe the whole tree first: that runs the user's `into_element`
258    // code, which must not see the tree borrowed or subscribe whatever
259    // effect is building this window.
260    let child = untrack(|| panel.child.take().map(|child| child.into_element()));
261    let path: Rc<str> = format!("PanelWindow[{}]", panel.output.name).into();
262    let cx = cx
263      .child()
264      .named(path.to_string())
265      .at(Rc::new(Anchor::default()));
266    let owner = cx.owner();
267    let output = panel.output.clone();
268    let tree =
269      Rc::new_cyclic(|this| RefCell::new(Tree::new(this.clone(), "PanelWindow", output, cx, path)));
270    borrow(&tree).mount_panel(panel, child);
271    Window { tree, owner }
272  }
273
274  /// The output the window is on.
275  pub fn output(&self) -> Output {
276    self.tree.borrow().output.clone()
277  }
278
279  /// The layer-surface settings. Read them only after the first
280  /// [`Runtime::flush`]: settings given as closures are bindings, which
281  /// first run then, and until they do these are the defaults.
282  ///
283  /// [`Runtime::flush`]: crabshell_reactive::Runtime::flush
284  pub fn surface(&self) -> SurfaceConfig {
285    let tree = self.tree.borrow();
286    let surface = &tree.surface;
287    let exclusive_zone = surface.exclusive_zone.unwrap_or_else(|| {
288      let thickness = match surface.anchors.edge() {
289        Some(Edge::Top | Edge::Bottom) => surface.height,
290        Some(Edge::Left | Edge::Right) => surface.width,
291        None => 0,
292      };
293      i32::try_from(thickness).unwrap_or(i32::MAX)
294    });
295    SurfaceConfig {
296      anchors: surface.anchors,
297      width: surface.width,
298      height: surface.height,
299      exclusive_zone,
300      layer: surface.layer,
301    }
302  }
303
304  /// Whether the settings in [`surface`](Self::surface) changed since the
305  /// last call. The first call returns `true`, even before the first flush,
306  /// when [`surface`](Self::surface) still holds the defaults.
307  pub fn take_surface_changed(&self) -> bool {
308    std::mem::take(&mut borrow(&self.tree).surface.changed)
309  }
310
311  /// The window's size in logical pixels: the size the compositor gave a
312  /// panel, or the size a popup's content wants, from its last layout.
313  pub fn size(&self) -> Size {
314    self.tree.borrow().size
315  }
316
317  /// Sets the size the compositor gave the window, from a configure
318  /// event. A new size needs a layout.
319  pub fn set_size(&self, size: Size) {
320    let mut tree = borrow(&self.tree);
321    if tree.size != size {
322      tree.size = size;
323      tree.needs_layout = true;
324    }
325  }
326
327  /// Whether something changed that needs [`layout`](Self::layout).
328  pub fn needs_layout(&self) -> bool {
329    self.tree.borrow().needs_layout
330  }
331
332  /// Lays the window out at its size, measuring text with `text`, and
333  /// records the areas that moved or changed as damage.
334  pub fn layout(&self, text: &mut TextSystem) {
335    borrow(&self.tree).layout(text);
336  }
337
338  /// Whether any area needs repainting.
339  pub fn has_damage(&self) -> bool {
340    !self.tree.borrow().damage.is_empty()
341  }
342
343  /// Takes the areas to repaint since the last call, in logical pixels.
344  /// They may overlap.
345  pub fn take_damage(&self) -> Vec<Rect> {
346    std::mem::take(&mut borrow(&self.tree).damage)
347  }
348
349  /// A read-only view of the laid-out tree, for drawing. Release it before
350  /// the next flush or pointer event.
351  pub fn view(&self) -> WindowView<'_> {
352    WindowView::new(self.tree.borrow())
353  }
354}
355
356impl Drop for Window {
357  fn drop(&mut self) {
358    self.owner.dispose();
359  }
360}