Refactor rewrite passes (#639)

Create run_rewrite_passes function.
Remove redundant uses of synthetic WidgetState.
Small bugfixes.
Move RenderRoot::root_layout code into the layout pass. Rename
update_new_widgets pass to update_widget_tree.
This commit is contained in:
Olivier FAURE 2024-10-12 16:37:49 +02:00 committed by GitHub
parent 9899a700c7
commit 76c808b454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 134 additions and 250 deletions

View File

@ -235,6 +235,7 @@ impl MasonryState<'_> {
use_system_fonts: true,
size_policy: WindowSizePolicy::User,
scale_factor,
test_font: None,
},
),
renderer: None,

View File

@ -72,11 +72,9 @@ fn compose_widget(
}
// --- MARK: ROOT ---
pub(crate) fn root_compose(root: &mut RenderRoot, global_root_state: &mut WidgetState) {
pub(crate) fn root_compose(root: &mut RenderRoot) {
let _span = info_span!("compose").entered();
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
compose_widget(&mut root.state, root_widget, root_state, false, Vec2::ZERO);
global_root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
}

View File

@ -8,9 +8,7 @@ use winit::keyboard::{KeyCode, PhysicalKey};
use crate::passes::merge_state_up;
use crate::render_root::RenderRoot;
use crate::{
AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId, WidgetState,
};
use crate::{AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId};
// --- MARK: HELPERS ---
fn get_target_widget(
@ -35,7 +33,6 @@ fn get_target_widget(
fn run_event_pass<E>(
root: &mut RenderRoot,
root_state: &mut WidgetState,
target: Option<WidgetId>,
event: &E,
allow_pointer_capture: bool,
@ -74,20 +71,13 @@ fn run_event_pass<E>(
target_widget_id = parent_id;
}
// Merge root widget state with synthetic state created at beginning of pass
root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
Handled::from(is_handled)
}
// TODO - Send synthetic MouseLeave events
// --- MARK: POINTER_EVENT ---
pub(crate) fn root_on_pointer_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,
event: &PointerEvent,
) -> Handled {
pub(crate) fn root_on_pointer_event(root: &mut RenderRoot, event: &PointerEvent) -> Handled {
let _span = info_span!("pointer_event").entered();
if !event.is_high_density() {
debug!("Running ON_POINTER_EVENT pass with {}", event.short_name());
@ -99,7 +89,6 @@ pub(crate) fn root_on_pointer_event(
let handled = run_event_pass(
root,
root_state,
target_widget_id,
event,
matches!(event, PointerEvent::PointerDown(..)),
@ -135,11 +124,7 @@ pub(crate) fn root_on_pointer_event(
// - If a Widget has focus, then none of its parents is hidden
// --- MARK: TEXT EVENT ---
pub(crate) fn root_on_text_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,
event: &TextEvent,
) -> Handled {
pub(crate) fn root_on_text_event(root: &mut RenderRoot, event: &TextEvent) -> Handled {
let _span = info_span!("text_event").entered();
if !event.is_high_density() {
debug!("Running ON_TEXT_EVENT pass with {}", event.short_name());
@ -147,16 +132,9 @@ pub(crate) fn root_on_text_event(
let target = root.state.focused_widget;
let mut handled = run_event_pass(
root,
root_state,
target,
event,
false,
|widget, ctx, event| {
widget.on_text_event(ctx, event);
},
);
let mut handled = run_event_pass(root, target, event, false, |widget, ctx, event| {
widget.on_text_event(ctx, event);
});
// Handle Tab focus
if let TextEvent::KeyboardKey(key, mods) = event {
@ -185,26 +163,15 @@ pub(crate) fn root_on_text_event(
}
// --- MARK: ACCESS EVENT ---
pub(crate) fn root_on_access_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,
event: &AccessEvent,
) -> Handled {
pub(crate) fn root_on_access_event(root: &mut RenderRoot, event: &AccessEvent) -> Handled {
let _span = info_span!("access_event").entered();
debug!("Running ON_ACCESS_EVENT pass with {}", event.short_name());
let target = Some(event.target);
let mut handled = run_event_pass(
root,
root_state,
target,
event,
false,
|widget, ctx, event| {
widget.on_access_event(ctx, event);
},
);
let mut handled = run_event_pass(root, target, event, false, |widget, ctx, event| {
widget.on_access_event(ctx, event);
});
// Handle focus events
match event.action {

View File

@ -5,11 +5,12 @@
//! before any translations applied in [`compose`](crate::passes::compose).
//! Most of the logic for this pass happens in [`Widget::layout`] implementations.
use dpi::LogicalSize;
use smallvec::SmallVec;
use tracing::{info_span, trace};
use vello::kurbo::{Point, Rect, Size};
use crate::render_root::RenderRoot;
use crate::render_root::{RenderRoot, RenderRootSignal, WindowSizePolicy};
use crate::tree_arena::ArenaRefChildren;
use crate::widget::WidgetState;
use crate::{BoxConstraints, LayoutCtx, Widget, WidgetPod};
@ -313,24 +314,37 @@ pub(crate) fn run_layout_on<W: Widget>(
}
// --- MARK: ROOT ---
pub(crate) fn root_layout(
root: &mut RenderRoot,
synthetic_root_state: &mut WidgetState,
bc: &BoxConstraints,
) -> Size {
pub(crate) fn root_layout(root: &mut RenderRoot) {
if !root.root_state().needs_layout {
return;
}
let _span = info_span!("layout").entered();
let window_size = root.get_kurbo_size();
let bc = match root.size_policy {
WindowSizePolicy::User => BoxConstraints::tight(window_size),
WindowSizePolicy::Content => BoxConstraints::UNBOUNDED,
};
let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size());
let root_state_token = root.widget_arena.widget_states.root_token_mut();
let root_widget_token = root.widget_arena.widgets.root_token_mut();
let mut ctx = LayoutCtx {
global_state: &mut root.state,
widget_state: synthetic_root_state,
widget_state: &mut dummy_state,
widget_state_children: root_state_token,
widget_children: root_widget_token,
};
let size = run_layout_on(&mut ctx, &mut root.root, bc);
let size = run_layout_on(&mut ctx, &mut root.root, &bc);
ctx.place_child(&mut root.root, Point::ORIGIN);
size
if let WindowSizePolicy::Content = root.size_policy {
let new_size = LogicalSize::new(size.width, size.height).to_physical(root.scale_factor);
if root.size != new_size {
root.size = new_size;
root.state.emit_signal(RenderRootSignal::SetSize(new_size));
}
}
}

View File

@ -6,7 +6,7 @@ use tracing::info_span;
use crate::passes::merge_state_up;
use crate::render_root::RenderRoot;
use crate::widget::WidgetMut;
use crate::{MutateCtx, Widget, WidgetId, WidgetState};
use crate::{MutateCtx, Widget, WidgetId};
pub(crate) fn mutate_widget<R>(
root: &mut RenderRoot,
@ -44,15 +44,9 @@ pub(crate) fn mutate_widget<R>(
// TODO - Add link to mutate pass documentation
/// Apply any deferred mutations (created using [`...Ctx::mutate_later`](crate::LayoutCtx::mutate_later)).
pub(crate) fn run_mutate_pass(root: &mut RenderRoot, root_state: &mut WidgetState) {
// TODO - Factor out into a "pre-event" function?
// root.state.next_focused_widget = root.state.focused_widget;
pub(crate) fn run_mutate_pass(root: &mut RenderRoot) {
let callbacks = std::mem::take(&mut root.state.mutate_callbacks);
for callback in callbacks {
mutate_widget(root, callback.id, callback.callback);
}
root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
// root.post_event_processing(&mut root_state);
}

View File

@ -77,17 +77,15 @@ fn run_single_update_pass(
}
}
// ----------------
// --- MARK: UPDATE POINTER ---
pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot, root_state: &mut WidgetState) {
pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
let pointer_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into());
// Release pointer capture if target can no longer hold it.
if let Some(id) = root.state.pointer_capture_target {
if !root.is_still_interactive(id) {
root.state.pointer_capture_target = None;
root_on_pointer_event(root, root_state, &PointerEvent::new_pointer_leave());
root_on_pointer_event(root, &PointerEvent::new_pointer_leave());
}
}
@ -180,15 +178,12 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot, root_state: &mut Wi
root.state.cursor_icon = new_cursor;
root.state.hovered_path = next_hovered_path;
// Merge root widget state with synthetic state created at beginning of pass
root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
}
// ----------------
// --- MARK: UPDATE FOCUS ---
pub(crate) fn run_update_focus_pass(root: &mut RenderRoot, root_state: &mut WidgetState) {
pub(crate) fn run_update_focus_pass(root: &mut RenderRoot) {
// If the focused widget is disabled, stashed or removed, we set
// the focused id to None
if let Some(id) = root.state.next_focused_widget {
@ -282,9 +277,6 @@ pub(crate) fn run_update_focus_pass(root: &mut RenderRoot, root_state: &mut Widg
root.state.focused_widget = root.state.next_focused_widget;
root.state.focused_path = next_focused_path;
// Merge root widget state with synthetic state created at beginning of pass
root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
}
// ----------------
@ -492,7 +484,7 @@ pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) {
// ----------------
// --- MARK: UPDATE TREE ---
fn update_new_widgets(
fn update_widget_tree(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
@ -570,13 +562,13 @@ fn update_new_widgets(
widget.reborrow_mut(),
state.children,
|widget, mut state| {
update_new_widgets(global_state, widget, state.reborrow_mut());
update_widget_tree(global_state, widget, state.reborrow_mut());
parent_state.merge_up(state.item);
},
);
}
pub(crate) fn run_update_new_widgets_pass(root: &mut RenderRoot) {
pub(crate) fn run_update_widget_tree_pass(root: &mut RenderRoot) {
let _span = info_span!("update_new_widgets").entered();
if root.root.incomplete() {
@ -590,7 +582,7 @@ pub(crate) fn run_update_new_widgets_pass(root: &mut RenderRoot) {
}
let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id());
update_new_widgets(&mut root.state, root_widget, root_state.reborrow_mut());
update_widget_tree(&mut root.state, root_widget, root_state.reborrow_mut());
}
// ----------------

View File

@ -27,16 +27,14 @@ use crate::passes::paint::root_paint;
use crate::passes::recurse_on_children;
use crate::passes::update::{
run_update_anim_pass, run_update_disabled_pass, run_update_focus_chain_pass,
run_update_focus_pass, run_update_new_widgets_pass, run_update_pointer_pass,
run_update_scroll_pass, run_update_stashed_pass,
run_update_focus_pass, run_update_pointer_pass, run_update_scroll_pass,
run_update_stashed_pass, run_update_widget_tree_pass,
};
use crate::text::TextBrush;
use crate::tree_arena::{ArenaMut, TreeArena};
use crate::widget::WidgetArena;
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
use crate::{
AccessEvent, Action, BoxConstraints, CursorIcon, Handled, QueryCtx, Widget, WidgetId, WidgetPod,
};
use crate::{AccessEvent, Action, CursorIcon, Handled, QueryCtx, Widget, WidgetId, WidgetPod};
// --- MARK: STRUCTS ---
@ -98,6 +96,15 @@ pub struct RenderRootOptions {
pub use_system_fonts: bool,
pub size_policy: WindowSizePolicy,
pub scale_factor: f64,
/// Add a font from its raw data for use in tests.
/// The font is added to the fallback chain for Latin scripts.
/// This is expected to be used with `use_system_fonts = false`
/// to ensure rendering is consistent cross-platform.
///
/// We expect to develop a much more fully-featured font API in the future, but
/// this is necessary for our testing of Masonry.
pub test_font: Option<Vec<u8>>,
}
pub enum RenderRootSignal {
@ -120,6 +127,7 @@ impl RenderRoot {
use_system_fonts,
size_policy,
scale_factor,
test_font,
}: RenderRootOptions,
) -> Self {
let mut root = RenderRoot {
@ -159,21 +167,23 @@ impl RenderRoot {
rebuild_access_tree: true,
};
// We run a set of passes to initialize the widget tree
run_update_new_widgets_pass(&mut root);
// TODO - Remove this line
let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size());
root.post_event_processing(&mut dummy_state);
// We run a layout pass right away to have a SetSize signal ready
if size_policy == WindowSizePolicy::Content {
root.root_layout();
if let Some(test_font_data) = test_font {
let families = root.register_fonts(test_font_data);
// Make sure that all of these fonts are in the fallback chain for the Latin script.
// <https://en.wikipedia.org/wiki/Script_(Unicode)#Latn>
root.state
.font_context
.collection
.append_fallbacks(*b"Latn", families.iter().map(|(family, _)| *family));
}
// We run a set of passes to initialize the widget tree
root.run_rewrite_passes();
root
}
fn root_state(&mut self) -> &mut WidgetState {
pub(crate) fn root_state(&mut self) -> &mut WidgetState {
self.widget_arena
.widget_states
.root_token_mut()
@ -192,8 +202,9 @@ impl RenderRoot {
}
WindowEvent::Resize(size) => {
self.size = size;
self.root_state().request_layout = true;
self.root_state().needs_layout = true;
self.state.emit_signal(RenderRootSignal::RequestRedraw);
self.run_rewrite_passes();
Handled::Yes
}
WindowEvent::AnimFrame => {
@ -205,7 +216,8 @@ impl RenderRoot {
let last = self.last_anim.take();
let elapsed_ns = last.map(|t| now.duration_since(t).as_nanos()).unwrap_or(0) as u64;
self.root_anim_frame(elapsed_ns);
run_update_anim_pass(self, elapsed_ns);
self.run_rewrite_passes();
// If this animation will continue, store the time.
// If a new animation starts, then it will have zero reported elapsed time.
@ -231,13 +243,6 @@ impl RenderRoot {
self.root_on_text_event(event)
}
pub(crate) fn root_anim_frame(&mut self, elapsed_ns: u64) {
run_update_anim_pass(self, elapsed_ns);
let mut root_state = self.widget_arena.get_state_mut(self.root.id()).item.clone();
self.post_event_processing(&mut root_state);
}
/// Registers all fonts that exist in the given data.
///
/// Returns a list of pairs each containing the family identifier and fonts
@ -249,31 +254,10 @@ impl RenderRoot {
self.state.font_context.collection.register_fonts(data)
}
/// Add a font from its raw data for use in tests.
/// The font is added to the fallback chain for Latin scripts.
/// This is expected to be used with
/// [`RenderRootOptions.use_system_fonts = false`](RenderRootOptions::use_system_fonts)
/// to ensure rendering is consistent cross-platform.
///
/// We expect to develop a much more fully-featured font API in the future, but
/// this is necessary for our testing of Masonry.
pub fn add_test_font(
&mut self,
data: Vec<u8>,
) -> Vec<(fontique::FamilyId, Vec<fontique::FontInfo>)> {
let families = self.register_fonts(data);
// Make sure that all of these fonts are in the fallback chain for the Latin script.
// <https://en.wikipedia.org/wiki/Script_(Unicode)#Latn>
self.state
.font_context
.collection
.append_fallbacks(*b"Latn", families.iter().map(|(family, _)| *family));
families
}
pub fn redraw(&mut self) -> (Scene, TreeUpdate) {
if self.root_state().needs_layout {
self.root_layout();
// TODO - Rewrite more clearly after run_rewrite_passes is rewritten
self.run_rewrite_passes();
}
if self.root_state().needs_layout {
warn!("Widget requested layout during layout pass");
@ -379,8 +363,7 @@ impl RenderRoot {
f(widget_mut)
});
let mut root_state = self.widget_arena.get_state_mut(self.root.id()).item.clone();
self.post_event_processing(&mut root_state);
self.run_rewrite_passes();
res
}
@ -395,20 +378,17 @@ impl RenderRoot {
) -> R {
let res = mutate_widget(self, id, f);
let mut root_state = self.widget_arena.get_state_mut(self.root.id()).item.clone();
self.post_event_processing(&mut root_state);
self.run_rewrite_passes();
res
}
// --- MARK: POINTER_EVENT ---
fn root_on_pointer_event(&mut self, event: PointerEvent) -> Handled {
let mut dummy_state = WidgetState::synthetic(self.root.id(), self.get_kurbo_size());
let handled = root_on_pointer_event(self, &event);
run_update_pointer_pass(self);
let handled = root_on_pointer_event(self, &mut dummy_state, &event);
run_update_pointer_pass(self, &mut dummy_state);
self.post_event_processing(&mut dummy_state);
self.run_rewrite_passes();
self.get_root_widget().debug_validate(false);
handled
@ -416,16 +396,14 @@ impl RenderRoot {
// --- MARK: TEXT_EVENT ---
fn root_on_text_event(&mut self, event: TextEvent) -> Handled {
let mut dummy_state = WidgetState::synthetic(self.root.id(), self.get_kurbo_size());
if matches!(event, TextEvent::FocusChange(false)) {
root_on_pointer_event(self, &mut dummy_state, &PointerEvent::new_pointer_leave());
root_on_pointer_event(self, &PointerEvent::new_pointer_leave());
}
let handled = root_on_text_event(self, &mut dummy_state, &event);
run_update_focus_pass(self, &mut dummy_state);
let handled = root_on_text_event(self, &event);
run_update_focus_pass(self);
self.post_event_processing(&mut dummy_state);
self.run_rewrite_passes();
self.get_root_widget().debug_validate(false);
handled
@ -433,8 +411,6 @@ impl RenderRoot {
// --- MARK: ACCESS_EVENT ---
pub fn root_on_access_event(&mut self, event: ActionRequest) {
let mut dummy_state = WidgetState::synthetic(self.root.id(), self.get_kurbo_size());
let Ok(id) = event.target.0.try_into() else {
warn!("Received ActionRequest with id 0. This shouldn't be possible.");
return;
@ -445,36 +421,12 @@ impl RenderRoot {
data: event.data,
};
root_on_access_event(self, &mut dummy_state, &event);
root_on_access_event(self, &event);
self.post_event_processing(&mut dummy_state);
self.run_rewrite_passes();
self.get_root_widget().debug_validate(false);
}
// --- MARK: LAYOUT ---
pub(crate) fn root_layout(&mut self) {
let window_size = self.get_kurbo_size();
let bc = match self.size_policy {
WindowSizePolicy::User => BoxConstraints::tight(window_size),
WindowSizePolicy::Content => BoxConstraints::UNBOUNDED,
};
let mut dummy_state = WidgetState::synthetic(self.root.id(), self.get_kurbo_size());
let size = root_layout(self, &mut dummy_state, &bc);
if let WindowSizePolicy::Content = self.size_policy {
let new_size = LogicalSize::new(size.width, size.height).to_physical(self.scale_factor);
if self.size != new_size {
self.size = new_size;
self.state.emit_signal(RenderRootSignal::SetSize(new_size));
}
}
run_update_pointer_pass(self, &mut dummy_state);
self.post_event_processing(&mut dummy_state);
}
// --- MARK: PAINT ---
fn root_paint(&mut self) -> Scene {
root_paint(self)
@ -501,44 +453,33 @@ impl RenderRoot {
kurbo::Size::new(size.width, size.height)
}
// --- MARK: POST-EVENT ---
fn post_event_processing(&mut self, widget_state: &mut WidgetState) {
// If children are changed during the handling of an event,
// we need to send RouteWidgetAdded now, so that they are ready for update/layout.
if widget_state.children_changed {
run_update_new_widgets_pass(self);
}
// --- MARK: REWRITE PASSES ---
/// Run all rewrite passes on widget tree.
///
/// Rewrite passes are passes which occur after external events, and
/// update flags and internal values to a consistent state.
///
/// See Pass Spec RFC for details. (TODO - Link to doc instead.)
pub(crate) fn run_rewrite_passes(&mut self) {
// TODO - Rerun passes if invalidation flags are still set
if self.state.debug_logger.layout_tree.root.is_none() {
self.state.debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32);
}
// Note: this code doesn't do any short-circuiting, because each pass is
// expected to have its own early exits.
// Calling a run_xxx_pass (or root_xxx) should always be very fast if
// the pass doesn't need to do anything.
if !self.state.scroll_request_targets.is_empty() {
run_update_scroll_pass(self);
}
run_mutate_pass(self);
run_update_widget_tree_pass(self);
run_update_disabled_pass(self);
run_update_stashed_pass(self);
run_update_focus_chain_pass(self);
run_update_focus_pass(self);
root_layout(self);
run_update_scroll_pass(self);
root_compose(self);
run_update_pointer_pass(self);
if self.root_state().needs_compose && !self.root_state().needs_layout {
root_compose(self, widget_state);
}
// Update the disabled and stashed state if necessary
// Always do this before updating the focus-chain
if self.root_state().needs_update_disabled {
run_update_disabled_pass(self);
}
if self.root_state().needs_update_stashed {
run_update_stashed_pass(self);
}
// Update the focus-chain if necessary
// Always do this before sending focus change, since this event updates the focus chain.
if self.root_state().update_focus_chain {
run_update_focus_chain_pass(self);
}
run_update_focus_pass(self, widget_state);
if self.root_state().request_anim {
if self.root_state().needs_anim {
self.state.emit_signal(RenderRootSignal::RequestAnimFrame);
}
@ -552,8 +493,6 @@ impl RenderRoot {
{
self.state.emit_signal(RenderRootSignal::RequestRedraw);
}
run_mutate_pass(self, widget_state);
}
pub(crate) fn request_render_all(&mut self) {
@ -579,6 +518,7 @@ impl RenderRoot {
let (root_widget, mut root_state) = self.widget_arena.get_pair_mut(self.root.id());
request_render_all_in(root_widget, root_state.reborrow_mut());
self.state.emit_signal(RenderRootSignal::RequestRedraw);
}
// Checks whether the given id points to a widget that is "interactive".

View File

@ -18,6 +18,7 @@ use winit::event::Ime;
use crate::action::Action;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::event::{PointerButton, PointerEvent, PointerState, TextEvent, WindowEvent};
use crate::passes::update::run_update_anim_pass;
use crate::render_root::{RenderRoot, RenderRootOptions, RenderRootSignal, WindowSizePolicy};
use crate::testing::{screenshots::get_image_diff, snapshot_utils::get_cargo_workspace};
use crate::tracing_backend::try_init_test_tracing;
@ -66,15 +67,9 @@ pub const HARNESS_DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29
///
/// **(TODO - Painting invalidation might not be accurate.)**
///
/// One minor difference is that layout is always calculated after every event, whereas
/// in normal execution it is only calculated before paint. This might be create subtle
/// differences in cases where timers are programmed to fire at the same time: in normal
/// execution, they'll execute back-to-back; in the harness, they'll be separated with
/// layout calls.
///
/// Also, paint only happens when the user explicitly calls rendering methods, whereas in
/// a normal applications you could reasonably expect multiple paint calls between eg any
/// two clicks.
/// One minor difference is that paint only happens when the user explicitly calls rendering
/// methods, whereas in a normal applications you could reasonably expect multiple paint calls
/// between eg any two clicks.
///
/// ## Example
///
@ -179,6 +174,12 @@ impl TestHarness {
// harnesses.
let _ = try_init_test_tracing();
const ROBOTO: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/fonts/roboto/Roboto-Regular.ttf"
));
let data = ROBOTO.to_vec();
let mut harness = TestHarness {
render_root: RenderRoot::new(
root_widget,
@ -186,18 +187,13 @@ impl TestHarness {
use_system_fonts: false,
size_policy: WindowSizePolicy::User,
scale_factor: 1.0,
test_font: Some(data),
},
),
mouse_state,
window_size,
background_color,
};
const ROBOTO: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/fonts/roboto/Roboto-Regular.ttf"
));
let data = ROBOTO.to_vec();
harness.render_root.add_test_font(data);
harness.process_window_event(WindowEvent::Resize(window_size));
harness
@ -212,9 +208,7 @@ impl TestHarness {
/// as will any resulting commands. Commands created as a result of this event
/// will also be dispatched.
pub fn process_window_event(&mut self, event: WindowEvent) -> Handled {
let handled = self.render_root.handle_window_event(event);
self.process_state_after_event();
handled
self.render_root.handle_window_event(event)
}
/// Send an event to the widget.
@ -223,9 +217,7 @@ impl TestHarness {
/// as will any resulting commands. Commands created as a result of this event
/// will also be dispatched.
pub fn process_pointer_event(&mut self, event: PointerEvent) -> Handled {
let handled = self.render_root.handle_pointer_event(event);
self.process_state_after_event();
handled
self.render_root.handle_pointer_event(event)
}
/// Send an event to the widget.
@ -234,15 +226,7 @@ impl TestHarness {
/// as will any resulting commands. Commands created as a result of this event
/// will also be dispatched.
pub fn process_text_event(&mut self, event: TextEvent) -> Handled {
let handled = self.render_root.handle_text_event(event);
self.process_state_after_event();
handled
}
fn process_state_after_event(&mut self) {
if self.root_widget().ctx.widget_state.needs_layout {
self.render_root.root_layout();
}
self.render_root.handle_text_event(event)
}
// --- MARK: RENDER ---
@ -415,24 +399,18 @@ impl TestHarness {
let event = TextEvent::Ime(Ime::Commit(c.to_string()));
self.render_root.handle_text_event(event);
}
self.process_state_after_event();
}
pub fn focus_on(&mut self, id: Option<WidgetId>) {
self.render_root.state.next_focused_widget = id;
// FIXME - Change this once run_rewrite_passes is merged
let mut dummy_state = crate::WidgetState::synthetic(
self.render_root.root.id(),
self.render_root.get_kurbo_size(),
);
crate::passes::update::run_update_focus_pass(&mut self.render_root, &mut dummy_state);
self.render_root.run_rewrite_passes();
}
// TODO - Fold into move_timers_forward
/// Send animation events to the widget tree
pub fn animate_ms(&mut self, ms: u64) {
self.render_root.root_anim_frame(ms * 1_000_000);
self.process_state_after_event();
run_update_anim_pass(&mut self.render_root, ms * 1_000_000);
self.render_root.run_rewrite_passes();
}
#[cfg(FALSE)]
@ -521,9 +499,7 @@ impl TestHarness {
&mut self,
f: impl FnOnce(WidgetMut<'_, Box<dyn Widget>>) -> R,
) -> R {
let res = self.render_root.edit_root_widget(f);
self.process_state_after_event();
res
self.render_root.edit_root_widget(f)
}
/// Get a [`WidgetMut`] to a specific widget.
@ -534,9 +510,7 @@ impl TestHarness {
id: WidgetId,
f: impl FnOnce(WidgetMut<'_, Box<dyn Widget>>) -> R,
) -> R {
let res = self.render_root.edit_widget(id, f);
self.process_state_after_event();
res
self.render_root.edit_widget(id, f)
}
/// Pop next action from the queue

View File

@ -11,6 +11,10 @@ expression: record
L(
BuildFocusChain,
),
Layout(
0.0W×0.0H,
),
Compose,
Layout(
400.0W×400.0H,
),