Add persisted state diagram authoring and context-bound gestures
This commit is contained in:
@@ -15,7 +15,10 @@ use iced::{
|
||||
use oa_core::*;
|
||||
use std::collections::BTreeMap;
|
||||
pub(crate) mod scenario;
|
||||
mod state_scene;
|
||||
pub(crate) mod state_canvas;
|
||||
pub(crate) mod state_edit;
|
||||
pub(crate) mod state_menu;
|
||||
pub(crate) mod state_view;
|
||||
pub(crate) mod states;
|
||||
pub(crate) mod topology;
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -27,7 +30,8 @@ pub enum Event {
|
||||
}
|
||||
impl Event {
|
||||
pub fn starts_form(&self) -> bool {
|
||||
matches!(self,
|
||||
matches!(
|
||||
self,
|
||||
Self::Topology(topology::Event::Start(..))
|
||||
| Self::Scenario(scenario::Event::Start(..))
|
||||
| Self::States(states::Event::Start(..) | states::Event::Edit(..))
|
||||
@@ -45,6 +49,8 @@ impl Event {
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
pub active_owner: Option<Id>,
|
||||
pub diagram: state_view::State,
|
||||
topology: topology::State,
|
||||
scenario: scenario::State,
|
||||
states: states::State,
|
||||
@@ -130,7 +136,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
||||
label: format!("{} · {}", e.kind(), e.name),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let owner = app.behavior.pending_owner().or_else(|| selected_behavior(p, app.selected));
|
||||
let owner = selected_owner(app);
|
||||
let selected = owner
|
||||
.and_then(|id| choices.iter().find(|c| c.id == id))
|
||||
.or(choices.first())
|
||||
@@ -140,8 +146,11 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
||||
return out.push(label("Start with a Process, Scenario or State machine using Create element. Add occurrences or regions inside it to describe behavior.")).padding(24).into();
|
||||
};
|
||||
let e = &p.elements[&selected.id];
|
||||
if e.kind() == Kind::StateMachine {
|
||||
out = column![text("States & modes").size(20)].spacing(8);
|
||||
}
|
||||
out = out.push(
|
||||
pick_list(choices, Some(selected), |c| Message::Select(c.id))
|
||||
pick_list(choices, Some(selected), Message::BehaviorOwner)
|
||||
.width(Fill)
|
||||
.text_size(13)
|
||||
.padding(10),
|
||||
@@ -152,7 +161,20 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
||||
if e.kind() == Kind::Scenario {
|
||||
return scenario::view(app, e.id, out);
|
||||
}
|
||||
states::view(app, e.id, out)
|
||||
state_view::view(app, e.id, out)
|
||||
}
|
||||
|
||||
pub fn selected_owner(app: &App) -> Option<Id> {
|
||||
let valid = |id: &Id| {
|
||||
app.project().elements.get(id).is_some_and(|e| {
|
||||
e.layer() == app.layer && (e.kind().is_sequence() || e.kind() == Kind::StateMachine)
|
||||
})
|
||||
};
|
||||
app.behavior
|
||||
.pending_owner()
|
||||
.or(app.behavior.active_owner.filter(valid))
|
||||
.or_else(|| selected_behavior(app.project(), app.selected).filter(valid))
|
||||
.or_else(|| app.project().elements.keys().find(|id| valid(id)).copied())
|
||||
}
|
||||
/// Mirror the core's legacy-versus-explicit process interpretation. Existing
|
||||
/// exchange-only lists still describe ordered paths until explicitly migrated.
|
||||
@@ -179,7 +201,7 @@ fn uses_explicit_topology(p: &Project, owner: Id) -> bool {
|
||||
)
|
||||
})
|
||||
}
|
||||
fn selected_behavior(p: &Project, selected: Id) -> Option<Id> {
|
||||
pub fn selected_behavior(p: &Project, selected: Id) -> Option<Id> {
|
||||
let mut current = Some(
|
||||
p.relations
|
||||
.get(&selected)
|
||||
@@ -570,12 +592,9 @@ impl canvas::Program<Message> for Scene {
|
||||
) && let Some(p) = cursor.position_in(bounds)
|
||||
{
|
||||
let p = Point::new(p.x / self.scale, p.y / self.scale);
|
||||
if let Some(b) = self
|
||||
.boxes
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|b| b.rect.contains(p) && (!self.is_container(b) || p.y < b.rect.y + 38.))
|
||||
{
|
||||
if let Some(b) = self.boxes.iter().rev().find(|b| {
|
||||
b.rect.contains(p) && (b.marker != Some(Kind::Occurrence) || p.y < b.rect.y + 38.)
|
||||
}) {
|
||||
return Some(Action::publish(Message::Select(b.id)).and_capture());
|
||||
}
|
||||
if let Some(l) = self.lines.iter().find(|l| {
|
||||
@@ -620,10 +639,7 @@ impl canvas::Program<Message> for Scene {
|
||||
&Path::rectangle(b.rect.position(), b.rect.size()),
|
||||
Stroke::default().with_color(color),
|
||||
);
|
||||
} else if !b
|
||||
.marker
|
||||
.is_some_and(|k| k.is_state() && !k.is_stable_state())
|
||||
{
|
||||
} else {
|
||||
let path = Path::rounded_rectangle(b.rect.position(), b.rect.size(), 8.into());
|
||||
f.fill(&path, Color::from_rgb8(243, 248, 248));
|
||||
f.stroke(
|
||||
@@ -686,45 +702,9 @@ impl canvas::Program<Message> for Scene {
|
||||
}
|
||||
}
|
||||
for b in &self.boxes {
|
||||
let pseudo = b
|
||||
.marker
|
||||
.is_some_and(|k| k.is_state() && !k.is_stable_state());
|
||||
if pseudo {
|
||||
state_scene::draw_symbol(
|
||||
&mut f,
|
||||
b,
|
||||
if b.id == self.selected {
|
||||
style::TEAL
|
||||
} else {
|
||||
style::INK
|
||||
},
|
||||
);
|
||||
}
|
||||
let prefix = match b.marker {
|
||||
Some(Kind::Initial) => "INITIAL · ",
|
||||
Some(Kind::Final) => "FINAL · ",
|
||||
Some(Kind::Mode) => "MODE · ",
|
||||
Some(Kind::State) => "STATE · ",
|
||||
Some(Kind::Choice) => "CHOICE · ",
|
||||
Some(Kind::Junction) => "JUNCTION · ",
|
||||
Some(Kind::Fork) => "FORK · ",
|
||||
Some(Kind::Join) => "JOIN · ",
|
||||
Some(Kind::ShallowHistory) => "H · ",
|
||||
Some(Kind::DeepHistory) => "H* · ",
|
||||
Some(Kind::EntryPoint) => "ENTRY · ",
|
||||
Some(Kind::ExitPoint) => "EXIT · ",
|
||||
Some(Kind::Terminate) => "TERMINATE · ",
|
||||
_ => "",
|
||||
};
|
||||
f.fill_text(Text {
|
||||
content: format!(
|
||||
"{prefix}{}",
|
||||
short(&b.title, ((b.rect.width - 25.) / 7.) as usize)
|
||||
),
|
||||
position: Point::new(
|
||||
b.rect.x + if pseudo { 38. } else { 12. },
|
||||
b.rect.y + if pseudo { 18. } else { 12. },
|
||||
),
|
||||
content: short(&b.title, ((b.rect.width - 25.) / 7.) as usize),
|
||||
position: Point::new(b.rect.x + 12., b.rect.y + 12.),
|
||||
size: 13.into(),
|
||||
color: if b.id == self.selected {
|
||||
style::TEAL
|
||||
|
||||
Reference in New Issue
Block a user