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, Signal, untrack};
9
10use crate::cx::{Anchor, Cx};
11use crate::geometry::{Rect, Size};
12use crate::input::Handler;
13use crate::keyboard::{KeyEvent, KeyHandler};
14use crate::prop::{IntoProp, Prop};
15use crate::region::Region;
16use crate::style::Style;
17use crate::text::TextSystem;
18use crate::tree::{Tree, borrow};
19use crate::view::WindowView;
20use crate::widgets::Widget;
21
22/// A monitor, as the compositor names it.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Output {
25  name: String,
26}
27
28impl Output {
29  /// An output called `name`, such as `"DP-1"`. The platform creates
30  /// these from the compositor's announcements.
31  pub fn new(name: impl Into<String>) -> Output {
32    Output { name: name.into() }
33  }
34
35  /// The output's name, such as `"DP-1"`. It is an owned `String` so a
36  /// `move` closure can keep it.
37  pub fn name(&self) -> String {
38    self.name.clone()
39  }
40}
41
42/// An edge of the output.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Edge {
45  /// The top edge.
46  Top,
47  /// The bottom edge.
48  Bottom,
49  /// The left edge.
50  Left,
51  /// The right edge.
52  Right,
53}
54
55impl Edge {
56  const fn bit(self) -> u8 {
57    match self {
58      Edge::Top => 1,
59      Edge::Bottom => 2,
60      Edge::Left => 4,
61      Edge::Right => 8,
62    }
63  }
64}
65
66/// The edges a window is anchored to. Combine edges with `|`:
67/// `Edge::Top | Edge::Left | Edge::Right`.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct Anchors(u8);
70
71impl Anchors {
72  /// Anchored to no edge: the compositor centers the window.
73  pub const NONE: Anchors = Anchors(0);
74
75  /// Whether `edge` is one of the anchors.
76  pub const fn contains(self, edge: Edge) -> bool {
77    self.0 & edge.bit() != 0
78  }
79
80  /// The edge the window sits against and reserves space along: the only
81  /// anchor, or the one anchor whose two neighbors are also anchors (`Top`
82  /// for `Top | Left | Right`). `None` for a corner or no clear edge.
83  pub fn edge(self) -> Option<Edge> {
84    let [top, bottom, left, right] =
85      [Edge::Top, Edge::Bottom, Edge::Left, Edge::Right].map(|edge| self.contains(edge));
86    match (top, bottom, left, right) {
87      (true, false, false, false) | (true, false, true, true) => Some(Edge::Top),
88      (false, true, false, false) | (false, true, true, true) => Some(Edge::Bottom),
89      (false, false, true, false) | (true, true, true, false) => Some(Edge::Left),
90      (false, false, false, true) | (true, true, false, true) => Some(Edge::Right),
91      _ => None,
92    }
93  }
94}
95
96impl From<Edge> for Anchors {
97  fn from(edge: Edge) -> Anchors {
98    Anchors(edge.bit())
99  }
100}
101
102impl BitOr for Edge {
103  type Output = Anchors;
104
105  fn bitor(self, other: Edge) -> Anchors {
106    Anchors(self.bit() | other.bit())
107  }
108}
109
110impl BitOr<Edge> for Anchors {
111  type Output = Anchors;
112
113  fn bitor(self, edge: Edge) -> Anchors {
114    Anchors(self.0 | edge.bit())
115  }
116}
117
118/// The layer-shell layer a window is drawn in, from bottom to top.
119#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
120pub enum Layer {
121  /// Below everything, with the wallpaper.
122  Background,
123  /// Below windows.
124  Bottom,
125  /// Above windows. The default.
126  #[default]
127  Top,
128  /// Above everything, fullscreen windows included.
129  Overlay,
130}
131
132/// Whether and when a window takes the keyboard.
133#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
134pub enum KeyboardFocus {
135  /// Never. The default.
136  #[default]
137  None,
138  /// Always, while the window is in the top or overlay layer: nothing
139  /// else gets keys. For launchers and other windows that hold the
140  /// keyboard until they close.
141  Exclusive,
142  /// When the user clicks it, like an ordinary window. A compositor older
143  /// than layer-shell version 4 can't do this, and the window then takes
144  /// no keyboard focus.
145  OnDemand,
146}
147
148/// Space between a window and the output edges it is anchored to, in
149/// logical pixels. Edges the window isn't anchored to ignore theirs.
150#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
151pub struct Margins {
152  /// From the top edge.
153  pub top: i32,
154  /// From the right edge.
155  pub right: i32,
156  /// From the bottom edge.
157  pub bottom: i32,
158  /// From the left edge.
159  pub left: i32,
160}
161
162impl Margins {
163  /// The same margin on every edge.
164  pub const fn all(margin: i32) -> Margins {
165    Margins::new(margin, margin, margin, margin)
166  }
167
168  /// Each edge's margin, clockwise from the top, as in CSS.
169  pub const fn new(top: i32, right: i32, bottom: i32, left: i32) -> Margins {
170    Margins {
171      top,
172      right,
173      bottom,
174      left,
175    }
176  }
177
178  /// The margin on `edge`.
179  pub const fn on(self, edge: Edge) -> i32 {
180    match edge {
181      Edge::Top => self.top,
182      Edge::Right => self.right,
183      Edge::Bottom => self.bottom,
184      Edge::Left => self.left,
185    }
186  }
187}
188
189/// A bar: a layer-shell surface on one output with one child.
190///
191/// This is a description; [`Window::new`] builds it. By default it takes
192/// no keyboard focus. Every property takes a value or a closure.
193///
194/// ```
195/// use crabshell_ui::{Edge, Output, PanelWindow, Text};
196///
197/// let output = Output::new("DP-1");
198/// let bar = PanelWindow::new(&output)
199///     .anchors(Edge::Top | Edge::Left | Edge::Right)
200///     .height(30)
201///     .child(Text::new("hello"));
202/// ```
203pub struct PanelWindow {
204  pub(crate) output: Output,
205  pub(crate) anchors: Option<Prop<Anchors>>,
206  pub(crate) width: Option<Prop<u32>>,
207  pub(crate) height: Option<Prop<u32>>,
208  pub(crate) exclusive_zone: Option<Prop<i32>>,
209  pub(crate) layer: Option<Prop<Layer>>,
210  pub(crate) margins: Option<Prop<Margins>>,
211  pub(crate) keyboard_focus: Option<Prop<KeyboardFocus>>,
212  pub(crate) namespace: Option<Prop<String>>,
213  pub(crate) hints: Hints,
214  pub(crate) on_key: Option<KeyHandler>,
215  pub(crate) style: Option<Prop<Style>>,
216  pub(crate) child: Option<Box<dyn Widget>>,
217}
218
219impl PanelWindow {
220  /// A window on `output`, anchored to no edge, in the top layer.
221  pub fn new(output: &Output) -> PanelWindow {
222    PanelWindow {
223      output: output.clone(),
224      anchors: None,
225      width: None,
226      height: None,
227      exclusive_zone: None,
228      layer: None,
229      margins: None,
230      keyboard_focus: None,
231      namespace: None,
232      hints: Hints::default(),
233      on_key: None,
234      style: None,
235      child: None,
236    }
237  }
238
239  /// Sets the edges the window is anchored to.
240  pub fn anchors(mut self, anchors: impl IntoProp<Anchors>) -> PanelWindow {
241    self.anchors = Some(anchors.into_prop());
242    self
243  }
244
245  /// Sets the width in logical pixels. 0, the default, lets the compositor
246  /// choose, which it does when the window is anchored left and right.
247  pub fn width(mut self, width: impl IntoProp<u32>) -> PanelWindow {
248    self.width = Some(width.into_prop());
249    self
250  }
251
252  /// Sets the height in logical pixels. 0, the default, lets the
253  /// compositor choose, which it does when the window is anchored top and
254  /// bottom.
255  pub fn height(mut self, height: impl IntoProp<u32>) -> PanelWindow {
256    self.height = Some(height.into_prop());
257    self
258  }
259
260  /// Sets how much space, in logical pixels, other windows keep clear
261  /// along the anchored edge. The default is the window's height for a top
262  /// or bottom bar and its width for a left or right one. wlroots
263  /// compositors add the margin on that edge to the zone. 0 keeps clear
264  /// of other windows' zones without reserving any; -1 ignores them and
265  /// covers them.
266  pub fn exclusive_zone(mut self, zone: impl IntoProp<i32>) -> PanelWindow {
267    self.exclusive_zone = Some(zone.into_prop());
268    self
269  }
270
271  /// Sets the layer.
272  pub fn layer(mut self, layer: impl IntoProp<Layer>) -> PanelWindow {
273    self.layer = Some(layer.into_prop());
274    self
275  }
276
277  /// Sets the space between the window and the edges it is anchored to.
278  pub fn margins(mut self, margins: impl IntoProp<Margins>) -> PanelWindow {
279    self.margins = Some(margins.into_prop());
280    self
281  }
282
283  /// Sets whether the window takes the keyboard. The default is never.
284  pub fn keyboard_focus(mut self, focus: impl IntoProp<KeyboardFocus>) -> PanelWindow {
285    self.keyboard_focus = Some(focus.into_prop());
286    self
287  }
288
289  /// Sets the layer-shell namespace, which compositors use to tell
290  /// windows apart, as in Sway's `layer_effects`. The default is
291  /// `"crabshell"`, which also replaces a name holding a NUL byte.
292  /// Compositors read it once, so a change after the window is shown
293  /// applies only when it is shown again, as on a new output.
294  pub fn namespace(mut self, namespace: impl IntoProp<String>) -> PanelWindow {
295    self.namespace = Some(namespace.into_prop());
296    self
297  }
298
299  /// Sets the part of the window that takes pointer input; clicks outside
300  /// it reach whatever is below. `None`, the default, is the whole window,
301  /// and [`Region::empty`] lets every click through.
302  pub fn mask(mut self, mask: impl IntoProp<Option<Region>>) -> PanelWindow {
303    self.hints.mask = Some(mask.into_prop());
304    self
305  }
306
307  /// Asks the compositor to blur what is behind this part of the window,
308  /// so a translucent background looks frosted. `None`, the default,
309  /// blurs nothing. It needs `ext-background-effect-v1`; a compositor
310  /// without it shows the window unblurred, and the shell logs that once.
311  ///
312  /// ```
313  /// use crabshell_ui::{Output, PanelWindow, Region};
314  ///
315  /// // The whole of a 30 pixel bar, however wide the output.
316  /// let bar = PanelWindow::new(&Output::new("DP-1"))
317  ///     .height(30)
318  ///     .blur(Some(Region::rect(0, 0, u32::MAX, 30)));
319  /// ```
320  pub fn blur(mut self, region: impl IntoProp<Option<Region>>) -> PanelWindow {
321    self.hints.blur = Some(region.into_prop());
322    self
323  }
324
325  /// Keeps the screen from blanking or locking for idleness while `true`
326  /// and the window is shown, as a video player does. It needs
327  /// `idle-inhibit-unstable-v1`; without it, that is logged once.
328  pub fn inhibit_idle(mut self, inhibit: impl IntoProp<bool>) -> PanelWindow {
329    self.hints.inhibit_idle = Some(inhibit.into_prop());
330    self
331  }
332
333  /// While `true` and the window has the keyboard, the compositor's own
334  /// shortcuts, such as Sway's `bindsym`s, go to the window as keys
335  /// instead, as a remote desktop or a game wants. The compositor may
336  /// keep some, such as one that takes them back. It needs
337  /// `keyboard-shortcuts-inhibit-unstable-v1`; without it, that is logged
338  /// once.
339  pub fn inhibit_shortcuts(mut self, inhibit: impl IntoProp<bool>) -> PanelWindow {
340    self.hints.inhibit_shortcuts = Some(inhibit.into_prop());
341    self
342  }
343
344  /// The compositor writes `true` to `signal` while it honors
345  /// [`inhibit_shortcuts`](Self::inhibit_shortcuts), and `false` once it
346  /// stops, as when the user takes its shortcuts back, or the window
347  /// stops asking or goes. Give each window a signal of its own.
348  pub fn shortcuts_inhibited(mut self, signal: Signal<bool>) -> PanelWindow {
349    self.hints.shortcuts_inhibited = Some(signal);
350    self
351  }
352
353  /// While `signal` is `true`, this window and the shown windows given
354  /// the same signal, with the popups they open, hold the keyboard and
355  /// pointer focus: a click anywhere else ends the grab, and the
356  /// compositor writes `false` to `signal`, as it does when every one of
357  /// them hides. Writing `false` ends the grab, and `true` asks again. It
358  /// needs Hyprland's `hyprland-focus-grab-v1`; without it, that is
359  /// logged once and `signal` is left as it is.
360  pub fn focus_grab(mut self, signal: Signal<bool>) -> PanelWindow {
361    self.hints.focus_grab = Some(signal);
362    self
363  }
364
365  /// Sets the opacity the compositor shows the whole window at, from 0.0,
366  /// unseen, to 1.0, the default. It fades the blur behind the window
367  /// along with its pixels. A style's `opacity` is different: Crabshell
368  /// paints that into the window's pixels, and a blur behind them stays
369  /// as strong. A value outside 0.0 to 1.0 is clamped and NaN reads as
370  /// 1.0, logged once with the window's path. It needs Hyprland's
371  /// `hyprland-surface-v1`, from Hyprland 0.47; without it, that is
372  /// logged once and the window shows as painted.
373  pub fn compositor_opacity(mut self, opacity: impl IntoProp<f32>) -> PanelWindow {
374    self.hints.compositor_opacity = Some(opacity.into_prop());
375    self
376  }
377
378  /// Tells the compositor which part of the window holds anything to
379  /// see, so it can skip drawing the rest, which helps most when the
380  /// window is blurred. It is only a hint, and it must cover every pixel
381  /// that isn't fully transparent: what it leaves out may not show.
382  /// `None`, the default, is the whole window, and so is a region with
383  /// nothing inside the window. It needs version 2 of Hyprland's
384  /// `hyprland-surface-v1`; without it, that is logged once.
385  pub fn visible_mask(mut self, region: impl IntoProp<Option<Region>>) -> PanelWindow {
386    self.hints.visible_mask = Some(region.into_prop());
387    self
388  }
389
390  /// Runs `handler` for each key the window gets that its focused button
391  /// didn't use. It returns `true` when it used the key; otherwise Tab,
392  /// Enter and Space do what they do by default. The window gets keys
393  /// only with a [`keyboard_focus`](Self::keyboard_focus).
394  pub fn on_key(mut self, handler: impl Fn(&Cx, &KeyEvent) -> bool + 'static) -> PanelWindow {
395    self.on_key = Some(Rc::new(Handler::new(handler)));
396    self
397  }
398
399  /// Sets the window's style. Its color and font are inherited by every
400  /// [`Text`](crate::Text) in the window that doesn't set its own.
401  pub fn style(mut self, style: impl IntoProp<Style>) -> PanelWindow {
402    self.style = Some(style.into_prop());
403    self
404  }
405
406  /// Sets the window's one child, which fills the window.
407  pub fn child(mut self, child: impl Widget) -> PanelWindow {
408    self.child = Some(Box::new(child));
409    self
410  }
411}
412
413/// What a window asks of the compositor besides its layer-shell, toplevel
414/// or popup settings. A popup sets only its mask, compositor opacity and
415/// visible mask.
416#[derive(Default)]
417pub(crate) struct Hints {
418  pub(crate) mask: Option<Prop<Option<Region>>>,
419  pub(crate) compositor_opacity: Option<Prop<f32>>,
420  pub(crate) visible_mask: Option<Prop<Option<Region>>>,
421  pub(crate) blur: Option<Prop<Option<Region>>>,
422  pub(crate) inhibit_idle: Option<Prop<bool>>,
423  pub(crate) inhibit_shortcuts: Option<Prop<bool>>,
424  pub(crate) shortcuts_inhibited: Option<Signal<bool>>,
425  pub(crate) focus_grab: Option<Signal<bool>>,
426}
427
428/// What the platform needs to set up the window's layer surface.
429#[derive(Clone, Debug, PartialEq)]
430pub struct SurfaceConfig {
431  /// The anchored edges.
432  pub anchors: Anchors,
433  /// The requested width; 0 lets the compositor choose.
434  pub width: u32,
435  /// The requested height; 0 lets the compositor choose.
436  pub height: u32,
437  /// The exclusive zone, with the default worked out.
438  pub exclusive_zone: i32,
439  /// The layer.
440  pub layer: Layer,
441  /// The margins from the anchored edges.
442  pub margins: Margins,
443  /// Whether the window takes the keyboard.
444  pub keyboard_focus: KeyboardFocus,
445  /// The layer-shell namespace.
446  pub namespace: String,
447  /// The part that takes pointer input; `None` is all of it.
448  pub mask: Option<Region>,
449}
450
451impl Default for SurfaceConfig {
452  /// A layer-shell surface's defaults: no anchors, sizes or zone, in the
453  /// top layer, without the keyboard.
454  fn default() -> SurfaceConfig {
455    SurfaceConfig {
456      anchors: Anchors::NONE,
457      width: 0,
458      height: 0,
459      exclusive_zone: 0,
460      layer: Layer::Top,
461      margins: Margins::default(),
462      keyboard_focus: KeyboardFocus::None,
463      namespace: String::from(DEFAULT_NAMESPACE),
464      mask: None,
465    }
466  }
467}
468
469/// The layer-shell namespace unless a window sets its own.
470pub(crate) const DEFAULT_NAMESPACE: &str = "crabshell";
471
472/// A live window: the tree of nodes built from a [`PanelWindow`].
473///
474/// The platform owns one per layer surface. Each event-loop iteration it
475/// runs the reactive flush, then calls [`layout`](Self::layout) if
476/// [`needs_layout`](Self::needs_layout), then draws
477/// [`take_damage`](Self::take_damage) from [`view`](Self::view). When
478/// nothing changed, there is nothing to lay out or draw.
479///
480/// Dropping the window disposes its owner, which frees every node,
481/// binding and handler in it.
482pub struct Window {
483  pub(crate) tree: Rc<RefCell<Tree>>,
484  pub(crate) owner: Owner,
485}
486
487impl Window {
488  /// Builds `panel` under a new child owner of `cx`.
489  ///
490  /// Properties set to values take effect now. Bindings are effects, so
491  /// they first run at the next [`Runtime::flush`]; run it before the
492  /// first layout.
493  ///
494  /// [`Runtime::flush`]: crabshell_reactive::Runtime::flush
495  pub fn new(cx: &Cx, mut panel: PanelWindow) -> Window {
496    // Describe the whole tree first: that runs the user's `into_element`
497    // code, which must not see the tree borrowed or subscribe whatever
498    // effect is building this window.
499    let child = untrack(|| panel.child.take().map(|child| child.into_element()));
500    let path: Rc<str> = format!("PanelWindow[{}]", panel.output.name).into();
501    let cx = cx
502      .child()
503      .named(path.to_string())
504      .at(Rc::new(Anchor::default()));
505    let owner = cx.owner();
506    let output = panel.output.clone();
507    let tree =
508      Rc::new_cyclic(|this| RefCell::new(Tree::new(this.clone(), "PanelWindow", output, cx, path)));
509    borrow(&tree).mount_panel(panel, child);
510    Window { tree, owner }
511  }
512
513  /// The output the window is on.
514  pub fn output(&self) -> Output {
515    self.tree.borrow().output.clone()
516  }
517
518  /// The layer-surface settings. Read them only after the first
519  /// [`Runtime::flush`]: settings given as closures are bindings, which
520  /// first run then, and until they do these are the defaults.
521  ///
522  /// [`Runtime::flush`]: crabshell_reactive::Runtime::flush
523  pub fn surface(&self) -> SurfaceConfig {
524    let tree = self.tree.borrow();
525    let surface = &tree.surface;
526    let exclusive_zone = surface.exclusive_zone.unwrap_or_else(|| {
527      let thickness = match surface.anchors.edge() {
528        Some(Edge::Top | Edge::Bottom) => surface.height,
529        Some(Edge::Left | Edge::Right) => surface.width,
530        None => 0,
531      };
532      i32::try_from(thickness).unwrap_or(i32::MAX)
533    });
534    SurfaceConfig {
535      anchors: surface.anchors,
536      width: surface.width,
537      height: surface.height,
538      exclusive_zone,
539      layer: surface.layer,
540      margins: surface.margins,
541      keyboard_focus: surface.keyboard_focus,
542      namespace: surface.namespace.clone(),
543      mask: surface.mask.clone(),
544    }
545  }
546
547  /// The region a panel or floating window asks the compositor to blur
548  /// behind, as its `blur` property says.
549  pub fn blur(&self) -> Option<Region> {
550    self.tree.borrow().surface.blur.clone()
551  }
552
553  /// Whether a panel or floating window asks to keep the screen from
554  /// idling, as its `inhibit_idle` property says.
555  pub fn inhibits_idle(&self) -> bool {
556    self.tree.borrow().surface.inhibit_idle
557  }
558
559  /// Whether a panel or floating window asks for the compositor's
560  /// shortcuts while it has the keyboard, as its `inhibit_shortcuts`
561  /// property says.
562  pub fn inhibits_shortcuts(&self) -> bool {
563    self.tree.borrow().surface.inhibit_shortcuts
564  }
565
566  /// The signal a panel or floating window was given with
567  /// `shortcuts_inhibited`, which the platform writes.
568  pub fn shortcuts_inhibited(&self) -> Option<Signal<bool>> {
569    self.tree.borrow().surface.shortcuts_inhibited
570  }
571
572  /// The signal a panel or floating window was given with `focus_grab`:
573  /// windows given the same one share a grab.
574  pub fn focus_grab(&self) -> Option<Signal<bool>> {
575    self.tree.borrow().surface.focus_grab
576  }
577
578  /// The opacity the window asks the compositor to show it at, as its
579  /// `compositor_opacity` property says: always from 0.0 to 1.0.
580  pub fn compositor_opacity(&self) -> f32 {
581    self.tree.borrow().surface.compositor_opacity
582  }
583
584  /// The part of the window it tells the compositor holds anything to
585  /// see, as its `visible_mask` property says; `None` is all of it.
586  pub fn visible_mask(&self) -> Option<Region> {
587    self.tree.borrow().surface.visible_mask.clone()
588  }
589
590  /// Whether the settings in [`surface`](Self::surface) changed since the
591  /// last call. The first call returns `true`, even before the first flush,
592  /// when [`surface`](Self::surface) still holds the defaults.
593  pub fn take_surface_changed(&self) -> bool {
594    std::mem::take(&mut borrow(&self.tree).surface.changed)
595  }
596
597  /// The window's size in logical pixels: the size the compositor gave a
598  /// panel, or the size a popup's content wants within any limit, from
599  /// its last layout.
600  pub fn size(&self) -> Size {
601    self.tree.borrow().size
602  }
603
604  /// Sets the size the compositor gave the window, from a configure
605  /// event. A new size needs a layout.
606  pub fn set_size(&self, size: Size) {
607    let mut tree = borrow(&self.tree);
608    if tree.size != size {
609      tree.size = size;
610      tree.needs_layout = true;
611    }
612  }
613
614  /// Whether something changed that needs [`layout`](Self::layout).
615  pub fn needs_layout(&self) -> bool {
616    let tree = self.tree.borrow();
617    tree.needs_layout || tree.needs_place
618  }
619
620  /// Lays the window out at its size, measuring text with `text`, and
621  /// records the areas that moved or changed as damage.
622  pub fn layout(&self, text: &mut TextSystem) {
623    borrow(&self.tree).layout(text);
624  }
625
626  /// Whether any area needs repainting.
627  pub fn has_damage(&self) -> bool {
628    !self.tree.borrow().damage.is_empty()
629  }
630
631  /// Takes the areas to repaint since the last call, in logical pixels.
632  /// They may overlap.
633  pub fn take_damage(&self) -> Vec<Rect> {
634    std::mem::take(&mut borrow(&self.tree).damage)
635  }
636
637  /// A read-only view of the laid-out tree, for drawing. Release it before
638  /// the next flush or pointer event.
639  pub fn view(&self) -> WindowView<'_> {
640    WindowView::new(self.tree.borrow())
641  }
642}
643
644impl Drop for Window {
645  fn drop(&mut self) {
646    // A `hovered` signal owned elsewhere mustn't stay `true`.
647    self.pointer_leave();
648    self.owner.dispose();
649  }
650}