1use crate::{
2 app::{AppContext, MutableAppContext, WindowInvalidation},
3 elements::Element,
4 font_cache::FontCache,
5 json::{self, ToJson},
6 platform::Event,
7 text_layout::TextLayoutCache,
8 Action, AnyAction, AssetCache, ElementBox, Scene,
9};
10use pathfinder_geometry::vector::{vec2f, Vector2F};
11use serde_json::json;
12use std::{
13 collections::{HashMap, HashSet},
14 sync::Arc,
15};
16
17pub struct Presenter {
18 window_id: usize,
19 rendered_views: HashMap<usize, ElementBox>,
20 parents: HashMap<usize, usize>,
21 font_cache: Arc<FontCache>,
22 text_layout_cache: TextLayoutCache,
23 asset_cache: Arc<AssetCache>,
24 last_mouse_moved_event: Option<Event>,
25 titlebar_height: f32,
26}
27
28impl Presenter {
29 pub fn new(
30 window_id: usize,
31 titlebar_height: f32,
32 font_cache: Arc<FontCache>,
33 text_layout_cache: TextLayoutCache,
34 asset_cache: Arc<AssetCache>,
35 cx: &MutableAppContext,
36 ) -> Self {
37 Self {
38 window_id,
39 rendered_views: cx.render_views(window_id, titlebar_height),
40 parents: HashMap::new(),
41 font_cache,
42 text_layout_cache,
43 asset_cache,
44 last_mouse_moved_event: None,
45 titlebar_height,
46 }
47 }
48
49 pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
50 let mut view_id = app.focused_view_id(self.window_id).unwrap();
51 let mut path = vec![view_id];
52 while let Some(parent_id) = self.parents.get(&view_id).copied() {
53 path.push(parent_id);
54 view_id = parent_id;
55 }
56 path.reverse();
57 path
58 }
59
60 pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, cx: &AppContext) {
61 for view_id in invalidation.removed {
62 invalidation.updated.remove(&view_id);
63 self.rendered_views.remove(&view_id);
64 self.parents.remove(&view_id);
65 }
66 for view_id in invalidation.updated {
67 self.rendered_views.insert(
68 view_id,
69 cx.render_view(self.window_id, view_id, self.titlebar_height)
70 .unwrap(),
71 );
72 }
73 }
74
75 pub fn build_scene(
76 &mut self,
77 window_size: Vector2F,
78 scale_factor: f32,
79 cx: &mut MutableAppContext,
80 ) -> Scene {
81 let mut scene = Scene::new(scale_factor);
82
83 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
84 self.layout(window_size, cx);
85 let mut paint_cx = PaintContext {
86 scene: &mut scene,
87 font_cache: &self.font_cache,
88 text_layout_cache: &self.text_layout_cache,
89 rendered_views: &mut self.rendered_views,
90 app: cx.as_ref(),
91 };
92 paint_cx.paint(root_view_id, Vector2F::zero());
93 self.text_layout_cache.finish_frame();
94
95 if let Some(event) = self.last_mouse_moved_event.clone() {
96 self.dispatch_event(event, cx)
97 }
98 } else {
99 log::error!("could not find root_view_id for window {}", self.window_id);
100 }
101
102 scene
103 }
104
105 fn layout(&mut self, size: Vector2F, cx: &mut MutableAppContext) {
106 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
107 self.layout_cx(cx)
108 .layout(root_view_id, SizeConstraint::strict(size));
109 }
110 }
111
112 pub fn layout_cx<'a>(&'a mut self, cx: &'a mut MutableAppContext) -> LayoutContext<'a> {
113 LayoutContext {
114 rendered_views: &mut self.rendered_views,
115 parents: &mut self.parents,
116 font_cache: &self.font_cache,
117 text_layout_cache: &self.text_layout_cache,
118 asset_cache: &self.asset_cache,
119 view_stack: Vec::new(),
120 app: cx,
121 }
122 }
123
124 pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
125 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
126 if matches!(event, Event::MouseMoved { .. }) {
127 self.last_mouse_moved_event = Some(event.clone());
128 }
129
130 let mut event_cx = EventContext {
131 rendered_views: &mut self.rendered_views,
132 dispatched_actions: Default::default(),
133 font_cache: &self.font_cache,
134 text_layout_cache: &self.text_layout_cache,
135 view_stack: Default::default(),
136 invalidated_views: Default::default(),
137 app: cx,
138 };
139 event_cx.dispatch_event(root_view_id, &event);
140
141 let invalidated_views = event_cx.invalidated_views;
142 let dispatch_directives = event_cx.dispatched_actions;
143
144 for view_id in invalidated_views {
145 cx.notify_view(self.window_id, view_id);
146 }
147 for directive in dispatch_directives {
148 cx.dispatch_action_any(self.window_id, &directive.path, directive.action.as_ref());
149 }
150 }
151 }
152
153 pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
154 cx.root_view_id(self.window_id)
155 .and_then(|root_view_id| self.rendered_views.get(&root_view_id))
156 .map(|root_element| {
157 root_element.debug(&DebugContext {
158 rendered_views: &self.rendered_views,
159 font_cache: &self.font_cache,
160 app: cx,
161 })
162 })
163 }
164}
165
166pub struct DispatchDirective {
167 pub path: Vec<usize>,
168 pub action: Box<dyn AnyAction>,
169}
170
171pub struct LayoutContext<'a> {
172 rendered_views: &'a mut HashMap<usize, ElementBox>,
173 parents: &'a mut HashMap<usize, usize>,
174 view_stack: Vec<usize>,
175 pub font_cache: &'a FontCache,
176 pub text_layout_cache: &'a TextLayoutCache,
177 pub asset_cache: &'a AssetCache,
178 pub app: &'a mut MutableAppContext,
179}
180
181impl<'a> LayoutContext<'a> {
182 fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
183 if let Some(parent_id) = self.view_stack.last() {
184 self.parents.insert(view_id, *parent_id);
185 }
186 self.view_stack.push(view_id);
187 let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
188 let size = rendered_view.layout(constraint, self);
189 self.rendered_views.insert(view_id, rendered_view);
190 self.view_stack.pop();
191 size
192 }
193}
194
195pub struct PaintContext<'a> {
196 rendered_views: &'a mut HashMap<usize, ElementBox>,
197 pub scene: &'a mut Scene,
198 pub font_cache: &'a FontCache,
199 pub text_layout_cache: &'a TextLayoutCache,
200 pub app: &'a AppContext,
201}
202
203impl<'a> PaintContext<'a> {
204 fn paint(&mut self, view_id: usize, origin: Vector2F) {
205 if let Some(mut tree) = self.rendered_views.remove(&view_id) {
206 tree.paint(origin, self);
207 self.rendered_views.insert(view_id, tree);
208 }
209 }
210}
211
212pub struct EventContext<'a> {
213 rendered_views: &'a mut HashMap<usize, ElementBox>,
214 dispatched_actions: Vec<DispatchDirective>,
215 pub font_cache: &'a FontCache,
216 pub text_layout_cache: &'a TextLayoutCache,
217 pub app: &'a mut MutableAppContext,
218 view_stack: Vec<usize>,
219 invalidated_views: HashSet<usize>,
220}
221
222impl<'a> EventContext<'a> {
223 fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
224 if let Some(mut element) = self.rendered_views.remove(&view_id) {
225 self.view_stack.push(view_id);
226 let result = element.dispatch_event(event, self);
227 self.view_stack.pop();
228 self.rendered_views.insert(view_id, element);
229 result
230 } else {
231 false
232 }
233 }
234
235 pub fn dispatch_action<A: Action>(&mut self, action: A) {
236 self.dispatched_actions.push(DispatchDirective {
237 path: self.view_stack.clone(),
238 action: Box::new(action),
239 });
240 }
241
242 pub fn notify(&mut self) {
243 self.invalidated_views
244 .insert(*self.view_stack.last().unwrap());
245 }
246}
247
248pub struct DebugContext<'a> {
249 rendered_views: &'a HashMap<usize, ElementBox>,
250 pub font_cache: &'a FontCache,
251 pub app: &'a AppContext,
252}
253
254#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub enum Axis {
256 Horizontal,
257 Vertical,
258}
259
260impl Axis {
261 pub fn invert(self) -> Self {
262 match self {
263 Self::Horizontal => Self::Vertical,
264 Self::Vertical => Self::Horizontal,
265 }
266 }
267}
268
269impl ToJson for Axis {
270 fn to_json(&self) -> serde_json::Value {
271 match self {
272 Axis::Horizontal => json!("horizontal"),
273 Axis::Vertical => json!("vertical"),
274 }
275 }
276}
277
278pub trait Vector2FExt {
279 fn along(self, axis: Axis) -> f32;
280}
281
282impl Vector2FExt for Vector2F {
283 fn along(self, axis: Axis) -> f32 {
284 match axis {
285 Axis::Horizontal => self.x(),
286 Axis::Vertical => self.y(),
287 }
288 }
289}
290
291#[derive(Copy, Clone, Debug)]
292pub struct SizeConstraint {
293 pub min: Vector2F,
294 pub max: Vector2F,
295}
296
297impl SizeConstraint {
298 pub fn new(min: Vector2F, max: Vector2F) -> Self {
299 Self { min, max }
300 }
301
302 pub fn strict(size: Vector2F) -> Self {
303 Self {
304 min: size,
305 max: size,
306 }
307 }
308
309 pub fn strict_along(axis: Axis, max: f32) -> Self {
310 match axis {
311 Axis::Horizontal => Self {
312 min: vec2f(max, 0.0),
313 max: vec2f(max, f32::INFINITY),
314 },
315 Axis::Vertical => Self {
316 min: vec2f(0.0, max),
317 max: vec2f(f32::INFINITY, max),
318 },
319 }
320 }
321
322 pub fn max_along(&self, axis: Axis) -> f32 {
323 match axis {
324 Axis::Horizontal => self.max.x(),
325 Axis::Vertical => self.max.y(),
326 }
327 }
328
329 pub fn min_along(&self, axis: Axis) -> f32 {
330 match axis {
331 Axis::Horizontal => self.min.x(),
332 Axis::Vertical => self.min.y(),
333 }
334 }
335}
336
337impl ToJson for SizeConstraint {
338 fn to_json(&self) -> serde_json::Value {
339 json!({
340 "min": self.min.to_json(),
341 "max": self.max.to_json(),
342 })
343 }
344}
345
346pub struct ChildView {
347 view_id: usize,
348}
349
350impl ChildView {
351 pub fn new(view_id: usize) -> Self {
352 Self { view_id }
353 }
354}
355
356impl Element for ChildView {
357 type LayoutState = ();
358 type PaintState = ();
359
360 fn layout(
361 &mut self,
362 constraint: SizeConstraint,
363 cx: &mut LayoutContext,
364 ) -> (Vector2F, Self::LayoutState) {
365 let size = cx.layout(self.view_id, constraint);
366 (size, ())
367 }
368
369 fn paint(
370 &mut self,
371 bounds: pathfinder_geometry::rect::RectF,
372 _: &mut Self::LayoutState,
373 cx: &mut PaintContext,
374 ) -> Self::PaintState {
375 cx.paint(self.view_id, bounds.origin());
376 }
377
378 fn dispatch_event(
379 &mut self,
380 event: &Event,
381 _: pathfinder_geometry::rect::RectF,
382 _: &mut Self::LayoutState,
383 _: &mut Self::PaintState,
384 cx: &mut EventContext,
385 ) -> bool {
386 cx.dispatch_event(self.view_id, event)
387 }
388
389 fn debug(
390 &self,
391 bounds: pathfinder_geometry::rect::RectF,
392 _: &Self::LayoutState,
393 _: &Self::PaintState,
394 cx: &DebugContext,
395 ) -> serde_json::Value {
396 json!({
397 "type": "ChildView",
398 "view_id": self.view_id,
399 "bounds": bounds.to_json(),
400 "child": if let Some(view) = cx.rendered_views.get(&self.view_id) {
401 view.debug(cx)
402 } else {
403 json!(null)
404 }
405 })
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 // #[test]
412 // fn test_responder_chain() {
413 // let settings = settings_rx(None);
414 // let mut app = App::new().unwrap();
415 // let workspace = app.add_model(|cx| Workspace::new(Vec::new(), cx));
416 // let (window_id, workspace_view) =
417 // app.add_window(|cx| WorkspaceView::new(workspace.clone(), settings, cx));
418
419 // let invalidations = Rc::new(RefCell::new(Vec::new()));
420 // let invalidations_ = invalidations.clone();
421 // app.on_window_invalidated(window_id, move |invalidation, _| {
422 // invalidations_.borrow_mut().push(invalidation)
423 // });
424
425 // let active_pane_id = workspace_view.update(&mut app, |view, cx| {
426 // cx.focus(view.active_pane());
427 // view.active_pane().id()
428 // });
429
430 // app.update(|app| {
431 // let mut presenter = Presenter::new(
432 // window_id,
433 // Rc::new(FontCache::new()),
434 // Rc::new(AssetCache::new()),
435 // app,
436 // );
437 // for invalidation in invalidations.borrow().iter().cloned() {
438 // presenter.update(vec2f(1024.0, 768.0), 2.0, Some(invalidation), app);
439 // }
440
441 // assert_eq!(
442 // presenter.responder_chain(app.cx()).unwrap(),
443 // vec![workspace_view.id(), active_pane_id]
444 // );
445 // });
446 // }
447}