use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector};
pub struct UserInterface<'a, Message, Theme, Renderer> {
root: Element<'a, Message, Theme, Renderer>,
base: layout::Node,
state: widget::Tree,
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,
{
pub fn build<E: Into<Element<'a, Message, Theme, Renderer>>>(
root: E,
bounds: Size,
cache: Cache,
renderer: &mut Renderer,
) -> Self {
let mut root = root.into();
let Cache { mut state } = cache;
state.diff(root.as_widget());
let base = root.as_widget_mut().layout(
&mut state,
renderer,
&layout::Limits::new(Size::ZERO, bounds),
);
UserInterface {
root,
base,
state,
overlay: None,
bounds,
}
}
pub fn update(
&mut self,
events: &[Event],
cursor: mouse::Cursor,
renderer: &mut Renderer,
messages: &mut Vec<Message>,
) -> (State, Vec<event::Status>) {
let mut outdated = false;
let mut redraw_request = window::RedrawRequest::Wait;
let mut input_method = InputMethod::Disabled;
let mut clipboard = Clipboard::new();
let mut has_layout_changed = false;
let viewport = Rectangle::with_size(self.bounds);
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, overlay_interaction) = if maybe_overlay.is_some() {
let bounds = self.bounds;
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);
overlay.update(event, Layout::new(&layout), cursor, renderer, &mut shell);
event_statuses.push(shell.event_status());
redraw_request = redraw_request.min(shell.redraw_request());
input_method.merge(shell.input_method());
clipboard.merge(shell.clipboard_mut());
if shell.is_layout_invalid() {
drop(maybe_overlay);
self.base = self.root.as_widget_mut().layout(
&mut self.state,
renderer,
&layout::Limits::new(Size::ZERO, self.bounds),
);
maybe_overlay = self
.root
.as_widget_mut()
.overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
&viewport,
Vector::ZERO,
)
.map(overlay::Nested::new);
if maybe_overlay.is_none() {
break;
}
overlay = maybe_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
layout = overlay.layout(renderer, bounds);
has_layout_changed = 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();
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,
vec![event::Status::Ignored; events.len()],
mouse::Interaction::None,
)
};
drop(maybe_overlay);
let event_statuses = events
.iter()
.zip(overlay_statuses)
.map(|(event, overlay_status)| {
if matches!(overlay_status, event::Status::Captured) {
return overlay_status;
}
let mut shell = Shell::new(messages);
self.root.as_widget_mut().update(
&mut self.state,
event,
Layout::new(&self.base),
base_cursor,
renderer,
&mut shell,
&viewport,
);
if shell.event_status() == event::Status::Captured {
self.overlay = None;
}
redraw_request = redraw_request.min(shell.redraw_request());
input_method.merge(shell.input_method());
clipboard.merge(shell.clipboard_mut());
shell.revalidate_layout(|| {
has_layout_changed = true;
self.base = self.root.as_widget_mut().layout(
&mut self.state,
renderer,
&layout::Limits::new(Size::ZERO, self.bounds),
);
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 layout = overlay.layout(renderer, self.bounds);
let interaction =
overlay.mouse_interaction(Layout::new(&layout), cursor, renderer);
self.overlay = Some(Overlay {
layout,
interaction,
});
}
});
if shell.are_widgets_invalid() {
outdated = true;
}
shell.event_status().merge(overlay_status)
})
.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,
clipboard,
has_layout_changed,
}
},
event_statuses,
)
}
pub fn draw(
&mut self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
cursor: mouse::Cursor,
) {
let viewport = Rectangle::with_size(self.bounds);
renderer.reset(viewport);
let base_cursor = match &self.overlay {
None
| Some(Overlay {
interaction: mouse::Interaction::None,
..
}) => cursor,
_ => mouse::Cursor::Unavailable,
};
self.root.as_widget().draw(
&self.state,
renderer,
theme,
style,
Layout::new(&self.base),
base_cursor,
&viewport,
);
let Self {
overlay,
root,
base,
..
} = self;
let Some(Overlay { layout, .. }) = overlay.as_ref() else {
return;
};
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);
}
}
pub fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
let viewport = Rectangle::with_size(self.bounds);
self.root.as_widget_mut().operate(
&mut self.state,
Layout::new(&self.base),
renderer,
operation,
);
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)
{
if self.overlay.is_none() {
self.overlay = Some(Overlay {
layout: overlay.layout(renderer, self.bounds),
interaction: mouse::Interaction::None,
});
}
overlay.operate(
Layout::new(&self.overlay.as_ref().unwrap().layout),
renderer,
operation,
);
}
}
pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
Self::build(self.root, bounds, Cache { state: self.state }, renderer)
}
pub fn into_cache(self) -> Cache {
Cache { state: self.state }
}
}
#[derive(Debug)]
pub struct Cache {
state: widget::Tree,
}
impl Cache {
pub fn new() -> Cache {
Cache {
state: widget::Tree::empty(),
}
}
}
impl Default for Cache {
fn default() -> Cache {
Cache::new()
}
}
#[derive(Debug)]
pub enum State {
Outdated,
Updated {
mouse_interaction: mouse::Interaction,
redraw_request: window::RedrawRequest,
input_method: InputMethod,
clipboard: Clipboard,
has_layout_changed: bool,
},
}
impl State {
pub fn has_layout_changed(&self) -> bool {
match self {
State::Outdated => true,
State::Updated {
has_layout_changed, ..
} => *has_layout_changed,
}
}
}