gled-ui 2.32.1

gled's egui interface, shared by the desktop app and the browser
Documentation
use egui::{CentralPanel, Response, Ui, Widget};
use std::collections::BTreeMap;

pub(super) struct SettingsMenu<'a, S> {
    selected_submenu: &'a mut String,
    edit_state: &'a mut S,
    submenus: BTreeMap<String, Box<dyn FnOnce(&mut Ui, &mut S) + 'a>>,
}

impl<'a, S> SettingsMenu<'a, S> {
    pub(super) fn new(edit_state: &'a mut S, selected_submenu: &'a mut String) -> Self {
        Self {
            selected_submenu,
            edit_state,
            submenus: BTreeMap::default(),
        }
    }
    pub(super) fn add_submenu(
        mut self,
        submenu: String,
        ui_callback: impl FnOnce(&mut Ui, &mut S) + 'a,
    ) -> Self {
        self.submenus.insert(submenu, Box::new(ui_callback));
        self
    }
}

impl<'a, S> Widget for SettingsMenu<'a, S> {
    fn ui(mut self, ui: &mut Ui) -> Response {
        debug_assert!(!self.submenus.is_empty());
        egui::Panel::left("artnet_input_config")
            .resizable(true)
            .size_range(100.0..=200.0)
            .default_size(150.0)
            .show(ui, |ui| {
                ui.take_available_space();
                self.submenus.keys().for_each(|key| {
                    ui.selectable_value(self.selected_submenu, key.clone(), key);
                });
            });
        if !self.submenus.contains_key(self.selected_submenu) {
            *self.selected_submenu = self
                .submenus
                .keys()
                .next()
                .expect("settings menu must have at least one submenu")
                .clone();
        }

        let submenu = self
            .submenus
            .remove(self.selected_submenu)
            .expect("submenu was just confirmed to be present");
        CentralPanel::default()
            .show(ui, |ui| submenu(ui, self.edit_state))
            .response
    }
}