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

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