1use crate::{
2 app::{AppContext, MutableAppContext, WindowInvalidation},
3 elements::Element,
4 font_cache::FontCache,
5 geometry::rect::RectF,
6 json::{self, ToJson},
7 platform::{CursorStyle, Event},
8 scene::CursorRegion,
9 text_layout::TextLayoutCache,
10 Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox,
11 ElementStateContext, Entity, FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel,
12 ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View,
13 ViewHandle, WeakModelHandle, WeakViewHandle,
14};
15use pathfinder_geometry::vector::{vec2f, Vector2F};
16use serde_json::json;
17use std::{
18 collections::{HashMap, HashSet},
19 marker::PhantomData,
20 ops::{Deref, DerefMut},
21 sync::Arc,
22};
23
24pub struct Presenter {
25 window_id: usize,
26 pub(crate) rendered_views: HashMap<usize, ElementBox>,
27 parents: HashMap<usize, usize>,
28 cursor_regions: Vec<CursorRegion>,
29 mouse_regions: Vec<MouseRegion>,
30 font_cache: Arc<FontCache>,
31 text_layout_cache: TextLayoutCache,
32 asset_cache: Arc<AssetCache>,
33 last_mouse_moved_event: Option<Event>,
34 hovered_region_id: Option<MouseRegionId>,
35 clicked_region: Option<MouseRegion>,
36 titlebar_height: f32,
37}
38
39impl Presenter {
40 pub fn new(
41 window_id: usize,
42 titlebar_height: f32,
43 font_cache: Arc<FontCache>,
44 text_layout_cache: TextLayoutCache,
45 asset_cache: Arc<AssetCache>,
46 cx: &mut MutableAppContext,
47 ) -> Self {
48 Self {
49 window_id,
50 rendered_views: cx.render_views(window_id, titlebar_height),
51 parents: HashMap::new(),
52 cursor_regions: Default::default(),
53 mouse_regions: Default::default(),
54 font_cache,
55 text_layout_cache,
56 asset_cache,
57 last_mouse_moved_event: None,
58 hovered_region_id: None,
59 clicked_region: None,
60 titlebar_height,
61 }
62 }
63
64 pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
65 if let Some(view_id) = app.focused_view_id(self.window_id) {
66 self.dispatch_path_from(view_id)
67 } else {
68 Vec::new()
69 }
70 }
71
72 pub(crate) fn dispatch_path_from(&self, mut view_id: usize) -> Vec<usize> {
73 let mut path = Vec::new();
74 path.push(view_id);
75 while let Some(parent_id) = self.parents.get(&view_id).copied() {
76 path.push(parent_id);
77 view_id = parent_id;
78 }
79 path.reverse();
80 path
81 }
82
83 pub fn invalidate(
84 &mut self,
85 invalidation: &mut WindowInvalidation,
86 cx: &mut MutableAppContext,
87 ) {
88 cx.start_frame();
89 for view_id in &invalidation.removed {
90 invalidation.updated.remove(&view_id);
91 self.rendered_views.remove(&view_id);
92 self.parents.remove(&view_id);
93 }
94 for view_id in &invalidation.updated {
95 self.rendered_views.insert(
96 *view_id,
97 cx.render_view(RenderParams {
98 window_id: self.window_id,
99 view_id: *view_id,
100 titlebar_height: self.titlebar_height,
101 hovered_region_id: self.hovered_region_id,
102 clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
103 refreshing: false,
104 })
105 .unwrap(),
106 );
107 }
108 }
109
110 pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, cx: &mut MutableAppContext) {
111 self.invalidate(invalidation, cx);
112 for (view_id, view) in &mut self.rendered_views {
113 if !invalidation.updated.contains(view_id) {
114 *view = cx
115 .render_view(RenderParams {
116 window_id: self.window_id,
117 view_id: *view_id,
118 titlebar_height: self.titlebar_height,
119 hovered_region_id: self.hovered_region_id,
120 clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
121 refreshing: true,
122 })
123 .unwrap();
124 }
125 }
126 }
127
128 pub fn build_scene(
129 &mut self,
130 window_size: Vector2F,
131 scale_factor: f32,
132 refreshing: bool,
133 cx: &mut MutableAppContext,
134 ) -> Scene {
135 let mut scene = Scene::new(scale_factor);
136
137 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
138 self.layout(window_size, refreshing, cx);
139 let mut paint_cx = self.build_paint_context(&mut scene, cx);
140 paint_cx.paint(
141 root_view_id,
142 Vector2F::zero(),
143 RectF::new(Vector2F::zero(), window_size),
144 );
145 self.text_layout_cache.finish_frame();
146 self.cursor_regions = scene.cursor_regions();
147 self.mouse_regions = scene.mouse_regions();
148
149 if cx.window_is_active(self.window_id) {
150 if let Some(event) = self.last_mouse_moved_event.clone() {
151 self.dispatch_event(event, cx)
152 }
153 }
154 } else {
155 log::error!("could not find root_view_id for window {}", self.window_id);
156 }
157
158 scene
159 }
160
161 fn layout(&mut self, size: Vector2F, refreshing: bool, cx: &mut MutableAppContext) {
162 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
163 self.build_layout_context(refreshing, cx)
164 .layout(root_view_id, SizeConstraint::strict(size));
165 }
166 }
167
168 pub fn build_layout_context<'a>(
169 &'a mut self,
170 refreshing: bool,
171 cx: &'a mut MutableAppContext,
172 ) -> LayoutContext<'a> {
173 LayoutContext {
174 rendered_views: &mut self.rendered_views,
175 parents: &mut self.parents,
176 font_cache: &self.font_cache,
177 font_system: cx.platform().fonts(),
178 text_layout_cache: &self.text_layout_cache,
179 asset_cache: &self.asset_cache,
180 view_stack: Vec::new(),
181 refreshing,
182 hovered_region_id: self.hovered_region_id,
183 clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
184 titlebar_height: self.titlebar_height,
185 app: cx,
186 }
187 }
188
189 pub fn build_paint_context<'a>(
190 &'a mut self,
191 scene: &'a mut Scene,
192 cx: &'a mut MutableAppContext,
193 ) -> PaintContext {
194 PaintContext {
195 scene,
196 font_cache: &self.font_cache,
197 text_layout_cache: &self.text_layout_cache,
198 rendered_views: &mut self.rendered_views,
199 app: cx,
200 }
201 }
202
203 pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
204 if let Some(root_view_id) = cx.root_view_id(self.window_id) {
205 let mut unhovered_region = None;
206 let mut hovered_region = None;
207 let mut clicked_region = None;
208
209 match event {
210 Event::LeftMouseDown { position, .. } => {
211 for region in self.mouse_regions.iter().rev() {
212 if region.bounds.contains_point(position) {
213 self.clicked_region = Some(region.clone());
214 break;
215 }
216 }
217 }
218 Event::LeftMouseUp {
219 position,
220 click_count,
221 ..
222 } => {
223 if let Some(region) = self.clicked_region.take() {
224 if region.bounds.contains_point(position) {
225 clicked_region = Some((region, position, click_count));
226 }
227 }
228 }
229 Event::MouseMoved {
230 position,
231 left_mouse_down,
232 } => {
233 self.last_mouse_moved_event = Some(event.clone());
234
235 if !left_mouse_down {
236 let mut style_to_assign = CursorStyle::Arrow;
237 for region in self.cursor_regions.iter().rev() {
238 if region.bounds.contains_point(position) {
239 style_to_assign = region.style;
240 break;
241 }
242 }
243 cx.platform().set_cursor_style(style_to_assign);
244
245 for region in self.mouse_regions.iter().rev() {
246 if region.bounds.contains_point(position) {
247 if hovered_region.is_none() {
248 hovered_region = Some(region.clone());
249 }
250 } else {
251 if self.hovered_region_id == Some(region.id()) {
252 unhovered_region = Some(region.clone())
253 }
254 }
255 }
256 }
257 }
258 Event::LeftMouseDragged { position } => {
259 self.last_mouse_moved_event = Some(Event::MouseMoved {
260 position,
261 left_mouse_down: true,
262 });
263 }
264 _ => {}
265 }
266
267 self.hovered_region_id = hovered_region.as_ref().map(MouseRegion::id);
268
269 let mut event_cx = self.build_event_context(cx);
270 if let Some(unhovered_region) = unhovered_region {
271 if let Some(hover_callback) = unhovered_region.hover {
272 event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
273 hover_callback(false, event_cx)
274 })
275 }
276 }
277
278 if let Some(hovered_region) = hovered_region {
279 if let Some(hover_callback) = hovered_region.hover {
280 event_cx.with_current_view(hovered_region.view_id, |event_cx| {
281 hover_callback(true, event_cx)
282 })
283 }
284 }
285
286 if let Some((clicked_region, position, click_count)) = clicked_region {
287 if let Some(click_callback) = clicked_region.click {
288 event_cx.with_current_view(clicked_region.view_id, |event_cx| {
289 click_callback(position, click_count, event_cx)
290 })
291 }
292 }
293
294 event_cx.dispatch_event(root_view_id, &event);
295
296 let invalidated_views = event_cx.invalidated_views;
297 let dispatch_directives = event_cx.dispatched_actions;
298
299 for view_id in invalidated_views {
300 cx.notify_view(self.window_id, view_id);
301 }
302 for directive in dispatch_directives {
303 cx.dispatch_action_any(self.window_id, &directive.path, directive.action.as_ref());
304 }
305 }
306 }
307
308 pub fn build_event_context<'a>(
309 &'a mut self,
310 cx: &'a mut MutableAppContext,
311 ) -> EventContext<'a> {
312 EventContext {
313 rendered_views: &mut self.rendered_views,
314 dispatched_actions: Default::default(),
315 font_cache: &self.font_cache,
316 text_layout_cache: &self.text_layout_cache,
317 view_stack: Default::default(),
318 invalidated_views: Default::default(),
319 notify_count: 0,
320 app: cx,
321 }
322 }
323
324 pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
325 let view = cx.root_view(self.window_id)?;
326 Some(json!({
327 "root_view": view.debug_json(cx),
328 "root_element": self.rendered_views.get(&view.id())
329 .map(|root_element| {
330 root_element.debug(&DebugContext {
331 rendered_views: &self.rendered_views,
332 font_cache: &self.font_cache,
333 app: cx,
334 })
335 })
336 }))
337 }
338}
339
340pub struct DispatchDirective {
341 pub path: Vec<usize>,
342 pub action: Box<dyn Action>,
343}
344
345pub struct LayoutContext<'a> {
346 rendered_views: &'a mut HashMap<usize, ElementBox>,
347 parents: &'a mut HashMap<usize, usize>,
348 view_stack: Vec<usize>,
349 pub font_cache: &'a Arc<FontCache>,
350 pub font_system: Arc<dyn FontSystem>,
351 pub text_layout_cache: &'a TextLayoutCache,
352 pub asset_cache: &'a AssetCache,
353 pub app: &'a mut MutableAppContext,
354 pub refreshing: bool,
355 titlebar_height: f32,
356 hovered_region_id: Option<MouseRegionId>,
357 clicked_region_id: Option<MouseRegionId>,
358}
359
360impl<'a> LayoutContext<'a> {
361 fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
362 if let Some(parent_id) = self.view_stack.last() {
363 self.parents.insert(view_id, *parent_id);
364 }
365 self.view_stack.push(view_id);
366 let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
367 let size = rendered_view.layout(constraint, self);
368 self.rendered_views.insert(view_id, rendered_view);
369 self.view_stack.pop();
370 size
371 }
372
373 pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
374 where
375 F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
376 V: View,
377 {
378 handle.update(self.app, |view, cx| {
379 let mut render_cx = RenderContext {
380 app: cx,
381 window_id: handle.window_id(),
382 view_id: handle.id(),
383 view_type: PhantomData,
384 titlebar_height: self.titlebar_height,
385 hovered_region_id: self.hovered_region_id,
386 clicked_region_id: self.clicked_region_id,
387 refreshing: self.refreshing,
388 };
389 f(view, &mut render_cx)
390 })
391 }
392}
393
394impl<'a> Deref for LayoutContext<'a> {
395 type Target = MutableAppContext;
396
397 fn deref(&self) -> &Self::Target {
398 self.app
399 }
400}
401
402impl<'a> DerefMut for LayoutContext<'a> {
403 fn deref_mut(&mut self) -> &mut Self::Target {
404 self.app
405 }
406}
407
408impl<'a> ReadView for LayoutContext<'a> {
409 fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
410 self.app.read_view(handle)
411 }
412}
413
414impl<'a> ReadModel for LayoutContext<'a> {
415 fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
416 self.app.read_model(handle)
417 }
418}
419
420impl<'a> UpgradeModelHandle for LayoutContext<'a> {
421 fn upgrade_model_handle<T: Entity>(
422 &self,
423 handle: &WeakModelHandle<T>,
424 ) -> Option<ModelHandle<T>> {
425 self.app.upgrade_model_handle(handle)
426 }
427
428 fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
429 self.app.model_handle_is_upgradable(handle)
430 }
431
432 fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
433 self.app.upgrade_any_model_handle(handle)
434 }
435}
436
437impl<'a> UpgradeViewHandle for LayoutContext<'a> {
438 fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
439 self.app.upgrade_view_handle(handle)
440 }
441
442 fn upgrade_any_view_handle(&self, handle: &crate::AnyWeakViewHandle) -> Option<AnyViewHandle> {
443 self.app.upgrade_any_view_handle(handle)
444 }
445}
446
447impl<'a> ElementStateContext for LayoutContext<'a> {
448 fn current_view_id(&self) -> usize {
449 *self.view_stack.last().unwrap()
450 }
451}
452
453pub struct PaintContext<'a> {
454 rendered_views: &'a mut HashMap<usize, ElementBox>,
455 pub scene: &'a mut Scene,
456 pub font_cache: &'a FontCache,
457 pub text_layout_cache: &'a TextLayoutCache,
458 pub app: &'a AppContext,
459}
460
461impl<'a> PaintContext<'a> {
462 fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
463 if let Some(mut tree) = self.rendered_views.remove(&view_id) {
464 tree.paint(origin, visible_bounds, self);
465 self.rendered_views.insert(view_id, tree);
466 }
467 }
468}
469
470impl<'a> Deref for PaintContext<'a> {
471 type Target = AppContext;
472
473 fn deref(&self) -> &Self::Target {
474 self.app
475 }
476}
477
478pub struct EventContext<'a> {
479 rendered_views: &'a mut HashMap<usize, ElementBox>,
480 dispatched_actions: Vec<DispatchDirective>,
481 pub font_cache: &'a FontCache,
482 pub text_layout_cache: &'a TextLayoutCache,
483 pub app: &'a mut MutableAppContext,
484 pub notify_count: usize,
485 view_stack: Vec<usize>,
486 invalidated_views: HashSet<usize>,
487}
488
489impl<'a> EventContext<'a> {
490 fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
491 if let Some(mut element) = self.rendered_views.remove(&view_id) {
492 let result =
493 self.with_current_view(view_id, |this| element.dispatch_event(event, this));
494 self.rendered_views.insert(view_id, element);
495 result
496 } else {
497 false
498 }
499 }
500
501 fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
502 where
503 F: FnOnce(&mut Self) -> T,
504 {
505 self.view_stack.push(view_id);
506 let result = f(self);
507 self.view_stack.pop();
508 result
509 }
510
511 pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
512 self.dispatched_actions.push(DispatchDirective {
513 path: self.view_stack.clone(),
514 action,
515 });
516 }
517
518 pub fn dispatch_action<A: Action>(&mut self, action: A) {
519 self.dispatch_any_action(Box::new(action));
520 }
521
522 pub fn notify(&mut self) {
523 self.notify_count += 1;
524 if let Some(view_id) = self.view_stack.last() {
525 self.invalidated_views.insert(*view_id);
526 }
527 }
528
529 pub fn notify_count(&self) -> usize {
530 self.notify_count
531 }
532}
533
534impl<'a> Deref for EventContext<'a> {
535 type Target = MutableAppContext;
536
537 fn deref(&self) -> &Self::Target {
538 self.app
539 }
540}
541
542impl<'a> DerefMut for EventContext<'a> {
543 fn deref_mut(&mut self) -> &mut Self::Target {
544 self.app
545 }
546}
547
548pub struct DebugContext<'a> {
549 rendered_views: &'a HashMap<usize, ElementBox>,
550 pub font_cache: &'a FontCache,
551 pub app: &'a AppContext,
552}
553
554#[derive(Clone, Copy, Debug, Eq, PartialEq)]
555pub enum Axis {
556 Horizontal,
557 Vertical,
558}
559
560impl Axis {
561 pub fn invert(self) -> Self {
562 match self {
563 Self::Horizontal => Self::Vertical,
564 Self::Vertical => Self::Horizontal,
565 }
566 }
567}
568
569impl ToJson for Axis {
570 fn to_json(&self) -> serde_json::Value {
571 match self {
572 Axis::Horizontal => json!("horizontal"),
573 Axis::Vertical => json!("vertical"),
574 }
575 }
576}
577
578pub trait Vector2FExt {
579 fn along(self, axis: Axis) -> f32;
580}
581
582impl Vector2FExt for Vector2F {
583 fn along(self, axis: Axis) -> f32 {
584 match axis {
585 Axis::Horizontal => self.x(),
586 Axis::Vertical => self.y(),
587 }
588 }
589}
590
591#[derive(Copy, Clone, Debug)]
592pub struct SizeConstraint {
593 pub min: Vector2F,
594 pub max: Vector2F,
595}
596
597impl SizeConstraint {
598 pub fn new(min: Vector2F, max: Vector2F) -> Self {
599 Self { min, max }
600 }
601
602 pub fn strict(size: Vector2F) -> Self {
603 Self {
604 min: size,
605 max: size,
606 }
607 }
608
609 pub fn strict_along(axis: Axis, max: f32) -> Self {
610 match axis {
611 Axis::Horizontal => Self {
612 min: vec2f(max, 0.0),
613 max: vec2f(max, f32::INFINITY),
614 },
615 Axis::Vertical => Self {
616 min: vec2f(0.0, max),
617 max: vec2f(f32::INFINITY, max),
618 },
619 }
620 }
621
622 pub fn max_along(&self, axis: Axis) -> f32 {
623 match axis {
624 Axis::Horizontal => self.max.x(),
625 Axis::Vertical => self.max.y(),
626 }
627 }
628
629 pub fn min_along(&self, axis: Axis) -> f32 {
630 match axis {
631 Axis::Horizontal => self.min.x(),
632 Axis::Vertical => self.min.y(),
633 }
634 }
635
636 pub fn constrain(&self, size: Vector2F) -> Vector2F {
637 vec2f(
638 size.x().min(self.max.x()).max(self.min.x()),
639 size.y().min(self.max.y()).max(self.min.y()),
640 )
641 }
642}
643
644impl ToJson for SizeConstraint {
645 fn to_json(&self) -> serde_json::Value {
646 json!({
647 "min": self.min.to_json(),
648 "max": self.max.to_json(),
649 })
650 }
651}
652
653pub struct ChildView {
654 view: AnyViewHandle,
655}
656
657impl ChildView {
658 pub fn new(view: impl Into<AnyViewHandle>) -> Self {
659 Self { view: view.into() }
660 }
661}
662
663impl Element for ChildView {
664 type LayoutState = ();
665 type PaintState = ();
666
667 fn layout(
668 &mut self,
669 constraint: SizeConstraint,
670 cx: &mut LayoutContext,
671 ) -> (Vector2F, Self::LayoutState) {
672 let size = cx.layout(self.view.id(), constraint);
673 (size, ())
674 }
675
676 fn paint(
677 &mut self,
678 bounds: RectF,
679 visible_bounds: RectF,
680 _: &mut Self::LayoutState,
681 cx: &mut PaintContext,
682 ) -> Self::PaintState {
683 cx.paint(self.view.id(), bounds.origin(), visible_bounds);
684 }
685
686 fn dispatch_event(
687 &mut self,
688 event: &Event,
689 _: RectF,
690 _: RectF,
691 _: &mut Self::LayoutState,
692 _: &mut Self::PaintState,
693 cx: &mut EventContext,
694 ) -> bool {
695 cx.dispatch_event(self.view.id(), event)
696 }
697
698 fn debug(
699 &self,
700 bounds: RectF,
701 _: &Self::LayoutState,
702 _: &Self::PaintState,
703 cx: &DebugContext,
704 ) -> serde_json::Value {
705 json!({
706 "type": "ChildView",
707 "view_id": self.view.id(),
708 "bounds": bounds.to_json(),
709 "view": self.view.debug_json(cx.app),
710 "child": if let Some(view) = cx.rendered_views.get(&self.view.id()) {
711 view.debug(cx)
712 } else {
713 json!(null)
714 }
715 })
716 }
717}