Add captured process and scenario canvas removal
This commit is contained in:
@@ -10,6 +10,7 @@ use iced::{keyboard, mouse};
|
||||
struct Input {
|
||||
scope: Option<Scope>,
|
||||
focused: bool,
|
||||
focus_pending: bool,
|
||||
pointer: Option<Point>,
|
||||
pan: Option<(mouse::Button, Point)>,
|
||||
space: bool,
|
||||
@@ -21,9 +22,11 @@ impl operation::Focusable for Input {
|
||||
}
|
||||
fn focus(&mut self) {
|
||||
self.focused = true;
|
||||
self.focus_pending = false;
|
||||
}
|
||||
fn unfocus(&mut self) {
|
||||
self.focused = false;
|
||||
self.focus_pending = false;
|
||||
self.pan = None;
|
||||
self.space = false;
|
||||
}
|
||||
@@ -92,6 +95,7 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
}
|
||||
if !self.keyboard {
|
||||
state.focused = false;
|
||||
state.focus_pending = false;
|
||||
}
|
||||
state.scope = Some(self.scope);
|
||||
tree.diff_children(std::slice::from_ref(&self.content));
|
||||
@@ -143,6 +147,11 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
return;
|
||||
}
|
||||
let state = tree.state.downcast_mut::<Input>();
|
||||
// A traversal operation is applied after this Iced event batch. Keep
|
||||
// its focus witness, but retire keyboard admission immediately.
|
||||
if state.focus_pending && matches!(event, Event::Keyboard(_)) {
|
||||
return;
|
||||
}
|
||||
let changed_bounds = state.bounds.is_some_and(|bounds| bounds != layout.bounds());
|
||||
state.bounds = Some(layout.bounds());
|
||||
if changed_bounds || !self.keyboard {
|
||||
@@ -151,6 +160,7 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
}
|
||||
if !self.keyboard {
|
||||
state.focused = false;
|
||||
state.focus_pending = false;
|
||||
}
|
||||
if let Event::Mouse(mouse::Event::CursorMoved { position }) = event {
|
||||
state.pointer = Some(*position);
|
||||
@@ -178,6 +188,7 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
state.pan = None;
|
||||
state.space = false;
|
||||
state.focused = false;
|
||||
state.focus_pending = false;
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorLeft) => {
|
||||
state.pan = None;
|
||||
@@ -190,6 +201,7 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
cancel = true;
|
||||
}
|
||||
state.focused = inside && self.keyboard;
|
||||
state.focus_pending = false;
|
||||
if inside
|
||||
&& self.keyboard
|
||||
&& (*button == mouse::Button::Middle
|
||||
@@ -242,6 +254,7 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
repeat: false,
|
||||
..
|
||||
}) if self.keyboard && modifiers.is_empty() => {
|
||||
state.focus_pending = true;
|
||||
camera = Some(CameraEvent::ToggleFocus);
|
||||
capture = true;
|
||||
cancel = true;
|
||||
@@ -258,12 +271,14 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
capture = true;
|
||||
}
|
||||
Key::Named(Named::Tab) => {
|
||||
state.focus_pending = true;
|
||||
camera = Some(CameraEvent::Traverse(modifiers.shift()));
|
||||
capture = true;
|
||||
}
|
||||
Key::Named(Named::Escape) => {
|
||||
state.pan = None;
|
||||
state.space = false;
|
||||
state.focus_pending = true;
|
||||
camera = Some(CameraEvent::Leave);
|
||||
capture = true;
|
||||
}
|
||||
@@ -309,7 +324,9 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Viewport<'_,
|
||||
shell.request_redraw();
|
||||
return;
|
||||
}
|
||||
if matches!(event, Event::Keyboard(_)) && (!state.focused || !self.keyboard) {
|
||||
if matches!(event, Event::Keyboard(_))
|
||||
&& (!state.focused || !self.keyboard || state.pan.is_some() || state.space)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let child_cursor = if inside {
|
||||
|
||||
@@ -66,6 +66,34 @@ impl std::fmt::Display for MessageStyle {
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) fn removal_test_point(app: &App, owner: Id, id: Id) -> Point {
|
||||
let scene = super::Scene::sequence(app.project(), owner, id);
|
||||
let point = scene
|
||||
.boxes
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|item| item.id == id && !item.title.is_empty())
|
||||
.map(|item| Point::new(item.rect.x + 20., item.rect.y + 20.))
|
||||
.or_else(|| {
|
||||
scene
|
||||
.lines
|
||||
.iter()
|
||||
.find(|line| line.id == id && !line.label.is_empty())
|
||||
.map(|line| line.label_at + iced::Vector::new(6., 8.))
|
||||
})
|
||||
.expect("rendered scenario target");
|
||||
let presentation = &app.behavior.scenario.presentation;
|
||||
Point::ORIGIN
|
||||
+ presentation.pan
|
||||
+ iced::Vector::new(point.x, point.y) * presentation.scale(scene.size)
|
||||
}
|
||||
|
||||
pub(crate) fn removal_target(p: &Project, owner: Id, id: Id) -> bool {
|
||||
super::selected_behavior(p, id) == Some(owner)
|
||||
&& super::Scene::sequence(p, owner, id).removal_target(owner, id)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Context {
|
||||
pub document: Id,
|
||||
@@ -108,6 +136,7 @@ pub enum CanvasEvent {
|
||||
Back(Context),
|
||||
Choose(Context, CanvasTool),
|
||||
Select(Context, Id),
|
||||
Remove(Context, Id),
|
||||
Begin {
|
||||
context: Context,
|
||||
slot: Slot,
|
||||
@@ -147,6 +176,7 @@ impl CanvasEvent {
|
||||
| Self::Back(context)
|
||||
| Self::Choose(context, _)
|
||||
| Self::Select(context, _)
|
||||
| Self::Remove(context, _)
|
||||
| Self::Begin { context, .. }
|
||||
| Self::BeginOccurrence { context, .. }
|
||||
| Self::Reorder { context, .. } => *context,
|
||||
@@ -1934,7 +1964,7 @@ pub(crate) mod direct_tests {
|
||||
epoch: 0,
|
||||
}
|
||||
}
|
||||
pub(super) fn placement_fixture() -> (Fixture, Id, Id, Id, Id, Id) {
|
||||
pub(crate) fn placement_fixture() -> (Fixture, Id, Id, Id, Id, Id) {
|
||||
let mut f = fixture();
|
||||
let p = &mut f.project;
|
||||
let machine = p
|
||||
|
||||
@@ -30,6 +30,7 @@ pub enum CanvasEvent {
|
||||
Scrolled(Context, scenario::ScrollArea, iced::Vector),
|
||||
Choose(Context, CanvasTool),
|
||||
Select(Context, Id),
|
||||
Remove(Context, Id),
|
||||
SelectQualified(Context, OccurrenceEndpoint),
|
||||
ExpandQualified(Context, OccurrenceEndpoint),
|
||||
OpenQualified(Context, OccurrenceEndpoint),
|
||||
@@ -70,6 +71,7 @@ impl CanvasEvent {
|
||||
Self::Scrolled(c, ..)
|
||||
| Self::Choose(c, ..)
|
||||
| Self::Select(c, ..)
|
||||
| Self::Remove(c, ..)
|
||||
| Self::SelectQualified(c, ..)
|
||||
| Self::ExpandQualified(c, ..)
|
||||
| Self::OpenQualified(c, ..)
|
||||
@@ -507,6 +509,12 @@ impl Input {
|
||||
}
|
||||
}
|
||||
impl Graph<'_> {
|
||||
fn removal_target(&self, owner: Id, id: Id) -> bool {
|
||||
id != owner
|
||||
&& super::super::selected_behavior(self.project, id) == Some(owner)
|
||||
&& (self.nodes.iter().any(|node| node.id == id)
|
||||
|| self.edges.iter().any(|edge| edge.id == id))
|
||||
}
|
||||
fn ordered_path_at(&self, point: Point) -> Option<&OrderedPath> {
|
||||
self.ordered_paths.iter().find(|path| {
|
||||
path.points.windows(2).any(|pair| {
|
||||
@@ -635,6 +643,29 @@ impl Graph<'_> {
|
||||
state.pointer = None;
|
||||
return Some(Action::request_redraw());
|
||||
}
|
||||
canvas::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||
key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Delete),
|
||||
modifiers,
|
||||
repeat: false,
|
||||
..
|
||||
}) if editable
|
||||
&& tool == CanvasTool::Select
|
||||
&& (modifiers.is_empty() || *modifiers == iced::keyboard::Modifiers::SHIFT)
|
||||
&& state.press.is_none()
|
||||
&& state.source.is_none() =>
|
||||
{
|
||||
let context = context?;
|
||||
if self.removal_target(context.owner, self.selected) {
|
||||
return Some(
|
||||
Action::publish(Message::TopologyCanvas(CanvasEvent::Remove(
|
||||
context,
|
||||
self.selected,
|
||||
)))
|
||||
.and_capture(),
|
||||
);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
canvas::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||
key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Escape),
|
||||
..
|
||||
@@ -952,6 +983,55 @@ impl Graph<'_> {
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) fn selecting(&self) -> bool {
|
||||
self.tool == CanvasTool::Select
|
||||
}
|
||||
pub(crate) fn removal_target(&self, p: &Project, owner: Id, id: Id) -> bool {
|
||||
self.qualified_selection(p, owner).is_none()
|
||||
&& Graph::expanded_qualified(p, owner, id, &self.expanded, &self.contextual_expanded)
|
||||
.removal_target(owner, id)
|
||||
}
|
||||
pub(crate) fn has_qualified_selection(&self, p: &Project, owner: Id) -> bool {
|
||||
self.qualified_selection(p, owner).is_some()
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) fn removal_test_point(
|
||||
&self,
|
||||
app: &App,
|
||||
owner: Id,
|
||||
id: Id,
|
||||
qualified: Option<&OccurrenceEndpoint>,
|
||||
) -> Point {
|
||||
let graph = Graph::expanded_qualified(
|
||||
app.project(),
|
||||
owner,
|
||||
id,
|
||||
&self.expanded,
|
||||
&self.contextual_expanded,
|
||||
);
|
||||
let point = graph
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.id == id)
|
||||
.map(|node| {
|
||||
qualified.map_or(
|
||||
Point::new(node.rect.x + 20., node.rect.y + 25.),
|
||||
|endpoint| node.endpoint_rect(endpoint).center(),
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
graph
|
||||
.edges
|
||||
.iter()
|
||||
.find(|edge| edge.id == id)
|
||||
.map(|edge| edge.label.center())
|
||||
})
|
||||
.expect("rendered process target");
|
||||
Point::ORIGIN
|
||||
+ self.pan
|
||||
+ iced::Vector::new(point.x, point.y)
|
||||
* self.scale(graph.size, (app.window_size.width - 600.).max(200.))
|
||||
}
|
||||
pub(crate) fn reveal_selection(&mut self, p: &Project, owner: Id, id: Id) -> bool {
|
||||
let graph =
|
||||
Graph::expanded_qualified(p, owner, id, &self.expanded, &self.contextual_expanded);
|
||||
|
||||
Reference in New Issue
Block a user