268 lines
8.2 KiB
Rust
268 lines
8.2 KiB
Rust
//! A narrow dispatch boundary for Allocation work gestures. Iced rows visit
|
|
//! siblings after capture; refusing inside Canvas alone still blurs an inspector
|
|
//! field. Refuse before dispatching that press to any ordinary child instead.
|
|
use crate::{
|
|
app::{
|
|
App, Message,
|
|
allocation_drag::{self, Event as WorkEvent},
|
|
},
|
|
diagram::DiagramCanvas,
|
|
};
|
|
use iced::advanced::{
|
|
Clipboard, Layout, Shell, Widget, layout, overlay, renderer,
|
|
widget::{Id, Operation, Tree, tree},
|
|
};
|
|
use iced::{
|
|
Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector, keyboard, mouse,
|
|
};
|
|
|
|
pub const CANVAS_ID: &str = "allocation-work-canvas";
|
|
pub const ARRANGE_SHORTCUT: &str = "Alt+A";
|
|
|
|
struct Root<'a> {
|
|
app: &'a App,
|
|
content: Element<'a, Message>,
|
|
}
|
|
#[derive(Default)]
|
|
struct State {
|
|
pointer: Option<Point>,
|
|
refused_left: bool,
|
|
}
|
|
#[derive(Default)]
|
|
struct CanvasBounds(Option<Rectangle>);
|
|
impl Operation for CanvasBounds {
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn Operation)) {
|
|
visit(self);
|
|
}
|
|
fn container(&mut self, id: Option<&Id>, bounds: Rectangle) {
|
|
if id == Some(&Id::new(CANVAS_ID)) {
|
|
self.0 = Some(bounds);
|
|
}
|
|
}
|
|
}
|
|
pub fn root<'a>(app: &'a App, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
|
Element::new(Root {
|
|
app,
|
|
content: content.into(),
|
|
})
|
|
}
|
|
fn arrange_key(event: &Event) -> bool {
|
|
matches!(event, Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key: keyboard::Key::Character(key), modifiers, repeat: false, ..
|
|
}) if key.eq_ignore_ascii_case("a") && *modifiers == keyboard::Modifiers::ALT)
|
|
}
|
|
impl Root<'_> {
|
|
fn dirty_object(&self, bounds: Rectangle, screen: Point) -> bool {
|
|
let Some(diagram) = self.app.active_diagram else {
|
|
return false;
|
|
};
|
|
if !allocation_drag::contains(bounds, screen) {
|
|
return false;
|
|
}
|
|
let canvas = DiagramCanvas {
|
|
allocation: Some(self.app.allocation_capability()),
|
|
project: self.app.project(),
|
|
view_index: Some(self.app.document.view_index()),
|
|
diagram,
|
|
selected: self.app.selected,
|
|
navigation_focus: None,
|
|
zoom: self.app.zoom,
|
|
pan: self.app.pan,
|
|
context: self.app.route_context().unwrap(),
|
|
selected_bend: None,
|
|
editable: false,
|
|
placing: false,
|
|
connection: None,
|
|
};
|
|
canvas.work_dirty_point(bounds, screen)
|
|
}
|
|
}
|
|
impl Widget<Message, Theme, Renderer> for Root<'_> {
|
|
fn tag(&self) -> tree::Tag {
|
|
tree::Tag::of::<State>()
|
|
}
|
|
fn state(&self) -> tree::State {
|
|
tree::State::new(State::default())
|
|
}
|
|
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: &Renderer,
|
|
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: &Renderer,
|
|
operation: &mut dyn Operation,
|
|
) {
|
|
self.content
|
|
.as_widget_mut()
|
|
.operate(&mut tree.children[0], layout, renderer, operation);
|
|
}
|
|
fn update(
|
|
&mut self,
|
|
tree: &mut Tree,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
viewport: &Rectangle,
|
|
) {
|
|
let state = tree.state.downcast_mut::<State>();
|
|
match event {
|
|
Event::Mouse(mouse::Event::CursorMoved { position }) => state.pointer = Some(*position),
|
|
Event::Mouse(mouse::Event::CursorLeft) => state.pointer = None,
|
|
Event::Window(iced::window::Event::RedrawRequested(_)) => {
|
|
state.pointer = cursor.position()
|
|
}
|
|
Event::Window(iced::window::Event::Unfocused) => {
|
|
state.pointer = None;
|
|
state.refused_left = false;
|
|
}
|
|
_ => {}
|
|
}
|
|
if state.refused_left {
|
|
if matches!(
|
|
event,
|
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
|
) {
|
|
state.refused_left = false;
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
if matches!(event, Event::Mouse(mouse::Event::CursorMoved { .. })) {
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
}
|
|
if self.app.allocation_input_owner() && !shell.is_event_captured() {
|
|
if arrange_key(event) {
|
|
// App rechecks clean ownership before arming. A dirty field
|
|
// retains its actual widget focus and selection on refusal.
|
|
shell.publish(Message::WorkDrag(WorkEvent::ArrangeNext));
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
if self.app.allocation_drag.arrange_next
|
|
&& matches!(
|
|
event,
|
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key: keyboard::Key::Named(keyboard::key::Named::Escape),
|
|
..
|
|
})
|
|
)
|
|
{
|
|
shell.publish(Message::Escape);
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
if !self.app.allocation_drag_clean()
|
|
&& matches!(
|
|
event,
|
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
|
)
|
|
&& let Some(screen) = state.pointer.or_else(|| cursor.position())
|
|
{
|
|
let mut canvas = CanvasBounds::default();
|
|
self.content.as_widget_mut().operate(
|
|
&mut tree.children[0],
|
|
layout,
|
|
renderer,
|
|
&mut canvas,
|
|
);
|
|
if canvas
|
|
.0
|
|
.is_some_and(|bounds| self.dirty_object(bounds, screen))
|
|
{
|
|
state.refused_left = true;
|
|
shell.publish(Message::WorkDrag(WorkEvent::Refused(
|
|
self.app.allocation_drag.epoch,
|
|
allocation_drag::FINISH_EDIT.into(),
|
|
)));
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
self.content.as_widget_mut().update(
|
|
&mut tree.children[0],
|
|
event,
|
|
layout,
|
|
cursor,
|
|
renderer,
|
|
clipboard,
|
|
shell,
|
|
viewport,
|
|
);
|
|
}
|
|
fn draw(
|
|
&self,
|
|
tree: &Tree,
|
|
renderer: &mut Renderer,
|
|
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: &Renderer,
|
|
) -> 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: &Renderer,
|
|
viewport: &Rectangle,
|
|
translation: Vector,
|
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
|
self.content.as_widget_mut().overlay(
|
|
&mut tree.children[0],
|
|
layout,
|
|
renderer,
|
|
viewport,
|
|
translation,
|
|
)
|
|
}
|
|
}
|