use egui::{Color32, Stroke};
pub struct MonoText;
impl MonoText {
pub const PRIMARY: Color32 = Color32::from_rgb(0xf0, 0xf0, 0xf2);
pub const SECONDARY: Color32 = Color32::from_rgb(0xd1, 0xd5, 0xdb);
pub const TERTIARY: Color32 = Color32::from_rgb(0x9c, 0xa3, 0xaf);
pub const MUTED: Color32 = Color32::from_rgb(0x6b, 0x72, 0x80);
pub const GHOST: Color32 = Color32::from_rgb(0x4a, 0x54, 0x68);
pub const CHROME: Color32 = Color32::from_rgb(0x55, 0x5f, 0x72);
}
pub struct MonoSemantics;
impl MonoSemantics {
pub const SUCCESS: Color32 = Color32::from_rgb(0x34, 0xa8, 0x53);
pub fn success_bg() -> Color32 {
with_alpha(Self::SUCCESS, 0.12)
}
pub fn success_border() -> Color32 {
with_alpha(Self::SUCCESS, 0.30)
}
pub const WARNING: Color32 = Color32::from_rgb(0xfb, 0xbf, 0x24);
pub fn warning_bg() -> Color32 {
with_alpha(Self::WARNING, 0.12)
}
pub const DANGER: Color32 = Color32::from_rgb(0xf2, 0x8b, 0x82);
pub fn danger_bg() -> Color32 {
with_alpha(Self::DANGER, 0.08)
}
pub fn danger_border() -> Color32 {
with_alpha(Self::DANGER, 0.25)
}
pub fn separator() -> Color32 {
Color32::from_rgba_unmultiplied(255, 255, 255, 18)
}
pub fn separator_stroke() -> Stroke {
Stroke::new(1.0_f32, Self::separator())
}
}
pub struct MonolithSurfaces;
impl MonolithSurfaces {
pub const SURFACE_0: Color32 = Color32::from_rgb(0x08, 0x08, 0x0a); pub const SURFACE_1: Color32 = Color32::from_rgb(0x10, 0x10, 0x12); pub const SURFACE_2: Color32 = Color32::from_rgb(0x18, 0x18, 0x1a); pub const SURFACE_3: Color32 = Color32::from_rgb(0x22, 0x22, 0x24); pub const SURFACE_4: Color32 = Color32::from_rgb(0x2a, 0x2a, 0x2d); pub const SURFACE_5: Color32 = Color32::from_rgb(0x34, 0x34, 0x37); pub const SURFACE_6: Color32 = Color32::from_rgb(0x40, 0x40, 0x44); pub const SURFACE_7: Color32 = Color32::from_rgb(0x4c, 0x4c, 0x50);
pub const TAB_ACTIVE: Color32 = Color32::from_rgb(0x26, 0x26, 0x28);
}
pub struct MonoType;
impl MonoType {
pub const DISPLAY: f32 = 22.0; pub const HEADING: f32 = 17.0; pub const SUBHEADING: f32 = 15.0; pub const BODY: f32 = 13.5; pub const LABEL: f32 = 12.0; pub const CAPTION: f32 = 11.0; pub const MICRO: f32 = 10.0;
pub const MONO_DATA: f32 = 11.0; pub const MONO_SMALL: f32 = 10.0; pub const MONO_MICRO: f32 = 9.5; }
pub struct MonoSpace;
impl MonoSpace {
pub const XS: f32 = 4.0;
pub const SM: f32 = 8.0;
pub const MD: f32 = 12.0;
pub const LG: f32 = 16.0;
pub const XL: f32 = 20.0;
pub const XXL: f32 = 24.0;
pub const XXXL: f32 = 32.0;
}
pub struct MonoLayout;
impl MonoLayout {
pub const SIDEBAR_WIDTH: f32 = 200.0;
pub const TOOLBAR_HEIGHT: f32 = 48.0;
pub const STATUS_BAR_HEIGHT: f32 = 26.0;
pub const CORNER_RADIUS_SM: u8 = 4;
pub const CORNER_RADIUS_MD: u8 = 6;
pub const CORNER_RADIUS_LG: u8 = 8;
pub const SEPARATOR_THICKNESS: f32 = 1.0;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrandPreset {
PlasmCore, OxidizedGold, VioletReaction, CoolantLiquid, CriticalMass, }
impl BrandPreset {
pub fn name(&self) -> &'static str {
match self {
BrandPreset::PlasmCore => "Plasma Core",
BrandPreset::OxidizedGold => "Oxidized Gold",
BrandPreset::VioletReaction => "Violet Reaction",
BrandPreset::CoolantLiquid => "Coolant Liquid",
BrandPreset::CriticalMass => "Critical Mass",
}
}
pub fn primary(&self) -> Color32 {
match self {
BrandPreset::PlasmCore => hex_to_color32("#22d3ee"),
BrandPreset::OxidizedGold => hex_to_color32("#f59e0b"),
BrandPreset::VioletReaction => hex_to_color32("#a855f7"),
BrandPreset::CoolantLiquid => hex_to_color32("#06b6d4"),
BrandPreset::CriticalMass => hex_to_color32("#ef4444"),
}
}
pub fn secondary(&self) -> Color32 {
match self {
BrandPreset::PlasmCore => hex_to_color32("#a855f7"),
BrandPreset::OxidizedGold => hex_to_color32("#fbbf24"),
BrandPreset::VioletReaction => hex_to_color32("#e11d48"),
BrandPreset::CoolantLiquid => hex_to_color32("#2dd4bf"),
BrandPreset::CriticalMass => hex_to_color32("#991b1b"),
}
}
pub fn primary_bg(&self) -> Color32 {
with_alpha(self.primary(), 0.12)
}
pub fn primary_active(&self) -> Color32 {
with_alpha(self.primary(), 0.20)
}
pub fn primary_border(&self) -> Color32 {
with_alpha(self.primary(), 0.30)
}
}
pub fn hex_to_color32(hex: &str) -> Color32 {
let hex = hex.trim_start_matches('#');
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
Color32::from_rgb(r, g, b)
}
pub fn with_alpha(color: Color32, alpha: f32) -> Color32 {
let arr = color.to_array();
Color32::from_rgba_unmultiplied(arr[0], arr[1], arr[2], (alpha * 255.0) as u8)
}
pub fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 {
let t = t.clamp(0.0, 1.0);
let ar = a.to_array();
let br = b.to_array();
Color32::from_rgb(
(ar[0] as f32 + (br[0] as f32 - ar[0] as f32) * t) as u8,
(ar[1] as f32 + (br[1] as f32 - ar[1] as f32) * t) as u8,
(ar[2] as f32 + (br[2] as f32 - ar[2] as f32) * t) as u8,
)
}
pub fn apply_monolith_dark(ctx: &egui::Context, brand: BrandPreset) {
let mut visuals = egui::Visuals::dark();
visuals.window_fill = MonolithSurfaces::SURFACE_1;
visuals.panel_fill = MonolithSurfaces::SURFACE_3;
visuals.faint_bg_color = MonolithSurfaces::SURFACE_2;
visuals.extreme_bg_color = MonolithSurfaces::SURFACE_0;
let primary = brand.primary();
visuals.widgets.inactive.bg_fill = MonolithSurfaces::SURFACE_4;
visuals.widgets.inactive.fg_stroke = Stroke::new(1.0_f32, MonoText::SECONDARY);
visuals.widgets.inactive.corner_radius = egui::CornerRadius::same(MonoLayout::CORNER_RADIUS_MD);
visuals.widgets.inactive.weak_bg_fill = MonolithSurfaces::SURFACE_3;
visuals.widgets.hovered.bg_fill = MonolithSurfaces::SURFACE_5;
visuals.widgets.hovered.fg_stroke = Stroke::new(1.0_f32, primary);
visuals.widgets.hovered.corner_radius = egui::CornerRadius::same(MonoLayout::CORNER_RADIUS_MD);
visuals.widgets.hovered.weak_bg_fill = MonolithSurfaces::SURFACE_5;
visuals.widgets.active.bg_fill = brand.primary_active();
visuals.widgets.active.fg_stroke = Stroke::new(1.5_f32, primary);
visuals.widgets.active.corner_radius = egui::CornerRadius::same(MonoLayout::CORNER_RADIUS_MD);
visuals.widgets.active.weak_bg_fill = brand.primary_active();
visuals.widgets.noninteractive.bg_fill = MonolithSurfaces::SURFACE_3;
visuals.widgets.noninteractive.fg_stroke = Stroke::new(1.0_f32, MonoText::SECONDARY);
visuals.widgets.noninteractive.corner_radius =
egui::CornerRadius::same(MonoLayout::CORNER_RADIUS_MD);
visuals.selection.bg_fill = brand.primary_bg();
visuals.selection.stroke = Stroke::new(1.0_f32, primary);
visuals.hyperlink_color = primary;
visuals.widgets.noninteractive.fg_stroke = Stroke::new(1.0_f32, MonoSemantics::separator());
ctx.set_visuals(visuals);
}
pub fn load_fonts(ctx: &egui::Context) {
let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert(
"outfit_regular".to_owned(),
std::sync::Arc::new(egui::FontData::from_owned(
include_bytes!("../assets/Outfit-Regular.ttf").to_vec(),
)),
);
fonts.font_data.insert(
"outfit_bold".to_owned(),
std::sync::Arc::new(egui::FontData::from_owned(
include_bytes!("../assets/Outfit-Bold.ttf").to_vec(),
)),
);
fonts.font_data.insert(
"dm_mono".to_owned(),
std::sync::Arc::new(egui::FontData::from_owned(
include_bytes!("../assets/DMMono-Regular.ttf").to_vec(),
)),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "outfit_regular".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "dm_mono".to_owned());
ctx.set_fonts(fonts);
tracing::debug!("MonolithUI fonts loaded (Outfit + DM Mono)");
}