gled-ui 2.31.2

gled's egui interface, shared by the desktop app and the browser
Documentation
use egui::{
    Button, Color32, Frame, InnerResponse, KeyboardShortcut, Response, Stroke, Ui, UiBuilder,
};
use gled_model::asset::scene::grid::GridLocation;

pub(crate) mod animation_editor;
pub(crate) mod asset;
pub(crate) mod asset_preview;
pub(crate) mod asset_tree;
pub(crate) mod color;
pub(crate) mod curve_editor;
pub(crate) mod effect;
pub(crate) mod file_pick;
pub(crate) mod frame_requests;
pub(crate) mod gled_slider;
pub(crate) mod input;
pub(crate) mod layout_selection;
pub(crate) mod local_action;
pub(crate) mod logo;
pub(crate) mod multiplied_curve;
pub(crate) mod pills;
pub(crate) mod project_edits;
pub(crate) mod scene_effect_editor;
pub(crate) mod scene_instance;
pub mod temperature;
#[cfg(not(debug_assertions))]
#[cfg(not(target_arch = "wasm32"))]
pub mod update_check;
pub mod window_common;
pub(crate) mod windows;

pub(crate) static FRAME_STROKE: Stroke = Stroke {
    width: 0.3,
    color: Color32::WHITE,
};

pub(crate) trait ChangeButton {
    fn change_button(&mut self, ui: &mut Ui) -> bool;
}

impl ChangeButton for f32 {
    fn change_button(&mut self, ui: &mut Ui) -> bool {
        let mut changed = false;

        let response = ui.add(egui::Slider::new(self, 0.0..=1.0));
        if response.changed() {
            changed = true;
        }

        changed
    }
}

pub(crate) trait OverwriteChangeButton: Default + ChangeButton {
    const NAME: &'static str;
}

// for overwrites
impl<T: OverwriteChangeButton> ChangeButton for Option<T> {
    fn change_button(&mut self, ui: &mut Ui) -> bool {
        let mut changed = false;

        let mut overwrite = self.is_some();
        ui.checkbox(&mut overwrite, format!("Overwrite {}", T::NAME));
        if self.is_none() && overwrite {
            *self = Some(T::default());
            changed = true;
        } else if self.is_some() && !overwrite {
            *self = None;
            changed = true;
        }
        if let Some(asset_id) = self {
            ui.vertical_centered_justified(|ui| {
                changed |= asset_id.change_button(ui);
            });
        }

        changed
    }
}

/// Width for the contents of each of `cells` frames side by side in a
/// horizontal row, each with `margin` on both sides, so that the row fills
/// `ui` and no more. A wider row makes a window that sizes itself to its
/// contents, as tool windows do in the browser, a little wider every frame.
pub(crate) fn row_cell_width(ui: &Ui, cells: usize, margin: f32) -> f32 {
    let cells = cells.max(1) as f32;
    let spacing = ui.spacing().item_spacing.x * (cells - 1.0);
    ((ui.available_width() - spacing) / cells - 2.0 * margin)
        .floor()
        .max(0.0)
}

pub(crate) fn scoped_frame<T>(
    ui: &mut Ui,
    ui_builder: UiBuilder,
    frame: Frame,
    add_contents: impl FnOnce(&mut Ui) -> T,
) -> InnerResponse<T> {
    ui.scope_builder(ui_builder, |ui| {
        frame
            .show(ui, |ui| {
                ui.take_available_space();
                add_contents(ui)
            })
            .inner
    })
}

#[derive(Debug)]
pub(crate) struct ContextMenuAction {
    pub(crate) keyboard_shortcut: KeyboardShortcut,
    pub(crate) description: String,
    pub(crate) ctx_action: fn(GridLocation),
    pub(crate) key_action: fn(),
}

#[derive(Debug, Default)]
pub(crate) struct ContextMenuBuilder {
    actions: Vec<ContextMenuAction>,
}

impl ContextMenuBuilder {
    pub(crate) fn add_action(mut self, action: ContextMenuAction) -> Self {
        self.actions.push(action);
        self
    }
    pub(crate) fn show(self, response: &mut Response, location: GridLocation) {
        response.context_menu(|ctx_menu_ui| {
            for action in &self.actions {
                let context_button = Button::new(action.description.to_string())
                    .shortcut_text(ctx_menu_ui.ctx().format_shortcut(&action.keyboard_shortcut));
                if ctx_menu_ui.add(context_button).clicked() {
                    (action.ctx_action)(location);
                    return;
                }
            }
        });
        for action in &self.actions {
            if !response.ctx.egui_wants_keyboard_input()
                && response
                    .ctx
                    .input_mut(|i| i.consume_shortcut(&action.keyboard_shortcut))
            {
                (action.key_action)();
            }
        }
    }
}