From 54b607b2efc65cc104f0e5ae63a88b9853ae5dde Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 25 Aug 2024 12:40:46 +0200 Subject: [PATCH] Implement update_scrolls pass Note: This PRs comes with a lot of new TODO items. Addressing most of these items is difficult without major refactors. This pass is still mostly functional. --- masonry/src/contexts.rs | 17 ++++++++++++--- masonry/src/passes/event.rs | 39 ----------------------------------- masonry/src/passes/update.rs | 27 ++++++++++++++++++++++-- masonry/src/render_root.rs | 11 ++++++++-- masonry/src/widget/portal.rs | 35 +++++++++++++++++++++++++++++-- masonry/src/widget/textbox.rs | 2 ++ 6 files changed, 83 insertions(+), 48 deletions(-) diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index 08580b3de..38d520374 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -59,7 +59,6 @@ pub struct EventCtx<'a> { pub(crate) widget_children: ArenaMutChildren<'a, Box>, pub(crate) allow_pointer_capture: bool, pub(crate) is_handled: bool, - pub(crate) request_pan_to_child: Option, } /// A context provided to the [`lifecycle`] method on widgets. @@ -604,8 +603,20 @@ impl EventCtx<'_> { } /// Send a signal to parent widgets to scroll this widget into view. - pub fn request_pan_to_this(&mut self) { - self.request_pan_to_child = Some(self.widget_state.layout_rect()); + pub fn request_scroll_to_this(&mut self) { + let rect = self.widget_state.layout_rect(); + self.global_state + .scroll_request_targets + .push((self.widget_state.id, rect)); + } + + /// Send a signal to parent widgets to scroll this area into view. + /// + /// `rect` is in local coordinates. + pub fn request_scroll_to(&mut self, rect: Rect) { + self.global_state + .scroll_request_targets + .push((self.widget_state.id, rect)); } // TODO - Remove diff --git a/masonry/src/passes/event.rs b/masonry/src/passes/event.rs index 872cc0583..ae6e5a3e6 100644 --- a/masonry/src/passes/event.rs +++ b/masonry/src/passes/event.rs @@ -54,7 +54,6 @@ fn run_event_pass( widget_children: widget_mut.children, allow_pointer_capture, is_handled: false, - request_pan_to_child: None, }; let widget = widget_mut.item; @@ -194,41 +193,3 @@ pub(crate) fn root_on_access_event( handled } - -// These functions were carved out of WidgetPod code during a previous refactor -// The general "pan to child" logic needs to be added back in. -#[cfg(FALSE)] -fn pan_to_child() { - // TODO - there's some dubious logic here - if let Some(target_rect) = inner_ctx.request_pan_to_child { - self.pan_to_child(parent_ctx, target_rect); - let (state, _) = parent_ctx - .widget_state_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let new_rect = target_rect.with_origin(target_rect.origin() + state.origin.to_vec2()); - parent_ctx.request_pan_to_child = Some(new_rect); - } -} - -#[cfg(FALSE)] -fn pan_to_child(&mut self, parent_ctx: &mut EventCtx, rect: Rect) { - let id = self.id().to_raw(); - let (widget, widget_token) = parent_ctx - .widget_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let (state, state_token) = parent_ctx - .widget_state_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let mut inner_ctx = LifeCycleCtx { - global_state: parent_ctx.global_state, - widget_state: state, - widget_state_children: state_token, - widget_children: widget_token, - }; - let event = LifeCycle::RequestPanToChild(rect); - - widget.lifecycle(&mut inner_ctx, &event); -} diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index 45973b141..df6ddd559 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -4,11 +4,11 @@ use std::collections::HashSet; use cursor_icon::CursorIcon; -use tracing::trace; +use tracing::{info_span, trace}; use crate::passes::merge_state_up; use crate::render_root::{RenderRoot, RenderRootSignal}; -use crate::{LifeCycleCtx, StatusChange, Widget, WidgetId, WidgetState}; +use crate::{LifeCycle, LifeCycleCtx, StatusChange, Widget, WidgetId, WidgetState}; fn get_id_path(root: &RenderRoot, widget_id: Option) -> Vec { let Some(widget_id) = widget_id else { @@ -145,3 +145,26 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot, root_state: &mut Wi // Pass root widget state to synthetic state create at beginning of pass root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item); } + +// ---------------- + +pub(crate) fn run_update_scroll_pass(root: &mut RenderRoot) { + let _span = info_span!("update_scroll").entered(); + + let scroll_request_targets = std::mem::take(&mut root.state.scroll_request_targets); + for (target, rect) in scroll_request_targets { + let mut target_rect = rect; + + run_targeted_update_pass(root, Some(target), |widget, ctx| { + let event = LifeCycle::RequestPanToChild(rect); + widget.lifecycle(ctx, &event); + + // TODO - We should run the compose method after this, so + // translations are updated and the rect passed to parents + // is more accurate. + + let state = &ctx.widget_state; + target_rect = target_rect + state.translation + state.origin.to_vec2(); + }); + } +} diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index 61e25ad72..017ae1838 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -7,7 +7,7 @@ use accesskit::{ActionRequest, Tree, TreeUpdate}; use parley::fontique::{self, Collection, CollectionOptions}; use parley::{FontContext, LayoutContext}; use tracing::{info_span, warn}; -use vello::kurbo::{self, Point}; +use vello::kurbo::{self, Point, Rect}; use vello::Scene; #[cfg(not(target_arch = "wasm32"))] @@ -24,7 +24,7 @@ use crate::passes::compose::root_compose; use crate::passes::event::{root_on_access_event, root_on_pointer_event, root_on_text_event}; use crate::passes::mutate::{mutate_widget, run_mutate_pass}; use crate::passes::paint::root_paint; -use crate::passes::update::run_update_pointer_pass; +use crate::passes::update::{run_update_pointer_pass, run_update_scroll_pass}; use crate::text::TextBrush; use crate::tree_arena::TreeArena; use crate::widget::WidgetArena; @@ -55,11 +55,13 @@ pub struct RenderRoot { pub(crate) widget_arena: WidgetArena, } +// TODO - Document these fields. pub(crate) struct RenderRootState { pub(crate) debug_logger: DebugLogger, pub(crate) signal_queue: VecDeque, pub(crate) focused_widget: Option, pub(crate) next_focused_widget: Option, + pub(crate) scroll_request_targets: Vec<(WidgetId, Rect)>, pub(crate) hovered_path: Vec, pub(crate) pointer_capture_target: Option, pub(crate) cursor_icon: CursorIcon, @@ -132,6 +134,7 @@ impl RenderRoot { signal_queue: VecDeque::new(), focused_widget: None, next_focused_widget: None, + scroll_request_targets: Vec::new(), hovered_path: Vec::new(), pointer_capture_target: None, cursor_icon: CursorIcon::Default, @@ -547,6 +550,10 @@ impl RenderRoot { self.state.debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32); } + if !self.state.scroll_request_targets.is_empty() { + run_update_scroll_pass(self); + } + if self.root_state().needs_compose && !self.root_state().needs_layout { root_compose(self, widget_state); } diff --git a/masonry/src/widget/portal.rs b/masonry/src/widget/portal.rs index d06b6fa44..03abbbdca 100644 --- a/masonry/src/widget/portal.rs +++ b/masonry/src/widget/portal.rs @@ -141,6 +141,31 @@ impl Portal { false } } + + // Note - Rect is in child coordinates + // TODO - Merge with pan_viewport_to + // Right now these functions are just different enough to be a pain to merge. + pub fn pan_viewport_to_raw( + &mut self, + portal_size: Size, + content_size: Size, + target: Rect, + ) -> bool { + let viewport = Rect::from_origin_size(self.viewport_pos, portal_size); + + let new_pos_x = compute_pan_range( + viewport.min_x()..viewport.max_x(), + target.min_x()..target.max_x(), + ) + .start; + let new_pos_y = compute_pan_range( + viewport.min_y()..viewport.max_y(), + target.min_y()..target.max_y(), + ) + .start; + + self.set_viewport_pos_raw(portal_size, content_size, Point::new(new_pos_x, new_pos_y)) + } } // --- MARK: WIDGETMUT --- @@ -311,8 +336,14 @@ impl Widget for Portal { LifeCycle::WidgetAdded => { ctx.register_as_portal(); } - //TODO - //LifeCycle::RequestPanToChild(target_rect) => {} + LifeCycle::RequestPanToChild(target) => { + let portal_size = ctx.size(); + let content_size = ctx.get_raw_ref(&mut self.child).ctx().layout_rect().size(); + + // FIXME - Update scrollbars + self.pan_viewport_to_raw(portal_size, content_size, *target); + ctx.request_compose(); + } _ => {} } diff --git a/masonry/src/widget/textbox.rs b/masonry/src/widget/textbox.rs index 598ab410e..8a85f2d8e 100644 --- a/masonry/src/widget/textbox.rs +++ b/masonry/src/widget/textbox.rs @@ -207,6 +207,8 @@ impl Widget for Textbox { let result = self.editor.text_event(ctx, event); // If focused on a link and enter pressed, follow it? if result.is_handled() { + // TODO - Use request_scroll_to with cursor rect + ctx.request_scroll_to_this(); ctx.set_handled(); // TODO: only some handlers need this repaint ctx.request_layout();