Remove register methods (#636)

Replace with `Widget::accepts_xxx()` methods
Tweak accessibility code
Remove `BuildFocusChain` event

Remove `WidgetState::is_portal`, `register_as_portal()`, and overall
remove the idea of a first-class "portal" concept.
This commit is contained in:
Olivier FAURE 2024-10-13 03:22:20 +02:00 committed by GitHub
parent a26bf11119
commit 4ad4506bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 126 additions and 125 deletions

View File

@ -335,9 +335,19 @@ impl_context_method!(
self.widget_state.has_focus
}
/// Whether this specific widget is in the focus chain.
pub fn is_in_focus_chain(&self) -> bool {
self.widget_state.in_focus_chain
/// Whether this widget gets pointer events and hovered status.
pub fn accepts_pointer_interaction(&self) -> bool {
self.widget_state.accepts_pointer_interaction
}
/// Whether this widget gets text focus.
pub fn accepts_focus(&self) -> bool {
self.widget_state.accepts_focus
}
/// Whether this widget gets IME events.
pub fn accepts_text_input(&self) -> bool {
self.widget_state.accepts_text_input
}
/// The disabled state of a widget.
@ -791,34 +801,6 @@ impl RegisterCtx<'_> {
}
}
impl LifeCycleCtx<'_> {
/// Register this widget to be eligile to accept focus automatically.
///
/// This should only be called in response to a [`LifeCycle::BuildFocusChain`] event.
///
/// See [`EventCtx::is_focused`](Self::is_focused) for more information about focus.
///
/// [`LifeCycle::BuildFocusChain`]: crate::LifeCycle::BuildFocusChain
pub fn register_for_focus(&mut self) {
trace!("register_for_focus");
self.widget_state.focus_chain.push(self.widget_id());
self.widget_state.in_focus_chain = true;
}
/// Register this widget as accepting text input.
pub fn register_as_text_input(&mut self) {
self.widget_state.is_text_input = true;
}
// TODO - remove - See issue https://github.com/linebender/xilem/issues/366
/// Register this widget as a portal.
///
/// This should only be used by scroll areas.
pub fn register_as_portal(&mut self) {
self.widget_state.is_portal = true;
}
}
// --- MARK: UPDATE LAYOUT ---
impl LayoutCtx<'_> {
#[track_caller]

View File

@ -292,16 +292,6 @@ pub enum LifeCycle {
/// [`is_stashed`]: crate::EventCtx::is_stashed
/// [`set_stashed`]: crate::EventCtx::set_stashed
StashedChanged(bool),
/// Called when the widget tree changes and Masonry wants to rebuild the
/// Focus-chain.
///
/// It is the only place from which [`register_for_focus`] should be called.
/// By doing so the widget can get focused by other widgets using [`focus_next`] or [`focus_prev`].
///
/// [`register_for_focus`]: crate::LifeCycleCtx::register_for_focus
/// [`focus_next`]: crate::EventCtx::focus_next
/// [`focus_prev`]: crate::EventCtx::focus_prev
BuildFocusChain,
/// Called when a child widgets uses
/// [`EventCtx::request_pan_to_this`](crate::EventCtx::request_pan_to_this).
@ -488,26 +478,6 @@ impl PointerState {
}
impl LifeCycle {
// TODO - link this to documentation of stashed widgets - See issue https://github.com/linebender/xilem/issues/372
/// Whether this event should be sent to widgets which are currently not visible and not
/// accessible.
///
/// If a widget changes which children are `hidden` it must call [`children_changed`].
/// For a more detailed explanation of the `hidden` state, see [`Event::should_propagate_to_hidden`].
///
/// [`children_changed`]: crate::EventCtx::children_changed
/// [`Event::should_propagate_to_hidden`]: Event::should_propagate_to_hidden
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
LifeCycle::WidgetAdded => true,
LifeCycle::AnimFrame(_) => true,
LifeCycle::DisabledChanged(_) => true,
LifeCycle::StashedChanged(_) => true,
LifeCycle::BuildFocusChain => false,
LifeCycle::RequestPanToChild(_) => false,
}
}
/// Short name, for debug logging.
///
/// Essentially returns the enum variant name.
@ -517,7 +487,6 @@ impl LifeCycle {
LifeCycle::AnimFrame(_) => "AnimFrame",
LifeCycle::DisabledChanged(_) => "DisabledChanged",
LifeCycle::StashedChanged(_) => "StashedChanged",
LifeCycle::BuildFocusChain => "BuildFocusChain",
LifeCycle::RequestPanToChild(_) => "RequestPanToChild",
}
}

View File

@ -113,7 +113,7 @@ fn build_access_node(widget: &mut dyn Widget, ctx: &mut AccessCtx) -> NodeBuilde
if ctx.widget_state.clip.is_some() {
node.set_clips_children();
}
if ctx.is_in_focus_chain() && !ctx.is_disabled() {
if ctx.accepts_focus() && !ctx.is_disabled() && !ctx.is_stashed() {
node.add_action(accesskit::Action::Focus);
}
if ctx.is_focused() {

View File

@ -234,7 +234,7 @@ pub(crate) fn run_layout_inner<W: Widget>(
// TODO - This check might be redundant with the code updating local_paint_rect
let child_rect = child_state.paint_rect();
if !rect_contains(&state.local_paint_rect, &child_rect) && !state.is_portal {
if !rect_contains(&state.local_paint_rect, &child_rect) && state.clip.is_none() {
debug_panic!(
"Error in '{}' {}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' {}",
widget.short_type_name(),

View File

@ -248,7 +248,7 @@ pub(crate) fn run_update_focus_pass(root: &mut RenderRoot) {
if prev_focused != next_focused {
let was_ime_active = root.state.is_ime_active;
let is_ime_active = if let Some(id) = next_focused {
root.widget_arena.get_state(id).item.is_text_input
root.widget_arena.get_state(id).item.accepts_text_input
} else {
false
};
@ -551,8 +551,11 @@ fn update_widget_tree(
"{} received LifeCycle::WidgetAdded",
widget.item.short_type_name()
);
state.item.accepts_pointer_interaction = widget.item.accepts_pointer_interaction();
state.item.accepts_focus = widget.item.accepts_focus();
state.item.accepts_text_input = widget.item.accepts_text_input();
state.item.is_new = false;
}
state.item.is_new = false;
// We can recurse on this widget's children, because they have already been added
// to the arena above.
@ -598,7 +601,7 @@ pub(crate) fn run_update_widget_tree_pass(root: &mut RenderRoot) {
fn update_focus_chain_for_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
state: ArenaMut<'_, WidgetState>,
parent_focus_chain: &mut Vec<WidgetId>,
) {
let _span = widget.item.make_trace_span().entered();
@ -612,16 +615,9 @@ fn update_focus_chain_for_widget(
state.item.has_focus = global_state.focused_widget == Some(id);
let had_focus = state.item.has_focus;
state.item.in_focus_chain = false;
state.item.focus_chain.clear();
{
let mut ctx = LifeCycleCtx {
global_state,
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
};
widget.item.lifecycle(&mut ctx, &LifeCycle::BuildFocusChain);
if state.item.accepts_focus {
state.item.focus_chain.push(id);
}
state.item.update_focus_chain = false;

View File

@ -45,6 +45,9 @@ pub const REPLACE_CHILD: Selector = Selector::new("masonry-test.replace-child");
/// This widget is generic over its state, which is passed in at construction time.
pub struct ModularWidget<S> {
state: S,
accepts_pointer_interaction: bool,
accepts_focus: bool,
accepts_text_input: bool,
on_pointer_event: Option<Box<PointerEventFn<S>>>,
on_text_event: Option<Box<TextEventFn<S>>>,
on_access_event: Option<Box<AccessEventFn<S>>>,
@ -128,6 +131,9 @@ impl<S> ModularWidget<S> {
pub fn new(state: S) -> Self {
ModularWidget {
state,
accepts_pointer_interaction: true,
accepts_focus: false,
accepts_text_input: false,
on_pointer_event: None,
on_text_event: None,
on_access_event: None,
@ -143,6 +149,21 @@ impl<S> ModularWidget<S> {
}
}
pub fn accepts_pointer_interaction(mut self, flag: bool) -> Self {
self.accepts_pointer_interaction = flag;
self
}
pub fn accepts_focus(mut self, flag: bool) -> Self {
self.accepts_focus = flag;
self
}
pub fn accepts_text_input(mut self, flag: bool) -> Self {
self.accepts_text_input = flag;
self
}
pub fn pointer_event_fn(
mut self,
f: impl FnMut(&mut S, &mut EventCtx, &PointerEvent) + 'static,
@ -315,6 +336,18 @@ impl<S: 'static> Widget for ModularWidget<S> {
SmallVec::new()
}
}
fn accepts_pointer_interaction(&self) -> bool {
self.accepts_pointer_interaction
}
fn accepts_focus(&self) -> bool {
self.accepts_focus
}
fn accepts_text_input(&self) -> bool {
self.accepts_text_input
}
}
impl ReplaceChild {

View File

@ -58,7 +58,7 @@ impl Button {
/// ```
pub fn from_label(label: Label) -> Button {
Button {
label: WidgetPod::new(label.with_skip_pointer(true)),
label: WidgetPod::new(label.with_pointer_interaction(false)),
}
}
}

View File

@ -29,7 +29,7 @@ impl Checkbox {
pub fn new(checked: bool, text: impl Into<ArcStr>) -> Checkbox {
Checkbox {
checked,
label: WidgetPod::new(Label::new(text).with_skip_pointer(true)),
label: WidgetPod::new(Label::new(text).with_pointer_interaction(false)),
}
}
@ -37,7 +37,7 @@ impl Checkbox {
pub fn from_label(checked: bool, label: Label) -> Checkbox {
Checkbox {
checked,
label: WidgetPod::new(label.with_skip_pointer(true)),
label: WidgetPod::new(label.with_pointer_interaction(false)),
}
}
}

View File

@ -43,7 +43,7 @@ pub struct Label {
line_break_mode: LineBreaking,
show_disabled: bool,
brush: TextBrush,
skip_pointer: bool,
interactive: bool,
}
// --- MARK: BUILDERS ---
@ -55,14 +55,15 @@ impl Label {
line_break_mode: LineBreaking::Overflow,
show_disabled: true,
brush: crate::theme::TEXT_COLOR.into(),
skip_pointer: false,
interactive: true,
}
}
// TODO - Rename
// TODO - Document
pub fn with_skip_pointer(mut self, skip_pointer: bool) -> Self {
self.skip_pointer = skip_pointer;
/// Sets the value returned by [`accepts_pointer_interaction`].
///
/// True by default.
pub fn with_pointer_interaction(mut self, interactive: bool) -> Self {
self.interactive = interactive;
self
}
@ -204,7 +205,6 @@ impl Widget for Label {
// TODO: Parley seems to require a relayout when colours change
ctx.request_layout();
}
LifeCycle::BuildFocusChain => {}
_ => {}
}
}
@ -258,14 +258,14 @@ impl Widget for Label {
node.set_name(self.text().as_ref().to_string());
}
fn skip_pointer(&self) -> bool {
self.skip_pointer
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
SmallVec::new()
}
fn accepts_pointer_interaction(&self) -> bool {
self.interactive
}
fn make_trace_span(&self) -> Span {
trace_span!("Label")
}

View File

@ -329,9 +329,6 @@ impl<W: Widget> Widget for Portal<W> {
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
match event {
LifeCycle::WidgetAdded => {
ctx.register_as_portal();
}
LifeCycle::RequestPanToChild(target) => {
let portal_size = ctx.size();
let content_size = ctx.get_raw_ref(&mut self.child).ctx().layout_rect().size();

View File

@ -225,9 +225,6 @@ impl Widget for Prose {
// TODO: Parley seems to require a relayout when colours change
ctx.request_layout();
}
LifeCycle::BuildFocusChain => {
// When we add links to `Prose`, they will probably need to be handled here.
}
_ => {}
}
}

View File

@ -118,11 +118,7 @@ fn check_pointer_capture_outside_pointer_down() {
fn check_pointer_capture_text_event() {
let id = WidgetId::next();
let widget = ModularWidget::new(())
.lifecycle_fn(|_, ctx, event| {
if let LifeCycle::WidgetAdded = event {
ctx.register_for_focus();
}
})
.accepts_focus(true)
.text_event_fn(|_, ctx, _event| {
ctx.capture_pointer();
})

View File

@ -8,9 +8,6 @@ expression: record
L(
WidgetAdded,
),
L(
BuildFocusChain,
),
Layout(
0.0W×0.0H,
),

View File

@ -244,9 +244,6 @@ impl Widget for Textbox {
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
match event {
LifeCycle::WidgetAdded => {
ctx.register_as_text_input();
}
LifeCycle::DisabledChanged(disabled) => {
if self.show_disabled {
if *disabled {
@ -258,9 +255,6 @@ impl Widget for Textbox {
// TODO: Parley seems to require a relayout when colours change
ctx.request_layout();
}
LifeCycle::BuildFocusChain => {
ctx.register_for_focus();
}
_ => {}
}
}
@ -340,6 +334,14 @@ impl Widget for Textbox {
SmallVec::new()
}
fn accepts_focus(&self) -> bool {
true
}
fn accepts_text_input(&self) -> bool {
true
}
fn make_trace_span(&self) -> Span {
trace_span!("Textbox")
}

View File

@ -151,9 +151,31 @@ pub trait Widget: AsAny {
/// responsible for visiting all their children during `layout` and `register_children`.
fn children_ids(&self) -> SmallVec<[WidgetId; 16]>;
// TODO - Rename
// TODO - Document
fn skip_pointer(&self) -> bool {
/// Whether this widget gets pointer events and hovered status. True by default.
///
/// If false, the widget will be treated as "transparent" for the pointer, meaning
/// that the pointer will be considered as hovering whatever is under this widget.
///
/// **Note:** The value returned by this method is cached at widget creation and can't be changed.
fn accepts_pointer_interaction(&self) -> bool {
true
}
/// Whether this widget gets text focus. False by default.
///
/// If true, pressing Tab can focus this widget.
///
/// **Note:** The value returned by this method is cached at widget creation and can't be changed.
fn accepts_focus(&self) -> bool {
false
}
/// Whether this widget gets IME events. False by default.
///
/// If true, focusing this widget will start an IME session.
///
/// **Note:** The value returned by this method is cached at widget creation and can't be changed.
fn accepts_text_input(&self) -> bool {
false
}
@ -220,7 +242,7 @@ pub trait Widget: AsAny {
// The position must be inside the child's layout and inside the child's clip path (if
// any).
if !child.ctx().is_stashed()
&& !child.widget.skip_pointer()
&& child.ctx().accepts_pointer_interaction()
&& child.ctx().window_layout_rect().contains(pos)
{
return Some(child);
@ -397,8 +419,16 @@ impl Widget for Box<dyn Widget> {
self.deref().children_ids()
}
fn skip_pointer(&self) -> bool {
self.deref().skip_pointer()
fn accepts_pointer_interaction(&self) -> bool {
self.deref().accepts_pointer_interaction()
}
fn accepts_focus(&self) -> bool {
self.deref().accepts_focus()
}
fn accepts_text_input(&self) -> bool {
self.deref().accepts_text_input()
}
fn make_trace_span(&self) -> Span {

View File

@ -62,12 +62,17 @@ pub(crate) struct WidgetState {
/// the baseline. Widgets that contain text or controls that expect to be
/// laid out alongside text can set this as appropriate.
pub(crate) baseline_offset: f64,
// TODO - Remove
pub(crate) is_portal: bool,
/// Tracks whether widget gets pointer events.
/// Should be immutable after `WidgetAdded` event.
pub(crate) accepts_pointer_interaction: bool,
/// Tracks whether widget gets text focus.
/// Should be immutable after `WidgetAdded` event.
pub(crate) accepts_focus: bool,
/// Tracks whether widget is eligible for IME events.
/// Should be immutable after `WidgetAdded` event.
pub(crate) is_text_input: bool,
pub(crate) accepts_text_input: bool,
/// The area of the widget that is being edited by
/// an IME, in local coordinates.
pub(crate) ime_area: Option<Rect>,
@ -145,9 +150,6 @@ pub(crate) struct WidgetState {
/// Descendants of the focused widget are not in the focused path.
pub(crate) has_focus: bool,
/// Whether this specific widget is in the focus chain.
pub(crate) in_focus_chain: 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.
@ -173,8 +175,9 @@ impl WidgetState {
is_expecting_place_child_call: false,
paint_insets: Insets::ZERO,
local_paint_rect: Rect::ZERO,
is_portal: false,
is_text_input: false,
accepts_pointer_interaction: true,
accepts_focus: false,
accepts_text_input: false,
ime_area: None,
clip: Default::default(),
translation: Vec2::ZERO,
@ -195,7 +198,6 @@ impl WidgetState {
request_accessibility: true,
needs_accessibility: true,
has_focus: false,
in_focus_chain: false,
request_anim: true,
needs_anim: true,
needs_update_disabled: true,