Checkpoint source 7 workflows and in-progress diagram authoring redesign
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
//! Compact neutral tool palette. Tool state never changes its allocated width.
|
||||
use super::*;
|
||||
use iced::{
|
||||
Point, Rectangle, Size, Theme, mouse,
|
||||
widget::{
|
||||
canvas::{self, Frame, Geometry, Path, Stroke},
|
||||
container, tooltip,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Icon {
|
||||
Element(Kind),
|
||||
Arrow,
|
||||
Existing,
|
||||
Select,
|
||||
Collapse(bool),
|
||||
}
|
||||
impl canvas::Program<Message> for Icon {
|
||||
type State = ();
|
||||
fn draw(
|
||||
&self,
|
||||
_: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_: &Theme,
|
||||
bounds: Rectangle,
|
||||
_: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let mut f = Frame::new(renderer, bounds.size());
|
||||
let color = match self {
|
||||
Self::Element(k) => crate::style::kind_color(*k),
|
||||
_ => crate::style::INK,
|
||||
};
|
||||
let stroke = Stroke::default().with_color(color).with_width(1.4);
|
||||
let line = |points: &[(f32, f32)]| {
|
||||
Path::new(|p| {
|
||||
p.move_to(Point::new(points[0].0, points[0].1));
|
||||
for &(x, y) in &points[1..] {
|
||||
p.line_to(Point::new(x, y));
|
||||
}
|
||||
})
|
||||
};
|
||||
match self {
|
||||
Self::Element(Kind::Actor | Kind::SystemActor | Kind::Role) => {
|
||||
f.stroke(&Path::circle(Point::new(8., 4.), 2.5), stroke);
|
||||
for points in [
|
||||
&[(8., 6.5), (8., 11.)][..],
|
||||
&[(3., 8.), (13., 8.)],
|
||||
&[(4., 15.), (8., 11.), (12., 15.)],
|
||||
] {
|
||||
f.stroke(&line(points), stroke);
|
||||
}
|
||||
}
|
||||
Self::Element(Kind::Capability | Kind::SystemCapability) => {
|
||||
f.stroke(&Path::circle(Point::new(8., 8.), 6.), stroke);
|
||||
}
|
||||
Self::Element(Kind::Mission | Kind::OperationalMission) => {
|
||||
f.stroke(
|
||||
&line(&[(3., 15.), (3., 2.), (13., 2.), (10., 6.), (3., 6.)]),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
Self::Element(k) => {
|
||||
f.stroke(
|
||||
&Path::rounded_rectangle(
|
||||
Point::new(1., 3.),
|
||||
Size::new(14., 10.),
|
||||
if k.is_work() { 3.into() } else { 0.into() },
|
||||
),
|
||||
stroke,
|
||||
);
|
||||
if !k.is_work() {
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(4., 6.), Size::new(8., 4.)),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::Arrow => {
|
||||
f.stroke(&line(&[(1., 8.), (14., 8.), (10., 4.)]), stroke);
|
||||
f.stroke(&line(&[(14., 8.), (10., 12.)]), stroke);
|
||||
}
|
||||
Self::Existing => {
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(1., 1.), Size::new(10., 10.)),
|
||||
stroke,
|
||||
);
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(5., 5.), Size::new(10., 10.)),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
Self::Select => {
|
||||
f.stroke(
|
||||
&line(&[
|
||||
(3., 1.),
|
||||
(3., 14.),
|
||||
(7., 10.),
|
||||
(11., 15.),
|
||||
(13., 14.),
|
||||
(9., 9.),
|
||||
(15., 8.),
|
||||
(3., 1.),
|
||||
]),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
Self::Collapse(expanded) => {
|
||||
let (a, b) = if *expanded { (5., 11.) } else { (11., 5.) };
|
||||
f.stroke(&line(&[(a, 3.), (b, 8.), (a, 13.)]), stroke);
|
||||
}
|
||||
}
|
||||
vec![f.into_geometry()]
|
||||
}
|
||||
}
|
||||
fn tool<'a>(
|
||||
app: &App,
|
||||
icon: Icon,
|
||||
label: &'a str,
|
||||
message: Message,
|
||||
selected: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let width = if app.architecture_palette_expanded {
|
||||
156.
|
||||
} else {
|
||||
36.
|
||||
};
|
||||
let graphic = iced::widget::canvas(icon).width(16).height(16);
|
||||
let mut content = row![graphic].spacing(7).align_y(iced::Center);
|
||||
if app.architecture_palette_expanded {
|
||||
content = content.push(text(label).size(11).wrapping(text::Wrapping::None));
|
||||
}
|
||||
let mut enabled = app.architecture.as_ref().is_none_or(State::unnamed_tool)
|
||||
|| matches!(
|
||||
&message,
|
||||
Message::Architecture(Event::Cancel | Event::TogglePalette)
|
||||
);
|
||||
let mut hint = label.to_owned();
|
||||
if let Message::Connection(crate::connection_edit::Event::Choose(Some(connection))) = &message {
|
||||
let view = oa_core::projection::project_view(app.project(), app.active_diagram.unwrap());
|
||||
if !crate::connection_edit::visible(&view).any(|id| connection.can_start(app.project(), id))
|
||||
{
|
||||
enabled = false;
|
||||
hint.push_str(" — add or show a compatible source first");
|
||||
}
|
||||
}
|
||||
let control = button(content)
|
||||
.padding([5, 7])
|
||||
.height(28)
|
||||
.width(width - 4.)
|
||||
.clip(true)
|
||||
.style(if selected {
|
||||
crate::style::selected
|
||||
} else {
|
||||
crate::style::nav
|
||||
})
|
||||
.on_press_maybe(enabled.then_some(message));
|
||||
tooltip(
|
||||
control,
|
||||
container(text(hint).size(12))
|
||||
.padding(7)
|
||||
.style(crate::style::card),
|
||||
tooltip::Position::Left,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
pub fn view(app: &App) -> Element<'_, Message> {
|
||||
let mut content = column![tool(
|
||||
app,
|
||||
Icon::Collapse(app.architecture_palette_expanded),
|
||||
if app.architecture_palette_expanded {
|
||||
"Collapse tools"
|
||||
} else {
|
||||
"Expand tools"
|
||||
},
|
||||
Message::Architecture(Event::TogglePalette),
|
||||
false
|
||||
)]
|
||||
.spacing(3);
|
||||
let group = |name: &'static str| -> Element<'_, Message> {
|
||||
if app.architecture_palette_expanded {
|
||||
container(text(name).size(10).color(crate::style::MUTED))
|
||||
.padding([5, 4])
|
||||
.into()
|
||||
} else {
|
||||
iced::widget::rule::horizontal(1).into()
|
||||
}
|
||||
};
|
||||
content = content.push(tool(
|
||||
app,
|
||||
Icon::Select,
|
||||
if app.architecture.is_some() {
|
||||
"Cancel / select"
|
||||
} else {
|
||||
"Select / move"
|
||||
},
|
||||
if app.architecture.is_some() {
|
||||
Message::Architecture(Event::Cancel)
|
||||
} else {
|
||||
Message::Connection(crate::connection_edit::Event::Choose(None))
|
||||
},
|
||||
app.architecture.is_none() && app.connection.is_none(),
|
||||
));
|
||||
for (heading, work) in [
|
||||
("Participants / capabilities", false),
|
||||
("Activities / functions", true),
|
||||
] {
|
||||
let choices: Vec<_> = kinds(app)
|
||||
.into_iter()
|
||||
.filter(|k| k.is_work() == work)
|
||||
.collect();
|
||||
if !choices.is_empty() {
|
||||
content = content.push(group(heading));
|
||||
}
|
||||
for kind in choices {
|
||||
let active = app
|
||||
.architecture
|
||||
.as_ref()
|
||||
.is_some_and(|s| matches!(s.saved.intent,Intent::Create{kind:k,..} if kind==k));
|
||||
content = content.push(tool(
|
||||
app,
|
||||
Icon::Element(kind),
|
||||
kind.label(),
|
||||
Message::Architecture(Event::Start(kind, None)),
|
||||
active,
|
||||
));
|
||||
}
|
||||
}
|
||||
content = content.push(group("Connections"));
|
||||
for connection in crate::connection_tool::Tool::for_layer(app.layer) {
|
||||
let d = &app.project().diagrams[&app.active_diagram.unwrap()];
|
||||
let kind = match connection {
|
||||
crate::connection_tool::Tool::Interaction => Kind::Activity,
|
||||
crate::connection_tool::Tool::FunctionalExchange => Kind::Function,
|
||||
crate::connection_tool::Tool::CommunicationMean => Kind::Entity,
|
||||
crate::connection_tool::Tool::BoundaryExchange => Kind::System,
|
||||
};
|
||||
if !d.projection.includes(kind) {
|
||||
continue;
|
||||
}
|
||||
content = content.push(tool(
|
||||
app,
|
||||
Icon::Arrow,
|
||||
connection.label(),
|
||||
Message::Connection(crate::connection_edit::Event::Choose(Some(*connection))),
|
||||
app.connection
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.tool == *connection),
|
||||
));
|
||||
}
|
||||
content = content.push(group("Common")).push(tool(
|
||||
app,
|
||||
Icon::Existing,
|
||||
"Add existing…",
|
||||
Message::Architecture(Event::Existing(None)),
|
||||
false,
|
||||
));
|
||||
container(scrollable(content))
|
||||
.width(if app.architecture_palette_expanded {
|
||||
156
|
||||
} else {
|
||||
36
|
||||
})
|
||||
.height(iced::Fill)
|
||||
.padding(2)
|
||||
.style(crate::style::panel)
|
||||
.into()
|
||||
}
|
||||
Reference in New Issue
Block a user