pub mod components;
pub mod context;
pub mod manager;
pub mod presets;
pub mod semantic;
pub use components::{
BtnStyle, CandleStyle, ChartStyle, ComponentStyles, ContextMenuStyle, DialogStyle, InputStyle,
MenuStyle, PanelStyle, SettingsDialogStyle, ToolbarStyle, VolumeStyle,
};
pub use context::{ThemeContextExt, ThemeData, ThemeUiExt};
pub use manager::ThemeManager;
pub use presets::ThemePreset;
pub use semantic::{
ChartSemanticTokens, DrawingSemanticTokens, IndicatorSemanticTokens, SemanticTokens,
UiSemanticTokens,
};
use crate::tokens::DESIGN_TOKENS;
use egui::Color32;
#[derive(Clone, Debug)]
pub struct Theme {
pub preset: ThemePreset,
pub semantic: SemanticTokens,
pub components: ComponentStyles,
}
impl Default for Theme {
fn default() -> Self {
Self::from_preset(ThemePreset::Classic)
}
}
impl Theme {
pub fn from_preset(preset: ThemePreset) -> Self {
let semantic =
SemanticTokens::from_design_tokens(preset.is_dark_ui(), preset.is_dark_chart());
let components = ComponentStyles::from_semantic(&semantic);
Self {
preset,
semantic,
components,
}
}
pub fn classic() -> Self {
Self::from_preset(ThemePreset::Classic)
}
pub fn dark() -> Self {
Self::from_preset(ThemePreset::Dark)
}
pub fn light() -> Self {
Self::from_preset(ThemePreset::Light)
}
pub fn midnight() -> Self {
Self::from_preset(ThemePreset::Midnight)
}
pub fn high_contrast() -> Self {
Self::from_preset(ThemePreset::HighContrast)
}
#[inline]
pub fn name(&self) -> &'static str {
self.preset.display_name()
}
#[inline]
pub fn is_dark_ui(&self) -> bool {
self.preset.is_dark_ui()
}
#[inline]
pub fn is_dark_chart(&self) -> bool {
self.preset.is_dark_chart()
}
#[inline]
pub fn background(&self) -> Color32 {
self.semantic.chart.bg
}
#[inline]
pub fn grid(&self) -> Color32 {
self.semantic.chart.grid_line
}
#[inline]
pub fn text(&self) -> Color32 {
self.semantic.chart.axis_text
}
#[inline]
pub fn bullish(&self) -> Color32 {
self.semantic.chart.candle_up
}
#[inline]
pub fn bearish(&self) -> Color32 {
self.semantic.chart.candle_down
}
#[inline]
pub fn crosshair(&self) -> Color32 {
self.semantic.chart.crosshair_line
}
#[inline]
pub fn volume_bullish(&self) -> Color32 {
self.semantic.chart.volume_up
}
#[inline]
pub fn volume_bearish(&self) -> Color32 {
self.semantic.chart.volume_down
}
#[inline]
pub fn axis_background(&self) -> Color32 {
self.semantic.chart.bg_axis
}
#[inline]
pub fn footprint_poc(&self) -> Color32 {
DESIGN_TOKENS.semantic.footprint.poc
}
#[inline]
pub fn footprint_value_area(&self) -> Color32 {
DESIGN_TOKENS.semantic.footprint.value_area
}
#[inline]
pub fn bullish_alpha(&self, alpha: u8) -> Color32 {
let [r, g, b, _] = self.semantic.chart.candle_up.to_array();
Color32::from_rgba_unmultiplied(r, g, b, alpha)
}
#[inline]
pub fn bearish_alpha(&self, alpha: u8) -> Color32 {
let [r, g, b, _] = self.semantic.chart.candle_down.to_array();
Color32::from_rgba_unmultiplied(r, g, b, alpha)
}
#[inline]
pub fn tpo_poc(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.poc
}
#[inline]
pub fn tpo_value_area(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.value_area
}
#[inline]
pub fn tpo_initial_balance(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.initial_balance
}
#[inline]
pub fn tpo_single_print(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.single_print
}
#[inline]
pub fn tpo_session_separator(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.session_separator
}
#[inline]
pub fn tpo_letter_default(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.letter_default
}
#[inline]
pub fn tpo_opening_range(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.opening_range
}
#[inline]
pub fn tpo_grid(&self) -> Color32 {
DESIGN_TOKENS.semantic.tpo.grid
}
pub fn tpo_period_color(&self, period_idx: usize) -> Color32 {
let tpo = &DESIGN_TOKENS.semantic.tpo;
match period_idx % 12 {
0 => tpo.period_1,
1 => tpo.period_2,
2 => tpo.period_3,
3 => tpo.period_4,
4 => tpo.period_5,
5 => tpo.period_6,
6 => tpo.period_7,
7 => tpo.period_8,
8 => tpo.period_9,
9 => tpo.period_10,
10 => tpo.period_11,
_ => tpo.period_12,
}
}
#[inline]
pub fn warning(&self) -> Color32 {
self.semantic.ui.warning
}
#[inline]
pub fn text_alpha(&self, alpha: u8) -> Color32 {
let [r, g, b, _] = self.semantic.chart.axis_text.to_array();
Color32::from_rgba_unmultiplied(r, g, b, alpha)
}
pub fn apply_to_config(
&self,
mut config: crate::config::ChartConfig,
) -> crate::config::ChartConfig {
config.background_color = self.semantic.chart.bg;
config.grid_color = self.semantic.chart.grid_line;
config.text_color = self.semantic.chart.axis_text;
config.bullish_color = self.semantic.chart.candle_up;
config.bearish_color = self.semantic.chart.candle_down;
config.bullish_border_color = Some(self.semantic.chart.candle_up_border);
config.bearish_border_color = Some(self.semantic.chart.candle_down_border);
config.bullish_wick_color = Some(self.semantic.chart.candle_up_wick);
config.bearish_wick_color = Some(self.semantic.chart.candle_down_wick);
config
}
}
pub fn apply_to_egui(ctx: &egui::Context, theme: &Theme) {
let mut visuals = if theme.is_dark_ui() {
egui::Visuals::dark()
} else {
egui::Visuals::light()
};
let ui = &theme.semantic.ui;
visuals.panel_fill = ui.panel_bg;
visuals.window_fill = ui.panel_bg;
visuals.extreme_bg_color = ui.panel_bg;
visuals.faint_bg_color = ui.panel_bg;
visuals.widgets.noninteractive.bg_fill = ui.panel_bg;
visuals.widgets.inactive.bg_fill = ui.panel_bg;
visuals.widgets.hovered.bg_fill = ui.btn_bg_hover;
visuals.widgets.active.bg_fill = ui.btn_bg_active;
visuals.widgets.open.bg_fill = ui.btn_bg_active;
visuals.widgets.noninteractive.bg_stroke =
egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.border);
visuals.widgets.inactive.bg_stroke =
egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.border_subtle);
visuals.widgets.hovered.bg_stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.border);
visuals.widgets.active.bg_stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.accent);
visuals.widgets.noninteractive.fg_stroke =
egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.text_secondary);
visuals.widgets.inactive.fg_stroke =
egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.text_secondary);
visuals.widgets.hovered.fg_stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.text);
visuals.widgets.active.fg_stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.text);
visuals.selection.bg_fill = ui.accent;
visuals.selection.stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.hairline, ui.accent);
visuals.hyperlink_color = ui.accent;
visuals.window_stroke = egui::Stroke::NONE;
visuals.window_shadow = egui::epaint::Shadow {
offset: [2, 4],
blur: 8,
spread: 0,
color: Color32::from_black_alpha(80),
};
ctx.set_visuals(visuals);
let mut style = (*ctx.style()).clone();
style.spacing.window_margin = egui::Margin::ZERO;
style.spacing.menu_margin = egui::Margin::ZERO;
style.spacing.item_spacing = egui::Vec2::ZERO;
ctx.set_style(style);
ctx.set_theme_data(ThemeData::from_theme(theme));
}
pub fn all_presets() -> &'static [ThemePreset] {
ThemePreset::all()
}
pub fn all_themes() -> Vec<Theme> {
ThemePreset::all()
.iter()
.map(|p| Theme::from_preset(*p))
.collect()
}