Files
ARCADIA-Studio/crates/oa-desktop/src/behavior/state_menu.rs
T

197 lines
7.3 KiB
Rust

//! 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("Duplicate view", Message::DuplicateDiagram(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),
})
}