Make `Textbox` focusable and trigger redraw in response to `request_layout` (#537)

Fixes #301.
This commit is contained in:
Olivier FAURE 2024-09-16 17:31:37 +02:00 committed by GitHub
parent a56d1e2467
commit 09d9ad555d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 66 additions and 36 deletions

View File

@ -0,0 +1,43 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
//! This is a very small example to demonstrate tab focus.
//! It will probably be removed in the future.
// On Windows platform, don't show a console when opening the app.
#![windows_subsystem = "windows"]
use masonry::app_driver::{AppDriver, DriverCtx};
use masonry::dpi::LogicalSize;
use masonry::widget::{Flex, RootWidget, Textbox};
use masonry::{Action, WidgetId};
use winit::window::Window;
const VERTICAL_WIDGET_SPACING: f64 = 20.0;
struct Driver;
impl AppDriver for Driver {
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, _action: Action) {}
}
pub fn main() {
let main_widget = Flex::column()
.with_child(Textbox::new(""))
.with_child(Textbox::new(""))
.with_spacer(VERTICAL_WIDGET_SPACING);
let window_size = LogicalSize::new(400.0, 400.0);
let window_attributes = Window::default_attributes()
.with_title("Two textboxes")
.with_resizable(true)
.with_min_inner_size(window_size);
masonry::event_loop_runner::run(
masonry::event_loop_runner::EventLoop::with_user_event(),
window_attributes,
RootWidget::new(main_widget),
Driver,
)
.unwrap();
}

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::render_root::RenderRoot;
use crate::tree_arena::ArenaMutChildren;
use accesskit::{NodeBuilder, NodeId, TreeUpdate};
use tracing::debug;
use tracing::info_span;
@ -35,12 +34,7 @@ fn build_accessibility_tree(
widget.item.short_type_name(),
id.to_raw(),
);
let current_node = build_access_node(
widget.item,
state.item,
state.children.reborrow_mut(),
scale_factor,
);
let current_node = build_access_node(widget.item, state.item, scale_factor);
let mut ctx = AccessCtx {
global_state,
@ -74,8 +68,8 @@ fn build_accessibility_tree(
widget.reborrow_mut(),
state.children,
|widget, mut state| {
// TODO - We skip updating stashed items.
// This may have knock-on effects we'd need to document.
// TODO - We don't skip updating stashed items because doing so
// is error-prone. We may want to revisit that decision.
if state.item.is_stashed {
return;
}
@ -92,29 +86,15 @@ fn build_accessibility_tree(
);
}
fn build_access_node(
widget: &dyn Widget,
state: &WidgetState,
state_children: ArenaMutChildren<'_, WidgetState>,
scale_factor: f64,
) -> NodeBuilder {
fn build_access_node(widget: &dyn Widget, state: &WidgetState, scale_factor: f64) -> NodeBuilder {
let mut node = NodeBuilder::new(widget.accessibility_role());
node.set_bounds(to_accesskit_rect(state.window_layout_rect(), scale_factor));
// TODO - We skip listing stashed items.
// This may have knock-on effects we'd need to document.
node.set_children(
widget
.children_ids()
.iter()
.copied()
.filter(|id| {
!state_children
.get_child(id.to_raw())
.unwrap()
.item
.is_stashed
})
.map(|id| id.into())
.collect::<Vec<NodeId>>(),
);

View File

@ -154,9 +154,9 @@ pub(crate) fn root_on_text_event(
// Handle Tab focus
if let TextEvent::KeyboardKey(key, mods) = event {
if handled == Handled::No
&& key.physical_key == PhysicalKey::Code(KeyCode::Tab)
if key.physical_key == PhysicalKey::Code(KeyCode::Tab)
&& key.state == ElementState::Pressed
&& handled == Handled::No
{
if !mods.shift_key() {
root.state.next_focused_widget = root.widget_from_focus_chain(true);

View File

@ -527,7 +527,11 @@ impl RenderRoot {
// We request a redraw if either the render tree or the accessibility
// tree needs to be rebuilt. Usually both happen at the same time.
// A redraw will trigger a rebuild of the accessibility tree.
if self.root_state().needs_paint || self.root_state().needs_accessibility {
// TODO - We assume that a relayout will trigger a repaint
if self.root_state().needs_paint
|| self.root_state().needs_accessibility
|| self.root_state().needs_layout
{
self.state
.signal_queue
.push_back(RenderRootSignal::RequestRedraw);

View File

@ -204,6 +204,15 @@ impl<T: Selectable> TextWithSelection<T> {
}
}
/// Call when this widget becomes focused
pub fn focus_gained(&mut self) {
if self.selection.is_none() {
// TODO - We need to have some "memory" of the text selected instead.
self.selection = Some(Selection::caret(self.text().len(), Affinity::Downstream));
}
self.needs_selection_update = true;
}
/// Call when another widget becomes focused
pub fn focus_lost(&mut self) {
self.selection = None;

View File

@ -116,7 +116,6 @@ impl WidgetMut<'_, Label> {
let ret = f(&mut self.widget.text_layout);
if self.widget.text_layout.needs_rebuild() {
self.ctx.request_layout();
self.ctx.request_paint();
}
ret
}

View File

@ -152,7 +152,6 @@ impl Widget for Prose {
let made_change = self.text_layout.pointer_down(inner_origin, state, *button);
if made_change {
ctx.request_layout();
ctx.request_paint();
ctx.request_focus();
ctx.capture_pointer();
}
@ -167,7 +166,6 @@ impl Widget for Prose {
{
// We might have changed text colours, so we need to re-request a layout
ctx.request_layout();
ctx.request_paint();
}
}
}
@ -188,7 +186,6 @@ impl Widget for Prose {
ctx.set_handled();
// TODO: only some handlers need this repaint
ctx.request_layout();
ctx.request_paint();
}
}

View File

@ -235,7 +235,9 @@ impl Widget for Textbox {
// TODO: Stop focusing on any links
}
StatusChange::FocusChanged(true) => {
self.editor.focus_gained();
// TODO: Focus on first link
ctx.request_layout();
}
_ => {}
}

View File

@ -228,7 +228,6 @@ impl WidgetMut<'_, VariableLabel> {
let ret = f(&mut self.widget.text_layout);
if self.widget.text_layout.needs_rebuild() {
self.ctx.request_layout();
self.ctx.request_paint();
}
ret
}
@ -247,7 +246,6 @@ impl WidgetMut<'_, VariableLabel> {
if !self.ctx.is_disabled() {
self.widget.text_layout.invalidate();
self.ctx.request_layout();
self.ctx.request_paint();
}
}
/// Set the font size for this text.
@ -269,13 +267,12 @@ impl WidgetMut<'_, VariableLabel> {
/// How to handle overflowing lines.
pub fn set_line_break_mode(&mut self, line_break_mode: LineBreaking) {
self.widget.line_break_mode = line_break_mode;
self.ctx.request_paint();
self.ctx.request_layout();
}
/// Set the weight which this font will target.
pub fn set_target_weight(&mut self, target: f32, over_millis: f32) {
self.widget.weight.move_to(target, over_millis);
self.ctx.request_layout();
self.ctx.request_paint();
self.ctx.request_anim_frame();
}
}
@ -343,7 +340,6 @@ impl Widget for VariableLabel {
ctx.request_anim_frame();
}
ctx.request_layout();
ctx.request_paint();
}
_ => {}
}