Files
ARCADIA-Studio/crates/oa-desktop/src/app/architecture_authoring/palette.rs
T

263 lines
8.4 KiB
Rust

//! 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 = iced::widget::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()];
if !connection.available_in(app.layer, d.projection) {
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()
}