Files
kiln/kiln-egui/src/font_dialog.rs
T
2026-05-30 20:20:09 -05:00

181 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use eframe::egui;
use egui::{Color32, DragValue, Rect, Stroke, vec2};
use crate::font::BitmapFont;
use kiln_core::game::FontSpec;
/// 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;
}
}