VNOX-Client/src/ui/theme/mod.rs
loki5512344 bf0b237afd Initial commit: split VNOX monorepo into VNOX-Client
Desktop client with egui + wgpu UI, audio pipeline (cpal/opus/rodio),
networking (LNEx protocol), and identity management with encrypted vault.

Jitter buffer module extracted from voice-node for standalone operation.
2026-07-09 11:52:57 +02:00

144 lines
5.6 KiB
Rust

pub mod icons;
pub mod widgets;
pub use widgets::{avatar, icon_btn, icon_btn_drawn};
use eframe::egui::{self, Color32, CornerRadius, FontDefinitions, Stroke, Visuals};
pub const BG_BASE: Color32 = Color32::from_rgb(13, 13, 13);
pub const BG_SURFACE: Color32 = Color32::from_rgb(22, 22, 22);
pub const BG_ELEVATED: Color32 = Color32::from_rgb(28, 28, 28);
pub const BG_INTERACTIVE: Color32 = Color32::from_rgb(30, 30, 30);
pub const ACCENT: Color32 = Color32::from_rgb(230, 126, 34);
pub const ACCENT_HOVER: Color32 = Color32::from_rgb(245, 145, 55);
pub const ACCENT_SOFT: Color32 = Color32::from_rgb(200, 110, 30);
pub const ACCENT_10: Color32 = Color32::from_rgba_premultiplied(230, 126, 34, 25);
pub const ACCENT_20: Color32 = Color32::from_rgba_premultiplied(230, 126, 34, 50);
pub const BORDER_ACCENT: Color32 = Color32::from_rgba_premultiplied(230, 126, 34, 60);
pub const SUCCESS: Color32 = Color32::from_rgb(80, 180, 90);
pub const WARNING: Color32 = Color32::from_rgb(220, 170, 60);
pub const ERROR: Color32 = Color32::from_rgb(210, 90, 80);
pub const INFO: Color32 = Color32::from_rgb(90, 155, 210);
pub const TEXT_PRIMARY: Color32 = Color32::from_rgb(224, 224, 224);
pub const TEXT_SECONDARY: Color32 = Color32::from_rgb(170, 170, 170);
pub const TEXT_MUTED: Color32 = Color32::from_rgb(136, 136, 136);
pub const TEXT_DISABLED: Color32 = Color32::from_rgb(90, 90, 90);
pub const TEXT_DEAD: Color32 = Color32::from_rgb(55, 55, 55);
pub const BORDER_SUBTLE: Color32 = Color32::from_rgb(42, 42, 42);
pub const BORDER_DEFAULT: Color32 = Color32::from_rgb(51, 51, 51);
pub const BORDER_STRONG: Color32 = Color32::from_rgb(64, 64, 64);
pub const BG_STRIP: Color32 = BG_SURFACE;
pub const BG_PANEL: Color32 = BG_SURFACE;
pub const BORDER: Color32 = BORDER_DEFAULT;
pub const BORDER_FAINT: Color32 = BORDER_SUBTLE;
pub const BORDER_MID: Color32 = BORDER_STRONG;
pub const WARN: Color32 = WARNING;
pub const DANGER: Color32 = ERROR;
pub const BLUE: Color32 = INFO;
pub const TEXT_GHOST: Color32 = TEXT_DISABLED;
pub const TEXT_DIM: Color32 = TEXT_MUTED;
pub const ACCENT_DIM: Color32 = Color32::from_rgb(160, 90, 25);
pub const TEXT_FAINT: Color32 = TEXT_DEAD;
pub const R_SM: CornerRadius = CornerRadius::same(6);
pub const R_MD: CornerRadius = CornerRadius::same(10);
pub const R_LG: CornerRadius = CornerRadius::same(14);
pub const R_FULL: CornerRadius = CornerRadius::same(255);
const NAME_PALETTE: [Color32; 6] = [
Color32::from_rgb(170, 170, 170),
Color32::from_rgb(106, 158, 207),
Color32::from_rgb(124, 184, 122),
Color32::from_rgb(220, 170, 60),
Color32::from_rgb(190, 140, 200),
Color32::from_rgb(120, 190, 180),
];
pub fn user_color(user_id: &str) -> Color32 {
if user_id.is_empty() {
return TEXT_SECONDARY;
}
let hash: usize = user_id
.bytes()
.fold(0usize, |acc, b| acc.wrapping_add(b as usize));
NAME_PALETTE[hash % NAME_PALETTE.len()]
}
pub fn apply_visuals(ctx: &egui::Context) {
let mut v = Visuals::dark();
v.window_fill = BG_BASE;
v.panel_fill = BG_BASE;
v.extreme_bg_color = BG_INTERACTIVE;
v.faint_bg_color = BG_ELEVATED;
v.code_bg_color = BG_SURFACE;
v.hyperlink_color = ACCENT;
v.override_text_color = Some(TEXT_PRIMARY);
v.widgets.noninteractive.bg_fill = BG_SURFACE;
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, BORDER_SUBTLE);
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, TEXT_MUTED);
v.widgets.noninteractive.corner_radius = R_MD;
v.widgets.inactive.bg_fill = Color32::TRANSPARENT;
v.widgets.inactive.bg_stroke = Stroke::new(1.0, BORDER_DEFAULT);
v.widgets.inactive.fg_stroke = Stroke::new(1.0, TEXT_MUTED);
v.widgets.inactive.corner_radius = R_MD;
v.widgets.hovered.bg_fill = BG_INTERACTIVE;
v.widgets.hovered.bg_stroke = Stroke::new(1.0, BORDER_STRONG);
v.widgets.hovered.fg_stroke = Stroke::new(1.0, TEXT_PRIMARY);
v.widgets.hovered.corner_radius = R_MD;
v.widgets.active.bg_fill = BG_INTERACTIVE;
v.widgets.active.bg_stroke = Stroke::new(1.0, BORDER_ACCENT);
v.widgets.active.fg_stroke = Stroke::new(1.0, TEXT_PRIMARY);
v.widgets.active.corner_radius = R_MD;
v.widgets.open.bg_fill = BG_INTERACTIVE;
v.widgets.open.bg_stroke = Stroke::new(1.0, BORDER_STRONG);
v.widgets.open.fg_stroke = Stroke::new(1.0, TEXT_PRIMARY);
v.widgets.open.corner_radius = R_MD;
v.selection.bg_fill = ACCENT_10;
v.selection.stroke = Stroke::new(0.0, Color32::TRANSPARENT);
v.window_corner_radius = R_LG;
v.window_shadow = egui::epaint::Shadow::NONE;
v.popup_shadow = egui::epaint::Shadow::NONE;
v.window_stroke = Stroke::new(1.0, BORDER_DEFAULT);
ctx.set_visuals(v);
set_fonts(ctx);
set_spacing(ctx);
}
fn set_fonts(ctx: &egui::Context) {
let fonts = FontDefinitions::default();
ctx.set_fonts(fonts);
}
fn set_spacing(ctx: &egui::Context) {
let mut s = (*ctx.style()).clone();
s.text_styles = [
(egui::TextStyle::Small, egui::FontId::proportional(11.0)),
(egui::TextStyle::Body, egui::FontId::proportional(13.0)),
(egui::TextStyle::Button, egui::FontId::proportional(13.0)),
(egui::TextStyle::Heading, egui::FontId::proportional(15.0)),
(egui::TextStyle::Monospace, egui::FontId::monospace(12.0)),
]
.into();
s.spacing.item_spacing = egui::vec2(6.0, 4.0);
s.spacing.button_padding = egui::vec2(10.0, 5.0);
s.spacing.window_margin = egui::Margin::same(0);
s.spacing.menu_margin = egui::Margin::same(4);
s.spacing.indent = 16.0;
s.spacing.interact_size = egui::vec2(40.0, 28.0);
s.spacing.scroll.bar_width = 4.0;
ctx.set_style(s);
}