2026-05-19 00:07:04 -05:00
|
|
|
|
use eframe::egui;
|
|
|
|
|
|
use egui::{Color32, DragValue, Rect, Stroke, vec2};
|
|
|
|
|
|
|
|
|
|
|
|
use crate::font::BitmapFont;
|
2026-05-30 20:20:09 -05:00
|
|
|
|
use kiln_core::game::FontSpec;
|
2026-05-19 00:07:04 -05:00
|
|
|
|
|
|
|
|
|
|
/// State for the font-picker dialog.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Holds the in-progress edit of font path and tile dimensions, and a loaded
|
|
|
|
|
|
/// preview font so the user can see the tilesheet before applying.
|
|
|
|
|
|
pub(crate) struct FontDialogState {
|
|
|
|
|
|
/// Path string as edited by the user (may differ from the applied font).
|
|
|
|
|
|
pub path: String,
|
|
|
|
|
|
/// Tile width being edited.
|
|
|
|
|
|
pub tile_w: u32,
|
|
|
|
|
|
/// Tile height being edited.
|
|
|
|
|
|
pub tile_h: u32,
|
|
|
|
|
|
/// Loaded preview of the current path+dimensions, if valid.
|
|
|
|
|
|
pub preview: Option<BitmapFont>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl FontDialogState {
|
|
|
|
|
|
/// Creates dialog state pre-populated from an existing [`FontSpec`], or
|
|
|
|
|
|
/// defaults (8×8, empty path) when there is no board-level font.
|
|
|
|
|
|
pub(crate) fn from_spec(spec: Option<&FontSpec>) -> Self {
|
|
|
|
|
|
match spec {
|
|
|
|
|
|
Some(s) => Self {
|
|
|
|
|
|
path: s.path.clone(),
|
|
|
|
|
|
tile_w: s.tile_w,
|
|
|
|
|
|
tile_h: s.tile_h,
|
|
|
|
|
|
preview: None,
|
|
|
|
|
|
},
|
|
|
|
|
|
None => Self {
|
|
|
|
|
|
path: String::new(),
|
|
|
|
|
|
tile_w: 8,
|
|
|
|
|
|
tile_h: 8,
|
|
|
|
|
|
preview: None,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Renders the floating font-picker dialog.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Lets the user choose a PNG file, set tile dimensions, preview the tilesheet
|
|
|
|
|
|
/// with grid lines, and apply the selection. When the user clicks "Apply",
|
|
|
|
|
|
/// `font_spec` is written and `open` is set to `false`.
|
|
|
|
|
|
pub(crate) fn show(
|
|
|
|
|
|
ctx: &egui::Context,
|
|
|
|
|
|
open: &mut bool,
|
|
|
|
|
|
state: &mut FontDialogState,
|
|
|
|
|
|
font_spec: &mut Option<FontSpec>,
|
|
|
|
|
|
) {
|
|
|
|
|
|
let mut should_close = false;
|
|
|
|
|
|
|
|
|
|
|
|
egui::Window::new("Font")
|
|
|
|
|
|
.collapsible(false)
|
|
|
|
|
|
.resizable(true)
|
|
|
|
|
|
.open(open)
|
|
|
|
|
|
.show(ctx, |ui| {
|
|
|
|
|
|
// --- File chooser ---
|
|
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
|
|
ui.label("File:");
|
|
|
|
|
|
ui.text_edit_singleline(&mut state.path);
|
|
|
|
|
|
if ui.button("Browse…").clicked() {
|
|
|
|
|
|
if let Some(path) = rfd::FileDialog::new()
|
|
|
|
|
|
.add_filter("PNG image", &["png"])
|
|
|
|
|
|
.pick_file()
|
|
|
|
|
|
{
|
|
|
|
|
|
state.path = path.to_string_lossy().into_owned();
|
|
|
|
|
|
state.preview = None; // reload on next reload click
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// --- Tile dimensions ---
|
|
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
|
|
ui.label("Tile W:");
|
|
|
|
|
|
if ui
|
|
|
|
|
|
.add(DragValue::new(&mut state.tile_w).range(1..=64))
|
|
|
|
|
|
.changed()
|
|
|
|
|
|
{
|
|
|
|
|
|
state.preview = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
ui.label("Tile H:");
|
|
|
|
|
|
if ui
|
|
|
|
|
|
.add(DragValue::new(&mut state.tile_h).range(1..=64))
|
|
|
|
|
|
.changed()
|
|
|
|
|
|
{
|
|
|
|
|
|
state.preview = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ui.button("Load").clicked() {
|
|
|
|
|
|
state.preview = None;
|
|
|
|
|
|
if !state.path.is_empty() {
|
|
|
|
|
|
match BitmapFont::load(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
std::path::Path::new(&state.path),
|
|
|
|
|
|
state.tile_w,
|
|
|
|
|
|
state.tile_h,
|
|
|
|
|
|
) {
|
|
|
|
|
|
Ok(f) => state.preview = Some(f),
|
|
|
|
|
|
Err(e) => eprintln!("font load error: {e}"),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
|
|
|
|
// --- Preview ---
|
|
|
|
|
|
if let Some(font) = &state.preview {
|
|
|
|
|
|
let tw = font.tile_w as f32;
|
|
|
|
|
|
let th = font.tile_h as f32;
|
|
|
|
|
|
let preview_size = vec2(font.img_w() as f32, font.img_h() as f32);
|
|
|
|
|
|
|
|
|
|
|
|
egui::ScrollArea::both()
|
|
|
|
|
|
.max_width(400.0)
|
|
|
|
|
|
.max_height(200.0)
|
|
|
|
|
|
.id_salt("font_preview")
|
|
|
|
|
|
.show(ui, |ui| {
|
|
|
|
|
|
let (rect, _) = ui.allocate_exact_size(preview_size, egui::Sense::hover());
|
|
|
|
|
|
// Draw the full font texture.
|
|
|
|
|
|
ui.painter().image(
|
|
|
|
|
|
font.texture.id(),
|
|
|
|
|
|
rect,
|
|
|
|
|
|
Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
|
|
|
|
|
|
Color32::WHITE,
|
|
|
|
|
|
);
|
|
|
|
|
|
// Overlay grid lines at tile boundaries.
|
|
|
|
|
|
let p = ui.painter();
|
|
|
|
|
|
let cols = font.tile_cols();
|
|
|
|
|
|
let rows = font.img_h() / font.tile_h;
|
|
|
|
|
|
let stroke =
|
|
|
|
|
|
Stroke::new(0.5, Color32::from_rgba_unmultiplied(255, 255, 0, 120));
|
|
|
|
|
|
for c in 0..=cols {
|
|
|
|
|
|
let x = rect.min.x + c as f32 * tw;
|
|
|
|
|
|
p.line_segment(
|
|
|
|
|
|
[egui::pos2(x, rect.min.y), egui::pos2(x, rect.max.y)],
|
|
|
|
|
|
stroke,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
for r in 0..=rows {
|
|
|
|
|
|
let y = rect.min.y + r as f32 * th;
|
|
|
|
|
|
p.line_segment(
|
|
|
|
|
|
[egui::pos2(rect.min.x, y), egui::pos2(rect.max.x, y)],
|
|
|
|
|
|
stroke,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ui.label("(no preview — click Load after setting path and tile size)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
|
|
|
|
// --- Apply / Clear ---
|
|
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
|
|
let can_apply = state.preview.is_some();
|
|
|
|
|
|
if ui
|
|
|
|
|
|
.add_enabled(can_apply, egui::Button::new("Apply"))
|
|
|
|
|
|
.clicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
*font_spec = Some(FontSpec {
|
|
|
|
|
|
path: state.path.clone(),
|
|
|
|
|
|
tile_w: state.tile_w,
|
|
|
|
|
|
tile_h: state.tile_h,
|
|
|
|
|
|
});
|
|
|
|
|
|
should_close = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ui.button("Use default").clicked() {
|
|
|
|
|
|
*font_spec = None;
|
|
|
|
|
|
should_close = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if should_close {
|
|
|
|
|
|
*open = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|