Files
solidcraft-upstream/crates/ui-egui/src/theme.rs
T

293 lines
12 KiB
Rust

//! Colours and widget style: a dark theme (the default, a blue-grey in the spirit of desktop CAD
//! tools, viewport #2e3440) and a light one. Every colour the UI uses comes from [`Tokens`].
use std::sync::atomic::{AtomicBool, Ordering};
use egui::{Color32, Stroke};
static DARK: AtomicBool = AtomicBool::new(true);
/// Is the dark theme on?
pub fn is_dark() -> bool {
DARK.load(Ordering::Relaxed)
}
#[derive(Clone, Copy, Debug)]
pub struct Tokens {
pub app_bar: Color32,
pub toolbar: Color32,
pub panel: Color32,
pub panel_header: Color32,
pub border: Color32,
pub text: Color32,
pub text_dim: Color32,
pub accent: Color32,
pub accent_soft: Color32,
pub sketch_accent: Color32,
pub error: Color32,
pub warning: Color32,
pub icon: Color32,
pub icon_fill: Color32,
pub timeline: Color32,
pub hover: Color32,
// ---- viewport selection feedback ----
/// Selected face (flat fill).
pub sel_face: Color32,
/// Selected edge core and its lighter rim.
pub sel_edge: Color32,
pub sel_edge_rim: Color32,
/// Hovered edge: dark core with a light halo.
pub hover_edge: Color32,
pub hover_edge_halo: Color32,
/// How much lighter a hovered face is (grey levels).
pub hover_face_lift: u8,
/// Selected profile fill and outline.
pub sel_profile: Color32,
pub sel_profile_edge: Color32,
/// Hovered profile: faint tint and a bright outline.
pub hover_profile: Color32,
pub hover_profile_edge: Color32,
/// Selected vertex dot.
pub sel_vertex: Color32,
// ---- origin ----
pub origin_plane: Color32,
pub origin_plane_edge: Color32,
pub origin_plane_hover: Color32,
pub origin_point: Color32,
pub axis_x: Color32,
pub axis_y: Color32,
pub axis_z: Color32,
/// Construction planes.
pub construction_plane: Color32,
/// Construction axes and points.
pub construction_geom: Color32,
// ---- bodies and live previews ----
pub body: Color32,
pub body_edge: Color32,
/// Command manipulators (direction arrows, handles).
pub manipulator: Color32,
/// New surface in a feature preview.
pub preview_add: Color32,
/// Removed material in a feature preview (translucent).
pub preview_cut: Color32,
// ---- viewport ----
/// Viewport background (top and bottom of the gradient; equal for a flat fill).
pub viewport_top: Color32,
pub viewport_bottom: Color32,
pub grid: Color32,
pub grid_major: Color32,
/// A whole selected body.
pub body_selected: Color32,
/// Sketch curves: under-constrained, fully constrained (and finished sketches).
pub sketch: Color32,
pub sketch_fixed: Color32,
/// Fixed (locked) sketch geometry.
pub sketch_locked: Color32,
/// Sketch dimensions: driving, driven (reference).
pub dimension: Color32,
pub dimension_driven: Color32,
/// Sketch points: fully constrained, free.
pub sketch_point: Color32,
pub sketch_point_free: Color32,
/// Command dialogs: panel, border, and the filled selection chip.
pub dialog_bg: Color32,
pub dialog_border: Color32,
pub chip: Color32,
/// Inside of bodies cut by a section.
pub section_cap: Color32,
/// Closed sketch profiles (translucent fill).
pub profile_fill: Color32,
/// Constraint glyph boxes.
pub glyph_bg: Color32,
pub glyph_edge: Color32,
pub glyph_text: Color32,
/// Chips drawn over the viewport (status, navigation bar, dimension labels).
pub overlay: Color32,
/// Rubber band of the shape being drawn.
pub rubber_band: Color32,
/// Light text on the application bar.
pub app_bar_text: Color32,
/// Timeline items: normal and failed.
pub timeline_item: Color32,
pub timeline_item_error: Color32,
pub timeline_marker: Color32,
/// Button and field backgrounds on panels.
pub field: Color32,
// ---- box selection ----
pub box_window: Color32,
pub box_crossing: Color32,
}
impl Tokens {
/// The tokens of the current theme.
pub fn get() -> Tokens {
if is_dark() { Tokens::dark() } else { Tokens::light() }
}
pub const fn light() -> Tokens {
Tokens {
app_bar: Color32::from_rgb(52, 56, 64),
toolbar: Color32::from_rgb(246, 247, 249),
panel: Color32::from_rgb(250, 251, 252),
panel_header: Color32::from_rgb(236, 238, 242),
border: Color32::from_rgb(208, 212, 220),
text: Color32::from_rgb(38, 42, 50),
text_dim: Color32::from_rgb(110, 116, 128),
accent: Color32::from_rgb(38, 120, 218),
accent_soft: Color32::from_rgb(214, 230, 250),
sketch_accent: Color32::from_rgb(46, 150, 90),
error: Color32::from_rgb(214, 54, 54),
warning: Color32::from_rgb(222, 150, 30),
icon: Color32::from_rgb(70, 78, 92),
icon_fill: Color32::from_rgb(178, 196, 222),
timeline: Color32::from_rgb(232, 235, 240),
hover: Color32::from_rgb(226, 234, 246),
sel_face: Color32::from_rgb(76, 127, 203),
sel_edge: Color32::from_rgb(0, 127, 255),
sel_edge_rim: Color32::from_rgb(127, 191, 255),
hover_edge: Color32::from_rgb(0, 0, 0),
hover_edge_halo: Color32::from_rgb(203, 203, 203),
hover_face_lift: 40,
sel_profile: Color32::from_rgb(26, 84, 167),
sel_profile_edge: Color32::from_rgb(47, 109, 198),
hover_profile: Color32::from_rgba_unmultiplied_const(75, 129, 253, 48),
hover_profile_edge: Color32::from_rgb(75, 129, 253),
sel_vertex: Color32::from_rgb(70, 110, 170),
origin_plane: Color32::from_rgba_unmultiplied_const(249, 184, 134, 94),
origin_plane_edge: Color32::from_rgba_unmultiplied_const(214, 140, 84, 200),
origin_plane_hover: Color32::from_rgba_unmultiplied_const(120, 130, 230, 150),
origin_point: Color32::from_rgb(214, 215, 218),
axis_x: Color32::from_rgb(255, 0, 0),
axis_y: Color32::from_rgb(0, 255, 0),
axis_z: Color32::from_rgb(2, 2, 248),
construction_plane: Color32::from_rgba_unmultiplied_const(249, 184, 134, 70),
construction_geom: Color32::from_rgb(232, 140, 48),
body: Color32::from_rgb(176, 186, 198),
body_edge: Color32::from_rgb(40, 44, 52),
manipulator: Color32::from_rgb(40, 110, 220),
preview_add: Color32::from_rgb(112, 160, 226),
preview_cut: Color32::from_rgba_unmultiplied_const(232, 84, 44, 110),
viewport_top: Color32::from_rgb(240, 243, 247),
viewport_bottom: Color32::from_rgb(203, 210, 221),
grid: Color32::from_rgb(196, 202, 212),
grid_major: Color32::from_rgb(170, 178, 190),
body_selected: Color32::from_rgb(110, 170, 235),
sketch: Color32::from_rgb(30, 90, 200),
sketch_fixed: Color32::from_rgb(20, 20, 20),
sketch_point: Color32::BLACK,
sketch_locked: Color32::from_rgb(0, 140, 60),
dimension: Color32::from_rgb(36, 40, 48),
dimension_driven: Color32::from_rgb(120, 126, 138),
sketch_point_free: Color32::from_rgb(30, 90, 200),
section_cap: Color32::from_rgb(214, 128, 72),
dialog_bg: Color32::from_rgb(250, 251, 252),
dialog_border: Color32::from_rgb(208, 212, 220),
chip: Color32::from_rgb(38, 120, 218),
profile_fill: Color32::from_rgba_unmultiplied_const(255, 196, 120, 70),
glyph_bg: Color32::WHITE,
glyph_edge: Color32::from_rgb(90, 150, 90),
glyph_text: Color32::from_rgb(40, 110, 40),
overlay: Color32::from_rgba_unmultiplied_const(255, 255, 255, 235),
rubber_band: Color32::from_rgb(30, 120, 230),
app_bar_text: Color32::from_rgb(230, 234, 242),
timeline_item: Color32::WHITE,
timeline_item_error: Color32::from_rgb(250, 220, 220),
timeline_marker: Color32::from_rgb(70, 76, 88),
field: Color32::WHITE,
box_window: Color32::from_rgb(38, 120, 218),
box_crossing: Color32::from_rgb(40, 160, 80),
}
}
/// Dark blue-grey: viewport #2e3440 as measured in the reference tool, chrome a little
/// darker, light text, a blue accent.
pub const fn dark() -> Tokens {
let mut t = Tokens::light();
t.app_bar = Color32::from_rgb(28, 31, 38);
t.toolbar = Color32::from_rgb(37, 41, 49);
t.panel = Color32::from_rgb(39, 43, 52);
t.panel_header = Color32::from_rgb(47, 52, 62);
t.border = Color32::from_rgb(63, 69, 82);
t.text = Color32::from_rgb(222, 226, 232);
t.text_dim = Color32::from_rgb(148, 155, 168);
t.accent = Color32::from_rgb(58, 136, 238);
t.accent_soft = Color32::from_rgb(42, 70, 110);
t.sketch_accent = Color32::from_rgb(110, 200, 140);
t.error = Color32::from_rgb(240, 100, 96);
t.warning = Color32::from_rgb(236, 170, 70);
t.icon = Color32::from_rgb(208, 214, 226);
t.icon_fill = Color32::from_rgb(86, 112, 150);
t.timeline = Color32::from_rgb(33, 37, 45);
t.hover = Color32::from_rgb(54, 61, 74);
t.hover_edge_halo = Color32::from_rgb(203, 203, 203);
t.origin_point = Color32::from_rgb(214, 215, 218);
t.viewport_top = Color32::from_rgb(46, 52, 64);
t.viewport_bottom = Color32::from_rgb(46, 52, 64);
t.grid = Color32::from_rgb(59, 65, 76);
t.grid_major = Color32::from_rgb(73, 79, 90);
t.body = Color32::from_rgb(152, 156, 162);
t.body_edge = Color32::from_rgb(16, 16, 18);
t.sketch = Color32::from_rgb(92, 150, 255);
t.sketch_fixed = Color32::from_rgb(232, 234, 238);
t.sketch_point = Color32::from_rgb(232, 234, 238);
t.sketch_locked = Color32::from_rgb(96, 204, 116);
t.dimension = Color32::from_rgb(224, 228, 234);
t.dimension_driven = Color32::from_rgb(142, 150, 164);
t.sketch_point_free = Color32::from_rgb(92, 150, 255);
t.profile_fill = Color32::from_rgba_unmultiplied_const(120, 150, 200, 40);
t.dialog_bg = Color32::from_rgb(59, 68, 83);
t.dialog_border = Color32::from_rgb(72, 83, 99);
t.chip = Color32::from_rgb(59, 101, 168);
t.glyph_bg = Color32::from_rgb(44, 50, 60);
t.glyph_edge = Color32::from_rgb(96, 170, 110);
t.glyph_text = Color32::from_rgb(150, 220, 160);
t.overlay = Color32::from_rgba_unmultiplied_const(34, 38, 46, 240);
t.rubber_band = Color32::from_rgb(92, 160, 255);
t.timeline_item = Color32::from_rgb(52, 58, 70);
t.timeline_item_error = Color32::from_rgb(110, 50, 54);
t.timeline_marker = Color32::from_rgb(170, 178, 192);
t.field = Color32::from_rgb(30, 34, 41);
t.box_window = Color32::from_rgb(70, 150, 255);
t.box_crossing = Color32::from_rgb(80, 200, 120);
t
}
}
/// How a physical material looks (the default appearance follows the material).
pub fn material_color(name: &str) -> Option<Color32> {
solvecraft_engine::doc::material_color(name).map(|[r, g, b]| Color32::from_rgb(r, g, b))
}
/// Switch themes (the visuals follow on the next [`apply`]).
pub fn set_dark(dark: bool) {
DARK.store(dark, Ordering::Relaxed);
}
pub fn apply(ctx: &egui::Context) {
let t = Tokens::get();
let mut v = if is_dark() { egui::Visuals::dark() } else { egui::Visuals::light() };
v.panel_fill = t.panel;
v.window_fill = t.panel;
v.window_stroke = Stroke::new(1.0, t.border);
v.extreme_bg_color = t.field;
v.faint_bg_color = t.panel_header;
v.override_text_color = Some(t.text);
v.widgets.inactive.weak_bg_fill = t.panel_header;
v.widgets.inactive.bg_fill = t.panel_header;
v.widgets.active.weak_bg_fill = t.accent_soft;
v.selection.bg_fill = t.accent_soft;
v.selection.stroke = Stroke::new(1.0, t.accent);
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, t.border);
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, t.text);
v.widgets.inactive.fg_stroke = Stroke::new(1.0, t.text);
v.widgets.hovered.weak_bg_fill = t.hover;
v.widgets.hovered.bg_fill = t.hover;
v.window_corner_radius = egui::CornerRadius::same(6);
ctx.set_theme(if is_dark() { egui::Theme::Dark } else { egui::Theme::Light });
ctx.set_visuals(v);
ctx.global_style_mut(|s| {
s.spacing.item_spacing = egui::vec2(6.0, 4.0);
s.spacing.button_padding = egui::vec2(6.0, 3.0);
});
}