341 lines
9.7 KiB
Rust
341 lines
9.7 KiB
Rust
//! A delayed focus request must still own the live parent tree when executed.
|
|
//! Collect first: a missing target must not unfocus unrelated fields.
|
|
use super::Context;
|
|
use iced::advanced::widget::{Id, Operation, Tree, operation};
|
|
use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer};
|
|
use iced::{Element, Event, Length, Rectangle, Size, Task, Theme, Vector, mouse};
|
|
use oa_core::Id as ModelId;
|
|
use std::collections::BTreeMap;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) struct Stamp {
|
|
document: ModelId,
|
|
owner: ModelId,
|
|
representation: super::Representation,
|
|
visit: ModelId,
|
|
revision: u64,
|
|
session: ModelId,
|
|
mount: ModelId,
|
|
}
|
|
impl Stamp {
|
|
pub(crate) fn new(context: Context, session: ModelId, mount: ModelId) -> Self {
|
|
Self {
|
|
document: context.document,
|
|
owner: context.scenario,
|
|
representation: context.representation,
|
|
visit: context.visit,
|
|
revision: context.revision,
|
|
session,
|
|
mount,
|
|
}
|
|
}
|
|
}
|
|
pub(crate) fn name_id(stamp: Stamp) -> Id {
|
|
Id::from(format!(
|
|
"{}:{}:{}:{:?}:{}:{}:{}:{}",
|
|
super::NAME_INPUT,
|
|
stamp.document,
|
|
stamp.owner,
|
|
stamp.representation,
|
|
stamp.visit,
|
|
stamp.revision,
|
|
stamp.session,
|
|
stamp.mount
|
|
))
|
|
}
|
|
struct Owner(Option<Stamp>);
|
|
struct Root<'a, Message, R: renderer::Renderer> {
|
|
content: Element<'a, Message, Theme, R>,
|
|
owner: Option<Stamp>,
|
|
}
|
|
pub(crate) fn root<'a, Message: 'a, R: renderer::Renderer + 'a>(
|
|
content: impl Into<Element<'a, Message, Theme, R>>,
|
|
owner: Option<Stamp>,
|
|
) -> Element<'a, Message, Theme, R> {
|
|
Element::new(Root {
|
|
content: content.into(),
|
|
owner,
|
|
})
|
|
}
|
|
impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Root<'_, Message, R> {
|
|
fn children(&self) -> Vec<Tree> {
|
|
vec![Tree::new(self.content.as_widget())]
|
|
}
|
|
fn diff(&self, tree: &mut Tree) {
|
|
tree.diff_children(std::slice::from_ref(&self.content));
|
|
}
|
|
fn size(&self) -> Size<Length> {
|
|
self.content.as_widget().size()
|
|
}
|
|
fn layout(&mut self, tree: &mut Tree, renderer: &R, limits: &layout::Limits) -> layout::Node {
|
|
self.content
|
|
.as_widget_mut()
|
|
.layout(&mut tree.children[0], renderer, limits)
|
|
}
|
|
fn operate(
|
|
&mut self,
|
|
tree: &mut Tree,
|
|
layout: Layout<'_>,
|
|
renderer: &R,
|
|
op: &mut dyn Operation,
|
|
) {
|
|
// This is computed from the current App view, never retained widget state.
|
|
op.custom(None, layout.bounds(), &mut Owner(self.owner));
|
|
self.content
|
|
.as_widget_mut()
|
|
.operate(&mut tree.children[0], layout, renderer, op);
|
|
}
|
|
fn update(
|
|
&mut self,
|
|
tree: &mut Tree,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &R,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
viewport: &Rectangle,
|
|
) {
|
|
self.content.as_widget_mut().update(
|
|
&mut tree.children[0],
|
|
event,
|
|
layout,
|
|
cursor,
|
|
renderer,
|
|
clipboard,
|
|
shell,
|
|
viewport,
|
|
);
|
|
}
|
|
fn draw(
|
|
&self,
|
|
tree: &Tree,
|
|
renderer: &mut R,
|
|
theme: &Theme,
|
|
style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
) {
|
|
self.content.as_widget().draw(
|
|
&tree.children[0],
|
|
renderer,
|
|
theme,
|
|
style,
|
|
layout,
|
|
cursor,
|
|
viewport,
|
|
);
|
|
}
|
|
fn mouse_interaction(
|
|
&self,
|
|
tree: &Tree,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
renderer: &R,
|
|
) -> mouse::Interaction {
|
|
self.content.as_widget().mouse_interaction(
|
|
&tree.children[0],
|
|
layout,
|
|
cursor,
|
|
viewport,
|
|
renderer,
|
|
)
|
|
}
|
|
fn overlay<'b>(
|
|
&'b mut self,
|
|
tree: &'b mut Tree,
|
|
layout: Layout<'b>,
|
|
renderer: &R,
|
|
viewport: &Rectangle,
|
|
translation: Vector,
|
|
) -> Option<overlay::Element<'b, Message, Theme, R>> {
|
|
self.content.as_widget_mut().overlay(
|
|
&mut tree.children[0],
|
|
layout,
|
|
renderer,
|
|
viewport,
|
|
translation,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Scroll {
|
|
index: usize,
|
|
bounds: Rectangle,
|
|
capacity: Vector,
|
|
offset: Vector,
|
|
}
|
|
struct Collect {
|
|
stamp: Stamp,
|
|
valid: bool,
|
|
translation: Vector,
|
|
scrolls: Vec<Scroll>,
|
|
pending: Option<Scroll>,
|
|
index: usize,
|
|
target: Option<(Rectangle, Vec<Scroll>)>,
|
|
}
|
|
pub(crate) fn task<Message: Send + 'static>(stamp: Stamp) -> Task<Message> {
|
|
iced::advanced::widget::operate(Collect {
|
|
stamp,
|
|
valid: false,
|
|
translation: Vector::ZERO,
|
|
scrolls: vec![],
|
|
pending: None,
|
|
index: 0,
|
|
target: None,
|
|
})
|
|
.discard()
|
|
}
|
|
impl Operation for Collect {
|
|
fn custom(&mut self, _: Option<&Id>, _: Rectangle, state: &mut dyn std::any::Any) {
|
|
if let Some(owner) = state.downcast_ref::<Owner>() {
|
|
self.valid = owner.0 == Some(self.stamp);
|
|
}
|
|
}
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn Operation)) {
|
|
let previous = (self.translation, self.scrolls.len());
|
|
if let Some(scroll) = self.pending.take() {
|
|
self.translation += scroll.offset;
|
|
self.scrolls.push(scroll);
|
|
}
|
|
visit(self);
|
|
self.translation = previous.0;
|
|
self.scrolls.truncate(previous.1);
|
|
}
|
|
fn scrollable(
|
|
&mut self,
|
|
_: Option<&Id>,
|
|
bounds: Rectangle,
|
|
content: Rectangle,
|
|
offset: Vector,
|
|
_: &mut dyn operation::Scrollable,
|
|
) {
|
|
self.pending = Some(Scroll {
|
|
index: self.index,
|
|
bounds: translated(bounds, self.translation),
|
|
capacity: Vector::new(
|
|
(content.width - bounds.width).max(0.),
|
|
(content.height - bounds.height).max(0.),
|
|
),
|
|
offset,
|
|
});
|
|
self.index += 1;
|
|
}
|
|
fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, _: &mut dyn operation::Focusable) {
|
|
if id == Some(&name_id(self.stamp)) {
|
|
self.target = Some((translated(bounds, self.translation), self.scrolls.clone()));
|
|
}
|
|
}
|
|
fn finish(&self) -> operation::Outcome<()> {
|
|
if !self.valid {
|
|
return operation::Outcome::None;
|
|
}
|
|
let Some((mut bounds, ancestors)) = self.target.clone() else {
|
|
return operation::Outcome::None;
|
|
};
|
|
let mut shifts = BTreeMap::new();
|
|
for ancestor in ancestors.iter().rev() {
|
|
let dx = reveal(
|
|
bounds.x,
|
|
bounds.width,
|
|
ancestor.bounds.x,
|
|
ancestor.bounds.width,
|
|
);
|
|
let dy = reveal(
|
|
bounds.y,
|
|
bounds.height,
|
|
ancestor.bounds.y,
|
|
ancestor.bounds.height,
|
|
);
|
|
// Drawing offsets are rounded; an already-visible axis must not
|
|
// acquire a scroll delta from a fractional capacity alone.
|
|
let delta = Vector::new(
|
|
if dx == 0. {
|
|
0.
|
|
} else {
|
|
(ancestor.offset.x + dx).clamp(0., ancestor.capacity.x) - ancestor.offset.x
|
|
},
|
|
if dy == 0. {
|
|
0.
|
|
} else {
|
|
(ancestor.offset.y + dy).clamp(0., ancestor.capacity.y) - ancestor.offset.y
|
|
},
|
|
);
|
|
if delta != Vector::ZERO {
|
|
shifts.insert(ancestor.index, delta);
|
|
}
|
|
bounds = translated(bounds, delta);
|
|
}
|
|
operation::Outcome::Chain(Box::new(Apply {
|
|
stamp: self.stamp,
|
|
valid: false,
|
|
shifts,
|
|
index: 0,
|
|
}))
|
|
}
|
|
}
|
|
fn translated(bounds: Rectangle, delta: Vector) -> Rectangle {
|
|
Rectangle {
|
|
x: bounds.x - delta.x,
|
|
y: bounds.y - delta.y,
|
|
..bounds
|
|
}
|
|
}
|
|
fn reveal(start: f32, length: f32, viewport: f32, size: f32) -> f32 {
|
|
if start < viewport || length > size {
|
|
start - viewport
|
|
} else {
|
|
(start + length - viewport - size).max(0.)
|
|
}
|
|
}
|
|
struct Apply {
|
|
stamp: Stamp,
|
|
valid: bool,
|
|
shifts: BTreeMap<usize, Vector>,
|
|
index: usize,
|
|
}
|
|
impl Operation for Apply {
|
|
fn custom(&mut self, _: Option<&Id>, _: Rectangle, state: &mut dyn std::any::Any) {
|
|
if let Some(owner) = state.downcast_ref::<Owner>() {
|
|
self.valid = owner.0 == Some(self.stamp);
|
|
}
|
|
}
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn Operation)) {
|
|
visit(self);
|
|
}
|
|
fn focusable(&mut self, id: Option<&Id>, _: Rectangle, state: &mut dyn operation::Focusable) {
|
|
if self.valid {
|
|
if id == Some(&name_id(self.stamp)) {
|
|
state.focus();
|
|
} else {
|
|
state.unfocus();
|
|
}
|
|
}
|
|
}
|
|
fn scrollable(
|
|
&mut self,
|
|
_: Option<&Id>,
|
|
bounds: Rectangle,
|
|
content: Rectangle,
|
|
_: Vector,
|
|
state: &mut dyn operation::Scrollable,
|
|
) {
|
|
if self.valid
|
|
&& let Some(delta) = self.shifts.get(&self.index)
|
|
{
|
|
state.scroll_by(
|
|
operation::scrollable::AbsoluteOffset {
|
|
x: delta.x,
|
|
y: delta.y,
|
|
},
|
|
bounds,
|
|
content,
|
|
);
|
|
}
|
|
self.index += 1;
|
|
}
|
|
}
|