//! 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> { 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), }) }