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;