zoom factor

This commit is contained in:
2026-05-31 23:51:51 -05:00
parent 067bb9bee0
commit dc5b903741
5 changed files with 44 additions and 17 deletions
+2
View File
@@ -320,6 +320,8 @@ pub struct Board {
pub portals: Vec<PortalDef>, pub portals: Vec<PortalDef>,
/// Optional font override for this board. When `None`, the app default is used. /// Optional font override for this board. When `None`, the app default is used.
pub font: Option<FontSpec>, pub font: Option<FontSpec>,
/// Integer scale factor applied to tiles when rendering this board. 1 = natural size.
pub zoom: u32,
/// Named Rhai scripts available on this board: script name → source text. /// Named Rhai scripts available on this board: script name → source text.
/// ///
/// All script source lives here; [`ObjectDef`]s and [`Board::board_script_name`] /// All script source lives here; [`ObjectDef`]s and [`Board::board_script_name`]
+7
View File
@@ -50,6 +50,9 @@ pub struct MapHeader {
/// Name of the board-level script in the `[scripts]` table, if any. /// Name of the board-level script in the `[scripts]` table, if any.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub board_script_name: Option<String>, pub board_script_name: Option<String>,
/// Integer tile scale factor. 1 = natural size. Omitted from files when 1 (the default).
#[serde(default = "default_zoom", skip_serializing_if = "is_zoom_default")]
pub zoom: u32,
} }
/// The `[font]` section of a map file, specifying a bitmap font for this board. /// The `[font]` section of a map file, specifying a bitmap font for this board.
@@ -150,6 +153,8 @@ pub(crate) struct MapFileObjectEntry {
} }
fn default_true() -> bool { true } fn default_true() -> bool { true }
fn default_zoom() -> u32 { 1 }
fn is_zoom_default(z: &u32) -> bool { *z == 1 }
/// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`]. /// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`].
/// Returns opaque black on any parse failure. /// Returns opaque black on any parse failure.
@@ -282,6 +287,7 @@ impl TryFrom<MapFile> for Board {
objects, objects,
portals: mf.portals, portals: mf.portals,
font, font,
zoom: mf.map.zoom,
scripts: mf.scripts, scripts: mf.scripts,
board_script_name: mf.map.board_script_name, board_script_name: mf.map.board_script_name,
}) })
@@ -399,6 +405,7 @@ impl From<&Board> for MapFile {
height: board.height, height: board.height,
player_start: [board.player.x, board.player.y], player_start: [board.player.x, board.player.y],
board_script_name: board.board_script_name.clone(), board_script_name: board.board_script_name.clone(),
zoom: board.zoom,
}, },
palette, palette,
grid: GridData { content }, grid: GridData { content },
+4
View File
@@ -227,6 +227,10 @@ pub(crate) fn show_editor_panel(
FontDialogState::from_spec(board.font.as_ref()); FontDialogState::from_spec(board.font.as_ref());
editor.font_dialog_open = true; editor.font_dialog_open = true;
} }
ui.separator();
ui.label("Zoom");
ui.add(egui::Slider::new(&mut board.zoom, 1..=8).text("×"));
} }
EditorTab::Scripts => { EditorTab::Scripts => {
+7 -5
View File
@@ -197,8 +197,9 @@ impl eframe::App for App {
// Snapshot the active font for this frame so all rendering uses the same one. // Snapshot the active font for this frame so all rendering uses the same one.
// We re-borrow active_font here since apply_font_spec may have changed board_font. // We re-borrow active_font here since apply_font_spec may have changed board_font.
let font = self.board_font.as_ref().unwrap_or(&self.default_font); let font = self.board_font.as_ref().unwrap_or(&self.default_font);
let tw = font.tile_w as f32; let zoom = self.state.board.zoom;
let th = font.tile_h as f32; let tw = font.tile_w as f32 * zoom as f32;
let th = font.tile_h as f32 * zoom as f32;
// --- Board rendering --- // --- Board rendering ---
// The board is drawn using the egui Painter API (direct 2D drawing). // The board is drawn using the egui Painter API (direct 2D drawing).
@@ -211,7 +212,7 @@ impl eframe::App for App {
// Edit mode: ScrollArea lets the user scroll when the board overflows. // Edit mode: ScrollArea lets the user scroll when the board overflows.
egui::ScrollArea::new([true, true]).show(ui, |ui| { egui::ScrollArea::new([true, true]).show(ui, |ui| {
let (rect, response) = ui.allocate_exact_size(board_px, Sense::click()); let (rect, response) = ui.allocate_exact_size(board_px, Sense::click());
draw_board(ui.painter(), rect.min, &self.state.board, font); draw_board(ui.painter(), rect.min, &self.state.board, font, zoom);
// When the Objects tab is active, overlay the object highlights. // When the Objects tab is active, overlay the object highlights.
if self.editor.tab == EditorTab::Objects { if self.editor.tab == EditorTab::Objects {
@@ -221,6 +222,7 @@ impl eframe::App for App {
&self.state.board, &self.state.board,
font, font,
self.editor.selected_object, self.editor.selected_object,
zoom,
); );
} }
@@ -264,8 +266,8 @@ impl eframe::App for App {
// Play mode: player-centered viewport, no scroll bars. // Play mode: player-centered viewport, no scroll bars.
let available = ui.available_rect_before_wrap(); let available = ui.available_rect_before_wrap();
let player = self.state.board.player; let player = self.state.board.player;
let origin = board_origin(available, board_w, board_h, player, font); let origin = board_origin(available, board_w, board_h, player, font, zoom);
draw_board(ui.painter(), origin, &self.state.board, font); draw_board(ui.painter(), origin, &self.state.board, font, zoom);
} }
}); });
+24 -12
View File
@@ -65,7 +65,7 @@ pub(crate) fn paint_glyph(painter: &egui::Painter, rect: Rect, glyph: &Glyph, fo
/// ///
/// `origin` is the pixel position of cell `(0, 0)` — the top-left corner of /// `origin` is the pixel position of cell `(0, 0)` — the top-left corner of
/// the board within the panel. Cell positions are computed from `origin` using /// the board within the panel. Cell positions are computed from `origin` using
/// the font's tile dimensions. /// the font's tile dimensions multiplied by `zoom`.
pub(crate) fn draw_glyph( pub(crate) fn draw_glyph(
painter: &egui::Painter, painter: &egui::Painter,
origin: Pos2, origin: Pos2,
@@ -73,9 +73,10 @@ pub(crate) fn draw_glyph(
y: usize, y: usize,
glyph: &Glyph, glyph: &Glyph,
font: &BitmapFont, font: &BitmapFont,
zoom: u32,
) { ) {
let tw = font.tile_w as f32; let tw = font.tile_w as f32 * zoom as f32;
let th = font.tile_h as f32; let th = font.tile_h as f32 * zoom as f32;
let tl = origin + vec2(x as f32 * tw, y as f32 * th); let tl = origin + vec2(x as f32 * tw, y as f32 * th);
let rect = Rect::from_min_size(tl, vec2(tw, th)); let rect = Rect::from_min_size(tl, vec2(tw, th));
paint_glyph(painter, rect, glyph, font); paint_glyph(painter, rect, glyph, font);
@@ -85,17 +86,24 @@ pub(crate) fn draw_glyph(
/// ///
/// Rendering order: grid (floor) → objects → player. Objects are drawn on top /// Rendering order: grid (floor) → objects → player. Objects are drawn on top
/// of their grid cell's background, which is revealed if the object moves away. /// of their grid cell's background, which is revealed if the object moves away.
pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board, font: &BitmapFont) { /// `zoom` scales each tile by an integer factor.
pub(crate) fn draw_board(
painter: &egui::Painter,
origin: Pos2,
board: &Board,
font: &BitmapFont,
zoom: u32,
) {
// Pass 1: grid floor. // Pass 1: grid floor.
for y in 0..board.height { for y in 0..board.height {
for x in 0..board.width { for x in 0..board.width {
let (glyph, _) = board.get(x, y); let (glyph, _) = board.get(x, y);
draw_glyph(painter, origin, x, y, glyph, font); draw_glyph(painter, origin, x, y, glyph, font, zoom);
} }
} }
// Pass 2: objects drawn on top of the floor. // Pass 2: objects drawn on top of the floor.
for obj in &board.objects { for obj in &board.objects {
draw_glyph(painter, origin, obj.x, obj.y, &obj.glyph, font); draw_glyph(painter, origin, obj.x, obj.y, &obj.glyph, font, zoom);
} }
// Pass 3: player overlay. Will be removed once the player becomes a scripted object. // Pass 3: player overlay. Will be removed once the player becomes a scripted object.
let player_glyph = Glyph::player(); let player_glyph = Glyph::player();
@@ -106,6 +114,7 @@ pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board, f
board.player.y as usize, board.player.y as usize,
&player_glyph, &player_glyph,
font, font,
zoom,
); );
} }
@@ -114,16 +123,18 @@ pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board, f
/// Dims the entire board with a semi-transparent fill, then for each /// Dims the entire board with a semi-transparent fill, then for each
/// [`ObjectDef`](crate::game::ObjectDef) redraws that cell at full brightness /// [`ObjectDef`](crate::game::ObjectDef) redraws that cell at full brightness
/// and outlines it in white. The selected object (by index into /// and outlines it in white. The selected object (by index into
/// `board.objects`) gets a yellow outline instead. /// `board.objects`) gets a yellow outline instead. `zoom` must match the zoom
/// used to draw the board.
pub(crate) fn draw_object_overlays( pub(crate) fn draw_object_overlays(
painter: &egui::Painter, painter: &egui::Painter,
origin: Pos2, origin: Pos2,
board: &Board, board: &Board,
font: &BitmapFont, font: &BitmapFont,
selected: Option<usize>, selected: Option<usize>,
zoom: u32,
) { ) {
let tw = font.tile_w as f32; let tw = font.tile_w as f32 * zoom as f32;
let th = font.tile_h as f32; let th = font.tile_h as f32 * zoom as f32;
// Dim the whole board so non-object cells recede visually. // Dim the whole board so non-object cells recede visually.
let board_rect = Rect::from_min_size( let board_rect = Rect::from_min_size(
@@ -166,16 +177,17 @@ pub(crate) fn pos_to_cell(origin: Pos2, pos: Pos2, tile_w: f32, tile_h: f32) ->
/// ///
/// If the board fits along an axis, it is centered. If it overflows, the /// If the board fits along an axis, it is centered. If it overflows, the
/// viewport is centered on the player and clamped so no empty space appears /// viewport is centered on the player and clamped so no empty space appears
/// at the board edges. /// at the board edges. `zoom` scales tile dimensions before computing pixel sizes.
pub(crate) fn board_origin( pub(crate) fn board_origin(
available: Rect, available: Rect,
board_w: usize, board_w: usize,
board_h: usize, board_h: usize,
player: Player, player: Player,
font: &BitmapFont, font: &BitmapFont,
zoom: u32,
) -> Pos2 { ) -> Pos2 {
let tw = font.tile_w as f32; let tw = font.tile_w as f32 * zoom as f32;
let th = font.tile_h as f32; let th = font.tile_h as f32 * zoom as f32;
let board_px_w = board_w as f32 * tw; let board_px_w = board_w as f32 * tw;
let board_px_h = board_h as f32 * th; let board_px_h = board_h as f32 * th;