diff --git a/masonry/src/action.rs b/masonry/src/action.rs index 29cce6754..63577df19 100644 --- a/masonry/src/action.rs +++ b/masonry/src/action.rs @@ -14,11 +14,16 @@ use crate::event::PointerButton; /// /// Note: Actions are still a WIP feature. pub enum Action { + /// A button was pressed. ButtonPressed(PointerButton), + /// Text changed. TextChanged(String), + /// Text entered. TextEntered(String), + /// A checkbox was checked. CheckboxChecked(bool), // FIXME - This is a huge hack + /// Other. Other(Box), } diff --git a/masonry/src/app_driver.rs b/masonry/src/app_driver.rs index 936b9b1d7..f44747510 100644 --- a/masonry/src/app_driver.rs +++ b/masonry/src/app_driver.rs @@ -4,11 +4,19 @@ use crate::event_loop_runner::MasonryState; use crate::{Action, RenderRoot, WidgetId}; +/// Context for the [`AppDriver`] trait. +/// +/// Currently holds a reference to the [`RenderRoot`]. pub struct DriverCtx<'a> { pub(crate) render_root: &'a mut RenderRoot, } +/// A trait for defining how your app interacts with the Masonry widget tree. +/// +/// When launching your app with [`crate::event_loop_runner::run`], you need to provide +/// a type that implements this trait. pub trait AppDriver { + /// A hook which will be executed when a widget emits an [`Action`]. fn on_action(&mut self, ctx: &mut DriverCtx<'_>, widget_id: WidgetId, action: Action); #[allow(unused_variables)] @@ -27,6 +35,7 @@ impl DriverCtx<'_> { self.render_root } + /// Returns true if something happened that requires a rewrite pass or a re-render. pub fn content_changed(&self) -> bool { self.render_root.needs_rewrite_passes() } diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index fb5c676e7..0487f2100 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -44,9 +44,7 @@ macro_rules! impl_context_method { /// When you declare a mutable reference type for your widget, methods of this type /// will have access to a `MutateCtx`. If that method mutates the widget in a way that /// requires a later pass (for instance, if your widget has a `set_color` method), -/// you will need to signal that change in the pass (eg `request_paint`). -/// -// TODO add tutorial - See https://github.com/linebender/xilem/issues/376 +/// you will need to signal that change in the pass (eg [`request_render`](MutateCtx::request_render)). pub struct MutateCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) parent_widget_state: Option<&'a mut WidgetState>, @@ -55,7 +53,9 @@ pub struct MutateCtx<'a> { pub(crate) widget_children: ArenaMutChildren<'a, Box>, } -/// A context provided to methods of widgets requiring shared, read-only access. +/// A context provided inside of [`WidgetRef`]. +/// +/// This context is passed to methods of widgets requiring shared, read-only access. #[derive(Clone, Copy)] pub struct QueryCtx<'a> { pub(crate) global_state: &'a RenderRootState, @@ -64,10 +64,7 @@ pub struct QueryCtx<'a> { pub(crate) widget_children: ArenaRefChildren<'a, Box>, } -/// A context provided to event handling methods of widgets. -/// -/// Widgets should call [`request_paint`](Self::request_paint) whenever an event causes a change -/// in the widget's appearance, to schedule a repaint. +/// A context provided to Widget event-handling methods. pub struct EventCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, @@ -78,7 +75,7 @@ pub struct EventCtx<'a> { pub(crate) is_handled: bool, } -/// A context provided to the [`Widget::register_children`] method on widgets. +/// A context provided to the [`Widget::register_children`] method. pub struct RegisterCtx<'a> { pub(crate) widget_state_children: ArenaMutChildren<'a, WidgetState>, pub(crate) widget_children: ArenaMutChildren<'a, Box>, @@ -86,9 +83,7 @@ pub struct RegisterCtx<'a> { pub(crate) registered_ids: Vec, } -/// A context provided to the [`update`] method on widgets. -/// -/// [`update`]: Widget::update +/// A context provided to the [`Widget::update`] method. pub struct UpdateCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, @@ -96,11 +91,8 @@ pub struct UpdateCtx<'a> { pub(crate) widget_children: ArenaMutChildren<'a, Box>, } -/// A context provided to layout handling methods of widgets. -/// -/// As of now, the main service provided is access to a factory for -/// creating text layout objects, which are likely to be useful -/// during widget layout. +// TODO - Change this once other layout methods are added. +/// A context provided to [`Widget::layout`] methods. pub struct LayoutCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, @@ -108,6 +100,7 @@ pub struct LayoutCtx<'a> { pub(crate) widget_children: ArenaMutChildren<'a, Box>, } +/// A context provided to the [`Widget::compose`] method. pub struct ComposeCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a mut WidgetState, @@ -115,7 +108,7 @@ pub struct ComposeCtx<'a> { pub(crate) widget_children: ArenaMutChildren<'a, Box>, } -/// A context passed to paint methods of widgets. +/// A context passed to [`Widget::paint`] method. pub struct PaintCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a WidgetState, @@ -124,6 +117,7 @@ pub struct PaintCtx<'a> { pub(crate) debug_paint: bool, } +/// A context passed to [`Widget::accessibility`] method. pub struct AccessCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, pub(crate) widget_state: &'a WidgetState, @@ -145,12 +139,12 @@ impl_context_method!( PaintCtx<'_>, AccessCtx<'_>, { - /// get the `WidgetId` of the current widget. + /// The `WidgetId` of the current widget. pub fn widget_id(&self) -> WidgetId { self.widget_state.id } - #[allow(dead_code)] + #[allow(dead_code, reason = "Copy-pasted for some types that don't need it")] /// Helper method to get a direct reference to a child widget from its `WidgetPod`. fn get_child(&self, child: &'_ WidgetPod) -> &'_ Child { let child_ref = self @@ -160,7 +154,7 @@ impl_context_method!( child_ref.item.as_dyn_any().downcast_ref::().unwrap() } - #[allow(dead_code)] + #[allow(dead_code, reason = "Copy-pasted for some types that don't need it")] /// Helper method to get a direct reference to a child widget's `WidgetState` from its `WidgetPod`. fn get_child_state(&self, child: &'_ WidgetPod) -> &'_ WidgetState { let child_state_ref = self @@ -293,12 +287,9 @@ impl_context_method!( // --- MARK: EVENT HANDLING --- impl EventCtx<'_> { - // TODO - clearly document all semantics of pointer capture when they've been decided on - // TODO - Figure out cases where widget should be notified of pointer capture - // loss /// Capture the pointer in the current widget. /// - /// Pointer capture is only allowed during a [`PointerDown`] event. It is a logic error to + /// [Pointer capture] is only allowed during a [`PointerDown`] event. It is a logic error to /// capture the pointer during any other event. /// /// A widget normally only receives pointer events when the pointer is inside the widget's @@ -318,6 +309,10 @@ impl EventCtx<'_> { /// released after handling of a [`PointerUp`] or [`PointerLeave`] event completes. A widget /// holding the pointer capture will be the target of these events. /// + /// If pointer capture is lost for external reasons (the widget is disabled, the window + /// lost focus, etc), the widget will still get a [`PointerLeave`] event. + /// + /// [Pointer capture]: crate::doc::doc_06_masonry_concepts#pointer-capture /// [`PointerDown`]: crate::PointerEvent::PointerDown /// [`PointerUp`]: crate::PointerEvent::PointerUp /// [`PointerLeave`]: crate::PointerEvent::PointerLeave @@ -333,8 +328,9 @@ impl EventCtx<'_> { self.global_state.pointer_capture_target = Some(self.widget_state.id); } - /// Release the pointer previously captured through [`capture_pointer`]. + /// Release the pointer previously [captured] through [`capture_pointer`]. /// + /// [captured]: crate::doc::doc_06_masonry_concepts#pointer-capture /// [`capture_pointer`]: EventCtx::capture_pointer pub fn release_pointer(&mut self) { self.global_state.pointer_capture_target = None; @@ -357,14 +353,14 @@ impl EventCtx<'_> { .push((self.widget_state.id, rect)); } - /// Set the event as "handled", which stops its propagation to other + /// Set the event as "handled", which stops its propagation to parent /// widgets. pub fn set_handled(&mut self) { trace!("set_handled"); self.is_handled = true; } - /// Determine whether the event has been handled by some other widget. + /// Determine whether the event has been handled. pub fn is_handled(&self) -> bool { self.is_handled } @@ -376,13 +372,13 @@ impl EventCtx<'_> { self.target } - /// Request keyboard focus. + /// Request [text focus]. /// /// Because only one widget can be focused at a time, multiple focus requests /// from different widgets during a single event cycle means that the last /// widget that requests focus will override the previous requests. /// - /// See [`is_focused`](Self::is_focused) for more information about focus. + /// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus pub fn request_focus(&mut self) { trace!("request_focus"); // We need to send the request even if we're currently focused, @@ -393,19 +389,19 @@ impl EventCtx<'_> { self.global_state.next_focused_widget = Some(id); } - /// Transfer focus to the widget with the given `WidgetId`. + /// Transfer [text focus] to the widget with the given `WidgetId`. /// - /// See [`is_focused`](Self::is_focused) for more information about focus. + /// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus pub fn set_focus(&mut self, target: WidgetId) { trace!("set_focus target={:?}", target); self.global_state.next_focused_widget = Some(target); } - /// Give up focus. + /// Give up [text focus]. /// /// This should only be called by a widget that currently has focus. /// - /// See [`is_focused`](Self::is_focused) for more information about focus. + /// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus pub fn resign_focus(&mut self) { trace!("resign_focus"); if self.has_focus() { @@ -448,7 +444,7 @@ impl LayoutCtx<'_> { } } - /// Compute layout of a child widget. + /// Compute the layout of a child widget. /// /// Container widgets must call this on every child as part of /// their [`layout`] method. @@ -522,7 +518,7 @@ impl LayoutCtx<'_> { /// /// ## Panics /// - /// This method will panic if the child's [`layout()`](WidgetPod::layout) method has not been called yet + /// This method will panic if the child's [`layout()`](LayoutCtx::run_layout) method has not been called yet /// and if [`LayoutCtx::place_child()`] has not been called for the child. #[track_caller] pub fn compute_insets_from_child( @@ -553,12 +549,12 @@ impl LayoutCtx<'_> { self.widget_state.baseline_offset = baseline; } - /// Returns whether this widget needs to call [`WidgetPod::layout`] + /// Returns whether this widget needs to call [`LayoutCtx::run_layout`]. pub fn needs_layout(&self) -> bool { self.widget_state.needs_layout } - /// Returns whether a child of this widget needs to call [`WidgetPod::layout`] + /// Returns whether a child of this widget needs to call [`LayoutCtx::run_layout`]. pub fn child_needs_layout(&self, child: &WidgetPod) -> bool { self.get_child_state(child).needs_layout } @@ -567,7 +563,7 @@ impl LayoutCtx<'_> { /// /// ## Panics /// - /// This method will panic if [`WidgetPod::layout`] has not been called yet for + /// This method will panic if [`LayoutCtx::run_layout`] has not been called yet for /// the child. #[track_caller] pub fn child_baseline_offset(&self, child: &WidgetPod) -> f64 { @@ -579,7 +575,7 @@ impl LayoutCtx<'_> { /// /// ## Panics /// - /// This method will panic if [`WidgetPod::layout`] and [`LayoutCtx::place_child`] + /// This method will panic if [`LayoutCtx::run_layout`] and [`LayoutCtx::place_child`] /// have not been called yet for the child. #[track_caller] pub fn child_layout_rect(&self, child: &WidgetPod) -> Rect { @@ -592,7 +588,7 @@ impl LayoutCtx<'_> { /// /// ## Panics /// - /// This method will panic if [`WidgetPod::layout`] and [`LayoutCtx::place_child`] + /// This method will panic if [`LayoutCtx::run_layout`] and [`LayoutCtx::place_child`] /// have not been called yet for the child. #[track_caller] pub fn child_paint_rect(&self, child: &WidgetPod) -> Rect { @@ -605,7 +601,7 @@ impl LayoutCtx<'_> { /// /// ## Panics /// - /// This method will panic if [`WidgetPod::layout`] has not been called yet for + /// This method will panic if [`LayoutCtx::run_layout`] has not been called yet for /// the child. #[track_caller] pub fn child_size(&self, child: &WidgetPod) -> Size { @@ -613,7 +609,7 @@ impl LayoutCtx<'_> { self.get_child_state(child).layout_rect().size() } - /// Skips running the layout pass and calling `place_child` on the child. + /// Skips running the layout pass and calling [`LayoutCtx::place_child`] on the child. /// /// This may be removed in the future. Currently it's useful for /// stashed children and children whose layout is cached. @@ -654,6 +650,8 @@ impl LayoutCtx<'_> { } impl ComposeCtx<'_> { + // TODO - Remove? + /// Returns whether [`ComposeCtx::compose`] will be called on this widget. pub fn needs_compose(&self) -> bool { self.widget_state.needs_compose } @@ -701,17 +699,19 @@ impl_context_method!( { /// The layout size. /// - /// This is the layout size as ultimately determined by the parent - /// container, on the previous layout pass. - /// - /// Generally it will be the same as the size returned by the child widget's - /// [`layout`] method. + /// This is the layout size returned by the [`layout`] method on the previous + /// layout pass. /// /// [`layout`]: Widget::layout pub fn size(&self) -> Size { self.widget_state.size } + // TODO - Remove? A widget doesn't really have a concept of its own "origin", + // it's more useful for the parent widget. + /// The layout rect of the widget. + /// + /// This is the layout [size](Self::size) and origin (in the parent's coordinate space) combined. pub fn layout_rect(&self) -> Rect { self.widget_state.layout_rect() } @@ -727,10 +727,17 @@ impl_context_method!( self.widget_state.window_origin() } + /// The layout rect of the widget in window coordinates. + /// + /// Combines the [size](Self::size) and [window origin](Self::window_origin). pub fn window_layout_rect(&self) -> Rect { self.widget_state.window_layout_rect() } + // TODO - Remove? See above. + /// The paint rect of the widget. + /// + /// Covers the area we expect to be invalidated when the widget is painted. pub fn paint_rect(&self) -> Rect { self.widget_state.paint_rect() } @@ -764,29 +771,29 @@ impl_context_method!( PaintCtx<'_>, AccessCtx<'_>, { + // TODO - Update once we introduce the is_hovered/has_hovered distinction. /// The "hovered" status of a widget. /// - /// A widget is "hovered" when the mouse is hovered over it. Widgets will + /// A widget is "hovered" when a pointer is hovering over it. Widgets will /// often change their appearance as a visual indication that they - /// will respond to mouse interaction. + /// will respond to pointer (usually mouse) interaction. /// /// The hovered status is computed from the widget's layout rect. In a /// container hierarchy, all widgets with layout rects containing the - /// mouse position have hovered status. + /// pointer position have hovered status. /// - /// Discussion: there is currently some confusion about whether a - /// widget can be considered hovered when some other widget has captured the - /// pointer (for example, when clicking one widget and dragging to the - /// next). The documentation should clearly state the resolution. + /// If the pointer is [captured], then only that widget and its parents + /// can have hovered status. If the pointer is captured but not hovering + /// over the captured widget, then no widget has the hovered status. + /// + /// [captured]: crate::doc::doc_06_masonry_concepts#pointer-capture pub fn is_hovered(&self) -> bool { self.widget_state.is_hovered } - /// Whether the pointer is captured by this widget. - /// - /// See [`capture_pointer`] for more information about pointer capture. + /// Whether the mouse is [captured] by this widget. /// - /// [`capture_pointer`]: EventCtx::capture_pointer + /// [captured]: crate::doc::doc_06_masonry_concepts#pointer-capture pub fn has_pointer_capture(&self) -> bool { self.global_state.pointer_capture_target == Some(self.widget_state.id) } @@ -814,9 +821,8 @@ impl_context_method!( self.global_state.focused_widget == Some(self.widget_id()) } - /// The (tree) focus status of a widget. + /// Whether this widget or any of its descendants is focused. /// - /// Returns `true` if either this specific widget or any one of its descendants is focused. /// To check if only this specific widget is focused use [`is_focused`](Self::is_focused). pub fn has_focus(&self) -> bool { self.widget_state.has_focus @@ -843,7 +849,7 @@ impl_context_method!( /// To make this widget explicitly disabled use [`set_disabled`]. /// /// Disabled means that this widget should not change the state of the application. What - /// that means is not entirely clear but in any it should not change its data. Therefore + /// that means is not entirely clear but in any case it should not change its data. Therefore /// others can use this as a safety mechanism to prevent the application from entering an /// illegal state. /// For an example the decrease button of a counter of type `usize` should be disabled if the @@ -854,9 +860,9 @@ impl_context_method!( self.widget_state.is_disabled } - /// Check is widget is stashed. + /// Check is widget is [stashed](). /// - /// **Note:** Stashed widgets are a WIP feature. + /// [stashed]: crate::doc::doc_06_masonry_concepts#stashed pub fn is_stashed(&self) -> bool { self.widget_state.is_stashed } @@ -895,14 +901,9 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, { self.widget_state.request_accessibility = true; } - /// Request a layout pass. + /// Request a [`layout`] pass. /// - /// A Widget's [`layout`] method is always called when the widget tree - /// changes, or the window is resized. - /// - /// If your widget would like to have layout called at any other time, - /// (such as if it would like to change the layout of children in - /// response to some event) it must call this method. + /// Call this method if the widget has changed in a way that requires a layout pass. /// /// [`layout`]: crate::Widget::layout pub fn request_layout(&mut self) { @@ -1051,6 +1052,8 @@ impl_context_method!( /// to the platform. The area can be used by the platform to, for example, place a /// candidate box near that area, while ensuring the area is not obscured. /// + /// If no IME area is set, the platform will use the widget's layout rect. + /// /// [focused]: EventCtx::request_focus /// [accepts text input]: Widget::accepts_text_input pub fn set_ime_area(&mut self, ime_area: Rect) { @@ -1244,6 +1247,7 @@ impl_get_raw!(EventCtx); impl_get_raw!(UpdateCtx); impl_get_raw!(LayoutCtx); +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] impl<'s> AccessCtx<'s> { pub fn get_raw_ref<'a, 'r, Child: Widget>( &'a mut self, @@ -1276,17 +1280,20 @@ impl<'s> AccessCtx<'s> { } } +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] pub struct RawWrapper<'a, Ctx, W> { ctx: Ctx, widget: &'a W, } +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] pub struct RawWrapperMut<'a, Ctx: IsContext, W> { parent_widget_state: &'a mut WidgetState, ctx: Ctx, widget: &'a mut W, } +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] impl RawWrapper<'_, Ctx, W> { pub fn widget(&self) -> &W { self.widget @@ -1297,6 +1304,7 @@ impl RawWrapper<'_, Ctx, W> { } } +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] impl RawWrapperMut<'_, Ctx, W> { pub fn widget(&mut self) -> &mut W { self.widget @@ -1315,7 +1323,10 @@ impl Drop for RawWrapperMut<'_, Ctx, W> { } mod private { - #[allow(unnameable_types)] // reason: see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/ + #[allow( + unnameable_types, + reason = "see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/" + )] pub trait Sealed {} } @@ -1323,7 +1334,11 @@ mod private { // We're exporting a trait with a method that returns a private type. // It's mostly fine because the trait is sealed anyway, but it's not great for documentation. -#[allow(private_interfaces)] // reason: see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/ +#[allow( + private_interfaces, + reason = "see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/" +)] +#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")] pub trait IsContext: private::Sealed { fn get_widget_state(&mut self) -> &mut WidgetState; } @@ -1332,7 +1347,10 @@ macro_rules! impl_context_trait { ($SomeCtx:tt) => { impl private::Sealed for $SomeCtx<'_> {} - #[allow(private_interfaces)] // reason: see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/ + #[allow( + private_interfaces, + reason = "see https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/" + )] impl IsContext for $SomeCtx<'_> { fn get_widget_state(&mut self) -> &mut WidgetState { self.widget_state diff --git a/masonry/src/event.rs b/masonry/src/event.rs index 165325557..3deade4da 100644 --- a/masonry/src/event.rs +++ b/masonry/src/event.rs @@ -19,11 +19,17 @@ use crate::kurbo::Rect; // TODO - switch anim frames to being about age / an absolute timestamp // instead of time elapsed. // (this will help in cases where we want to skip anim frames) + +/// A global event. #[derive(Debug, Clone)] pub enum WindowEvent { + /// The window's DPI factor changed. Rescale(f64), + /// The window was resized. Resize(PhysicalSize), + /// The animation frame requested by this window must run. AnimFrame, + /// The accessibility tree must be rebuilt. RebuildAccessTree, } @@ -181,53 +187,96 @@ impl From for PointerButtons { // TODO - How can RenderRoot express "I started a drag-and-drop op"? // TODO - Touchpad, Touch, AxisMotion // TODO - How to handle CursorEntered? +/// A pointer-related event. +/// +/// A pointer in this context can be a mouse, a pen, a touch screen, etc. Though +/// Masonry currently doesn't really support multiple pointers. #[derive(Debug, Clone)] pub enum PointerEvent { + /// A pointer was pressed. PointerDown(PointerButton, PointerState), + /// A pointer was released. PointerUp(PointerButton, PointerState), + /// A pointer was moved. PointerMove(PointerState), + /// A pointer entered the window. PointerEnter(PointerState), + /// A pointer left the window. PointerLeave(PointerState), + /// A mouse wheel event. MouseWheel(LogicalPosition, PointerState), + /// During a file drag-and-drop operation, a file was kept over the window. HoverFile(PathBuf, PointerState), + /// During a file drag-and-drop operation, a file was dropped on the window. DropFile(PathBuf, PointerState), + /// A file drag-and-drop operation was cancelled. HoverFileCancel(PointerState), + /// A pinch gesture was detected. Pinch(f64, PointerState), } // TODO - Clipboard Paste? // TODO skip is_synthetic=true events +/// A text-related event. #[derive(Debug, Clone)] pub enum TextEvent { + /// A keyboard event. KeyboardKey(KeyEvent, ModifiersState), + /// An IME event. Ime(Ime), + /// Modifier keys (e.g. Shift, Ctrl, Alt) were pressed or released. ModifierChange(ModifiersState), + /// The window took or lost focus. // TODO - Document difference with Update focus change FocusChange(bool), } +// TODO - Go into more detail. +/// An accessibility event. #[derive(Debug, Clone)] pub struct AccessEvent { + /// The action that was performed. pub action: accesskit::Action, + /// The data associated with the action. pub data: Option, } +/// The persistent state of a pointer. #[derive(Debug, Clone)] pub struct PointerState { // TODO // pub device_id: DeviceId, + /// The position of the pointer in physical coordinates. + /// This is the number of pixels from the top and left of the window. pub physical_position: PhysicalPosition, + + /// The position of the pointer in logical coordinates. + /// This is different from physical coordinates for high-DPI displays. pub position: LogicalPosition, + + /// The buttons that are currently pressed (mostly useful for the mouse). pub buttons: PointerButtons, + + /// The modifier keys (e.g. Shift, Ctrl, Alt) that are currently pressed. pub mods: Modifiers, + + /// The number of successive clicks registered. This is used to detect e.g. double-clicks. pub count: u8, + + // TODO - Find out why this was added, maybe remove it. + /// Currently unused. pub focus: bool, + + /// The force of a touch event. pub force: Option, } +/// The light/dark mode of the window. #[derive(Debug, Clone)] pub enum WindowTheme { + /// Light mode. Light, + /// Dark mode. Dark, } @@ -461,6 +510,9 @@ impl AccessEvent { } impl PointerState { + /// Create a new [`PointerState`] with dummy values. + /// + /// Mostly used for testing. pub fn empty() -> Self { Self { physical_position: PhysicalPosition::new(0.0, 0.0), diff --git a/masonry/src/event_loop_runner.rs b/masonry/src/event_loop_runner.rs index 8f199e7f8..67b1f97af 100644 --- a/masonry/src/event_loop_runner.rs +++ b/masonry/src/event_loop_runner.rs @@ -1,6 +1,9 @@ // Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 +// We use allow because expect(missing_docs) is noisy with rust-analyzer. +#![allow(missing_docs, reason = "We have many as-yet undocumented items")] + use std::num::NonZeroUsize; use std::sync::Arc; diff --git a/masonry/src/lib.rs b/masonry/src/lib.rs index 7803aa174..8014c2b9a 100644 --- a/masonry/src/lib.rs +++ b/masonry/src/lib.rs @@ -126,8 +126,6 @@ #![expect(clippy::missing_assert_message, reason = "Deferred: Noisy")] #![expect(clippy::return_self_not_must_use, reason = "Deferred: Noisy")] #![expect(elided_lifetimes_in_paths, reason = "Deferred: Noisy")] -// https://github.com/rust-lang/rust/pull/130025 -#![allow(missing_docs, reason = "We have many as-yet undocumented items")] #![expect(unreachable_pub, reason = "Potentially controversial code style")] #![expect( unnameable_types, diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index ae55e9525..0eb3d274b 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -45,6 +45,12 @@ const INVALID_IME_AREA: Rect = Rect::new(f64::NAN, f64::NAN, f64::NAN, f64::NAN) // --- MARK: STRUCTS --- +/// The composition root of Masonry. +/// +/// This is the entry point for all user events, and the source of all signals to be sent to +/// winit or similar event loop runners, as well as 2D scenes and accessibility information. +/// +/// This is also the type that owns the widget tree. pub struct RenderRoot { pub(crate) root: WidgetPod>, pub(crate) size_policy: WindowSizePolicy, @@ -106,9 +112,20 @@ pub enum WindowSizePolicy { User, } +/// Options for creating a [`RenderRoot`]. pub struct RenderRootOptions { + /// If true, `fontique` will provide access to system fonts + /// using platform-specific APIs. pub use_system_fonts: bool, + + /// Defines how the window size should be determined. pub size_policy: WindowSizePolicy, + + /// The scale factor to use for rendering. + /// + /// Useful for high-DPI displays. + /// + /// `1.0` is a sensible default. pub scale_factor: f64, /// Add a font from its raw data for use in tests. @@ -121,35 +138,55 @@ pub struct RenderRootOptions { pub test_font: Option>, } +/// Objects emitted by the [`RenderRoot`] to signal that something has changed or require external actions. pub enum RenderRootSignal { + /// A widget has emitted an action. Action(Action, WidgetId), + /// An IME session has been started. StartIme, + /// The IME session has ended. EndIme, + /// The IME area has been moved. ImeMoved(LogicalPosition, LogicalSize), + /// The window needs to be redrawn. RequestRedraw, + /// The window should be redrawn for an animation frame. Currently this isn't really different from `RequestRedraw`. RequestAnimFrame, + /// The window should take focus. TakeFocus, + /// The mouse icon has changed. SetCursor(CursorIcon), + /// The window size has changed. SetSize(PhysicalSize), + /// The window title has changed. SetTitle(String), + /// The window is being dragged. DragWindow, + /// The window is being resized. DragResizeWindow(ResizeDirection), + /// The window is being maximized. ToggleMaximized, + /// The window is being minimized. Minimize, + /// The window is being closed. Exit, + /// The window menu is being shown. ShowWindowMenu(LogicalPosition), } impl RenderRoot { - pub fn new( - root_widget: impl Widget, - RenderRootOptions { + /// Create a new `RenderRoot` with the given options. + /// + /// Note that this doesn't create a window or start the event loop. + /// + /// See [`crate::event_loop_runner::run`] for that. + pub fn new(root_widget: impl Widget, options: RenderRootOptions) -> Self { + let RenderRootOptions { use_system_fonts, size_policy, scale_factor, test_font, - }: RenderRootOptions, - ) -> Self { + } = options; let mut root = Self { root: WidgetPod::new(root_widget).boxed(), size_policy, @@ -225,6 +262,7 @@ impl RenderRoot { } // --- MARK: WINDOW_EVENT --- + /// Handle a window event. pub fn handle_window_event(&mut self, event: WindowEvent) -> Handled { match event { WindowEvent::Rescale(scale_factor) => { @@ -268,6 +306,7 @@ impl RenderRoot { } // --- MARK: PUB FUNCTIONS --- + /// Handle a pointer event. pub fn handle_pointer_event(&mut self, event: PointerEvent) -> Handled { let _span = info_span!("pointer_event"); let handled = run_on_pointer_event_pass(self, &event); @@ -277,6 +316,7 @@ impl RenderRoot { handled } + /// Handle a text event. pub fn handle_text_event(&mut self, event: TextEvent) -> Handled { let _span = info_span!("text_event"); let handled = run_on_text_event_pass(self, &event); @@ -292,6 +332,7 @@ impl RenderRoot { handled } + /// Handle an accesskit event. pub fn handle_access_event(&mut self, event: ActionRequest) { let _span = info_span!("access_event"); let Ok(id) = event.target.0.try_into() else { @@ -321,6 +362,10 @@ impl RenderRoot { .register_fonts(data) } + /// Redraw the window. + /// + /// Returns an update to the accessibility tree and a Vello scene representing + /// the widget tree's current state. pub fn redraw(&mut self) -> (Scene, TreeUpdate) { if self.root_state().needs_layout { // TODO - Rewrite more clearly after run_rewrite_passes is rewritten @@ -333,17 +378,23 @@ impl RenderRoot { } // TODO - Handle invalidation regions - // TODO - Improve caching of scenes. ( run_paint_pass(self), run_accessibility_pass(self, self.scale_factor), ) } + /// Pop the oldest signal from the queue. pub fn pop_signal(&mut self) -> Option { self.global_state.signal_queue.pop_front() } + /// Pop the oldest signal from the queue that matches the predicate. + /// + /// Doesn't affect other signals. + /// + /// Note that you should still use [`Self::pop_signal`] to avoid letting the queue + /// grow indefinitely. pub fn pop_signal_matching( &mut self, predicate: impl Fn(&RenderRootSignal) -> bool, @@ -352,6 +403,7 @@ impl RenderRoot { self.global_state.signal_queue.remove(idx) } + /// Get the current icon that the mouse should display. pub fn cursor_icon(&self) -> CursorIcon { self.cursor_icon } diff --git a/masonry/src/testing/helper_widgets.rs b/masonry/src/testing/helper_widgets.rs index 7b2bb8abd..162eeb574 100644 --- a/masonry/src/testing/helper_widgets.rs +++ b/masonry/src/testing/helper_widgets.rs @@ -106,15 +106,25 @@ pub struct Recording(Rc>>); /// Each member of the enum corresponds to one of the methods on `Widget`. #[derive(Debug, Clone)] pub enum Record { + /// Pointer event. PE(PointerEvent), + /// Text event. TE(TextEvent), + /// Access event. AE(AccessEvent), + /// Animation frame. AF(u64), + /// Register children RC, + /// Update U(Update), + /// Layout. Records the size returned by the layout method. Layout(Size), + /// Compose. Compose, + /// Paint. Paint, + /// Accessibility. Access, } @@ -122,6 +132,7 @@ pub enum Record { /// /// Implements helper methods useful for unit testing. pub trait TestWidgetExt: Widget + Sized + 'static { + /// Wrap this widget in a [`Recorder`] that records all method calls. fn record(self, recording: &Recording) -> Recorder { Recorder { child: self, @@ -129,6 +140,7 @@ pub trait TestWidgetExt: Widget + Sized + 'static { } } + /// Wrap this widget in a [`SizedBox`] with the given id. fn with_id(self, id: WidgetId) -> SizedBox { SizedBox::new_with_id(self, id) } diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index d7434567e..9d19e8e55 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -3,6 +3,9 @@ //! Common widgets. +// We use allow because expect(missing_docs) is noisy with rust-analyzer. +#![allow(missing_docs, reason = "We have many as-yet undocumented items")] + #[allow(clippy::module_inception)] pub(crate) mod widget; mod widget_mut; diff --git a/masonry/src/widget/widget_state.rs b/masonry/src/widget/widget_state.rs index ba2bba866..a7b509b06 100644 --- a/masonry/src/widget/widget_state.rs +++ b/masonry/src/widget/widget_state.rs @@ -263,6 +263,7 @@ impl WidgetState { self.local_paint_rect + self.origin.to_vec2() } + // TODO - Remove /// The rectangle used when calculating layout with other widgets /// /// For more information, see [`WidgetPod::layout_rect`](crate::WidgetPod::layout_rect).