2026-05-17 19:26:57 -05:00
|
|
|
|
// Layout constants — which value controls which part of the window:
|
|
|
|
|
|
//
|
|
|
|
|
|
// One board cell:
|
|
|
|
|
|
//
|
|
|
|
|
|
// ◄── CELL_W (14 px) ──►
|
|
|
|
|
|
// ╔════════════════════╗ ▲
|
|
|
|
|
|
// ║ @ ║ │ CELL_H (20 px)
|
|
|
|
|
|
// ╚════════════════════╝ ▼
|
|
|
|
|
|
//
|
|
|
|
|
|
// Full window (Edit mode shown; side panel absent in Play mode):
|
|
|
|
|
|
//
|
|
|
|
|
|
// ◄────────────────── DEFAULT_WINDOW_W (840 px) ───────────────────►
|
|
|
|
|
|
// ┌────────────────────────────────────────────────────────────────┐ ▲
|
|
|
|
|
|
// │ File │ Play Edit │ │ MENU_H (24 px)
|
|
|
|
|
|
// ├────────────────────────────────────────────────┬───────────────┤ ▼
|
|
|
|
|
|
// │ PANEL_MARGIN (8 px) on all sides │ PALETTE_ │ ▲
|
|
|
|
|
|
// │ ┌──────────────────────────────────────────┐ │ PANEL_W │ │
|
|
|
|
|
|
// │ │ │ │ (200 px) │ │ DEFAULT_
|
|
|
|
|
|
// │ │ board cells (CELL_W × CELL_H each) │ │ Edit mode │ │ WINDOW_H
|
|
|
|
|
|
// │ │ │ │ only │ │ (524 px)
|
|
|
|
|
|
// │ └──────────────────────────────────────────┘ │ │ │
|
|
|
|
|
|
// │ │ │ ▼
|
|
|
|
|
|
// └────────────────────────────────────────────────┴───────────────┘
|
|
|
|
|
|
//
|
|
|
|
|
|
// MIN_WINDOW_W and MIN_WINDOW_H are derived: 10 cells wide / 5 cells tall plus margins.
|
|
|
|
|
|
|
2026-05-17 19:09:16 -05:00
|
|
|
|
use eframe::egui;
|
|
|
|
|
|
use egui::{Align2, FontId, Pos2, Rect, vec2};
|
|
|
|
|
|
|
|
|
|
|
|
use crate::game::{Board, Glyph, Player};
|
|
|
|
|
|
|
|
|
|
|
|
/// Width of one board cell in pixels.
|
|
|
|
|
|
pub(crate) const CELL_W: f32 = 14.0;
|
|
|
|
|
|
/// Height of one board cell in pixels.
|
|
|
|
|
|
pub(crate) const CELL_H: f32 = 20.0;
|
|
|
|
|
|
|
|
|
|
|
|
/// Approximate height of the egui menu bar in pixels, used for window sizing.
|
|
|
|
|
|
pub(crate) const MENU_H: f32 = 24.0;
|
|
|
|
|
|
/// Default inner margin that egui's CentralPanel adds on all sides, in pixels.
|
|
|
|
|
|
pub(crate) const PANEL_MARGIN: f32 = 8.0;
|
|
|
|
|
|
/// Default width of the editor side panel in pixels. The panel is resizable.
|
|
|
|
|
|
pub(crate) const PALETTE_PANEL_W: f32 = 200.0;
|
|
|
|
|
|
/// Default window width in pixels. Independent of map size.
|
|
|
|
|
|
pub(crate) const DEFAULT_WINDOW_W: f32 = 840.0;
|
|
|
|
|
|
/// Default window height in pixels. Independent of map size.
|
|
|
|
|
|
pub(crate) const DEFAULT_WINDOW_H: f32 = 524.0;
|
|
|
|
|
|
/// Minimum window width: enough room for 10 cells plus margins.
|
|
|
|
|
|
pub(crate) const MIN_WINDOW_W: f32 = CELL_W * 10.0 + 2.0 * PANEL_MARGIN;
|
|
|
|
|
|
/// Minimum window height: enough room for 5 cells plus menu and margins.
|
|
|
|
|
|
pub(crate) const MIN_WINDOW_H: f32 = CELL_H * 5.0 + MENU_H + 2.0 * PANEL_MARGIN;
|
|
|
|
|
|
|
|
|
|
|
|
/// Draws a single cell at grid position `(x, y)` using the given glyph.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Each cell is rendered as two layers:
|
|
|
|
|
|
/// 1. A filled rectangle in `glyph.bg` covering the full cell area.
|
|
|
|
|
|
/// 2. A monospace character (`glyph.ch`) in `glyph.fg`, centered in the cell.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// `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
|
|
|
|
|
|
/// [`CELL_W`] and [`CELL_H`].
|
|
|
|
|
|
pub(crate) fn draw_glyph(painter: &egui::Painter, origin: Pos2, x: usize, y: usize, glyph: &Glyph) {
|
|
|
|
|
|
let tl = origin + vec2(x as f32 * CELL_W, y as f32 * CELL_H);
|
|
|
|
|
|
let rect = Rect::from_min_size(tl, vec2(CELL_W, CELL_H));
|
|
|
|
|
|
let center = rect.center();
|
|
|
|
|
|
painter.rect_filled(rect, 0.0, glyph.bg);
|
|
|
|
|
|
painter.text(center, Align2::CENTER_CENTER, glyph.ch, FontId::monospace(CELL_H), glyph.fg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Draws all board cells then the player overlay at `origin`.
|
|
|
|
|
|
pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board) {
|
|
|
|
|
|
for y in 0..board.height {
|
|
|
|
|
|
for x in 0..board.width {
|
|
|
|
|
|
let (glyph, _) = board.get(x, y);
|
|
|
|
|
|
draw_glyph(painter, origin, x, y, glyph);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// The player is not a board cell; it is rendered on top as an overlay.
|
|
|
|
|
|
// This separate draw will be removed once the player becomes a scripted object.
|
|
|
|
|
|
let player_glyph = Glyph::player();
|
|
|
|
|
|
draw_glyph(painter, origin, board.player.x as usize, board.player.y as usize, &player_glyph);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Converts a pixel position to cell coordinates relative to `origin`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Uses floor so positions above or left of `origin` produce negative values —
|
|
|
|
|
|
/// callers must guard `>= 0` before treating the result as a valid cell index.
|
|
|
|
|
|
pub(crate) fn pos_to_cell(origin: Pos2, pos: Pos2) -> (i32, i32) {
|
|
|
|
|
|
let rel = pos - origin;
|
|
|
|
|
|
((rel.x / CELL_W).floor() as i32, (rel.y / CELL_H).floor() as i32)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Computes the pixel position of board cell (0, 0) within `available`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 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
|
|
|
|
|
|
/// at the board edges.
|
|
|
|
|
|
pub(crate) fn board_origin(available: Rect, board_w: usize, board_h: usize, player: Player) -> Pos2 {
|
|
|
|
|
|
let board_px_w = board_w as f32 * CELL_W;
|
|
|
|
|
|
let board_px_h = board_h as f32 * CELL_H;
|
|
|
|
|
|
|
|
|
|
|
|
let origin_x = if board_px_w <= available.width() {
|
|
|
|
|
|
// Board fits: center it horizontally.
|
|
|
|
|
|
available.min.x + (available.width() - board_px_w) / 2.0
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Board overflows: keep player at viewport center, clamp to board edges.
|
|
|
|
|
|
let player_px_x = player.x as f32 * CELL_W + CELL_W / 2.0;
|
|
|
|
|
|
(available.center().x - player_px_x)
|
|
|
|
|
|
.max(available.max.x - board_px_w) // right edge must reach viewport right
|
|
|
|
|
|
.min(available.min.x) // left edge must reach viewport left
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let origin_y = if board_px_h <= available.height() {
|
|
|
|
|
|
available.min.y + (available.height() - board_px_h) / 2.0
|
|
|
|
|
|
} else {
|
|
|
|
|
|
let player_px_y = player.y as f32 * CELL_H + CELL_H / 2.0;
|
|
|
|
|
|
(available.center().y - player_px_y)
|
|
|
|
|
|
.max(available.max.y - board_px_h)
|
|
|
|
|
|
.min(available.min.y)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Pos2::new(origin_x, origin_y)
|
|
|
|
|
|
}
|