mirror of https://github.com/linebender/xilem
Document most items in Masonry (#815)
Move `#[allow(missing_docs)]` to two modules. Missing docs now warn everywhere else by default. Remove `#[expect(rustdoc::broken_intra_doc_links)]` and fix all broken links.
This commit is contained in:
parent
522dfd4433
commit
51a355615f
|
@ -21,7 +21,6 @@ Full documentation at https://github.com/orium/cargo-rdme -->
|
|||
|
||||
<!-- Intra-doc links used in lib.rs should be evaluated here.
|
||||
See https://linebender.org/blog/doc-include/ for related discussion. -->
|
||||
[tracing_tracy]: https://crates.io/crates/tracing-tracy
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
|
@ -112,6 +111,7 @@ The following feature flags are available:
|
|||
[winit]: https://crates.io/crates/winit
|
||||
[Druid]: https://crates.io/crates/druid
|
||||
[Xilem]: https://crates.io/crates/xilem
|
||||
[tracing_tracy]: https://crates.io/crates/tracing-tracy
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
|
|
|
@ -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),
|
||||
CheckboxChecked(bool),
|
||||
/// A checkbox was toggled.
|
||||
CheckboxToggled(bool),
|
||||
// FIXME - This is a huge hack
|
||||
/// Other.
|
||||
Other(Box<dyn Any + Send>),
|
||||
}
|
||||
|
||||
|
@ -28,7 +33,7 @@ impl PartialEq for Action {
|
|||
(Self::ButtonPressed(l_button), Self::ButtonPressed(r_button)) => l_button == r_button,
|
||||
(Self::TextChanged(l0), Self::TextChanged(r0)) => l0 == r0,
|
||||
(Self::TextEntered(l0), Self::TextEntered(r0)) => l0 == r0,
|
||||
(Self::CheckboxChecked(l0), Self::CheckboxChecked(r0)) => l0 == r0,
|
||||
(Self::CheckboxToggled(l0), Self::CheckboxToggled(r0)) => l0 == r0,
|
||||
// FIXME
|
||||
// (Self::Other(val_l), Self::Other(val_r)) => false,
|
||||
_ => false,
|
||||
|
@ -42,7 +47,7 @@ impl std::fmt::Debug for Action {
|
|||
Self::ButtonPressed(button) => f.debug_tuple("ButtonPressed").field(button).finish(),
|
||||
Self::TextChanged(text) => f.debug_tuple("TextChanged").field(text).finish(),
|
||||
Self::TextEntered(text) => f.debug_tuple("TextEntered").field(text).finish(),
|
||||
Self::CheckboxChecked(b) => f.debug_tuple("CheckboxChecked").field(b).finish(),
|
||||
Self::CheckboxToggled(b) => f.debug_tuple("CheckboxChecked").field(b).finish(),
|
||||
Self::Other(_) => write!(f, "Other(...)"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use vello::kurbo::Size;
|
|||
/// The constraints are always [rounded away from zero] to integers
|
||||
/// to enable pixel perfect layout.
|
||||
///
|
||||
/// [`layout`]: crate::widget::Widget::layout
|
||||
/// [`layout`]: crate::Widget::layout
|
||||
/// [Flutter BoxConstraints]: https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html
|
||||
/// [rounded away from zero]: Size::expand
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
|
|
@ -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<dyn Widget>>,
|
||||
}
|
||||
|
||||
/// 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<dyn Widget>>,
|
||||
}
|
||||
|
||||
/// 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<dyn Widget>>,
|
||||
|
@ -86,9 +83,7 @@ pub struct RegisterCtx<'a> {
|
|||
pub(crate) registered_ids: Vec<WidgetId>,
|
||||
}
|
||||
|
||||
/// 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<dyn Widget>>,
|
||||
}
|
||||
|
||||
/// 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<dyn Widget>>,
|
||||
}
|
||||
|
||||
/// 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<dyn Widget>>,
|
||||
}
|
||||
|
||||
/// 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<Child: Widget>(&self, child: &'_ WidgetPod<Child>) -> &'_ Child {
|
||||
let child_ref = self
|
||||
|
@ -160,7 +154,7 @@ impl_context_method!(
|
|||
child_ref.item.as_dyn_any().downcast_ref::<Child>().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<Child: Widget>(&self, child: &'_ WidgetPod<Child>) -> &'_ 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.
|
||||
|
@ -503,10 +499,6 @@ impl LayoutCtx<'_> {
|
|||
/// to paint outside of your layout bounds. In this case, the argument
|
||||
/// should be an [`Insets`] struct that indicates where your widget
|
||||
/// needs to overpaint, relative to its bounds.
|
||||
///
|
||||
/// For more information, see [`WidgetPod::paint_insets`].
|
||||
///
|
||||
/// [`WidgetPod::paint_insets`]: crate::widget::WidgetPod::paint_insets
|
||||
pub fn set_paint_insets(&mut self, insets: impl Into<Insets>) {
|
||||
let insets = insets.into();
|
||||
self.widget_state.paint_insets = insets.nonnegative();
|
||||
|
@ -522,7 +514,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 +545,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<impl Widget>) -> bool {
|
||||
self.get_child_state(child).needs_layout
|
||||
}
|
||||
|
@ -567,7 +559,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<impl Widget>) -> f64 {
|
||||
|
@ -579,7 +571,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<impl Widget>) -> Rect {
|
||||
|
@ -592,7 +584,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<impl Widget>) -> Rect {
|
||||
|
@ -605,7 +597,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<impl Widget>) -> Size {
|
||||
|
@ -613,7 +605,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 +646,8 @@ impl LayoutCtx<'_> {
|
|||
}
|
||||
|
||||
impl ComposeCtx<'_> {
|
||||
// TODO - Remove?
|
||||
/// Returns whether [`Widget::compose`] will be called on this widget.
|
||||
pub fn needs_compose(&self) -> bool {
|
||||
self.widget_state.needs_compose
|
||||
}
|
||||
|
@ -701,17 +695,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 +723,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,59 +767,51 @@ 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.
|
||||
/// Whether a pointer is [captured] by this widget.
|
||||
///
|
||||
/// See [`capture_pointer`] for more information about pointer capture.
|
||||
/// The pointer will usually be the mouse. In future versions, this
|
||||
/// function will take a pointer id as input to test a specific pointer.
|
||||
///
|
||||
/// [`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)
|
||||
}
|
||||
|
||||
/// The focus status of a widget.
|
||||
/// The [text focus] status of a widget.
|
||||
///
|
||||
/// The focused widget is the one that receives keyboard events.
|
||||
///
|
||||
/// Returns `true` if this specific widget is focused.
|
||||
/// To check if any descendants are focused use [`has_focus`].
|
||||
///
|
||||
/// Focus means that the widget receives keyboard events.
|
||||
///
|
||||
/// A widget can request focus using the [`request_focus`] method.
|
||||
/// It's also possible to register for automatic focus via [`register_for_focus`].
|
||||
///
|
||||
/// If a widget gains or loses focus it will get a [`Update::FocusChanged`] event.
|
||||
///
|
||||
/// Only one widget at a time is focused. However due to the way events are routed,
|
||||
/// all ancestors of that widget will also receive keyboard events.
|
||||
///
|
||||
/// [`request_focus`]: EventCtx::request_focus
|
||||
/// [`register_for_focus`]: UpdateCtx::register_for_focus
|
||||
/// [`Update::FocusChanged`]: crate::Update::FocusChanged
|
||||
/// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus
|
||||
/// [`has_focus`]: Self::has_focus
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.global_state.focused_widget == Some(self.widget_id())
|
||||
}
|
||||
|
||||
/// The (tree) focus status of a widget.
|
||||
/// Whether this widget or any of its descendants are 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 +838,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 +849,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 +890,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) {
|
||||
|
@ -915,6 +905,7 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, {
|
|||
/// Request a [`compose`] pass.
|
||||
///
|
||||
/// The compose pass is often cheaper than the layout pass, because it can only transform individual widgets' position.
|
||||
///
|
||||
/// [`compose`]: crate::Widget::compose
|
||||
pub fn request_compose(&mut self) {
|
||||
trace!("request_compose");
|
||||
|
@ -1051,6 +1042,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 +1237,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 +1270,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<Ctx, W> RawWrapper<'_, Ctx, W> {
|
||||
pub fn widget(&self) -> &W {
|
||||
self.widget
|
||||
|
@ -1297,6 +1294,7 @@ impl<Ctx, W> RawWrapper<'_, Ctx, W> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs, reason = "RawWrapper is likely to be reworked")]
|
||||
impl<Ctx: IsContext, W> RawWrapperMut<'_, Ctx, W> {
|
||||
pub fn widget(&mut self) -> &mut W {
|
||||
self.widget
|
||||
|
@ -1315,7 +1313,10 @@ impl<Ctx: IsContext, W> 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 +1324,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 +1337,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
|
||||
|
|
|
@ -58,7 +58,10 @@ Focus marks whether a widget receives text events.
|
|||
To give a simple example, when you click a textbox, the textbox gets focus: anything you type on your keyboard will be sent to that textbox.
|
||||
|
||||
Focus can be changed with the tab key, or by clicking on a widget, both which Masonry automatically handles.
|
||||
Widgets can also set custom focus behavior.
|
||||
Masonry will only give focus to widgets that accept focus (see [`Widget::accepts_focus`]).
|
||||
Widgets can also use context types to request focus.
|
||||
|
||||
If a widget gains or loses focus it will get a [`FocusChanged`] event.
|
||||
|
||||
Note that widgets without text-edition capabilities such as buttons and checkboxes can also get focus.
|
||||
For instance, pressing space when a button is focused will trigger that button.
|
||||
|
@ -103,3 +106,5 @@ Safety rails aren't guaranteed to run and may be disabled even in debug mode for
|
|||
They should not be relied upon to check code correctness, but are meant to help you catch implementation errors early on during development.
|
||||
|
||||
[`PointerLeave`]: crate::PointerEvent::PointerLeave
|
||||
[`FocusChanged`]: crate::Update::FocusChanged
|
||||
[`Widget::accepts_focus`]: crate::Widget::accepts_focus
|
||||
|
|
|
@ -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<u32>),
|
||||
/// The animation frame requested by this window must run.
|
||||
AnimFrame,
|
||||
/// The accessibility tree must be rebuilt.
|
||||
RebuildAccessTree,
|
||||
}
|
||||
|
||||
|
@ -178,68 +184,119 @@ impl From<PointerButton> for PointerButtons {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO - Document units for MouseWheel and Pinch deltas.
|
||||
// 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.
|
||||
///
|
||||
/// A synthetic `PointerLeave` event may also be sent when a widget
|
||||
/// loses [pointer capture](crate::doc::doc_06_masonry_concepts#pointer-capture).
|
||||
PointerLeave(PointerState),
|
||||
/// A mouse wheel event.
|
||||
///
|
||||
/// The first tuple value is the scrolled distances. In most cases with a
|
||||
/// standard mouse wheel, x will be 0 and y will be the number of ticks
|
||||
/// scrolled. Trackballs and touchpads may produce non-zero values for x.
|
||||
MouseWheel(LogicalPosition<f64>, 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.
|
||||
///
|
||||
/// The first tuple value is the delta. Positive values indicate magnification
|
||||
/// (zooming in) and negative values indicate shrinking (zooming out).
|
||||
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<accesskit::ActionData>,
|
||||
}
|
||||
|
||||
/// 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<f64>,
|
||||
|
||||
/// The position of the pointer in logical coordinates.
|
||||
/// This is different from physical coordinates for high-DPI displays.
|
||||
pub position: LogicalPosition<f64>,
|
||||
|
||||
/// 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<Force>,
|
||||
}
|
||||
|
||||
/// The light/dark mode of the window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WindowTheme {
|
||||
/// Light mode.
|
||||
Light,
|
||||
/// Dark mode.
|
||||
Dark,
|
||||
}
|
||||
|
||||
// TODO - Rewrite that doc.
|
||||
/// Application life cycle events.
|
||||
/// Changes to widget state.
|
||||
///
|
||||
/// Unlike [`Event`]s, [`Update`] events are generated by Masonry, and
|
||||
/// may occur at different times during a given pass of the event loop. The
|
||||
/// [`Update::WidgetAdded`] event, for instance, may occur when the app
|
||||
/// first launches (during the handling of [`Event::WindowConnected`]) or it
|
||||
/// may occur during an [`on_event`](crate::Widget::on_event) pass, if some
|
||||
/// widget has been added then.
|
||||
/// Unlike [`PointerEvent`]s, [`TextEvent`]s and [`AccessEvent`]s, [`Update`] events
|
||||
/// are generated by Masonry, and may occur at different times during a given pass of
|
||||
/// the event loop.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(variant_size_differences)]
|
||||
|
@ -283,7 +340,7 @@ pub enum Update {
|
|||
StashedChanged(bool),
|
||||
|
||||
/// Called when a child widgets uses
|
||||
/// [`EventCtx::request_pan_to_this`](crate::EventCtx::request_pan_to_this).
|
||||
/// [`EventCtx::request_scroll_to_this`](crate::EventCtx::request_scroll_to_this).
|
||||
RequestPanToChild(Rect),
|
||||
|
||||
/// Called when the "hovered" status changes.
|
||||
|
@ -461,6 +518,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),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -547,6 +550,8 @@ impl MasonryState<'_> {
|
|||
}
|
||||
},
|
||||
WinitWindowEvent::MouseWheel { delta, .. } => {
|
||||
// TODO - This delta value doesn't quite make sense.
|
||||
// Figure out and document a better standard.
|
||||
let delta = match delta {
|
||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
LogicalPosition::new(x as f64, y as f64)
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
//! [winit]: https://crates.io/crates/winit
|
||||
//! [Druid]: https://crates.io/crates/druid
|
||||
//! [Xilem]: https://crates.io/crates/xilem
|
||||
//! [tracing_tracy]: https://crates.io/crates/tracing-tracy
|
||||
|
||||
// LINEBENDER LINT SET - lib.rs - v1
|
||||
// See https://linebender.org/wiki/canonical-lints/
|
||||
|
@ -109,10 +110,6 @@
|
|||
#![expect(clippy::allow_attributes, reason = "Deferred: Noisy")]
|
||||
#![expect(clippy::allow_attributes_without_reason, reason = "Deferred: Noisy")]
|
||||
// TODO: Remove any items listed as "Deferred"
|
||||
#![expect(
|
||||
rustdoc::broken_intra_doc_links,
|
||||
reason = "Deferred: Noisy. Tracked in https://github.com/linebender/xilem/issues/449"
|
||||
)]
|
||||
#![expect(clippy::needless_doctest_main, reason = "Deferred: Noisy")]
|
||||
#![expect(clippy::should_implement_trait, reason = "Deferred: Noisy")]
|
||||
#![cfg_attr(not(debug_assertions), expect(unused, reason = "Deferred: Noisy"))]
|
||||
|
@ -126,8 +123,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,
|
||||
|
|
|
@ -44,6 +44,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<Box<dyn Widget>>,
|
||||
pub(crate) size_policy: WindowSizePolicy,
|
||||
|
@ -104,9 +110,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.
|
||||
|
@ -119,35 +136,55 @@ pub struct RenderRootOptions {
|
|||
pub test_font: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// 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<f64>, LogicalSize<f64>),
|
||||
/// 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<u32>),
|
||||
/// 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<f64>),
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -222,6 +259,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) => {
|
||||
|
@ -265,6 +303,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);
|
||||
|
@ -274,6 +313,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);
|
||||
|
@ -289,6 +329,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 {
|
||||
|
@ -318,6 +359,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
|
||||
|
@ -330,17 +375,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<RenderRootSignal> {
|
||||
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,
|
||||
|
@ -349,6 +400,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
|
||||
}
|
||||
|
|
|
@ -101,6 +101,9 @@ use crate::{Color, Handled, Point, Size, Vec2, Widget, WidgetId};
|
|||
///
|
||||
/// # simple_button();
|
||||
/// ```
|
||||
///
|
||||
/// [`assert_render_snapshot`]: crate::assert_render_snapshot
|
||||
/// [`insta`]: https://docs.rs/insta/latest/insta/
|
||||
pub struct TestHarness {
|
||||
render_root: RenderRoot,
|
||||
mouse_state: PointerState,
|
||||
|
@ -171,8 +174,8 @@ impl Default for TestHarnessParams {
|
|||
impl TestHarness {
|
||||
/// Builds harness with given root widget.
|
||||
///
|
||||
/// Window size will be [`Self::DEFAULT_SIZE`].
|
||||
/// Background color will be [`Self::DEFAULT_BACKGROUND_COLOR`].
|
||||
/// Window size will be [`TestHarnessParams::DEFAULT_SIZE`].
|
||||
/// Background color will be [`TestHarnessParams::DEFAULT_BACKGROUND_COLOR`].
|
||||
pub fn create(root_widget: impl Widget) -> Self {
|
||||
Self::create_with(root_widget, TestHarnessParams::default())
|
||||
}
|
||||
|
|
|
@ -106,15 +106,25 @@ pub struct Recording(Rc<RefCell<VecDeque<Record>>>);
|
|||
/// 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<Self> {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub type ArcStr = std::sync::Arc<str>;
|
|||
/// The Parley [`parley::Brush`] used within Masonry.
|
||||
///
|
||||
/// This enables updating of brush details without performing relayouts;
|
||||
/// the inner values are indexes into the `brushes` argument to [`render_text`].
|
||||
/// the inner values are indexes into the `brushes` argument to [`render_text()`].
|
||||
#[derive(Clone, PartialEq, Default, Debug)]
|
||||
pub struct BrushIndex(pub usize);
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ impl Widget for Checkbox {
|
|||
PointerEvent::PointerUp(_, _) => {
|
||||
if ctx.has_pointer_capture() && ctx.is_hovered() && !ctx.is_disabled() {
|
||||
self.checked = !self.checked;
|
||||
ctx.submit_action(Action::CheckboxChecked(self.checked));
|
||||
ctx.submit_action(Action::CheckboxToggled(self.checked));
|
||||
trace!("Checkbox {:?} released", ctx.widget_id());
|
||||
}
|
||||
// Checked state impacts appearance and accessibility node
|
||||
|
@ -94,7 +94,7 @@ impl Widget for Checkbox {
|
|||
match event.action {
|
||||
accesskit::Action::Click => {
|
||||
self.checked = !self.checked;
|
||||
ctx.submit_action(Action::CheckboxChecked(self.checked));
|
||||
ctx.submit_action(Action::CheckboxToggled(self.checked));
|
||||
// Checked state impacts appearance and accessibility node
|
||||
ctx.request_render();
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ mod tests {
|
|||
harness.mouse_click_on(checkbox_id);
|
||||
assert_eq!(
|
||||
harness.pop_action(),
|
||||
Some((Action::CheckboxChecked(true), checkbox_id))
|
||||
Some((Action::CheckboxToggled(true), checkbox_id))
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(harness.root_widget());
|
||||
|
@ -260,7 +260,7 @@ mod tests {
|
|||
harness.mouse_click_on(checkbox_id);
|
||||
assert_eq!(
|
||||
harness.pop_action(),
|
||||
Some((Action::CheckboxChecked(false), checkbox_id))
|
||||
Some((Action::CheckboxToggled(false), checkbox_id))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -210,14 +210,14 @@ impl Flex {
|
|||
self
|
||||
}
|
||||
|
||||
/// Builder-style variant of [`WidgetMut::add_child`].
|
||||
/// Builder-style variant of [`Flex::add_child`].
|
||||
///
|
||||
/// Convenient for assembling a group of widgets in a single expression.
|
||||
pub fn with_child(self, child: impl Widget) -> Self {
|
||||
self.with_child_pod(WidgetPod::new(Box::new(child)))
|
||||
}
|
||||
|
||||
/// Builder-style variant of [`WidgetMut::add_child`], that takes the id that the child will have.
|
||||
/// Builder-style variant of [`Flex::add_child`], that takes the id that the child will have.
|
||||
///
|
||||
/// Useful for unit tests.
|
||||
pub fn with_child_id(self, child: impl Widget, id: WidgetId) -> Self {
|
||||
|
@ -266,7 +266,7 @@ impl Flex {
|
|||
/// If you are laying out standard controls in this container, you should
|
||||
/// generally prefer to use [`add_default_spacer`].
|
||||
///
|
||||
/// [`add_default_spacer`]: WidgetMut::add_default_spacer
|
||||
/// [`add_default_spacer`]: Flex::add_default_spacer
|
||||
pub fn with_spacer(mut self, mut len: f64) -> Self {
|
||||
if len < 0.0 {
|
||||
tracing::warn!("add_spacer called with negative length: {}", len);
|
||||
|
@ -422,7 +422,7 @@ impl Flex {
|
|||
/// If you are laying out standard controls in this container, you should
|
||||
/// generally prefer to use [`add_default_spacer`].
|
||||
///
|
||||
/// [`add_default_spacer`]: WidgetMut::add_default_spacer
|
||||
/// [`add_default_spacer`]: Flex::add_default_spacer
|
||||
pub fn add_spacer(this: &mut WidgetMut<'_, Self>, mut len: f64) {
|
||||
if len < 0.0 {
|
||||
tracing::warn!("add_spacer called with negative length: {}", len);
|
||||
|
@ -506,7 +506,7 @@ impl Flex {
|
|||
/// If you are laying out standard controls in this container, you should
|
||||
/// generally prefer to use [`add_default_spacer`].
|
||||
///
|
||||
/// [`add_default_spacer`]: WidgetMut::add_default_spacer
|
||||
/// [`add_default_spacer`]: Flex::add_default_spacer
|
||||
pub fn insert_spacer(this: &mut WidgetMut<'_, Self>, idx: usize, mut len: f64) {
|
||||
if len < 0.0 {
|
||||
tracing::warn!("add_spacer called with negative length: {}", len);
|
||||
|
|
|
@ -52,7 +52,7 @@ impl Grid {
|
|||
self
|
||||
}
|
||||
|
||||
/// Builder-style variant of [`WidgetMut::add_child`].
|
||||
/// Builder-style variant of [`Grid::add_child`].
|
||||
///
|
||||
/// Convenient for assembling a group of widgets in a single expression.
|
||||
pub fn with_child(self, child: impl Widget, params: GridParams) -> Self {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -69,7 +69,7 @@ impl<W: Widget> Portal<W> {
|
|||
///
|
||||
/// The default is `false`.
|
||||
///
|
||||
/// This setting affects how a `ClipBox` lays out its child.
|
||||
/// This setting affects how a `Portal` lays out its child.
|
||||
///
|
||||
/// - When it is `false` (the default), the child does not receive any upper
|
||||
/// bound on its height: the idea is that the child can be as tall as it
|
||||
|
@ -94,7 +94,7 @@ impl<W: Widget> Portal<W> {
|
|||
///
|
||||
/// If `false` (the default) there is no minimum constraint on the child's
|
||||
/// size. If `true`, the child is passed the same minimum constraints as
|
||||
/// the `ClipBox`.
|
||||
/// the `Portal`.
|
||||
pub fn content_must_fill(mut self, must_fill: bool) -> Self {
|
||||
self.must_fill = must_fill;
|
||||
self
|
||||
|
@ -198,11 +198,11 @@ impl<W: Widget> Portal<W> {
|
|||
}
|
||||
|
||||
/// Set whether the child's size must be greater than or equal the size of
|
||||
/// the `ClipBox`.
|
||||
/// the `Portal`.
|
||||
///
|
||||
/// See [`content_must_fill`] for more details.
|
||||
///
|
||||
/// [`content_must_fill`]: ClipBox::content_must_fill
|
||||
/// [`content_must_fill`]: Portal::content_must_fill
|
||||
pub fn set_content_must_fill(this: &mut WidgetMut<'_, Self>, must_fill: bool) {
|
||||
this.widget.must_fill = must_fill;
|
||||
this.ctx.request_layout();
|
||||
|
|
|
@ -287,6 +287,7 @@ impl SizedBox {
|
|||
/// notably, it can be any [`Color`], any gradient, or an [`Image`].
|
||||
///
|
||||
/// [`Image`]: vello::peniko::Image
|
||||
/// [`Color`]: crate::Color
|
||||
pub fn background(mut self, brush: impl Into<Brush>) -> Self {
|
||||
self.background = Some(brush.into());
|
||||
self
|
||||
|
@ -373,6 +374,7 @@ impl SizedBox {
|
|||
/// notably, it can be any [`Color`], any gradient, or an [`Image`].
|
||||
///
|
||||
/// [`Image`]: vello::peniko::Image
|
||||
/// [`Color`]: crate::Color
|
||||
pub fn set_background(this: &mut WidgetMut<'_, Self>, brush: impl Into<Brush>) {
|
||||
this.widget.background = Some(brush.into());
|
||||
this.ctx.request_paint_only();
|
||||
|
|
|
@ -27,9 +27,8 @@ use crate::{
|
|||
/// `WidgetId`s are generated automatically for all widgets in the widget tree.
|
||||
/// More specifically, each [`WidgetPod`](crate::WidgetPod) has a unique `WidgetId`.
|
||||
///
|
||||
/// These ids are used internally to route events, and can be used to communicate
|
||||
/// between widgets, by submitting a command (as with [`EventCtx::submit_command`])
|
||||
/// and passing a `WidgetId` as the [`Target`](crate::Target).
|
||||
/// These ids are used internally to route events, and can be used to fetch a specific
|
||||
/// widget for testing or event handling.
|
||||
///
|
||||
/// A widget can retrieve its id via methods on the various contexts, such as
|
||||
/// [`UpdateCtx::widget_id`].
|
||||
|
@ -52,38 +51,48 @@ impl WidgetId {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO - Add tutorial: implementing a widget - See https://github.com/linebender/xilem/issues/376
|
||||
/// The trait implemented by all widgets.
|
||||
///
|
||||
/// For details on how to implement this trait, see tutorial **(TODO)**
|
||||
/// For details on how to implement this trait, see the [tutorials](crate::doc).
|
||||
///
|
||||
/// Whenever external events affect the given widget, methods [`on_event`],
|
||||
/// [`on_status_change`](Self::on_status_change) and [`update`](Self::update)
|
||||
/// are called. Later on, when the widget is laid out and displayed, methods
|
||||
/// [`layout`](Self::layout) and [`paint`](Self::paint) are called.
|
||||
/// Whenever external events affect the given widget, methods
|
||||
/// [`on_pointer_event`](Self::on_pointer_event),
|
||||
/// [`on_text_event`](Self::on_text_event),
|
||||
/// [`on_access_event`](Self::on_access_event),
|
||||
/// [`on_anim_frame`](Self::on_anim_frame) and [`update`](Self::update) are called.
|
||||
///
|
||||
/// Later on, when the widget is laid out and displayed, methods
|
||||
/// [`layout`](Self::layout), [`compose`](Self::compose), [`paint`](Self::paint) and
|
||||
/// [`accessibility`](Self::accessibility) are called.
|
||||
///
|
||||
/// These trait methods are provided with a corresponding context. The widget can
|
||||
/// request things and cause actions by calling methods on that context.
|
||||
///
|
||||
/// Widgets also have a [`children`](Self::children) method. Leaf widgets return an empty array,
|
||||
/// whereas container widgets return an array of [`WidgetRef`]. Container widgets
|
||||
/// have some validity invariants to maintain regarding their children.
|
||||
/// Widgets also have a [`children_ids`](Self::children_ids) method. Leaf widgets return an empty array,
|
||||
/// whereas container widgets return an array of [`WidgetId`].
|
||||
/// Container widgets have some validity invariants to maintain regarding their children.
|
||||
///
|
||||
/// Generally speaking, widgets aren't used directly. They are stored in
|
||||
/// [`WidgetPod`](crate::WidgetPod)s. Widget methods are called by `WidgetPod`s, and the
|
||||
/// widget is mutated either during a method call (eg `on_event` or `update`) or
|
||||
/// through a [`WidgetMut`](crate::widget::WidgetMut).
|
||||
/// Generally speaking, widgets aren't used directly. They are stored by Masonry and accessed
|
||||
/// through [`WidgetPod`](crate::WidgetPod)s. Widget methods are called by Masonry, and a
|
||||
/// widget should only be mutated either during a method call or through a [`WidgetMut`](crate::widget::WidgetMut).
|
||||
#[allow(unused_variables)]
|
||||
pub trait Widget: AsAny {
|
||||
/// Handle an event - usually user interaction.
|
||||
/// Handle a pointer event.
|
||||
///
|
||||
/// A number of different events (in the [`Event`] enum) are handled in this
|
||||
/// method call. A widget can handle these events in a number of ways, such as
|
||||
/// requesting things from the [`EventCtx`] or mutating the data.
|
||||
/// Pointer events will target the widget under the pointer, and then the
|
||||
/// event will bubble to each of its parents.
|
||||
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {}
|
||||
|
||||
/// Handle a text event.
|
||||
///
|
||||
/// Text events will target the [focused widget], then bubble to each parent.
|
||||
///
|
||||
/// [focused widget]: crate::doc::doc_06_masonry_concepts#text-focus
|
||||
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {}
|
||||
|
||||
/// Handle an event from the platform's accessibility API.
|
||||
///
|
||||
/// Accessibility events target a specific widget id, then bubble to each parent.
|
||||
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {}
|
||||
|
||||
/// Called at the beginning of a new animation frame.
|
||||
|
@ -236,7 +245,7 @@ pub trait Widget: AsAny {
|
|||
|
||||
/// Return the cursor icon for this widget.
|
||||
///
|
||||
/// This will be called when the mouse moves or [`request_cursor_icon_change`](MutateCtx::request_cursor_icon_change) is called.
|
||||
/// This will be called when the mouse moves or [`request_cursor_icon_change`](crate::MutateCtx::request_cursor_icon_change) is called.
|
||||
///
|
||||
/// **pos** - the mouse position in global coordinates (e.g. `(0,0)` is the top-left corner of the
|
||||
/// window).
|
||||
|
|
|
@ -257,20 +257,17 @@ impl WidgetState {
|
|||
}
|
||||
|
||||
/// The paint region for this widget.
|
||||
///
|
||||
/// For more information, see [`WidgetPod::paint_rect`](crate::WidgetPod::paint_rect).
|
||||
pub fn paint_rect(&self) -> Rect {
|
||||
self.local_paint_rect + self.origin.to_vec2()
|
||||
}
|
||||
|
||||
/// The rectangle used when calculating layout with other widgets
|
||||
///
|
||||
/// For more information, see [`WidgetPod::layout_rect`](crate::WidgetPod::layout_rect).
|
||||
// TODO - Remove
|
||||
/// The rectangle used when calculating layout with other widgets.
|
||||
pub fn layout_rect(&self) -> Rect {
|
||||
Rect::from_origin_size(self.origin, self.size)
|
||||
}
|
||||
|
||||
/// The [`layout_rect`](crate::WidgetPod::layout_rect) in window coordinates.
|
||||
/// The [`layout_rect`](Self::layout_rect) in window coordinates.
|
||||
///
|
||||
/// This might not map to a visible area of the screen, eg if the widget is scrolled
|
||||
/// away.
|
||||
|
|
|
@ -75,7 +75,7 @@ where
|
|||
);
|
||||
match message.downcast::<masonry::Action>() {
|
||||
Ok(action) => {
|
||||
if let masonry::Action::CheckboxChecked(checked) = *action {
|
||||
if let masonry::Action::CheckboxToggled(checked) = *action {
|
||||
MessageResult::Action((self.callback)(app_state, checked))
|
||||
} else {
|
||||
tracing::error!("Wrong action type in Checkbox::message: {action:?}");
|
||||
|
|
Loading…
Reference in New Issue