Add persisted state diagram authoring and context-bound gestures
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
//! State actions capture their own diagram; architecture selection stays separate.
|
||||
use super::{
|
||||
state_edit::{self, Endpoint, Event, Tool},
|
||||
states,
|
||||
};
|
||||
use crate::{
|
||||
app::{App, Message},
|
||||
context_menu::{Entry, Menu},
|
||||
model_menu::{self, Opened},
|
||||
};
|
||||
use oa_core::*;
|
||||
|
||||
pub fn popup(app: &App, opened: &Opened, clean: bool) -> Option<Menu<Message>> {
|
||||
let project = app.project();
|
||||
let machine = super::selected_owner(app)
|
||||
.filter(|id| project.elements[id].kind() == Kind::StateMachine)?;
|
||||
let context = app.state_context();
|
||||
let request = &opened.request;
|
||||
let id = request.object;
|
||||
let item = |caption: &str, message: Message, enabled: bool| {
|
||||
if clean && enabled {
|
||||
Entry::action(
|
||||
caption,
|
||||
Message::Menu(model_menu::Event::Invoke(opened.token, Box::new(message))),
|
||||
)
|
||||
} else {
|
||||
Entry::disabled(caption)
|
||||
}
|
||||
};
|
||||
let diagram_event = Message::StateDiagram;
|
||||
let form = |tool| {
|
||||
Message::Behavior(super::Event::States(states::Event::Start(
|
||||
machine, tool, id,
|
||||
)))
|
||||
};
|
||||
let mut entries = vec![Entry::title(crate::views::short(project.named(id), 38))];
|
||||
if !clean {
|
||||
entries.push(Entry::title("Apply or cancel pending edits first"));
|
||||
}
|
||||
|
||||
if let Some(context) = context.filter(|context| context.diagram == id) {
|
||||
entries.extend([
|
||||
item("View properties", Message::EditProperties(id), true),
|
||||
item("Fit diagram", diagram_event(Event::Fit(context)), true),
|
||||
item(
|
||||
"Create another state view",
|
||||
diagram_event(Event::CreateView(machine)),
|
||||
true,
|
||||
),
|
||||
Entry::separator(),
|
||||
item(
|
||||
"Create region…",
|
||||
diagram_event(Event::Choose(Tool::Region)),
|
||||
true,
|
||||
),
|
||||
item(
|
||||
"New transition with details…",
|
||||
form(states::Tool::Transition),
|
||||
true,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
entries.extend([
|
||||
item("Properties", Message::EditProperties(id), true),
|
||||
item("Open source", Message::SourceForSelection, true),
|
||||
item(
|
||||
"Connections & references",
|
||||
Message::InspectorTab(crate::navigation::InspectorTab::Connections),
|
||||
true,
|
||||
),
|
||||
]);
|
||||
if let Some(element) = project.elements.get(&id)
|
||||
&& projection::state_machine_contains(project, machine, id)
|
||||
{
|
||||
entries.push(Entry::separator());
|
||||
let creates = if element.kind() == Kind::Region {
|
||||
Some((
|
||||
"Create state…",
|
||||
Kind::State,
|
||||
states::Tool::Vertex,
|
||||
Tool::Vertex(Kind::State),
|
||||
))
|
||||
} else if element.kind() == Kind::StateMachine || element.kind().is_stable_state() {
|
||||
Some((
|
||||
"Create region…",
|
||||
Kind::Region,
|
||||
states::Tool::Region,
|
||||
Tool::Region,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some((caption, kind, typed, tool)) = creates {
|
||||
entries.push(if let Some(context) = context {
|
||||
item(
|
||||
caption,
|
||||
diagram_event(Event::Choose(tool)),
|
||||
state_edit::eligible_owner(project, context.diagram, id, kind),
|
||||
)
|
||||
} else {
|
||||
item(caption, form(typed), true)
|
||||
});
|
||||
}
|
||||
entries.push(item(
|
||||
"New transition with details…",
|
||||
form(states::Tool::Transition),
|
||||
true,
|
||||
));
|
||||
if let Some(context) = context
|
||||
&& let Some(node) = request
|
||||
.placement
|
||||
.and_then(|placement| project.diagrams[&context.diagram].nodes.get(&placement))
|
||||
.filter(|node| node.element == id)
|
||||
{
|
||||
entries.push(Entry::separator());
|
||||
entries.push(item(
|
||||
"Placement properties",
|
||||
Message::EditProperties(node.id),
|
||||
true,
|
||||
));
|
||||
if element.kind() == Kind::StateMachine
|
||||
|| element.kind() == Kind::Region
|
||||
|| element.kind().is_stable_state()
|
||||
{
|
||||
let mut changed = node.clone();
|
||||
changed.collapsed = !changed.collapsed;
|
||||
entries.push(item(
|
||||
if node.collapsed {
|
||||
"Unfold contents"
|
||||
} else {
|
||||
"Fold contents"
|
||||
},
|
||||
Message::Command(Command::EditNode {
|
||||
diagram: context.diagram,
|
||||
original: node.id,
|
||||
node: changed,
|
||||
}),
|
||||
true,
|
||||
));
|
||||
}
|
||||
entries.push(item(
|
||||
"Hide from this diagram",
|
||||
Message::Command(Command::Delete(node.id)),
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(relation) = project.relations.get(&id)
|
||||
&& relation.kind() == RelationKind::StateTransition
|
||||
&& projection::state_machine_contains(project, machine, relation.source())
|
||||
&& projection::state_machine_contains(project, machine, relation.target())
|
||||
{
|
||||
entries.push(Entry::separator());
|
||||
let edit = context.map_or_else(
|
||||
|| Message::Behavior(super::Event::States(states::Event::Edit(id))),
|
||||
|context| diagram_event(Event::EditTransition(context, id)),
|
||||
);
|
||||
entries.push(item("Edit transition", edit, true));
|
||||
if let Some(context) = context {
|
||||
let visible = projection::project_view(project, context.diagram)
|
||||
.edges
|
||||
.iter()
|
||||
.any(|edge| edge.id == id);
|
||||
for (caption, endpoint) in [
|
||||
("Reconnect source", Endpoint::Source),
|
||||
("Reconnect target", Endpoint::Target),
|
||||
] {
|
||||
entries.push(item(
|
||||
caption,
|
||||
diagram_event(Event::Choose(Tool::Reconnect {
|
||||
relation: id,
|
||||
endpoint,
|
||||
})),
|
||||
visible,
|
||||
));
|
||||
}
|
||||
if !visible {
|
||||
entries.push(Entry::title(
|
||||
"Use transition details for hidden or internal endpoints",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if id != project.id {
|
||||
entries.push(Entry::separator());
|
||||
entries.push(item("Delete from model…", Message::Delete, true));
|
||||
}
|
||||
}
|
||||
Some(Menu {
|
||||
token: opened.token,
|
||||
anchor: request.anchor,
|
||||
entries,
|
||||
on_close: Message::Menu(model_menu::Event::Close),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user