tilo_app/framework/
endpoint.rs1use std::collections::HashMap;
12
13use tilo_actions::ActionOutput;
14use tilo_resource::Effect;
15use ui_core::host_operation::{ResourceOperation, ResourceOperationStart, ResourceOperations};
16use ui_core::taffy::{NodeId, Style, TaffyTree};
17use ui_core::theme::Theme;
18use ui_core::vello::kurbo::{Point, Rect};
19use ui_core::widget::{Edits, TaffyTreeExt as _, Widget};
20
21#[derive(Clone, Debug)]
25pub struct EffectCaller {
26 pub invocation: tilo_actions::InvocationId,
27 pub invoker: Option<tilo_actions::ActionInvoker>,
28}
29
30type Handler<E> = Box<dyn FnMut(E, &EffectCaller) -> Result<ActionOutput, String>>;
31
32pub struct EffectEndpoint<E: Effect> {
34 handle: Handler<E>,
35 handled: &'static [&'static str],
37 rect: Rect,
38}
39
40impl<E: Effect> EffectEndpoint<E> {
41 pub fn new(
43 handle: impl FnMut(E, &EffectCaller) -> Result<ActionOutput, String> + 'static,
44 ) -> Self {
45 Self {
46 handle: Box::new(handle),
47 handled: Box::leak(Box::new([E::OPERATION])),
49 rect: Rect::ZERO,
50 }
51 }
52}
53
54impl<E: Effect> Widget for EffectEndpoint<E> {
55 ui_core::provides!(dyn ResourceOperations);
56
57 fn build_layout(
58 &mut self,
59 taffy: &mut TaffyTree<()>,
60 _theme: &Theme,
61 node_map: &mut HashMap<NodeId, *mut dyn Widget>,
62 ) -> NodeId {
63 taffy.widget_leaf(
64 Style {
65 display: ui_core::taffy::Display::None,
66 ..Style::default()
67 },
68 node_map,
69 self,
70 )
71 }
72
73 fn apply_layout(&mut self, _taffy: &TaffyTree<()>, _node: NodeId, origin: Point) {
74 self.rect = Rect::from_origin_size(origin, (0.0, 0.0));
75 }
76
77 fn needs_redraw(&self) -> bool {
78 false
79 }
80
81 fn is_layout_dirty(&self) -> bool {
82 false
83 }
84
85 fn name(&self) -> &'static str {
86 "EffectEndpoint"
87 }
88
89 fn get_rect(&self) -> Rect {
90 self.rect
91 }
92}
93
94impl<E: Effect> ResourceOperations for EffectEndpoint<E> {
95 fn local_resource_operation_handlers(&self) -> &'static [&'static str] {
96 self.handled
97 }
98
99 fn start_operation(
100 &mut self,
101 op: &ResourceOperation<'_>,
102 _doc: &mut Edits<'_>,
103 _path: &ui_core::automerge::ObjId,
104 ) -> Result<Option<ResourceOperationStart>, String> {
105 let (request, _custody) = op.with_custody();
106 if request.type_id != E::OPERATION {
107 return Ok(None);
108 }
109 op.check()?;
110 let effect = E::from_payload(request.payload).map_err(|error| error.to_string())?;
111 let caller = EffectCaller {
112 invocation: request.invocation,
113 invoker: ui_core::invocation_scope::current_invoker(),
114 };
115 (self.handle)(effect, &caller).map(|output| Some(ResourceOperationStart::Completed(output)))
116 }
117}