Decouple mouse::Interaction from UserInterface::draw

This commit is contained in:
Héctor Ramón Jiménez 2025-05-02 23:11:47 +02:00
parent 87afd5b79d
commit 301aac794b
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
6 changed files with 171 additions and 187 deletions

View file

@ -122,7 +122,7 @@ fn benchmark<'a>(
&mut renderer, &mut renderer,
); );
let _ = user_interface.draw( user_interface.draw(
&mut renderer, &mut renderer,
&Theme::Dark, &Theme::Dark,
&core::renderer::Style { &core::renderer::Style {

View file

@ -268,7 +268,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
renderer, renderer,
); );
let _ = interface.update( let (state, _) = interface.update(
&[Event::Window( &[Event::Window(
window::Event::RedrawRequested( window::Event::RedrawRequested(
Instant::now(), Instant::now(),
@ -280,7 +280,21 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
&mut Vec::new(), &mut Vec::new(),
); );
let mouse_interaction = interface.draw( // Update the mouse cursor
if let user_interface::State::Updated {
mouse_interaction,
..
} = state
{
window.set_cursor(
conversion::mouse_interaction(
mouse_interaction,
),
);
}
// Draw the interface
interface.draw(
renderer, renderer,
&Theme::Dark, &Theme::Dark,
&renderer::Style::default(), &renderer::Style::default(),
@ -297,11 +311,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
// Present the frame // Present the frame
frame.present(); frame.present();
// Update the mouse cursor
window.set_cursor(conversion::mouse_interaction(
mouse_interaction,
));
} }
Err(error) => match error { Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => { wgpu::SurfaceError::OutOfMemory => {

View file

@ -27,10 +27,15 @@ pub struct UserInterface<'a, Message, Theme, Renderer> {
root: Element<'a, Message, Theme, Renderer>, root: Element<'a, Message, Theme, Renderer>,
base: layout::Node, base: layout::Node,
state: widget::Tree, state: widget::Tree,
overlay: Option<layout::Node>, overlay: Option<Overlay>,
bounds: Size, bounds: Size,
} }
struct Overlay {
layout: layout::Node,
interaction: mouse::Interaction,
}
impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
@ -185,15 +190,13 @@ where
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
) -> (State, Vec<event::Status>) { ) -> (State, Vec<event::Status>) {
use std::mem::ManuallyDrop;
let mut outdated = false; let mut outdated = false;
let mut redraw_request = window::RedrawRequest::Wait; let mut redraw_request = window::RedrawRequest::Wait;
let mut input_method = InputMethod::Disabled; let mut input_method = InputMethod::Disabled;
let viewport = Rectangle::with_size(self.bounds); let viewport = Rectangle::with_size(self.bounds);
let mut manual_overlay = ManuallyDrop::new( let mut maybe_overlay = self
self.root .root
.as_widget_mut() .as_widget_mut()
.overlay( .overlay(
&mut self.state, &mut self.state,
@ -202,13 +205,13 @@ where
&viewport, &viewport,
Vector::ZERO, Vector::ZERO,
) )
.map(overlay::Nested::new), .map(overlay::Nested::new);
);
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() { let (base_cursor, overlay_statuses, overlay_interaction) =
if maybe_overlay.is_some() {
let bounds = self.bounds; let bounds = self.bounds;
let mut overlay = manual_overlay.as_mut().unwrap(); let mut overlay = maybe_overlay.as_mut().unwrap();
let mut layout = overlay.layout(renderer, bounds); let mut layout = overlay.layout(renderer, bounds);
let mut event_statuses = Vec::new(); let mut event_statuses = Vec::new();
@ -229,7 +232,7 @@ where
input_method.merge(shell.input_method()); input_method.merge(shell.input_method());
if shell.is_layout_invalid() { if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay); drop(maybe_overlay);
self.base = self.root.as_widget().layout( self.base = self.root.as_widget().layout(
&mut self.state, &mut self.state,
@ -237,8 +240,8 @@ where
&layout::Limits::new(Size::ZERO, self.bounds), &layout::Limits::new(Size::ZERO, self.bounds),
); );
manual_overlay = ManuallyDrop::new( maybe_overlay = self
self.root .root
.as_widget_mut() .as_widget_mut()
.overlay( .overlay(
&mut self.state, &mut self.state,
@ -247,14 +250,13 @@ where
&viewport, &viewport,
Vector::ZERO, Vector::ZERO,
) )
.map(overlay::Nested::new), .map(overlay::Nested::new);
);
if manual_overlay.is_none() { if maybe_overlay.is_none() {
break; break;
} }
overlay = manual_overlay.as_mut().unwrap(); overlay = maybe_overlay.as_mut().unwrap();
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
layout = overlay.layout(renderer, bounds); layout = overlay.layout(renderer, bounds);
@ -266,34 +268,43 @@ where
} }
} }
let base_cursor = if manual_overlay let (base_cursor, interaction) =
.as_mut() if let Some(overlay) = maybe_overlay.as_mut() {
.and_then(|overlay| { let interaction = cursor
cursor.position().map(|cursor_position| { .position()
.map(|cursor_position| {
overlay.mouse_interaction( overlay.mouse_interaction(
Layout::new(&layout), Layout::new(&layout),
mouse::Cursor::Available(cursor_position), mouse::Cursor::Available(cursor_position),
renderer, renderer,
) != mouse::Interaction::None )
}) })
}) .unwrap_or_default();
.unwrap_or_default()
{ if interaction == mouse::Interaction::None {
mouse::Cursor::Unavailable (cursor, mouse::Interaction::None)
} else { } else {
cursor (mouse::Cursor::Unavailable, interaction)
}
} else {
(cursor, mouse::Interaction::None)
}; };
self.overlay = Some(layout); self.overlay = Some(Overlay {
layout,
interaction,
});
(base_cursor, event_statuses) (base_cursor, event_statuses, interaction)
} else { } else {
(cursor, vec![event::Status::Ignored; events.len()]) (
cursor,
vec![event::Status::Ignored; events.len()],
mouse::Interaction::None,
)
}; };
let viewport = Rectangle::with_size(self.bounds); drop(maybe_overlay);
let _ = ManuallyDrop::into_inner(manual_overlay);
let event_statuses = events let event_statuses = events
.iter() .iter()
@ -341,11 +352,25 @@ where
}) })
.collect(); .collect();
let mouse_interaction =
if overlay_interaction == mouse::Interaction::None {
self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
base_cursor,
&viewport,
renderer,
)
} else {
overlay_interaction
};
( (
if outdated { if outdated {
State::Outdated State::Outdated
} else { } else {
State::Updated { State::Updated {
mouse_interaction,
redraw_request, redraw_request,
input_method, input_method,
} }
@ -433,54 +458,19 @@ where
theme: &Theme, theme: &Theme,
style: &renderer::Style, style: &renderer::Style,
cursor: mouse::Cursor, cursor: mouse::Cursor,
) -> mouse::Interaction { ) {
// TODO: Move to shell level (?) // TODO: Move to shell level (?)
renderer.clear(); renderer.clear();
let viewport = Rectangle::with_size(self.bounds); let viewport = Rectangle::with_size(self.bounds);
let (base_cursor, overlay_interaction) = if let Some(mut overlay) = self let base_cursor = match &self.overlay {
.root None
.as_widget_mut() | Some(Overlay {
.overlay( interaction: mouse::Interaction::None,
&mut self.state, ..
Layout::new(&self.base), }) => cursor,
renderer, _ => mouse::Cursor::Unavailable,
&viewport,
Vector::ZERO,
)
.map(overlay::Nested::new)
{
let overlay_layout = self
.overlay
.take()
.unwrap_or_else(|| overlay.layout(renderer, self.bounds));
let (cursor, overlay_interaction) = {
let overlay_interaction =
cursor.position().map(|cursor_position| {
overlay.mouse_interaction(
Layout::new(&overlay_layout),
mouse::Cursor::Available(cursor_position),
renderer,
)
});
match overlay_interaction {
Some(mouse::Interaction::None) | None => {
(cursor, mouse::Interaction::None)
}
Some(interaction) => {
(mouse::Cursor::Unavailable, interaction)
}
}
};
self.overlay = Some(overlay_layout);
(cursor, overlay_interaction)
} else {
(cursor, mouse::Interaction::None)
}; };
self.root.as_widget().draw( self.root.as_widget().draw(
@ -493,14 +483,6 @@ where
&viewport, &viewport,
); );
let base_interaction = self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
base_cursor,
&viewport,
renderer,
);
let Self { let Self {
overlay, overlay,
root, root,
@ -508,15 +490,12 @@ where
.. ..
} = self; } = self;
// TODO: Currently, we need to call Widget::overlay twice to let Some(Overlay { layout, .. }) = overlay.as_ref() else {
// implement the painter's algorithm properly. return;
// };
// Once we have a proper persistent widget tree, we should be able to
// avoid this additional call. let overlay = root
overlay .as_widget_mut()
.as_ref()
.and_then(|layout| {
root.as_widget_mut()
.overlay( .overlay(
&mut self.state, &mut self.state,
Layout::new(base), Layout::new(base),
@ -524,24 +503,11 @@ where
&viewport, &viewport,
Vector::ZERO, Vector::ZERO,
) )
.map(overlay::Nested::new) .map(overlay::Nested::new);
.map(|mut overlay| {
overlay.draw(
renderer,
theme,
style,
Layout::new(layout),
cursor,
);
if overlay_interaction != mouse::Interaction::None { if let Some(mut overlay) = overlay {
overlay_interaction overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
} else {
base_interaction
} }
})
})
.unwrap_or(base_interaction)
} }
/// Applies a [`widget::Operation`] to the [`UserInterface`]. /// Applies a [`widget::Operation`] to the [`UserInterface`].
@ -572,11 +538,14 @@ where
.map(overlay::Nested::new) .map(overlay::Nested::new)
{ {
if self.overlay.is_none() { if self.overlay.is_none() {
self.overlay = Some(overlay.layout(renderer, self.bounds)); self.overlay = Some(Overlay {
layout: overlay.layout(renderer, self.bounds),
interaction: mouse::Interaction::None,
});
} }
overlay.operate( overlay.operate(
Layout::new(self.overlay.as_ref().unwrap()), Layout::new(&self.overlay.as_ref().unwrap().layout),
renderer, renderer,
operation, operation,
); );
@ -629,6 +598,8 @@ pub enum State {
/// The [`UserInterface`] is up-to-date and can be reused without /// The [`UserInterface`] is up-to-date and can be reused without
/// rebuilding. /// rebuilding.
Updated { Updated {
/// The current [`mouse::Interaction`] of the user interface.
mouse_interaction: mouse::Interaction,
/// The [`window::RedrawRequest`] describing when a redraw should be performed. /// The [`window::RedrawRequest`] describing when a redraw should be performed.
redraw_request: window::RedrawRequest, redraw_request: window::RedrawRequest,
/// The current [`InputMethod`] strategy of the user interface. /// The current [`InputMethod`] strategy of the user interface.

View file

@ -436,7 +436,7 @@ where
&mut self.messages, &mut self.messages,
); );
let _ = self.raw.draw( self.raw.draw(
&mut self.renderer, &mut self.renderer,
theme, theme,
&core::renderer::Style { &core::renderer::Style {

View file

@ -758,7 +758,7 @@ async fn run_instance<P>(
&mut messages, &mut messages,
); );
let new_mouse_interaction = ui.draw( ui.draw(
&mut window.renderer, &mut window.renderer,
window.state.theme(), window.state.theme(),
&renderer::Style { &renderer::Style {
@ -768,16 +768,6 @@ async fn run_instance<P>(
); );
draw_span.finish(); draw_span.finish();
if new_mouse_interaction != window.mouse_interaction {
window.raw.set_cursor(
conversion::mouse_interaction(
new_mouse_interaction,
),
);
window.mouse_interaction = new_mouse_interaction;
}
runtime.broadcast(subscription::Event::Interaction { runtime.broadcast(subscription::Event::Interaction {
window: id, window: id,
event: redraw_event, event: redraw_event,
@ -787,10 +777,12 @@ async fn run_instance<P>(
if let user_interface::State::Updated { if let user_interface::State::Updated {
redraw_request, redraw_request,
input_method, input_method,
mouse_interaction,
} = ui_state } = ui_state
{ {
window.request_redraw(redraw_request); window.request_redraw(redraw_request);
window.request_input_method(input_method); window.request_input_method(input_method);
window.update_mouse(mouse_interaction);
} }
window.draw_preedit(); window.draw_preedit();
@ -944,8 +936,11 @@ async fn run_instance<P>(
match ui_state { match ui_state {
user_interface::State::Updated { user_interface::State::Updated {
redraw_request: _redraw_request, redraw_request: _redraw_request,
mouse_interaction,
.. ..
} => { } => {
window.update_mouse(mouse_interaction);
#[cfg(not( #[cfg(not(
feature = "unconditional-rendering" feature = "unconditional-rendering"
))] ))]

View file

@ -246,6 +246,15 @@ where
} }
} }
pub fn update_mouse(&mut self, interaction: mouse::Interaction) {
if interaction != self.mouse_interaction {
self.raw
.set_cursor(conversion::mouse_interaction(interaction));
self.mouse_interaction = interaction;
}
}
pub fn draw_preedit(&mut self) { pub fn draw_preedit(&mut self) {
if let Some(preedit) = &self.preedit { if let Some(preedit) = &self.preedit {
preedit.draw( preedit.draw(