nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use super::config::{ThemeState, rgba, to_rgba};

pub fn render_theme_editor_window(
    ui_context: &egui::Context,
    theme_state: &mut ThemeState,
) -> bool {
    if !theme_state.show_theme_editor {
        return false;
    }

    let mut changed = false;
    let mut open = theme_state.show_theme_editor;

    egui::Window::new("Theme Editor")
        .open(&mut open)
        .default_width(340.0)
        .resizable(true)
        .show(ui_context, |ui| {
            egui::ScrollArea::vertical().show(ui, |ui| {
                changed = render_theme_editor(ui, theme_state);
            });
        });

    theme_state.show_theme_editor = open;
    changed
}

pub fn render_theme_editor(ui: &mut egui::Ui, theme_state: &mut ThemeState) -> bool {
    let mut changed = false;

    let current_label = theme_state
        .selected_preset_index
        .map(|index| theme_state.presets[index].name.clone())
        .unwrap_or_else(|| "Custom".to_string());

    let presets = &theme_state.presets;
    let preset = &mut theme_state.selected_preset_index;
    let preview = &mut theme_state.preview_theme_index;
    let config = &mut theme_state.current_config;

    ui.horizontal(|ui| {
        ui.label("Preset:");
        let combo_response = egui::ComboBox::from_id_salt("theme_preset_selector")
            .selected_text(&current_label)
            .show_ui(ui, |ui| {
                for (index, theme_preset) in presets.iter().enumerate() {
                    let response = ui.selectable_label(*preset == Some(index), &theme_preset.name);
                    if response.hovered() {
                        *preview = Some(index);
                    }
                    if response.clicked() {
                        *config = presets[index].clone();
                        *preset = Some(index);
                        *preview = None;
                        changed = true;
                    }
                }
            });
        if (combo_response.response.changed()
            || !combo_response.response.is_pointer_button_down_on())
            && combo_response.inner.is_none()
        {
            *preview = None;
        }
    });

    ui.add_space(8.0);

    ui.horizontal(|ui| {
        ui.label("Dark mode:");
        if ui.checkbox(&mut config.dark_mode, "").changed() {
            *preset = None;
            changed = true;
        }
    });

    ui.add_space(8.0);

    macro_rules! override_section {
        ($ui:expr, $label:expr, $preset:expr, $changed:expr, $( ($kind:ident, $field_label:expr, $field:expr) ),+ $(,)?) => {
            egui::CollapsingHeader::new($label)
                .default_open(false)
                .show($ui, |ui| {
                    $(
                        $changed |= override_section!(@render $kind, ui, $field_label, $field, $preset);
                    )+
                });
        };
        (@render $_kind:ident, $ui:expr, $label:expr, $field:expr, $preset:expr) => {
            render_override($ui, $label, $field, $preset)
        };
    }

    override_section!(
        ui,
        "Global Colors",
        preset,
        changed,
        (color, "Text Override", &mut config.override_text_color),
        (color, "Hyperlink", &mut config.hyperlink_color),
        (color, "Faint Background", &mut config.faint_bg_color),
        (color, "Extreme Background", &mut config.extreme_bg_color),
        (color, "Code Background", &mut config.code_bg_color),
        (color, "Warning", &mut config.warn_fg_color),
        (color, "Error", &mut config.error_fg_color),
        (color, "Window Fill", &mut config.window_fill),
        (color, "Panel Fill", &mut config.panel_fill),
    );

    override_section!(
        ui,
        "Window",
        preset,
        changed,
        (color, "Stroke Color", &mut config.window_stroke_color),
        (f32, "Stroke Width", &mut config.window_stroke_width),
        (f32, "Corner Radius", &mut config.window_corner_radius),
        (color, "Shadow Color", &mut config.window_shadow_color),
        (f32, "Shadow Size", &mut config.window_shadow_size),
        (f32, "Popup Shadow Size", &mut config.popup_shadow_size),
    );

    override_section!(
        ui,
        "Selection",
        preset,
        changed,
        (color, "Background", &mut config.selection_bg_fill),
        (color, "Stroke Color", &mut config.selection_stroke_color),
        (f32, "Stroke Width", &mut config.selection_stroke_width),
    );

    override_section!(
        ui,
        "Non-Interactive",
        preset,
        changed,
        (color, "Background", &mut config.noninteractive_bg_fill),
        (
            color,
            "Weak Background",
            &mut config.noninteractive_weak_bg_fill
        ),
        (
            color,
            "Border Stroke",
            &mut config.noninteractive_bg_stroke_color
        ),
        (
            f32,
            "Border Width",
            &mut config.noninteractive_bg_stroke_width
        ),
        (
            f32,
            "Corner Radius",
            &mut config.noninteractive_corner_radius
        ),
        (
            color,
            "Text Stroke",
            &mut config.noninteractive_fg_stroke_color
        ),
        (
            f32,
            "Text Width",
            &mut config.noninteractive_fg_stroke_width
        ),
        (f32, "Expansion", &mut config.noninteractive_expansion),
    );

    override_section!(
        ui,
        "Inactive",
        preset,
        changed,
        (color, "Background", &mut config.inactive_bg_fill),
        (color, "Weak Background", &mut config.inactive_weak_bg_fill),
        (color, "Border Stroke", &mut config.inactive_bg_stroke_color),
        (f32, "Border Width", &mut config.inactive_bg_stroke_width),
        (f32, "Corner Radius", &mut config.inactive_corner_radius),
        (color, "Text Stroke", &mut config.inactive_fg_stroke_color),
        (f32, "Text Width", &mut config.inactive_fg_stroke_width),
        (f32, "Expansion", &mut config.inactive_expansion),
    );

    override_section!(
        ui,
        "Hovered",
        preset,
        changed,
        (color, "Background", &mut config.hovered_bg_fill),
        (color, "Weak Background", &mut config.hovered_weak_bg_fill),
        (color, "Border Stroke", &mut config.hovered_bg_stroke_color),
        (f32, "Border Width", &mut config.hovered_bg_stroke_width),
        (f32, "Corner Radius", &mut config.hovered_corner_radius),
        (color, "Text Stroke", &mut config.hovered_fg_stroke_color),
        (f32, "Text Width", &mut config.hovered_fg_stroke_width),
        (f32, "Expansion", &mut config.hovered_expansion),
    );

    override_section!(
        ui,
        "Active",
        preset,
        changed,
        (color, "Background", &mut config.active_bg_fill),
        (color, "Weak Background", &mut config.active_weak_bg_fill),
        (color, "Border Stroke", &mut config.active_bg_stroke_color),
        (f32, "Border Width", &mut config.active_bg_stroke_width),
        (f32, "Corner Radius", &mut config.active_corner_radius),
        (color, "Text Stroke", &mut config.active_fg_stroke_color),
        (f32, "Text Width", &mut config.active_fg_stroke_width),
        (f32, "Expansion", &mut config.active_expansion),
    );

    override_section!(
        ui,
        "Open",
        preset,
        changed,
        (color, "Background", &mut config.open_bg_fill),
        (color, "Weak Background", &mut config.open_weak_bg_fill),
        (color, "Border Stroke", &mut config.open_bg_stroke_color),
        (f32, "Border Width", &mut config.open_bg_stroke_width),
        (f32, "Corner Radius", &mut config.open_corner_radius),
        (color, "Text Stroke", &mut config.open_fg_stroke_color),
        (f32, "Text Width", &mut config.open_fg_stroke_width),
        (f32, "Expansion", &mut config.open_expansion),
    );

    override_section!(
        ui,
        "Miscellaneous",
        preset,
        changed,
        (f32, "Resize Corner Size", &mut config.resize_corner_size),
        (f32, "Text Cursor Width", &mut config.text_cursor_width),
        (f32, "Clip Rect Margin", &mut config.clip_rect_margin),
        (bool, "Button Frame", &mut config.button_frame),
        (
            bool,
            "Collapsing Header Frame",
            &mut config.collapsing_header_frame
        ),
        (bool, "Indent Left Vline", &mut config.indent_has_left_vline),
        (bool, "Striped", &mut config.striped),
        (
            bool,
            "Slider Trailing Fill",
            &mut config.slider_trailing_fill
        ),
    );

    changed
}

trait OverrideValue: Copy {
    fn render_editor(ui: &mut egui::Ui, value: &mut Self) -> bool;
    fn render_placeholder(ui: &mut egui::Ui);
    fn default_value() -> Self;
}

impl OverrideValue for [u8; 4] {
    fn render_editor(ui: &mut egui::Ui, value: &mut Self) -> bool {
        let mut color = rgba(*value);
        if ui.color_edit_button_srgba(&mut color).changed() {
            *value = to_rgba(color);
            true
        } else {
            false
        }
    }

    fn render_placeholder(ui: &mut egui::Ui) {
        let placeholder = egui::Color32::from_gray(80);
        let (rect, _) = ui.allocate_exact_size(egui::vec2(20.0, 20.0), egui::Sense::hover());
        ui.painter().rect_stroke(
            rect,
            egui::CornerRadius::same(2),
            egui::Stroke::new(1.0, placeholder),
            egui::StrokeKind::Inside,
        );
    }

    fn default_value() -> Self {
        [128, 128, 128, 255]
    }
}

impl OverrideValue for f32 {
    fn render_editor(ui: &mut egui::Ui, value: &mut Self) -> bool {
        ui.add(egui::DragValue::new(value).speed(0.1).range(0.0..=100.0))
            .changed()
    }

    fn render_placeholder(ui: &mut egui::Ui) {
        ui.add_space(20.0);
    }

    fn default_value() -> Self {
        1.0
    }
}

impl OverrideValue for bool {
    fn render_editor(ui: &mut egui::Ui, value: &mut Self) -> bool {
        ui.checkbox(value, "").changed()
    }

    fn render_placeholder(ui: &mut egui::Ui) {
        ui.add_space(20.0);
    }

    fn default_value() -> Self {
        true
    }
}

fn render_override<T: OverrideValue>(
    ui: &mut egui::Ui,
    label: &str,
    value: &mut Option<T>,
    selected_preset_index: &mut Option<usize>,
) -> bool {
    let mut changed = false;

    ui.horizontal(|ui| {
        if let Some(current) = value.as_mut() {
            if T::render_editor(ui, current) {
                *selected_preset_index = None;
                changed = true;
            }
        } else {
            T::render_placeholder(ui);
        }

        ui.label(label);

        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
            if value.is_some() {
                if ui.small_button("Reset").clicked() {
                    *value = None;
                    *selected_preset_index = None;
                    changed = true;
                }
            } else if ui.small_button("Set").clicked() {
                *value = Some(T::default_value());
                *selected_preset_index = None;
                changed = true;
            }
        });
    });

    changed
}