03d6337f04
Splits the flat 432-line main.rs into four modules so each file has one clear job: render.rs owns layout constants and drawing primitives, editor.rs owns EditorTab/EditorState and the side panel, glyph_picker.rs owns the floating picker dialog with a decoupled (open, glyph) interface, and main.rs is reduced to AppMode, App, and the frame loop (~150 lines). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
4.3 KiB
Rust
112 lines
4.3 KiB
Rust
use eframe::egui;
|
||
use egui::{Align2, Color32, FontId, Rect, Sense, Stroke, vec2};
|
||
|
||
use crate::game::{Archetype, Glyph};
|
||
use crate::render::{CELL_H, CELL_W, pos_to_cell};
|
||
|
||
/// Renders the floating glyph-picker dialog and updates `glyph` in place.
|
||
///
|
||
/// Shows three sections: board palette (unique glyphs on the current board),
|
||
/// FG/BG color pickers, and a 16×6 printable-ASCII character grid.
|
||
/// Closes when the window's X button is clicked, writing `false` to `open`.
|
||
pub(crate) fn show(
|
||
ctx: &egui::Context,
|
||
open: &mut bool,
|
||
glyph: &mut Glyph,
|
||
board_cells: &[(Glyph, Archetype)],
|
||
) {
|
||
// Collect unique glyphs from the board before the window closure borrows
|
||
// `glyph` mutably, to avoid a borrow conflict with `board_cells`.
|
||
let board_glyphs: Vec<Glyph> = {
|
||
let mut seen: Vec<Glyph> = Vec::new();
|
||
for (g, _) in board_cells {
|
||
if !seen.contains(g) {
|
||
seen.push(*g);
|
||
}
|
||
}
|
||
seen
|
||
};
|
||
|
||
egui::Window::new("Glyph")
|
||
.collapsible(false)
|
||
.resizable(false)
|
||
.open(open)
|
||
.show(ctx, |ui| {
|
||
// ----- Board palette -----
|
||
ui.label("Board palette");
|
||
ui.horizontal_wrapped(|ui| {
|
||
for &g in &board_glyphs {
|
||
let (r, resp) =
|
||
ui.allocate_exact_size(vec2(CELL_W, CELL_H), Sense::click());
|
||
{
|
||
let p = ui.painter();
|
||
p.rect_filled(r, 0.0, g.bg);
|
||
p.text(r.center(), Align2::CENTER_CENTER, g.ch,
|
||
FontId::monospace(CELL_H), g.fg);
|
||
if g == *glyph {
|
||
p.rect_stroke(r, 0.0, Stroke::new(1.0, Color32::WHITE),
|
||
egui::epaint::StrokeKind::Middle);
|
||
}
|
||
}
|
||
if resp.clicked() {
|
||
*glyph = g;
|
||
}
|
||
}
|
||
});
|
||
|
||
ui.separator();
|
||
|
||
// ----- Colors -----
|
||
ui.label("Colors");
|
||
ui.horizontal(|ui| {
|
||
ui.label("FG");
|
||
egui::color_picker::color_edit_button_srgba(
|
||
ui, &mut glyph.fg, egui::color_picker::Alpha::Opaque);
|
||
ui.label("BG");
|
||
egui::color_picker::color_edit_button_srgba(
|
||
ui, &mut glyph.bg, egui::color_picker::Alpha::Opaque);
|
||
});
|
||
|
||
ui.separator();
|
||
|
||
// ----- Character grid -----
|
||
// 16 columns × 6 rows covering printable ASCII 0x20–0x7E.
|
||
ui.label("Character");
|
||
const CHAR_COLS: usize = 16;
|
||
let chars: Vec<char> =
|
||
(0x20u32..=0x7eu32).filter_map(char::from_u32).collect();
|
||
let rows = chars.len().div_ceil(CHAR_COLS);
|
||
let grid_size = vec2(CHAR_COLS as f32 * CELL_W, rows as f32 * CELL_H);
|
||
let (grid_rect, grid_resp) =
|
||
ui.allocate_exact_size(grid_size, Sense::click());
|
||
let fg = glyph.fg;
|
||
let bg = glyph.bg;
|
||
let cur_ch = glyph.ch;
|
||
{
|
||
let p = ui.painter();
|
||
for (i, &ch) in chars.iter().enumerate() {
|
||
let col = i % CHAR_COLS;
|
||
let row = i / CHAR_COLS;
|
||
let tl = grid_rect.min
|
||
+ vec2(col as f32 * CELL_W, row as f32 * CELL_H);
|
||
let cell = Rect::from_min_size(tl, vec2(CELL_W, CELL_H));
|
||
p.rect_filled(cell, 0.0, bg);
|
||
p.text(cell.center(), Align2::CENTER_CENTER, ch,
|
||
FontId::monospace(CELL_H), fg);
|
||
if ch == cur_ch {
|
||
p.rect_stroke(cell, 0.0, Stroke::new(1.0, Color32::WHITE),
|
||
egui::epaint::StrokeKind::Middle);
|
||
}
|
||
}
|
||
}
|
||
if grid_resp.clicked()
|
||
&& let Some(pos) = grid_resp.interact_pointer_pos() {
|
||
let (col, row) = pos_to_cell(grid_rect.min, pos);
|
||
let idx = row as usize * CHAR_COLS + col as usize;
|
||
if let Some(&ch) = chars.get(idx) {
|
||
glyph.ch = ch;
|
||
}
|
||
}
|
||
});
|
||
}
|