rosace_widgets/tree/
overlay_api.rs1use std::sync::Arc;
2use rosace_core::types::{Point, Rect};
3use rosace_render::Color;
4use rosace_state::Atom;
5use super::{Widget, PaintCtx, BoxedWidget};
6use super::overlay::{
7 FocusBehavior, InputBehavior, LayerPosition, OverlayEntry, ScrimConfig,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum OverlayKind {
14 Dropdown,
16 Sheet,
18 Dialog,
20 Tooltip,
22 Toast,
24}
25
26struct OverlayConfig {
29 kind: OverlayKind,
30 open: Atom<bool>,
31 content: Arc<dyn Fn() -> BoxedWidget + Send + Sync>,
32}
33
34pub struct WithOverlay<W: Widget> {
41 inner: W,
42 overlays: Vec<OverlayConfig>,
43}
44
45impl<W: Widget + 'static> WithOverlay<W> {
46 pub fn new(inner: W) -> Self {
47 Self { inner, overlays: Vec::new() }
48 }
49
50 fn push(mut self, kind: OverlayKind, open: Atom<bool>,
51 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
52 self.overlays.push(OverlayConfig { kind, open, content: Arc::new(content) });
53 self
54 }
55
56 pub fn dropdown(self, open: Atom<bool>,
58 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
59 self.push(OverlayKind::Dropdown, open, content)
60 }
61
62 pub fn sheet(self, open: Atom<bool>,
64 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
65 self.push(OverlayKind::Sheet, open, content)
66 }
67
68 pub fn dialog(self, open: Atom<bool>,
70 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
71 self.push(OverlayKind::Dialog, open, content)
72 }
73
74 pub fn rich_tooltip(self, content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
78 let open = rosace_state::use_atom(true);
80 self.push(OverlayKind::Tooltip, open, content)
81 }
82
83 pub fn toast(self, open: Atom<bool>,
88 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> Self {
89 self.push(OverlayKind::Toast, open, content)
90 }
91}
92
93impl<W: Widget + Send + Sync + 'static> Widget for WithOverlay<W> {
94 fn children(&self) -> super::Children<'_> {
95 super::Children::One(&self.inner)
96 }
97
98 fn paint(&self, ctx: &mut PaintCtx) {
99 self.inner.paint(ctx);
100 let anchor: Rect = ctx.rect;
101
102 for cfg in &self.overlays {
103 if !cfg.open.get() { continue; }
104
105 let content = (cfg.content)();
106 let open_atom = cfg.open.clone();
107
108 let entry = match cfg.kind {
109 OverlayKind::Dropdown => {
110 let pos = Point {
111 x: anchor.origin.x,
112 y: anchor.origin.y + anchor.size.height,
113 };
114 let dismiss = Arc::new(move || open_atom.set(false));
117 OverlayEntry::new(LayerPosition::Absolute(pos), content)
118 .input(InputBehavior::PassThrough)
119 .focus(FocusBehavior::PassThrough)
120 .scrim(ScrimConfig {
121 color: Color::TRANSPARENT,
122 on_tap: Some(dismiss),
123 exclude_rect: None,
124 })
125 }
126
127 OverlayKind::Sheet => {
128 let dismiss = Arc::new(move || open_atom.set(false));
129 OverlayEntry::new(LayerPosition::BottomAnchored, content)
130 .input(InputBehavior::PassThrough)
131 .focus(FocusBehavior::PassThrough)
132 .scrim(ScrimConfig {
133 color: Color::rgba(0, 0, 0, 100),
134 on_tap: Some(dismiss),
135 exclude_rect: None,
136 })
137 }
138
139 OverlayKind::Dialog => {
140 let dismiss = Arc::new(move || open_atom.set(false));
141 OverlayEntry::new(LayerPosition::Centered, content)
142 .input(InputBehavior::Block)
143 .focus(FocusBehavior::Trap)
144 .scrim(ScrimConfig {
145 color: Color::rgba(0, 0, 0, 160),
146 on_tap: Some(dismiss),
147 exclude_rect: None,
148 })
149 }
150
151 OverlayKind::Tooltip => {
152 OverlayEntry::new(LayerPosition::AboveCentered(anchor), content)
156 .input(InputBehavior::PassThrough)
157 .focus(FocusBehavior::Inert)
158 }
159
160 OverlayKind::Toast => {
161 OverlayEntry::new(LayerPosition::BottomCenter, content)
162 .input(InputBehavior::PassThrough)
163 .focus(FocusBehavior::Inert)
164 }
165 };
166
167 ctx.attach_overlay(entry);
171 }
172 }
173 }
175
176pub trait OverlayApi: Widget + Sized + Send + Sync + 'static {
196 fn dropdown(self, open: Atom<bool>,
197 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> WithOverlay<Self> {
198 WithOverlay::new(self).dropdown(open, content)
199 }
200
201 fn sheet(self, open: Atom<bool>,
202 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> WithOverlay<Self> {
203 WithOverlay::new(self).sheet(open, content)
204 }
205
206 fn dialog(self, open: Atom<bool>,
207 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> WithOverlay<Self> {
208 WithOverlay::new(self).dialog(open, content)
209 }
210
211 fn rich_tooltip(self,
212 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> WithOverlay<Self> {
213 WithOverlay::new(self).rich_tooltip(content)
214 }
215
216 fn toast(self, open: Atom<bool>,
217 content: impl Fn() -> BoxedWidget + Send + Sync + 'static) -> WithOverlay<Self> {
218 WithOverlay::new(self).toast(open, content)
219 }
220}
221
222impl<W: Widget + Send + Sync + 'static> OverlayApi for W {}