Files
ARCADIA-Studio/crates/oa-desktop/src/style.rs
T

159 lines
6.0 KiB
Rust

use iced::{
Border, Color, Theme,
theme::palette::{Extended, Pair},
widget::{button, container, text_editor, text_input},
};
use oa_core::Kind;
pub const BG: Color = Color::from_rgb(0.945, 0.956, 0.969);
pub const INK: Color = Color::from_rgb8(24, 33, 44);
pub const MUTED: Color = Color::from_rgb8(75, 89, 104);
pub const TEAL: Color = Color::from_rgb8(0, 102, 92);
/// Decorative dividers; essential outlines use BOUNDARY instead.
pub const LINE: Color = Color::from_rgb(0.86, 0.89, 0.91);
pub const BOUNDARY: Color = Color::from_rgb8(102, 117, 134);
const WARNING: Color = Color::from_rgb8(134, 91, 0);
const SELECTION: Color = Color::from_rgb8(217, 238, 232);
// Ordinary values remain INK; syntax selection has its own surface and palette.
const INPUT_SELECTION: Color = Color::from_rgb8(123, 142, 134);
const SOURCE_SELECTION: Color = Color::from_rgb8(128, 149, 139);
const SOURCE_INK: Color = Color::from_rgb8(33, 37, 41);
pub fn theme() -> Theme {
Theme::custom_with_fn(
"Arcadia Light",
iced::theme::Palette {
background: BG,
text: INK,
primary: TEAL,
success: TEAL,
warning: WARNING,
danger: Color::from_rgb(0.78, 0.22, 0.24),
},
|palette| {
let mut extended = Extended::generate(palette);
// Pair::new adjusts text toward a 6:1 target. Keep these reviewed
// colors exact; resolved style tests enforce each consumer contract.
let pair = |color, text| Pair { color, text };
extended.background.base = pair(BG, INK);
extended.background.weakest = pair(Color::WHITE, INK);
extended.background.weaker = pair(BG, INK);
// These pairs also supply pick-list/menu and disabled-control fills.
extended.background.weak = pair(Color::WHITE, INK);
extended.background.neutral = pair(LINE, INK);
extended.background.strong = pair(BOUNDARY, Color::WHITE);
extended.background.stronger = pair(MUTED, Color::WHITE);
extended.background.strongest = pair(MUTED, Color::WHITE);
extended.primary.base = pair(TEAL, Color::WHITE);
extended.primary.weak = pair(INPUT_SELECTION, INK);
extended.primary.strong = pair(Color::from_rgb8(0, 83, 75), Color::WHITE);
// Secondary base is both placeholder ink and a button background.
extended.secondary.base = pair(MUTED, Color::WHITE);
extended.secondary.weak = pair(LINE, INK);
extended.secondary.strong = pair(Color::from_rgb8(54, 68, 82), Color::WHITE);
extended.success.base = extended.primary.base;
extended.success.weak = pair(SELECTION, INK);
extended.success.strong = extended.primary.strong;
extended.warning.base = pair(WARNING, Color::WHITE);
extended.warning.weak = pair(Color::from_rgb8(250, 235, 203), INK);
extended.warning.strong = pair(Color::from_rgb8(103, 70, 0), Color::WHITE);
extended.danger.base = pair(palette.danger, Color::WHITE);
extended.danger.weak = pair(Color::from_rgb8(249, 227, 229), INK);
extended.danger.strong = pair(Color::from_rgb8(156, 34, 40), Color::WHITE);
extended
},
)
}
pub fn source_editor(theme: &Theme, status: text_editor::Status) -> text_editor::Style {
let mut style = text_editor::default(theme, status);
style.background = Color::WHITE.into();
style.value = SOURCE_INK;
style.placeholder = SOURCE_INK;
style.selection = SOURCE_SELECTION;
style
}
/// Inputs without an edit callback still support selection and copy in Iced.
pub fn selectable_input(theme: &Theme, status: text_input::Status) -> text_input::Style {
let mut style = text_input::default(theme, status);
if status == text_input::Status::Disabled {
style.value = INK;
}
style
}
pub fn card(_: &Theme) -> container::Style {
container::Style {
background: Some(Color::WHITE.into()),
border: Border {
color: LINE,
width: 1.,
radius: 10.into(),
},
..Default::default()
}
}
pub fn panel(_: &Theme) -> container::Style {
container::Style {
background: Some(Color::WHITE.into()),
border: Border {
color: LINE,
width: 1.,
radius: 0.into(),
},
..Default::default()
}
}
pub fn subtle(_: &Theme) -> container::Style {
container::Style {
background: Some(BG.into()),
border: Border {
radius: 8.into(),
..Default::default()
},
..Default::default()
}
}
pub fn nav(theme: &Theme, status: button::Status) -> button::Style {
let mut s = button::text(theme, status);
if status != button::Status::Disabled {
s.text_color = INK;
}
s.border.radius = 7.into();
if matches!(status, button::Status::Hovered | button::Status::Pressed) {
s.background = Some(BG.into());
}
s
}
pub fn selected(theme: &Theme, status: button::Status) -> button::Style {
let mut s = nav(theme, status);
s.background = Some(SELECTION.into());
s.border = Border {
color: TEAL,
width: 2.,
radius: 7.into(),
};
s
}
pub fn kind_color(kind: Kind) -> Color {
match kind {
Kind::Entity | Kind::System => Color::from_rgb(0.16, 0.40, 0.71),
Kind::Actor | Kind::SystemActor | Kind::Role => Color::from_rgb8(117, 68, 153),
Kind::Activity | Kind::Function => TEAL,
Kind::Capability | Kind::SystemCapability | Kind::Mission | Kind::OperationalMission => {
WARNING
}
Kind::Process
| Kind::FunctionalChain
| Kind::Scenario
| Kind::Occurrence
| Kind::Lifeline => Color::from_rgb(0.65, 0.33, 0.40),
Kind::StateMachine
| Kind::Region
| Kind::State
| Kind::Mode
| Kind::Initial
| Kind::Final => Color::from_rgb(0.38, 0.39, 0.68),
_ => Color::from_rgb8(50, 107, 128),
}
}
#[cfg(test)]
mod tests;