mirror of https://github.com/linebender/xilem
Implement stashed state (#601)
Add update_stashed pass Add is_still_interactive method
This commit is contained in:
parent
2642a9e146
commit
07dab9b73c
|
@ -331,8 +331,7 @@ impl_context_method!(
|
|||
|
||||
/// Check is widget is stashed.
|
||||
///
|
||||
/// **Note:** Stashed widgets are a WIP feature
|
||||
// FIXME - take stashed parents into account
|
||||
/// **Note:** Stashed widgets are a WIP feature.
|
||||
pub fn is_stashed(&self) -> bool {
|
||||
self.widget_state.is_stashed
|
||||
}
|
||||
|
@ -579,14 +578,10 @@ impl_context_method!(
|
|||
///
|
||||
/// This will *not* trigger a layout pass.
|
||||
///
|
||||
/// **Note:** Stashed widgets are a WIP feature
|
||||
/// **Note:** Stashed widgets are a WIP feature.
|
||||
pub fn set_stashed(&mut self, child: &mut WidgetPod<impl Widget>, stashed: bool) {
|
||||
if self.get_child_state_mut(child).is_stashed != stashed {
|
||||
self.widget_state.children_changed = true;
|
||||
self.widget_state.update_focus_chain = true;
|
||||
}
|
||||
|
||||
self.get_child_state_mut(child).is_stashed = stashed;
|
||||
self.get_child_state_mut(child).needs_update_stashed = true;
|
||||
self.get_child_state_mut(child).is_explicitly_stashed = stashed;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -77,6 +77,13 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot, root_state: &mut Wi
|
|||
let pointer_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into());
|
||||
|
||||
// -- UPDATE HOVERED WIDGETS --
|
||||
// Release pointer capture if target can no longer hold it.
|
||||
if let Some(id) = root.state.pointer_capture_target {
|
||||
if !root.is_still_interactive(id) {
|
||||
// TODO - Send PointerLeave event
|
||||
root.state.pointer_capture_target = None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut next_hovered_widget = if let Some(pos) = pointer_pos {
|
||||
// TODO - Apply scale?
|
||||
|
@ -175,10 +182,10 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot, root_state: &mut Wi
|
|||
// ----------------
|
||||
|
||||
pub(crate) fn run_update_focus_pass(root: &mut RenderRoot, root_state: &mut WidgetState) {
|
||||
// If the focused widget ends up disabled or removed, we set
|
||||
// If the focused widget is disabled, stashed or removed, we set
|
||||
// the focused id to None
|
||||
if let Some(id) = root.state.next_focused_widget {
|
||||
if !root.widget_arena.has(id) || root.widget_arena.get_state_mut(id).item.is_disabled {
|
||||
if !root.is_still_interactive(id) {
|
||||
root.state.next_focused_widget = None;
|
||||
}
|
||||
}
|
||||
|
@ -288,16 +295,11 @@ fn update_disabled_for_widget(
|
|||
.item
|
||||
.lifecycle(&mut ctx, &LifeCycle::DisabledChanged(disabled));
|
||||
state.item.is_disabled = disabled;
|
||||
state.item.update_focus_chain = true;
|
||||
}
|
||||
|
||||
state.item.needs_update_disabled = false;
|
||||
|
||||
if disabled && global_state.next_focused_widget == Some(id) {
|
||||
// This may get overwritten. That's ok, because either way the
|
||||
// focused widget, if there's one, won't be disabled.
|
||||
global_state.next_focused_widget = None;
|
||||
}
|
||||
|
||||
let parent_state = state.item;
|
||||
recurse_on_children(
|
||||
id,
|
||||
|
@ -319,6 +321,61 @@ pub(crate) fn run_update_disabled_pass(root: &mut RenderRoot) {
|
|||
|
||||
// ----------------
|
||||
|
||||
// TODO - Document the stashed pass.
|
||||
// *Stashed* is for widgets that are no longer "part of the graph". So they can't get keyboard events, don't get painted, etc, but should keep some state.
|
||||
// The stereotypical use case would be the contents of hidden tabs in a "tab group" widget.
|
||||
// Scrolled-out widgets are *not* stashed.
|
||||
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn update_stashed_for_widget(
|
||||
global_state: &mut RenderRootState,
|
||||
mut widget: ArenaMut<'_, Box<dyn Widget>>,
|
||||
state: ArenaMut<'_, WidgetState>,
|
||||
parent_stashed: bool,
|
||||
) {
|
||||
let _span = widget.item.make_trace_span().entered();
|
||||
let id = state.item.id;
|
||||
|
||||
let stashed = state.item.is_explicitly_stashed || parent_stashed;
|
||||
if !state.item.needs_update_stashed && stashed == state.item.is_stashed {
|
||||
return;
|
||||
}
|
||||
|
||||
if stashed != state.item.is_stashed {
|
||||
// TODO - Send update event
|
||||
state.item.is_stashed = stashed;
|
||||
state.item.update_focus_chain = true;
|
||||
// Note: We don't need request_repaint because stashing doesn't actually change
|
||||
// how widgets are painted, only how the Scenes they create are composed.
|
||||
state.item.needs_paint = true;
|
||||
state.item.needs_accessibility = true;
|
||||
// TODO - Remove once accessibility can be composed, same as above.
|
||||
state.item.request_accessibility = true;
|
||||
}
|
||||
|
||||
state.item.needs_update_stashed = false;
|
||||
|
||||
let parent_state = state.item;
|
||||
recurse_on_children(
|
||||
id,
|
||||
widget.reborrow_mut(),
|
||||
state.children,
|
||||
|widget, mut state| {
|
||||
update_stashed_for_widget(global_state, widget, state.reborrow_mut(), stashed);
|
||||
parent_state.merge_up(state.item);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn run_update_stashed_pass(root: &mut RenderRoot) {
|
||||
let _span = info_span!("update_stashed").entered();
|
||||
|
||||
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
|
||||
update_stashed_for_widget(&mut root.state, root_widget, root_state, false);
|
||||
}
|
||||
|
||||
// ----------------
|
||||
|
||||
// This pass will update scroll positions in cases where a widget has requested to be
|
||||
// scrolled into view (usually a textbox getting text events).
|
||||
// Each parent that implements scrolling will update its scroll position to ensure the
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::passes::paint::root_paint;
|
|||
use crate::passes::update::{
|
||||
run_update_anim_pass, run_update_disabled_pass, run_update_focus_chain_pass,
|
||||
run_update_focus_pass, run_update_new_widgets_pass, run_update_pointer_pass,
|
||||
run_update_scroll_pass,
|
||||
run_update_scroll_pass, run_update_stashed_pass,
|
||||
};
|
||||
use crate::text::TextBrush;
|
||||
use crate::tree_arena::TreeArena;
|
||||
|
@ -505,11 +505,14 @@ impl RenderRoot {
|
|||
root_compose(self, widget_state);
|
||||
}
|
||||
|
||||
// Update the disabled state if necessary
|
||||
// Update the disabled and stashed state if necessary
|
||||
// Always do this before updating the focus-chain
|
||||
if self.root_state().needs_update_disabled {
|
||||
run_update_disabled_pass(self);
|
||||
}
|
||||
if self.root_state().needs_update_stashed {
|
||||
run_update_stashed_pass(self);
|
||||
}
|
||||
|
||||
// Update the focus-chain if necessary
|
||||
// Always do this before sending focus change, since this event updates the focus chain.
|
||||
|
@ -546,6 +549,17 @@ impl RenderRoot {
|
|||
}
|
||||
}
|
||||
|
||||
// Checks whether the given id points to a widget that is "interactive".
|
||||
// i.e. not disabled or stashed.
|
||||
// Only interactive widgets can have text focus or pointer capture.
|
||||
pub(crate) fn is_still_interactive(&self, id: WidgetId) -> bool {
|
||||
let Some(state) = self.widget_arena.widget_states.find(id.to_raw()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
!state.item.is_stashed && !state.item.is_disabled
|
||||
}
|
||||
|
||||
pub(crate) fn widget_from_focus_chain(&mut self, forward: bool) -> Option<WidgetId> {
|
||||
let focused_widget = self.state.focused_widget;
|
||||
let focused_idx = focused_widget.and_then(|focused_widget| {
|
||||
|
|
|
@ -194,7 +194,9 @@ impl<'w> WidgetRef<'w, dyn Widget> {
|
|||
}
|
||||
// TODO - Use Widget::get_child_at_pos method
|
||||
if let Some(child) = innermost_widget.children().into_iter().rev().find(|child| {
|
||||
!child.widget.skip_pointer() && child.state().window_layout_rect().contains(pos)
|
||||
!child.state().is_stashed
|
||||
&& !child.widget.skip_pointer()
|
||||
&& child.state().window_layout_rect().contains(pos)
|
||||
}) {
|
||||
innermost_widget = child;
|
||||
} else {
|
||||
|
|
|
@ -107,8 +107,10 @@ pub struct WidgetState {
|
|||
/// An animation must run on this widget or a descendant
|
||||
pub(crate) needs_anim: bool,
|
||||
|
||||
/// This widget or a descendant changed its `explicitly_disabled` value
|
||||
/// This widget or a descendant changed its `is_explicitly_disabled` value
|
||||
pub(crate) needs_update_disabled: bool,
|
||||
/// This widget or a descendant changed its `is_explicitly_stashed` value
|
||||
pub(crate) needs_update_stashed: bool,
|
||||
|
||||
pub(crate) update_focus_chain: bool,
|
||||
|
||||
|
@ -127,6 +129,12 @@ pub struct WidgetState {
|
|||
/// This widget or an ancestor has been disabled.
|
||||
pub(crate) is_disabled: bool,
|
||||
|
||||
// TODO - Document concept of "stashing".
|
||||
/// This widget has been stashed.
|
||||
pub(crate) is_explicitly_stashed: bool,
|
||||
/// This widget or an ancestor has been stashed.
|
||||
pub(crate) is_stashed: bool,
|
||||
|
||||
pub(crate) is_hot: bool,
|
||||
|
||||
/// In the focused path, starting from window and ending at the focused widget.
|
||||
|
@ -136,9 +144,6 @@ pub struct WidgetState {
|
|||
/// Whether this specific widget is in the focus chain.
|
||||
pub(crate) in_focus_chain: bool,
|
||||
|
||||
// TODO - document
|
||||
pub(crate) is_stashed: bool,
|
||||
|
||||
// --- DEBUG INFO ---
|
||||
// Used in event/lifecycle/etc methods that are expected to be called recursively
|
||||
// on a widget's children, to make sure each child was visited.
|
||||
|
@ -169,7 +174,9 @@ impl WidgetState {
|
|||
translation: Vec2::ZERO,
|
||||
translation_changed: false,
|
||||
is_explicitly_disabled: false,
|
||||
is_explicitly_stashed: false,
|
||||
is_disabled: false,
|
||||
is_stashed: false,
|
||||
baseline_offset: 0.0,
|
||||
is_new: true,
|
||||
is_hot: false,
|
||||
|
@ -186,12 +193,12 @@ impl WidgetState {
|
|||
request_anim: true,
|
||||
needs_anim: true,
|
||||
needs_update_disabled: true,
|
||||
needs_update_stashed: true,
|
||||
focus_chain: Vec::new(),
|
||||
children_changed: true,
|
||||
cursor: None,
|
||||
text_registrations: Vec::new(),
|
||||
update_focus_chain: true,
|
||||
is_stashed: false,
|
||||
#[cfg(debug_assertions)]
|
||||
needs_visit: VisitBool(false.into()),
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -216,6 +223,7 @@ impl WidgetState {
|
|||
request_anim: false,
|
||||
needs_anim: false,
|
||||
needs_update_disabled: false,
|
||||
needs_update_stashed: false,
|
||||
children_changed: false,
|
||||
update_focus_chain: false,
|
||||
..WidgetState::new(id, "<root>")
|
||||
|
|
Loading…
Reference in New Issue