2026-05-17 19:09:16 -05:00
|
|
|
use eframe::egui;
|
2026-05-18 22:01:21 -05:00
|
|
|
use egui::{Sense, vec2};
|
2026-05-17 19:09:16 -05:00
|
|
|
|
2026-05-19 00:07:04 -05:00
|
|
|
use crate::font::BitmapFont;
|
|
|
|
|
use crate::font_dialog::{FontDialogState, show as show_font_dialog};
|
|
|
|
|
use crate::game::{ALL_ARCHETYPES, Archetype, FontSpec, Glyph};
|
|
|
|
|
use crate::render::{PALETTE_PANEL_W, paint_glyph};
|
2026-05-17 19:09:16 -05:00
|
|
|
|
|
|
|
|
/// Which tab is active in the editor side panel.
|
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
|
|
|
pub(crate) enum EditorTab {
|
|
|
|
|
/// Archetype + glyph palette for painting cells.
|
|
|
|
|
Palette,
|
2026-05-19 00:07:04 -05:00
|
|
|
/// Board-level properties.
|
2026-05-17 19:09:16 -05:00
|
|
|
Board,
|
|
|
|
|
/// World-level properties (placeholder).
|
|
|
|
|
World,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Transient editor state. Only meaningful when the app is in Edit mode.
|
|
|
|
|
pub(crate) struct EditorState {
|
|
|
|
|
/// The archetype that will be stamped when the user clicks a cell.
|
|
|
|
|
pub(crate) selected: Archetype,
|
2026-05-19 00:07:04 -05:00
|
|
|
/// The glyph (tile index + colors) to stamp. Independent of the archetype's default.
|
2026-05-17 19:09:16 -05:00
|
|
|
pub(crate) glyph: Glyph,
|
|
|
|
|
/// Whether the glyph picker dialog is currently open.
|
|
|
|
|
pub(crate) glyph_picker_open: bool,
|
2026-05-19 00:07:04 -05:00
|
|
|
/// Whether the font picker dialog is currently open.
|
|
|
|
|
pub(crate) font_dialog_open: bool,
|
|
|
|
|
/// In-progress state for the font dialog while it is open.
|
|
|
|
|
pub(crate) font_dialog_state: FontDialogState,
|
2026-05-17 19:09:16 -05:00
|
|
|
/// Which side-panel tab is currently shown.
|
|
|
|
|
pub(crate) tab: EditorTab,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EditorState {
|
2026-05-19 00:07:04 -05:00
|
|
|
/// Creates editor state with `Wall` selected and all dialogs closed.
|
2026-05-17 19:09:16 -05:00
|
|
|
pub(crate) fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
selected: Archetype::Wall,
|
|
|
|
|
glyph: Archetype::Wall.default_glyph(),
|
|
|
|
|
glyph_picker_open: false,
|
2026-05-19 00:07:04 -05:00
|
|
|
font_dialog_open: false,
|
|
|
|
|
font_dialog_state: FontDialogState::from_spec(None),
|
2026-05-17 19:09:16 -05:00
|
|
|
tab: EditorTab::Palette,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Renders the editor side panel (must be called before `CentralPanel`).
|
|
|
|
|
///
|
2026-05-19 00:07:04 -05:00
|
|
|
/// Shows tab bar (Palette / Board / World) and the content for the active tab.
|
|
|
|
|
/// `font_spec` is the board's current font override (may be `None` for default).
|
|
|
|
|
/// `active_font` is the resolved font currently in use for rendering.
|
|
|
|
|
pub(crate) fn show_editor_panel(
|
|
|
|
|
ctx: &egui::Context,
|
|
|
|
|
editor: &mut EditorState,
|
|
|
|
|
font_spec: &mut Option<FontSpec>,
|
|
|
|
|
active_font: &BitmapFont,
|
|
|
|
|
) {
|
|
|
|
|
let tw = active_font.tile_w as f32;
|
|
|
|
|
let th = active_font.tile_h as f32;
|
|
|
|
|
|
2026-05-17 19:09:16 -05:00
|
|
|
egui::SidePanel::right("palette_panel")
|
|
|
|
|
.resizable(true)
|
|
|
|
|
.default_width(PALETTE_PANEL_W)
|
|
|
|
|
.show(ctx, |ui| {
|
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
|
ui.selectable_value(&mut editor.tab, EditorTab::Palette, "Palette");
|
2026-05-19 00:07:04 -05:00
|
|
|
ui.selectable_value(&mut editor.tab, EditorTab::Board, "Board");
|
|
|
|
|
ui.selectable_value(&mut editor.tab, EditorTab::World, "World");
|
2026-05-17 19:09:16 -05:00
|
|
|
});
|
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
|
|
match editor.tab {
|
|
|
|
|
EditorTab::Palette => {
|
|
|
|
|
ui.label("Element");
|
|
|
|
|
|
|
|
|
|
// Scrollable archetype list; shows at most 5 rows before scrolling.
|
2026-05-19 00:07:04 -05:00
|
|
|
let row_h = th + ui.spacing().item_spacing.y;
|
2026-05-17 19:09:16 -05:00
|
|
|
egui::ScrollArea::vertical()
|
|
|
|
|
.max_height(row_h * 5.0)
|
|
|
|
|
.id_salt("arch_list")
|
|
|
|
|
.show(ui, |ui| {
|
|
|
|
|
for &archetype in ALL_ARCHETYPES {
|
|
|
|
|
let is_selected = editor.selected == archetype;
|
|
|
|
|
let glyph = archetype.default_glyph();
|
|
|
|
|
ui.horizontal(|ui| {
|
2026-05-19 00:07:04 -05:00
|
|
|
let (r, cell_resp) =
|
|
|
|
|
ui.allocate_exact_size(vec2(tw, th), Sense::click());
|
|
|
|
|
paint_glyph(ui.painter(), r, &glyph, active_font);
|
2026-05-17 19:09:16 -05:00
|
|
|
let label_resp =
|
|
|
|
|
ui.selectable_label(is_selected, archetype.name());
|
|
|
|
|
if cell_resp.clicked() || label_resp.clicked() {
|
|
|
|
|
editor.selected = archetype;
|
2026-05-19 00:07:04 -05:00
|
|
|
editor.glyph = archetype.default_glyph();
|
2026-05-17 19:09:16 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
|
|
ui.label("Glyph");
|
|
|
|
|
// Paint a preview cell showing the current glyph, sized like a board cell.
|
|
|
|
|
let (cell_rect, glyph_btn) =
|
2026-05-19 00:07:04 -05:00
|
|
|
ui.allocate_exact_size(vec2(tw, th), Sense::click());
|
|
|
|
|
paint_glyph(ui.painter(), cell_rect, &editor.glyph, active_font);
|
2026-05-17 19:09:16 -05:00
|
|
|
if glyph_btn.clicked() {
|
|
|
|
|
editor.glyph_picker_open = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-19 00:07:04 -05:00
|
|
|
|
|
|
|
|
EditorTab::Board => {
|
|
|
|
|
let font_label = match font_spec {
|
|
|
|
|
Some(s) => s.path.clone(),
|
|
|
|
|
None => "(default)".to_owned(),
|
|
|
|
|
};
|
|
|
|
|
ui.label(format!("Font: {font_label}"));
|
|
|
|
|
if ui.button("Font…").clicked() {
|
|
|
|
|
editor.font_dialog_state = FontDialogState::from_spec(font_spec.as_ref());
|
|
|
|
|
editor.font_dialog_open = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 19:09:16 -05:00
|
|
|
EditorTab::World => {}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-19 00:07:04 -05:00
|
|
|
// Font dialog floats outside the side panel; show it here so it renders on top.
|
|
|
|
|
if editor.font_dialog_open {
|
|
|
|
|
show_font_dialog(
|
|
|
|
|
ctx,
|
|
|
|
|
&mut editor.font_dialog_open,
|
|
|
|
|
&mut editor.font_dialog_state,
|
|
|
|
|
font_spec,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|