Files
kiln/kiln-egui/src/font_dialog.rs
T

181 lines
6.4 KiB
Rust
Raw Normal View History

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;
}
}