nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use crate::mosaic::project::WindowLayout;
use crate::mosaic::widget::{Pane, Widget};
use crate::prelude::*;

use super::{LayoutEvent, Mosaic};

impl<W: Widget<C, M>, C, M> Mosaic<W, C, M> {
    pub fn switch_layout(&mut self, index: usize) -> Option<String> {
        if index >= self.layouts.len() {
            return None;
        }

        if let Some(current_tree) = self.tile_tree.take()
            && let Some(current_layout) = self.layouts.get_mut(self.active_layout_index)
        {
            current_layout.tree = current_tree;
        }

        self.active_layout_index = index;
        if let Some(layout) = self.layouts.get(index) {
            self.tile_tree = Some(layout.tree.clone());
            self.layout_modified = false;
            Some(layout.layout_name.clone())
        } else {
            None
        }
    }

    pub fn create_layout(
        &mut self,
        name: impl Into<String>,
        default_tree: egui_tiles::Tree<Pane<W>>,
    ) {
        self.layouts
            .push(WindowLayout::new(default_tree, name.into()));
    }

    pub fn save_current_layout(&mut self) -> Option<String> {
        if let Some(current_tree) = self.tile_tree.clone()
            && let Some(current_layout) = self.layouts.get_mut(self.active_layout_index)
        {
            current_layout.tree = current_tree;
            self.layout_modified = false;
            Some(current_layout.layout_name.clone())
        } else {
            None
        }
    }

    pub fn delete_current_layout(&mut self) -> Option<String> {
        if self.layouts.len() <= 1 {
            return None;
        }

        let deleted_name = self
            .layouts
            .get(self.active_layout_index)
            .map(|layout| layout.layout_name.clone())
            .unwrap_or_default();

        self.layouts.remove(self.active_layout_index);

        if self.active_layout_index >= self.layouts.len() {
            self.active_layout_index = self.layouts.len().saturating_sub(1);
        }

        if let Some(layout) = self.layouts.get(self.active_layout_index) {
            self.tile_tree = Some(layout.tree.clone());
        }

        self.editing_layout_index = None;
        self.layout_name_edit_buffer.clear();
        self.layout_modified = false;

        Some(deleted_name)
    }

    pub fn rename_layout(&mut self, index: usize, name: String) {
        if let Some(layout) = self.layouts.get_mut(index) {
            layout.layout_name = name;
        }
    }

    pub fn reset_layout(&mut self, default_tree: egui_tiles::Tree<Pane<W>>) {
        self.tile_tree = Some(default_tree.clone());
        if let Some(layout) = self.layouts.get_mut(self.active_layout_index) {
            layout.tree = default_tree;
        }
        self.layout_modified = true;
    }

    pub fn active_layout_name(&self) -> &str {
        self.layouts
            .get(self.active_layout_index)
            .map(|layout| layout.layout_name.as_str())
            .unwrap_or("Default")
    }

    pub fn layout_count(&self) -> usize {
        self.layouts.len()
    }

    pub fn active_layout_index(&self) -> usize {
        self.active_layout_index
    }

    pub fn layouts(&self) -> &[WindowLayout<W>] {
        &self.layouts
    }

    pub fn load_layouts(&mut self, layouts: Vec<WindowLayout<W>>, active_index: usize) {
        self.layouts = layouts;
        self.active_layout_index = active_index.min(self.layouts.len().saturating_sub(1));
        if let Some(layout) = self.layouts.get(self.active_layout_index) {
            self.tile_tree = Some(layout.tree.clone());
        }
        self.editing_layout_index = None;
        self.layout_name_edit_buffer.clear();
        self.layout_modified = false;
    }

    pub fn save_layouts(&mut self) -> (Vec<WindowLayout<W>>, usize) {
        if let Some(current_tree) = self.tile_tree.clone()
            && let Some(current_layout) = self.layouts.get_mut(self.active_layout_index)
        {
            current_layout.tree = current_tree;
        }
        (self.layouts.clone(), self.active_layout_index)
    }

    pub fn render_layout_section(
        &mut self,
        ui: &mut egui::Ui,
        default_tree_fn: impl Fn() -> egui_tiles::Tree<Pane<W>>,
    ) -> Vec<LayoutEvent> {
        let mut events = Vec::new();

        if self.editing_layout_index.is_some() {
            self.render_layout_name_editor(ui, &mut events);
        } else {
            self.render_layout_menu(ui, &mut events, default_tree_fn);
        }

        events
    }

    fn render_layout_name_editor(&mut self, ui: &mut egui::Ui, events: &mut Vec<LayoutEvent>) {
        ui.horizontal(|ui| {
            ui.add(
                egui::TextEdit::singleline(&mut self.layout_name_edit_buffer).desired_width(120.0),
            );

            if ui.button("").clicked() {
                self.editing_layout_index = None;
                self.layout_name_edit_buffer.clear();
            }

            if ui.button("").clicked() {
                if !self.layout_name_edit_buffer.trim().is_empty()
                    && let Some(index) = self.editing_layout_index
                {
                    let new_name = self.layout_name_edit_buffer.trim().to_string();
                    self.rename_layout(index, new_name.clone());
                    events.push(LayoutEvent::Renamed(new_name));
                }
                self.editing_layout_index = None;
                self.layout_name_edit_buffer.clear();
            }
        });
    }

    fn render_layout_menu(
        &mut self,
        ui: &mut egui::Ui,
        events: &mut Vec<LayoutEvent>,
        default_tree_fn: impl Fn() -> egui_tiles::Tree<Pane<W>>,
    ) {
        let light_green = egui::Color32::from_rgb(144, 238, 144);
        let layout_name = self.active_layout_name().to_string();

        let mut switch_to_layout: Option<usize> = None;
        let mut start_editing_layout: Option<(usize, String)> = None;
        let mut create_new = false;
        let mut save_current = false;
        let mut delete_current = false;
        let mut reset_layout = false;

        ui.menu_button(egui::RichText::new("📐 Layout").color(light_green), |ui| {
            if !self.layouts.is_empty() {
                if ui.button("Save Layout").clicked() {
                    save_current = true;
                    ui.close();
                }
                if ui.button("New Layout").clicked() {
                    create_new = true;
                    ui.close();
                }
                if ui.button("Reset Layout").clicked() {
                    reset_layout = true;
                    ui.close();
                }
                let can_delete = self.layouts.len() > 1;
                if ui
                    .add_enabled(can_delete, egui::Button::new("Delete Layout"))
                    .clicked()
                {
                    delete_current = true;
                    ui.close();
                }
                ui.separator();

                for (index, layout) in self.layouts.iter().enumerate() {
                    let is_active = index == self.active_layout_index;
                    ui.horizontal(|ui| {
                        let display_name = if is_active {
                            format!("{}", layout.layout_name)
                        } else {
                            layout.layout_name.clone()
                        };

                        if ui.selectable_label(is_active, &display_name).clicked() && !is_active {
                            switch_to_layout = Some(index);
                        }

                        if ui.small_button("").on_hover_text("Rename").clicked() {
                            start_editing_layout = Some((index, layout.layout_name.clone()));
                        }
                    });
                }
            } else {
                ui.label("No project loaded");
            }
        });

        let layout_display = if self.layout_modified {
            format!("{} *", layout_name)
        } else {
            layout_name.clone()
        };

        let text_color = if ui.visuals().dark_mode {
            egui::Color32::from_rgb(246, 247, 245)
        } else {
            egui::Color32::from_rgb(25, 25, 24)
        };
        ui.label(egui::RichText::new(&layout_display).color(text_color));

        if ui
            .small_button("")
            .on_hover_text("Edit layout name")
            .clicked()
        {
            self.editing_layout_index = Some(self.active_layout_index);
            self.layout_name_edit_buffer = layout_name;
        }

        if let Some(index) = switch_to_layout
            && let Some(name) = self.switch_layout(index)
        {
            events.push(LayoutEvent::Switched(name));
        }
        if let Some((index, name)) = start_editing_layout {
            self.editing_layout_index = Some(index);
            self.layout_name_edit_buffer = name;
        }
        if create_new {
            let new_name = format!("Layout {}", self.layouts.len() + 1);
            self.create_layout(new_name.clone(), default_tree_fn());
            events.push(LayoutEvent::Created(new_name));
        }
        if save_current && let Some(name) = self.save_current_layout() {
            events.push(LayoutEvent::Saved(name));
        }
        if delete_current && let Some(name) = self.delete_current_layout() {
            events.push(LayoutEvent::Deleted(name));
        }
        if reset_layout {
            self.reset_layout(default_tree_fn());
            events.push(LayoutEvent::Reset);
        }
    }
}