teksilo_widgets/
overlay_trigger.rs1use teksilo_canvas::{Rect, SizeProposal};
5use teksilo_core::accessibility::AccessNodeBuilder;
6use teksilo_core::build_context::BuildContext;
7use teksilo_core::signal::{Prop, Signal};
8use teksilo_core::widget::{LayoutContext, PendingChild, Widget, WidgetPlacement};
9use teksilo_core::widget_builder::HandlerSet;
10use teksilo_core::widget_id::WidgetId;
11
12pub struct OverlayTrigger {
27 child_id: Option<WidgetId>,
28 pending_child: Option<PendingChild>,
29 pending_handlers: Option<HandlerSet>,
30 name: Option<String>,
31 has_popup: Option<teksilo_core::accesskit::HasPopup>,
35 expanded_signal: Option<Signal<bool>>,
38 enabled: Prop<bool>,
44 on_activate: Option<std::rc::Rc<dyn Fn(&mut teksilo_core::widget::EventContext)>>,
48}
49
50impl OverlayTrigger {
51 pub(crate) fn new(child: Box<dyn Widget>, handlers: HandlerSet) -> Self {
52 Self::from_pending(PendingChild::Deferred(child), handlers)
53 }
54
55 pub(crate) fn from_id(id: WidgetId, handlers: HandlerSet) -> Self {
56 Self::from_pending(PendingChild::Id(id), handlers)
57 }
58
59 fn from_pending(pending: PendingChild, handlers: HandlerSet) -> Self {
60 Self {
61 child_id: None,
62 pending_child: Some(pending),
63 pending_handlers: Some(handlers),
64 name: None,
65 has_popup: None,
66 expanded_signal: None,
67 enabled: Prop::Static(true),
68 on_activate: None,
69 }
70 }
71
72 pub fn around(widget: impl Widget + 'static) -> Self {
74 Self::from_pending(PendingChild::Deferred(Box::new(widget)), HandlerSet::new())
75 }
76
77 pub fn around_id(id: WidgetId) -> Self {
79 Self::from_pending(PendingChild::Id(id), HandlerSet::new())
80 }
81
82 pub fn named(self, name: impl Into<String>) -> Self {
84 self.name(name)
85 }
86
87 pub fn has_on_activate(&self) -> bool {
89 self.on_activate.is_some()
90 }
91
92 pub fn on_activate(
95 mut self,
96 f: impl Fn(&mut teksilo_core::widget::EventContext) + 'static,
97 ) -> Self {
98 self.on_activate = Some(std::rc::Rc::new(f));
99 self
100 }
101
102 pub(crate) fn enabled(mut self, enabled: impl Into<Prop<bool>>) -> Self {
107 self.enabled = enabled.into();
108 self
109 }
110
111 pub(crate) fn name(mut self, name: impl Into<String>) -> Self {
112 self.name = Some(name.into());
113 self
114 }
115
116 pub(crate) fn has_popup(mut self, kind: teksilo_core::accesskit::HasPopup) -> Self {
117 self.has_popup = Some(kind);
118 self
119 }
120
121 pub(crate) fn expanded_when(mut self, signal: Signal<bool>) -> Self {
122 self.expanded_signal = Some(signal);
123 self
124 }
125}
126
127impl std::fmt::Debug for OverlayTrigger {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("OverlayTrigger")
130 .field("name", &self.name)
131 .finish()
132 }
133}
134
135impl Widget for OverlayTrigger {
136 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
137 let self_id = ctx.self_id();
142 ctx.enabled_when(self_id, self.enabled.clone());
143 if let Some(pending) = self.pending_child.take() {
144 self.child_id = Some(match pending {
145 PendingChild::Id(id) => id,
146 PendingChild::Deferred(w) => ctx.add_boxed(w),
147 });
148 }
149 let mut handlers = self.pending_handlers.take();
162 if let Some(activate) = self.on_activate.clone() {
163 let set = handlers.take().unwrap_or_default();
164 let tap = activate.clone();
165 let key = activate.clone();
166 let act = activate;
167 handlers = Some(
168 set.on_tap(move |_pos, ctx| tap(ctx))
169 .on_key(move |event, ctx| match event {
170 teksilo_core::event::WidgetEvent::KeyDown {
171 key: teksilo_core::event::Key::Enter | teksilo_core::event::Key::Space,
172 ..
173 } => {
174 key(ctx);
175 teksilo_core::event::EventResponse::Handled
176 }
177 _ => teksilo_core::event::EventResponse::Ignored,
178 })
179 .on_access_action(move |action, ctx| {
180 if action == teksilo_core::accesskit::Action::Click {
181 act(ctx);
182 teksilo_core::event::EventResponse::Handled
183 } else {
184 teksilo_core::event::EventResponse::Ignored
185 }
186 }),
187 );
188 }
189 if let Some(handlers) = handlers {
190 if let Some(child_id) = self.child_id {
191 ctx.apply_handlers(child_id, handlers);
192 } else {
193 ctx.apply_self_handlers(handlers);
195 }
196 }
197 if let Some(ref expanded_signal) = self.expanded_signal {
200 let registry = ctx.binding_registry();
201 expanded_signal.bind_to(
202 self_id,
203 registry,
204 teksilo_core::binding::BindingLevel::RepaintOnly,
205 );
206 }
207 self.children()
208 }
209
210 fn layout_response(
211 &self,
212 proposal: SizeProposal,
213 ctx: &LayoutContext,
214 ) -> teksilo_core::widget::LayoutResponse {
215 self.child_id
216 .and_then(|id| ctx.child_size(id, proposal))
217 .unwrap_or_else(|| proposal.resolve(0.0, 0.0))
218 .into()
219 }
220
221 fn place_children(
222 &self,
223 bounds: Rect,
224 _proposal: SizeProposal,
225 children: &mut [WidgetPlacement],
226 _ctx: &LayoutContext,
227 ) {
228 for child in children.iter_mut() {
229 child.origin = bounds.origin();
230 child.size = bounds.size();
231 }
232 }
233
234 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
235 builder.set_role(teksilo_core::accesskit::Role::Button);
236 if let Some(name) = &self.name {
237 builder.set_name(name.as_str());
238 }
239 if let Some(kind) = self.has_popup {
240 builder.set_has_popup(kind);
241 }
242 if let Some(ref signal) = self.expanded_signal {
243 builder.set_expanded(signal.get());
244 }
245 }
246
247 fn children(&self) -> Vec<WidgetId> {
248 self.child_id.into_iter().collect()
249 }
250}