263 lines
8.4 KiB
Rust
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()
|
|
}
|