Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
316dc559d7 |
@@ -642,7 +642,7 @@ impl App {
|
||||
if !self.project().contains(id) {
|
||||
return;
|
||||
}
|
||||
let previous_owner = crate::behavior::selected_owner(self);
|
||||
let previous_state_view = self.state_view_identity();
|
||||
self.selected = id;
|
||||
self.selected_node = None;
|
||||
if let Some(layer) = self.project().layer_of(id)
|
||||
@@ -656,10 +656,11 @@ impl App {
|
||||
&& let Some(owner) = crate::behavior::selected_behavior(self.project(), id)
|
||||
{
|
||||
self.behavior.active_owner = Some(owner);
|
||||
if previous_owner != Some(owner) {
|
||||
self.behavior.diagram = Default::default();
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
if self.reset_state_view_after_navigation(previous_state_view)
|
||||
&& self.page == Page::Behavior
|
||||
{
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
}
|
||||
self.refresh_draft();
|
||||
}
|
||||
@@ -1200,13 +1201,14 @@ impl App {
|
||||
Message::Page(page) => {
|
||||
if page != Page::Text || !self.draft_dirty() || self.apply() {
|
||||
if page == Page::Behavior && self.page != Page::Behavior {
|
||||
let previous_state_view = self.state_view_identity();
|
||||
if let Some(owner) =
|
||||
crate::behavior::selected_behavior(self.project(), self.selected)
|
||||
{
|
||||
self.behavior.active_owner = Some(owner);
|
||||
}
|
||||
self.behavior.active_owner = crate::behavior::selected_owner(self);
|
||||
self.behavior.diagram.fit = true;
|
||||
self.reset_state_view_after_navigation(previous_state_view);
|
||||
}
|
||||
self.page = page;
|
||||
self.selected_node = if page == Page::Behavior {
|
||||
@@ -1360,6 +1362,7 @@ impl App {
|
||||
}
|
||||
Message::Layer(layer) => {
|
||||
if self.apply() {
|
||||
let previous_state_view = self.state_view_identity();
|
||||
self.layer = layer;
|
||||
self.create_kind = if layer == Layer::Oa {
|
||||
Kind::Activity
|
||||
@@ -1375,6 +1378,8 @@ impl App {
|
||||
.map(|d| d.id);
|
||||
self.selected = self.project().id;
|
||||
self.selected_node = None;
|
||||
self.behavior.active_owner = crate::behavior::selected_owner(self);
|
||||
self.reset_state_view_after_navigation(previous_state_view);
|
||||
self.refresh_draft();
|
||||
self.fit();
|
||||
}
|
||||
|
||||
@@ -6,12 +6,35 @@ use crate::behavior::{
|
||||
};
|
||||
|
||||
impl App {
|
||||
/// The behavior view resolved independently of the current page. Capture
|
||||
/// this before selection/layer changes, while it still identifies the view
|
||||
/// whose camera and tool are retained during navigation.
|
||||
pub(super) fn state_view_identity(&self) -> (Option<Id>, Option<Id>) {
|
||||
let owner = behavior::selected_owner(self);
|
||||
let diagram = owner.and_then(|owner| behavior::state_view::diagram(self, owner));
|
||||
(owner, diagram)
|
||||
}
|
||||
|
||||
pub(super) fn reset_state_view_after_navigation(
|
||||
&mut self,
|
||||
previous: (Option<Id>, Option<Id>),
|
||||
) -> bool {
|
||||
let current = self.state_view_identity();
|
||||
if current == previous {
|
||||
return false;
|
||||
}
|
||||
self.behavior.diagram = behavior::state_view::State {
|
||||
diagram: current.1,
|
||||
..Default::default()
|
||||
};
|
||||
true
|
||||
}
|
||||
|
||||
pub fn state_context(&self) -> Option<Context> {
|
||||
if self.page != Page::Behavior {
|
||||
return None;
|
||||
}
|
||||
let owner = behavior::selected_owner(self)?;
|
||||
let diagram = behavior::state_view::diagram(self, owner)?;
|
||||
let diagram = self.state_view_identity().1?;
|
||||
Some(Context {
|
||||
document: self.canvas_document,
|
||||
diagram,
|
||||
|
||||
@@ -89,6 +89,232 @@ fn assert_source_model_agree(app: &App) {
|
||||
assert!(!app.source_dirty());
|
||||
}
|
||||
|
||||
struct NavigationFixture {
|
||||
app: App,
|
||||
machine_a: Id,
|
||||
diagram_a: Id,
|
||||
state_a: Id,
|
||||
transition_a: Id,
|
||||
root_event: Id,
|
||||
machine_b: Id,
|
||||
diagram_b: Id,
|
||||
state_b: Id,
|
||||
}
|
||||
|
||||
fn navigation_fixture(layer_b: Layer) -> NavigationFixture {
|
||||
let mut project = Project::empty();
|
||||
let mut machine = |layer, name| {
|
||||
let machine = project
|
||||
.add_element(Kind::StateMachine, layer, name, None)
|
||||
.unwrap();
|
||||
let region = project
|
||||
.add_element(Kind::Region, layer, "Main", Some(machine))
|
||||
.unwrap();
|
||||
let state = project
|
||||
.add_element(Kind::State, layer, "Ready", Some(region))
|
||||
.unwrap();
|
||||
(machine, state)
|
||||
};
|
||||
let (machine_a, state_a) = machine(Layer::Sa, "Machine A");
|
||||
let (machine_b, state_b) = machine(layer_b, "Machine B");
|
||||
let root_event = project
|
||||
.add_element(Kind::Event, Layer::Sa, "External event", None)
|
||||
.unwrap();
|
||||
let transition_a = project.relate(RelationKind::StateTransition, "Repeat", state_a, state_a);
|
||||
let plan_a = state_edit::create_view(&project, machine_a).unwrap();
|
||||
project = prepare_command(&project, &plan_a.command).unwrap().project;
|
||||
let plan_b = state_edit::create_view(&project, machine_b).unwrap();
|
||||
project = prepare_command(&project, &plan_b.command).unwrap().project;
|
||||
let mut app = App::with_project(project);
|
||||
send(&mut app, Message::Select(state_a));
|
||||
send(&mut app, Message::Page(Page::Behavior));
|
||||
send(
|
||||
&mut app,
|
||||
Message::BehaviorDiagram(Choice {
|
||||
id: plan_a.diagram,
|
||||
label: "A view".into(),
|
||||
}),
|
||||
);
|
||||
let context = app.state_context().unwrap();
|
||||
event(
|
||||
&mut app,
|
||||
Event::Viewport(context, iced::Size::new(820., 540.)),
|
||||
);
|
||||
event(&mut app, Event::Zoom(context, 0.65));
|
||||
event(
|
||||
&mut app,
|
||||
Event::Pan(context, iced::Vector::new(-1_400., -800.)),
|
||||
);
|
||||
event(
|
||||
&mut app,
|
||||
Event::Choose(Tool::Reconnect {
|
||||
relation: transition_a,
|
||||
endpoint: Endpoint::Target,
|
||||
}),
|
||||
);
|
||||
assert_eq!(app.state_context().unwrap().diagram, plan_a.diagram);
|
||||
assert!(!app.behavior.diagram.fit);
|
||||
NavigationFixture {
|
||||
app,
|
||||
machine_a,
|
||||
diagram_a: plan_a.diagram,
|
||||
state_a,
|
||||
transition_a,
|
||||
root_event,
|
||||
machine_b,
|
||||
diagram_b: plan_b.diagram,
|
||||
state_b,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_switched_state_view(f: &mut NavigationFixture) {
|
||||
assert_eq!(behavior::selected_owner(&f.app), Some(f.machine_b));
|
||||
assert_eq!(f.app.state_context().unwrap().diagram, f.diagram_b);
|
||||
let defaults = behavior::state_view::State::default();
|
||||
let state = &f.app.behavior.diagram;
|
||||
assert_eq!(state.diagram, Some(f.diagram_b));
|
||||
assert_eq!(
|
||||
state.tool,
|
||||
Tool::Select,
|
||||
"Reconnect from A must not be armed in B"
|
||||
);
|
||||
assert_eq!(state.pan, defaults.pan);
|
||||
assert_eq!(state.zoom, defaults.zoom);
|
||||
assert!(state.fit);
|
||||
assert!(
|
||||
state.viewport.is_none(),
|
||||
"B needs its own first viewport measurement"
|
||||
);
|
||||
assert!(state.placement.is_none());
|
||||
let context = f.app.state_context().unwrap();
|
||||
event(
|
||||
&mut f.app,
|
||||
Event::Viewport(context, iced::Size::new(820., 540.)),
|
||||
);
|
||||
assert!(
|
||||
f.app.behavior.diagram.pan != defaults.pan || f.app.behavior.diagram.zoom != defaults.zoom,
|
||||
"The unchanged widget size still fits a newly selected view"
|
||||
);
|
||||
}
|
||||
|
||||
fn catalog_selects_another_machine(layer_b: Layer) {
|
||||
let mut f = navigation_fixture(layer_b);
|
||||
let before = f.app.document.snapshot();
|
||||
send(&mut f.app, Message::Page(Page::Catalog));
|
||||
send(&mut f.app, Message::Select(f.state_b));
|
||||
assert_eq!(f.app.layer, layer_b);
|
||||
send(&mut f.app, Message::Page(Page::Behavior));
|
||||
assert_switched_state_view(&mut f);
|
||||
assert!(Arc::ptr_eq(&before, &f.app.document.snapshot()));
|
||||
assert_source_model_agree(&f.app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_navigation_catalog_selection_of_another_machine_resets_camera_and_tool() {
|
||||
catalog_selects_another_machine(Layer::Sa);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_navigation_catalog_selection_across_layers_resets_camera_and_tool() {
|
||||
catalog_selects_another_machine(Layer::Oa);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_navigation_layer_change_resets_the_new_machine_at_the_same_viewport_size() {
|
||||
let mut f = navigation_fixture(Layer::Oa);
|
||||
let before = f.app.document.snapshot();
|
||||
send(&mut f.app, Message::Layer(Layer::Oa));
|
||||
assert_switched_state_view(&mut f);
|
||||
assert!(Arc::ptr_eq(&before, &f.app.document.snapshot()));
|
||||
assert_source_model_agree(&f.app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_navigation_return_to_the_same_view_preserves_camera_tool_and_fit_mode() {
|
||||
for fit in [false, true] {
|
||||
let mut f = navigation_fixture(Layer::Sa);
|
||||
if fit {
|
||||
let context = f.app.state_context().unwrap();
|
||||
event(&mut f.app, Event::Fit(context));
|
||||
}
|
||||
let state = &f.app.behavior.diagram;
|
||||
let retained = (
|
||||
state.diagram,
|
||||
state.pan,
|
||||
state.zoom,
|
||||
state.tool,
|
||||
state.fit,
|
||||
state.viewport,
|
||||
);
|
||||
let before = f.app.document.snapshot();
|
||||
send(&mut f.app, Message::Page(Page::Catalog));
|
||||
send(&mut f.app, Message::Select(f.state_a));
|
||||
send(&mut f.app, Message::Page(Page::Behavior));
|
||||
let context = f.app.state_context().unwrap();
|
||||
event(
|
||||
&mut f.app,
|
||||
Event::Viewport(context, iced::Size::new(820., 540.)),
|
||||
);
|
||||
assert_eq!(behavior::selected_owner(&f.app), Some(f.machine_a));
|
||||
assert_eq!(context.diagram, f.diagram_a);
|
||||
let state = &f.app.behavior.diagram;
|
||||
assert_eq!(
|
||||
(
|
||||
state.diagram,
|
||||
state.pan,
|
||||
state.zoom,
|
||||
state.tool,
|
||||
state.fit,
|
||||
state.viewport
|
||||
),
|
||||
retained
|
||||
);
|
||||
assert_eq!(
|
||||
state.tool,
|
||||
Tool::Reconnect {
|
||||
relation: f.transition_a,
|
||||
endpoint: Endpoint::Target
|
||||
}
|
||||
);
|
||||
assert!(Arc::ptr_eq(&before, &f.app.document.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_navigation_root_event_and_same_layer_keep_the_current_camera_and_tool() {
|
||||
let mut f = navigation_fixture(Layer::Sa);
|
||||
let state = &f.app.behavior.diagram;
|
||||
let retained = (
|
||||
state.diagram,
|
||||
state.pan,
|
||||
state.zoom,
|
||||
state.tool,
|
||||
state.fit,
|
||||
state.viewport,
|
||||
);
|
||||
let before = f.app.document.snapshot();
|
||||
send(&mut f.app, Message::Select(f.root_event));
|
||||
assert_eq!(f.app.selected, f.root_event);
|
||||
send(&mut f.app, Message::Page(Page::Catalog));
|
||||
send(&mut f.app, Message::Page(Page::Behavior));
|
||||
send(&mut f.app, Message::Layer(Layer::Sa));
|
||||
assert_eq!(behavior::selected_owner(&f.app), Some(f.machine_a));
|
||||
assert_eq!(f.app.state_context().unwrap().diagram, f.diagram_a);
|
||||
let state = &f.app.behavior.diagram;
|
||||
assert_eq!(
|
||||
(
|
||||
state.diagram,
|
||||
state.pan,
|
||||
state.zoom,
|
||||
state.tool,
|
||||
state.fit,
|
||||
state.viewport
|
||||
),
|
||||
retained
|
||||
);
|
||||
assert!(Arc::ptr_eq(&before, &f.app.document.snapshot()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_view_places_only_its_machine_and_restores_exact_source_in_one_undo() {
|
||||
let mut f = fixture();
|
||||
|
||||
Reference in New Issue
Block a user