#![allow(clippy::unwrap_used)]
use crate::ReUi;
use egui::Color32;
#[derive(Clone, Copy, Debug)]
pub struct DesignTokens {
pub top_bar_color: egui::Color32,
pub bottom_bar_color: egui::Color32,
pub bottom_bar_stroke: egui::Stroke,
pub bottom_bar_rounding: egui::Rounding,
pub shadow_gradient_dark_start: egui::Color32,
pub tab_bar_color: egui::Color32,
}
impl DesignTokens {
pub fn load_and_apply(ctx: &egui::Context) -> Self {
apply_design_tokens(ctx)
}
}
fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens {
let apply_font = true;
let apply_font_size = true;
let json: serde_json::Value = serde_json::from_str(include_str!("../data/design_tokens.json"))
.expect("Failed to parse data/design_tokens.json");
let typography_default: Typography = get_alias(&json, "{Alias.Typography.Default.value}");
if apply_font {
assert_eq!(typography_default.fontFamily, "Inter");
assert_eq!(typography_default.fontWeight, "Medium");
let mut font_definitions = egui::FontDefinitions::default();
font_definitions.font_data.insert(
"Inter-Medium".into(),
egui::FontData::from_static(include_bytes!("../data/Inter-Medium.otf")),
);
font_definitions
.families
.get_mut(&egui::FontFamily::Proportional)
.unwrap()
.insert(0, "Inter-Medium".into());
ctx.set_fonts(font_definitions);
}
let mut egui_style = egui::Style {
visuals: egui::Visuals::dark(),
..Default::default()
};
if apply_font_size {
let font_size = parse_px(&typography_default.fontSize);
for text_style in [
egui::TextStyle::Body,
egui::TextStyle::Monospace,
egui::TextStyle::Button,
] {
egui_style.text_styles.get_mut(&text_style).unwrap().size = font_size;
}
egui_style
.text_styles
.get_mut(&egui::TextStyle::Heading)
.unwrap()
.size = 16.0;
egui_style.spacing.interact_size.y = 15.0;
}
egui_style
.text_styles
.insert(ReUi::welcome_screen_h1(), egui::FontId::proportional(28.0));
egui_style
.text_styles
.insert(ReUi::welcome_screen_h2(), egui::FontId::proportional(24.0));
egui_style
.text_styles
.insert(ReUi::welcome_screen_h3(), egui::FontId::proportional(15.0));
egui_style.text_styles.insert(
ReUi::welcome_screen_body(),
egui::FontId::proportional(13.0),
);
let panel_bg_color = get_aliased_color(&json, "{Alias.Color.Surface.Default.value}");
let floating_color = Color32::from_gray(38);
egui_style.visuals.extreme_bg_color = egui::Color32::BLACK;
egui_style.visuals.widgets.noninteractive.weak_bg_fill = panel_bg_color;
egui_style.visuals.widgets.noninteractive.bg_fill = panel_bg_color;
egui_style.visuals.button_frame = true;
egui_style.visuals.widgets.inactive.weak_bg_fill = Default::default(); egui_style.visuals.widgets.inactive.bg_fill = Color32::from_gray(40);
{
let hovered_color = Color32::from_gray(64); egui_style.visuals.widgets.hovered.weak_bg_fill = hovered_color;
egui_style.visuals.widgets.hovered.bg_fill = hovered_color;
egui_style.visuals.widgets.active.weak_bg_fill = hovered_color;
egui_style.visuals.widgets.active.bg_fill = hovered_color;
egui_style.visuals.widgets.open.weak_bg_fill = hovered_color;
egui_style.visuals.widgets.open.bg_fill = hovered_color;
}
{
egui_style.visuals.widgets.inactive.bg_stroke = Default::default();
egui_style.visuals.widgets.hovered.bg_stroke = Default::default();
egui_style.visuals.widgets.active.bg_stroke = Default::default();
egui_style.visuals.widgets.open.bg_stroke = Default::default();
}
{
egui_style.visuals.widgets.hovered.expansion = 2.0;
egui_style.visuals.widgets.active.expansion = 2.0;
egui_style.visuals.widgets.open.expansion = 2.0;
}
egui_style.visuals.selection.bg_fill =
get_aliased_color(&json, "{Alias.Color.Highlight.Default.value}");
egui_style.visuals.widgets.noninteractive.bg_stroke.color = Color32::from_gray(30);
let subdued = get_aliased_color(&json, "{Alias.Color.Text.Subdued.value}");
let default = get_aliased_color(&json, "{Alias.Color.Text.Default.value}");
let strong = get_aliased_color(&json, "{Alias.Color.Text.Strong.value}");
egui_style.visuals.widgets.noninteractive.fg_stroke.color = subdued; egui_style.visuals.widgets.inactive.fg_stroke.color = default; egui_style.visuals.widgets.active.fg_stroke.color = strong;
egui_style.visuals.popup_shadow = egui::epaint::Shadow::NONE;
egui_style.visuals.window_shadow = egui::epaint::Shadow::NONE;
egui_style.visuals.window_fill = floating_color; egui_style.visuals.window_stroke = egui::Stroke::NONE;
egui_style.visuals.panel_fill = panel_bg_color;
egui_style.visuals.window_rounding = crate::ReUi::window_rounding().into();
egui_style.visuals.menu_rounding = crate::ReUi::window_rounding().into();
let small_rounding = crate::ReUi::small_rounding().into();
egui_style.visuals.widgets.noninteractive.rounding = small_rounding;
egui_style.visuals.widgets.inactive.rounding = small_rounding;
egui_style.visuals.widgets.hovered.rounding = small_rounding;
egui_style.visuals.widgets.active.rounding = small_rounding;
egui_style.visuals.widgets.open.rounding = small_rounding;
egui_style.spacing.item_spacing = egui::vec2(8.0, 8.0);
egui_style.spacing.menu_margin = crate::ReUi::view_padding().into();
egui_style.visuals.striped = false;
egui_style.visuals.indent_has_left_vline = false;
egui_style.spacing.button_padding = egui::Vec2::new(1.0, 0.0); egui_style.spacing.indent = 14.0;
egui_style.spacing.combo_width = 8.0;
egui_style.spacing.scroll_bar_inner_margin = 2.0;
egui_style.spacing.scroll_bar_width = 6.0;
egui_style.spacing.scroll_bar_outer_margin = 2.0;
egui_style.visuals.hyperlink_color = default;
egui_style.visuals.image_loading_spinners = false;
ctx.set_style(egui_style);
DesignTokens {
top_bar_color: Color32::from_gray(20), bottom_bar_color: get_global_color(&json, "{Global.Color.Grey.150}"),
bottom_bar_stroke: egui::Stroke::new(1.0, egui::Color32::from_gray(47)), bottom_bar_rounding: egui::Rounding {
nw: 6.0,
ne: 6.0,
sw: 0.0,
se: 0.0,
}, shadow_gradient_dark_start: egui::Color32::from_black_alpha(77),
tab_bar_color: get_global_color(&json, "{Global.Color.Grey.175}"),
}
}
fn get_aliased_color(json: &serde_json::Value, alias_path: &str) -> egui::Color32 {
parse_color(get_alias_str(json, alias_path))
}
fn get_global_color(json: &serde_json::Value, global_path: &str) -> egui::Color32 {
parse_color(global_path_value(json, global_path).as_str().unwrap())
}
fn get_alias_str<'json>(json: &'json serde_json::Value, alias_path: &str) -> &'json str {
let global_path = follow_path_or_panic(json, alias_path).as_str().unwrap();
global_path_value(json, global_path).as_str().unwrap()
}
fn get_alias<T: serde::de::DeserializeOwned>(json: &serde_json::Value, alias_path: &str) -> T {
let global_path = follow_path_or_panic(json, alias_path).as_str().unwrap();
let global_value = global_path_value(json, global_path);
serde_json::from_value(global_value.clone()).unwrap_or_else(|err| {
panic!(
"Failed to convert {global_path:?} to {}: {err}. Json: {json:?}",
std::any::type_name::<T>()
)
})
}
fn global_path_value<'json>(
value: &'json serde_json::Value,
global_path: &str,
) -> &'json serde_json::Value {
follow_path_or_panic(value, global_path)
.get("value")
.unwrap()
}
fn follow_path_or_panic<'json>(
json: &'json serde_json::Value,
json_path: &str,
) -> &'json serde_json::Value {
follow_path(json, json_path).unwrap_or_else(|| panic!("Failed to find {json_path:?}"))
}
fn follow_path<'json>(
mut value: &'json serde_json::Value,
path: &str,
) -> Option<&'json serde_json::Value> {
let path = path.strip_prefix('{')?;
let path = path.strip_suffix('}')?;
for component in path.split('.') {
value = value.get(component)?;
}
Some(value)
}
#[allow(non_snake_case)]
#[derive(serde::Deserialize)]
struct Typography {
fontSize: String,
fontWeight: String,
fontFamily: String,
}
fn parse_px(pixels: &str) -> f32 {
pixels.strip_suffix("px").unwrap().parse().unwrap()
}
fn parse_color(color: &str) -> egui::Color32 {
#![allow(clippy::identity_op)]
let color = color.strip_prefix('#').unwrap();
if color.len() == 6 {
let color = u32::from_str_radix(color, 16).unwrap();
egui::Color32::from_rgb(
((color >> 16) & 0xff) as u8,
((color >> 8) & 0xff) as u8,
((color >> 0) & 0xff) as u8,
)
} else if color.len() == 8 {
let color = u32::from_str_radix(color, 16).unwrap();
egui::Color32::from_rgba_unmultiplied(
((color >> 24) & 0xff) as u8,
((color >> 16) & 0xff) as u8,
((color >> 8) & 0xff) as u8,
((color >> 0) & 0xff) as u8,
)
} else {
panic!()
}
}
#[test]
fn test_design_tokens() {
let ctx = egui::Context::default();
apply_design_tokens(&ctx);
let _ = ctx.run(Default::default(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Hello Test!");
});
});
}