Skip to main content

sequoia_elements/
popup.rs

1use sequoia_core::Edges;
2use sequoia_reactive::Signal;
3use sequoia_view::{EventCtx, OverlayEntry, OverlayId, PaintCtx};
4
5#[derive(Clone)]
6pub struct PopupState {
7    open: Signal<bool>,
8    entry: Signal<Option<OverlayId>>,
9    margin: Edges,
10}
11
12impl PopupState {
13    pub fn new(open: Signal<bool>) -> Self {
14        Self {
15            open,
16            entry: Signal::new(None),
17            margin: Edges::ZERO,
18        }
19    }
20
21    pub fn is_open(&self) -> bool {
22        self.open.get_untracked()
23    }
24
25    pub fn entry_id(&self) -> Option<OverlayId> {
26        self.entry.get_untracked()
27    }
28
29    pub fn open(&self) {
30        self.open.set(true);
31    }
32
33    pub fn close(&self) {
34        self.open.set(false);
35    }
36
37    pub fn toggle(&self) {
38        self.open.set(!self.open.get_untracked());
39    }
40
41    pub fn subscribe(&self) {
42        let _ = self.open.get();
43    }
44
45    pub fn sync_show(&self, ctx: &mut PaintCtx, make: impl FnOnce() -> OverlayEntry) {
46        if !self.open.get_untracked() {
47            if let Some(id) = self.entry.get_untracked() {
48                ctx.hide_overlay(id);
49                self.entry.set(None);
50            }
51            return;
52        }
53        if let Some(id) = self.entry.get_untracked() {
54            if !ctx.has_overlay(id) {
55                self.entry.set(None);
56                self.open.set(false);
57            }
58            return;
59        }
60        let id = ctx.show_overlay(make());
61        self.entry.set(Some(id));
62    }
63
64    pub fn toggle_in(&self, ctx: &mut EventCtx) {
65        if self.open.get_untracked() {
66            self.close_in(ctx);
67        } else {
68            self.open();
69        }
70        ctx.request_focus();
71        ctx.handled();
72    }
73
74    pub fn close_in(&self, ctx: &mut EventCtx) -> bool {
75        let was = self.open.get_untracked();
76        self.open.set(false);
77        if let Some(id) = self.entry.get_untracked() {
78            ctx.hide_overlay(id);
79            self.entry.set(None);
80        }
81        if was {
82            ctx.handled();
83        }
84        was
85    }
86
87    pub fn forget(&self) {
88        self.entry.set(None);
89    }
90
91    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
92        let m = margin.into();
93        self.margin = Edges::only(
94            sanitize_margin(m.left),
95            sanitize_margin(m.top),
96            sanitize_margin(m.right),
97            sanitize_margin(m.bottom),
98        );
99        self
100    }
101
102    pub fn margin_xy(self, x: f32, y: f32) -> Self {
103        self.margin(Edges::xy(x, y))
104    }
105
106    pub fn margin_x(mut self, x: f32) -> Self {
107        let x = sanitize_margin(x);
108        self.margin.left = x;
109        self.margin.right = x;
110        self
111    }
112
113    pub fn margin_y(mut self, y: f32) -> Self {
114        let y = sanitize_margin(y);
115        self.margin.top = y;
116        self.margin.bottom = y;
117        self
118    }
119
120    pub fn margin_left(mut self, v: f32) -> Self {
121        self.margin.left = sanitize_margin(v);
122        self
123    }
124
125    pub fn margin_right(mut self, v: f32) -> Self {
126        self.margin.right = sanitize_margin(v);
127        self
128    }
129
130    pub fn margin_top(mut self, v: f32) -> Self {
131        self.margin.top = sanitize_margin(v);
132        self
133    }
134
135    pub fn margin_bottom(mut self, v: f32) -> Self {
136        self.margin.bottom = sanitize_margin(v);
137        self
138    }
139}
140
141fn sanitize_margin(v: f32) -> f32 {
142    if v.is_finite() { v.max(0.0) } else { 0.0 }
143}