1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 12:50:04 -04:00

clean history

This commit is contained in:
adrien
2026-06-21 20:36:18 +02:00
parent 68b74530b7
commit b742e230a0
11 changed files with 579 additions and 7 deletions

View File

@@ -0,0 +1,140 @@
use std::collections::HashMap;
use eframe::egui::{
Color32, Style,
theme_plugin::{ThemeCache, ThemeStyle},
widget_style::{
ButtonStyle, Classes, HasClasses as _, StyleStruct as _, WidgetState, WidgetStyle,
},
};
use logos::Logos;
#[derive(Debug, Default, Clone)]
pub struct ESSEngine {
info: HashMap<String, Vec<(String, Value)>>,
cache: ThemeCache,
}
impl ESSEngine {
pub fn try_parse(ess: &str) -> Result<Self, String> {
let mut engine = Self::default();
let mut lexer = Token::lexer(ess);
let mut hash = HashMap::new();
while let Some(token) = lexer.next() {
if token == Ok(Token::Class) {
let selector = lexer.slice()[1..].to_owned();
if lexer
.next()
.is_some_and(|token| token.is_ok_and(|token| token != Token::Open))
{
return Err("No opening bracket found !".to_owned());
}
let mut declarations = vec![];
loop {
match lexer.next() {
Some(Ok(Token::Property)) => {
let property = lexer.slice().to_owned();
if lexer
.next()
.is_some_and(|token| token.is_ok_and(|token| token != Token::Is))
{
return Err("No separator between property and value !".to_owned());
}
let value = match lexer.next() {
Some(Ok(Token::Number)) => Value::Number(
lexer
.slice()
.to_owned()
.parse::<usize>()
.expect("Should be a positive integer"),
),
Some(Ok(Token::Color)) => Value::Color(
Color32::from_hex(lexer.slice())
.expect("Should be a valid hex"),
),
Some(Ok(v)) => return Err(format!("Invalid value : {v:?}")),
_ => return Err("Error".to_owned()),
};
declarations.push((property, value));
}
Some(Ok(Token::Close)) => break,
Some(Ok(v)) => {
return Err(format!("Missing close bracket, found : {v:?}"));
}
v => return Err(format!("Error : {v:?}")),
}
}
hash.insert(selector, declarations);
}
}
engine.info = hash;
Ok(engine)
}
}
impl ThemeStyle<WidgetStyle> for ESSEngine {
fn style(&mut self, classes: &Classes, state: WidgetState, base: &Style) -> WidgetStyle {
self.cache
.get(classes, state, || base.widget_style(classes, state))
}
}
impl ThemeStyle<ButtonStyle> for ESSEngine {
fn style(&mut self, classes: &Classes, state: WidgetState, base: &Style) -> ButtonStyle {
self.cache.get(classes, state, || {
let mut default = ButtonStyle::default_style(classes, state, base);
for classe in classes.list() {
if let Some(properties) = self.info.get(&classe.to_string()) {
for (property, value) in properties {
match property.as_str() {
"fill" => {
if let Value::Color(color) = value {
default.frame.fill = *color;
}
}
"border" => {
if let Value::Number(size) = value {
default.frame.stroke.width = *size as f32;
}
}
_ => {}
}
}
}
}
default
})
}
}
#[derive(Debug, Logos, PartialEq)]
enum Token {
#[token("{")]
Open,
#[token("}")]
Close,
#[token(":")]
Is,
#[regex(r"\.[a-zA-Z]+")]
Class,
#[regex(r"[a-zA-Z]+")]
Property,
#[regex(r"[0-9]+")]
Number,
#[regex(r"#(?:[0-9a-fA-F]{3}){1,2}")]
Color,
#[regex(r"[ \t\n\f;]+", logos::skip)]
Whitespace,
}
#[derive(Debug, Clone)]
enum Value {
Number(usize),
Color(Color32),
}

View File

@@ -0,0 +1,106 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![expect(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::egui::widget_style::WidgetStyle;
use eframe::egui::{
self, Button, Frame, Margin, Panel, UiBuilder,
widget_style::{ButtonStyle, HasClasses as _},
};
use crate::custom_engine::ESSEngine;
mod custom_engine;
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([600.0, 400.0]),
..Default::default()
};
let mut style_code = "
.red {
fill: #f00;
}
.blue {
fill: #00f;
border: 10;
}
"
.to_owned();
let mut toggled = false;
eframe::run_ui_native("My egui App", options, move |ui, _frame| {
// Register the theme plugin and which style they implement
if let Ok(engine) = ESSEngine::try_parse(&style_code) {
ui.add_theme::<WidgetStyle>(engine.clone());
ui.add_theme::<ButtonStyle>(engine);
}
ui.scope_builder(UiBuilder::new().with_class("body"), |ui| {
ui.label("body");
ui.label("central panel");
Panel::left("style_code").show(ui, |ui| {
ui.scope_builder(UiBuilder::new().with_class("panel_left"), |ui| {
ui.label(
"Live editor\n(type color hex to change the color of the dynamic button)",
);
if ui.text_edit_multiline(&mut style_code).changed()
&& let Ok(engine) = ESSEngine::try_parse(&style_code)
{
ui.replace_theme::<WidgetStyle>(engine.clone());
ui.replace_theme::<ButtonStyle>(engine);
}
// Should find a way to detect if a change is made on a plugin
// Maybe by saving the plugin in the system instead and invalidating the cache when it's pulled as mut ?
});
});
ui.scope_builder(UiBuilder::new().with_class("grid"), |ui| {
Frame::new().inner_margin(Margin::same(10)).show(ui, |ui| {
ui.scope_builder(UiBuilder::new().with_class("frame1"), |ui| {
let mut parent = Some(ui.stack());
let mut text = vec![];
let mut i: i32 = 0;
while let Some(p) = parent {
text.push(format!(
"{}{}class : '{}', kind : {:?}",
" ".repeat((2 * 0_i32.max(i - 1) + 1.min(i)) as usize),
if i > 0 { "\\- " } else { "" },
p.classes,
p.kind()
));
i += 1;
parent = p.parent.as_ref();
}
ui.label(format!(
"Current hierarchy (child to root):\n{}",
text.join("\n")
));
})
})
});
ui.add(Button::new("Normal"));
ui.add(Button::new("red").with_class("red"));
ui.add(Button::new("blue").with_class("blue"));
ui.add(Button::new("dynamic in engine A").with_class("dynamic"));
if ui
.add(
Button::new("red/blue")
.with_class_if("red", toggled)
.with_class_if("blue", !toggled),
)
.clicked()
{
toggled = !toggled;
}
});
})
}

View File

@@ -0,0 +1,8 @@
.red {
fill: #f00;
}
.blue {
fill: #00f;
border: 4;
}