333 lines
11 KiB
Rust
333 lines
11 KiB
Rust
use eframe::egui::{self, Color32};
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct AppTheme {
|
|
pub bg: egui::Color32,
|
|
pub panel: egui::Color32,
|
|
pub panel_strong: egui::Color32,
|
|
pub panel_deep: egui::Color32,
|
|
pub border: egui::Color32,
|
|
pub border_soft: egui::Color32,
|
|
pub text: egui::Color32,
|
|
pub text_dim: egui::Color32,
|
|
pub text_subtle: egui::Color32,
|
|
pub accent: egui::Color32,
|
|
pub accent_hot: egui::Color32,
|
|
pub radius: u8,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct DesignMetrics {
|
|
pub row_gap: f32,
|
|
pub item_gap: f32,
|
|
pub field_height: f32,
|
|
pub button_height: f32,
|
|
pub icon_button: egui::Vec2,
|
|
pub panel_padding: i8,
|
|
pub group_padding_x: i8,
|
|
pub group_padding_y: i8,
|
|
}
|
|
|
|
pub const ONE_DARK_PRO: AppTheme = AppTheme {
|
|
bg: egui::Color32::BLACK,
|
|
panel: egui::Color32::from_rgb(22, 28, 35),
|
|
panel_strong: egui::Color32::from_rgb(34, 43, 54),
|
|
panel_deep: egui::Color32::from_rgb(15, 20, 27),
|
|
border: egui::Color32::from_rgb(78, 93, 110),
|
|
border_soft: egui::Color32::from_rgb(50, 62, 76),
|
|
text: egui::Color32::from_rgb(235, 241, 247),
|
|
text_dim: egui::Color32::from_rgb(174, 185, 198),
|
|
text_subtle: egui::Color32::from_rgb(126, 139, 154),
|
|
accent: egui::Color32::from_rgb(41, 192, 204),
|
|
accent_hot: egui::Color32::from_rgb(247, 190, 84),
|
|
radius: 5,
|
|
};
|
|
|
|
pub const ACCENT_GREEN: egui::Color32 = egui::Color32::from_rgb(126, 203, 137);
|
|
pub const ACCENT_RED: egui::Color32 = egui::Color32::from_rgb(240, 99, 94);
|
|
pub const ACCENT_BLUE: egui::Color32 = egui::Color32::from_rgb(96, 170, 240);
|
|
pub const ACCENT_ORANGE: egui::Color32 = egui::Color32::from_rgb(242, 163, 87);
|
|
|
|
pub const METRICS: DesignMetrics = DesignMetrics {
|
|
row_gap: 10.0,
|
|
item_gap: 8.0,
|
|
field_height: 24.0,
|
|
button_height: 28.0,
|
|
icon_button: egui::vec2(30.0, 26.0),
|
|
panel_padding: 12,
|
|
group_padding_x: 10,
|
|
group_padding_y: 8,
|
|
};
|
|
|
|
pub mod layout {
|
|
pub const TITLE_BAR_HEIGHT: f32 = 36.0;
|
|
pub const CENTER_PANEL_TOP: f32 = 48.0;
|
|
pub const LEFT_X: f32 = 24.0;
|
|
pub const RIGHT_X: f32 = 1328.0;
|
|
pub const TOP_Y: f32 = 56.0;
|
|
pub const LEFT_SECOND_Y: f32 = 292.0;
|
|
pub const RIGHT_SECOND_Y: f32 = 388.0;
|
|
pub const LEFT_TAG_X: f32 = 24.0;
|
|
pub const RIGHT_TAG_X: f32 = 1810.0;
|
|
}
|
|
|
|
pub fn apply_theme(ctx: &egui::Context, theme: &AppTheme) {
|
|
let mut visuals = egui::Visuals::dark();
|
|
visuals.override_text_color = Some(theme.text);
|
|
visuals.panel_fill = theme.bg;
|
|
visuals.window_fill = theme.panel;
|
|
visuals.window_stroke = egui::Stroke::new(1.0, theme.border);
|
|
visuals.extreme_bg_color = theme.panel_deep;
|
|
visuals.faint_bg_color = theme.panel_strong;
|
|
visuals.code_bg_color = theme.panel_deep;
|
|
visuals.warn_fg_color = theme.accent_hot;
|
|
visuals.error_fg_color = ACCENT_RED;
|
|
|
|
visuals.widgets.noninteractive.bg_fill = theme.panel_strong;
|
|
visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, theme.border_soft);
|
|
visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, theme.text);
|
|
visuals.widgets.inactive.bg_fill = theme.panel_strong;
|
|
visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, theme.border_soft);
|
|
visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, theme.text);
|
|
visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(44, 57, 70);
|
|
visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.0, theme.accent);
|
|
visuals.widgets.hovered.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);
|
|
visuals.widgets.active.bg_fill = theme.accent;
|
|
visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, theme.accent_hot);
|
|
visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);
|
|
visuals.widgets.open.bg_fill = egui::Color32::from_rgb(39, 50, 62);
|
|
visuals.widgets.open.bg_stroke = egui::Stroke::new(1.0, theme.accent);
|
|
visuals.widgets.open.fg_stroke = egui::Stroke::new(1.0, theme.text);
|
|
|
|
visuals.selection.bg_fill = egui::Color32::from_rgb(35, 123, 140);
|
|
visuals.selection.stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);
|
|
visuals.hyperlink_color = theme.accent_hot;
|
|
ctx.set_visuals(visuals);
|
|
|
|
let mut style = (*ctx.global_style()).clone();
|
|
style.spacing.item_spacing = egui::vec2(METRICS.item_gap, 6.0);
|
|
style.spacing.button_padding = egui::vec2(12.0, 5.0);
|
|
style.spacing.window_margin = egui::Margin::same(METRICS.panel_padding);
|
|
style.spacing.interact_size = egui::vec2(72.0, METRICS.field_height);
|
|
style.spacing.combo_width = 112.0;
|
|
style.spacing.text_edit_width = 112.0;
|
|
style.visuals.window_corner_radius = egui::CornerRadius::same(theme.radius);
|
|
style.visuals.menu_corner_radius = egui::CornerRadius::same(4);
|
|
style.visuals.window_shadow = egui::epaint::Shadow {
|
|
offset: [0, 14],
|
|
blur: 28,
|
|
spread: 0,
|
|
color: egui::Color32::from_black_alpha(135),
|
|
};
|
|
ctx.set_global_style(style);
|
|
}
|
|
|
|
pub fn apply_fonts(ctx: &egui::Context) {
|
|
let mut fonts = egui::FontDefinitions::default();
|
|
|
|
fonts.font_data.insert(
|
|
"Hack-Bold".to_owned(),
|
|
egui::FontData::from_static(include_bytes!("../static/Hack-Bold.ttf")).into(),
|
|
);
|
|
|
|
let has_yahei = std::fs::read(r"C:\Windows\Fonts\msyh.ttc")
|
|
.or_else(|_| std::fs::read(r"C:\Windows\Fonts\msyhbd.ttc"))
|
|
.map(|font_data| {
|
|
fonts.font_data.insert(
|
|
"Microsoft-YaHei".to_owned(),
|
|
egui::FontData::from_owned(font_data).into(),
|
|
);
|
|
})
|
|
.is_ok();
|
|
|
|
fonts
|
|
.families
|
|
.entry(egui::FontFamily::Proportional)
|
|
.or_default()
|
|
.insert(0, "Hack-Bold".to_owned());
|
|
if has_yahei {
|
|
fonts
|
|
.families
|
|
.entry(egui::FontFamily::Proportional)
|
|
.or_default()
|
|
.push("Microsoft-YaHei".to_owned());
|
|
}
|
|
|
|
fonts
|
|
.families
|
|
.entry(egui::FontFamily::Monospace)
|
|
.or_default()
|
|
.insert(0, "Hack-Bold".to_owned());
|
|
if has_yahei {
|
|
fonts
|
|
.families
|
|
.entry(egui::FontFamily::Monospace)
|
|
.or_default()
|
|
.push("Microsoft-YaHei".to_owned());
|
|
}
|
|
|
|
ctx.set_fonts(fonts);
|
|
}
|
|
|
|
pub fn panel_frame(ctx: &egui::Context) -> egui::Frame {
|
|
let style = ctx.global_style();
|
|
egui::Frame::window(&style)
|
|
.fill(ONE_DARK_PRO.panel)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border))
|
|
.corner_radius(egui::CornerRadius::same(ONE_DARK_PRO.radius))
|
|
.inner_margin(egui::Margin::same(METRICS.panel_padding))
|
|
.shadow(egui::epaint::Shadow {
|
|
offset: [0, 14],
|
|
blur: 28,
|
|
spread: 0,
|
|
color: egui::Color32::from_black_alpha(135),
|
|
})
|
|
}
|
|
|
|
pub fn center_panel_frame() -> egui::Frame {
|
|
egui::Frame::new()
|
|
.fill(ONE_DARK_PRO.panel)
|
|
.corner_radius(egui::CornerRadius::same(ONE_DARK_PRO.radius))
|
|
.inner_margin(egui::Margin::symmetric(
|
|
METRICS.panel_padding + 2,
|
|
METRICS.panel_padding,
|
|
))
|
|
}
|
|
|
|
pub fn group_frame() -> egui::Frame {
|
|
egui::Frame::new()
|
|
.fill(ONE_DARK_PRO.panel_deep)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.inner_margin(egui::Margin::symmetric(
|
|
METRICS.group_padding_x,
|
|
METRICS.group_padding_y,
|
|
))
|
|
}
|
|
|
|
pub fn tag_button(label: impl Into<egui::WidgetText>) -> egui::Button<'static> {
|
|
egui::Button::new(label)
|
|
.fill(ONE_DARK_PRO.panel_strong)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(0.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn rich_tag_button(
|
|
label: impl Into<String>,
|
|
color: impl Into<Color32>,
|
|
) -> egui::Button<'static> {
|
|
let text = egui::RichText::new(label.into()).color(color);
|
|
|
|
egui::Button::new(text)
|
|
.fill(ONE_DARK_PRO.panel_strong)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(0.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn primary_button(label: impl Into<egui::WidgetText>) -> egui::Button<'static> {
|
|
egui::Button::new(label)
|
|
.fill(ONE_DARK_PRO.accent)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.accent_hot))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(112.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn danger_button(label: impl Into<egui::WidgetText>) -> egui::Button<'static> {
|
|
egui::Button::new(label)
|
|
.fill(ACCENT_RED)
|
|
.stroke(egui::Stroke::new(
|
|
1.0,
|
|
egui::Color32::from_rgb(255, 138, 126),
|
|
))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(112.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn accent_button(
|
|
label: impl Into<egui::WidgetText>,
|
|
fill: egui::Color32,
|
|
) -> egui::Button<'static> {
|
|
egui::Button::new(label)
|
|
.fill(fill)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(0.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn mode_button(label: &'static str, selected: bool) -> egui::Button<'static> {
|
|
let fill = if selected {
|
|
ONE_DARK_PRO.accent
|
|
} else {
|
|
ONE_DARK_PRO.panel_strong
|
|
};
|
|
let stroke = if selected {
|
|
ONE_DARK_PRO.accent_hot
|
|
} else {
|
|
ONE_DARK_PRO.border_soft
|
|
};
|
|
|
|
egui::Button::new(egui::RichText::new(label).color(egui::Color32::WHITE))
|
|
.fill(fill)
|
|
.stroke(egui::Stroke::new(1.0, stroke))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(egui::vec2(96.0, METRICS.button_height))
|
|
}
|
|
|
|
pub fn icon_button<'a>(icon: impl Into<egui::WidgetText>, size: egui::Vec2) -> egui::Button<'a> {
|
|
egui::Button::new(icon)
|
|
.fill(ONE_DARK_PRO.panel_strong)
|
|
.stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border))
|
|
.corner_radius(egui::CornerRadius::same(4))
|
|
.min_size(size)
|
|
}
|
|
|
|
pub fn panel_title(title: &'static str) -> egui::RichText {
|
|
egui::RichText::new(title)
|
|
.color(ONE_DARK_PRO.text_dim)
|
|
.size(13.0)
|
|
}
|
|
|
|
pub fn field_label(label: impl Into<String>) -> egui::RichText {
|
|
egui::RichText::new(label.into())
|
|
.color(ONE_DARK_PRO.text_dim)
|
|
.size(12.0)
|
|
}
|
|
|
|
pub fn value_text(text: impl Into<String>) -> egui::RichText {
|
|
egui::RichText::new(text.into())
|
|
.color(ONE_DARK_PRO.text)
|
|
.size(12.0)
|
|
}
|
|
|
|
pub fn subtle_text(text: impl Into<String>) -> egui::RichText {
|
|
egui::RichText::new(text.into())
|
|
.color(ONE_DARK_PRO.text_subtle)
|
|
.size(12.0)
|
|
}
|
|
|
|
pub fn dim_text() -> egui::Color32 {
|
|
ONE_DARK_PRO.text_dim
|
|
}
|
|
|
|
pub fn accent_text() -> egui::Color32 {
|
|
ONE_DARK_PRO.accent_hot
|
|
}
|
|
|
|
pub fn status_color_ok() -> egui::Color32 {
|
|
ACCENT_GREEN
|
|
}
|
|
|
|
pub fn status_color_warn() -> egui::Color32 {
|
|
ONE_DARK_PRO.accent_hot
|
|
}
|
|
|
|
pub fn status_color_error() -> egui::Color32 {
|
|
ACCENT_RED
|
|
}
|
|
|
|
pub fn status_color_info() -> egui::Color32 {
|
|
ACCENT_BLUE
|
|
}
|