object layer
This commit is contained in:
+26
-5
@@ -39,13 +39,15 @@ pub(crate) struct EditorState {
|
|||||||
pub(crate) selected_object: Option<usize>,
|
pub(crate) selected_object: Option<usize>,
|
||||||
/// Whether the glyph picker is open for editing the selected object's glyph.
|
/// Whether the glyph picker is open for editing the selected object's glyph.
|
||||||
pub(crate) object_glyph_picker_open: bool,
|
pub(crate) object_glyph_picker_open: bool,
|
||||||
/// Local copy of the selected object's cell glyph for the picker to edit.
|
/// Local copy of the selected object's glyph for the picker to edit.
|
||||||
///
|
///
|
||||||
/// Kept separate from `board.cells` to avoid a borrow conflict: `glyph_picker::show`
|
/// Kept separate from `board.objects` to avoid a borrow conflict while the
|
||||||
/// takes both `&mut Glyph` (edit target) and `&[(Glyph, Archetype)]` (board palette),
|
/// glyph picker holds `&mut Glyph` and the board is also borrowed. Written
|
||||||
/// and those can't both alias `board.cells`. This copy is written back to the board
|
/// back to `board.objects[i].glyph` each frame while an object is selected.
|
||||||
/// each frame while an object is selected.
|
|
||||||
pub(crate) object_editing_glyph: Glyph,
|
pub(crate) object_editing_glyph: Glyph,
|
||||||
|
/// When `true`, the next board click in the Objects tab places a new object.
|
||||||
|
/// Set by the "Add Object" button; cleared after placement or on tab switch.
|
||||||
|
pub(crate) placing_object: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorState {
|
impl EditorState {
|
||||||
@@ -61,6 +63,7 @@ impl EditorState {
|
|||||||
selected_object: None,
|
selected_object: None,
|
||||||
object_glyph_picker_open: false,
|
object_glyph_picker_open: false,
|
||||||
object_editing_glyph: Glyph::player(), // overwritten on first selection
|
object_editing_glyph: Glyph::player(), // overwritten on first selection
|
||||||
|
placing_object: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,10 +137,28 @@ pub(crate) fn show_editor_panel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorTab::Objects => {
|
EditorTab::Objects => {
|
||||||
|
// "Add Object" toggles placement mode; next board click places a new object.
|
||||||
|
let add_label = if editor.placing_object {
|
||||||
|
"Cancel Add"
|
||||||
|
} else {
|
||||||
|
"Add Object"
|
||||||
|
};
|
||||||
|
if ui.button(add_label).clicked() {
|
||||||
|
editor.placing_object = !editor.placing_object;
|
||||||
|
editor.selected_object = None;
|
||||||
|
}
|
||||||
|
if editor.placing_object {
|
||||||
|
ui.label("Click on the board to place.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
match editor.selected_object {
|
match editor.selected_object {
|
||||||
None => {
|
None => {
|
||||||
|
if !editor.placing_object {
|
||||||
ui.label("Click an object on the board to select it.");
|
ui.label("Click an object on the board to select it.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
// Collect sorted script names before taking a mutable borrow on objects.
|
// Collect sorted script names before taking a mutable borrow on objects.
|
||||||
let mut script_names: Vec<String> =
|
let mut script_names: Vec<String> =
|
||||||
|
|||||||
+24
-10
@@ -178,7 +178,8 @@ impl TryFrom<&str> for Archetype {
|
|||||||
match name {
|
match name {
|
||||||
"empty" => Ok(Archetype::Empty),
|
"empty" => Ok(Archetype::Empty),
|
||||||
"wall" => Ok(Archetype::Wall),
|
"wall" => Ok(Archetype::Wall),
|
||||||
"object" => Ok(Archetype::Object),
|
// "object" is intentionally absent: objects are not valid palette
|
||||||
|
// entries in map files. They live in [[objects]] with their own glyph.
|
||||||
_ => Err(format!("unknown archetype: {name}")),
|
_ => Err(format!("unknown archetype: {name}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,22 +198,28 @@ pub const ALL_ARCHETYPES: &[Archetype] = &[Archetype::Empty, Archetype::Wall, Ar
|
|||||||
/// tile. Objects are parsed from `[[objects]]` entries in `.toml` map files
|
/// tile. Objects are parsed from `[[objects]]` entries in `.toml` map files
|
||||||
/// and stored on [`Board`].
|
/// and stored on [`Board`].
|
||||||
///
|
///
|
||||||
|
/// Objects are rendered as an overlay on top of the board grid — the grid
|
||||||
|
/// cell at `(x, y)` holds the background (floor) that is revealed if the
|
||||||
|
/// object moves away. `glyph` is owned by the object itself and may be
|
||||||
|
/// mutated by its Rhai script at runtime.
|
||||||
|
///
|
||||||
/// Script text lives in [`Board::scripts`]; this struct holds only the name
|
/// Script text lives in [`Board::scripts`]; this struct holds only the name
|
||||||
/// used to look it up. Two `ObjectDef`s with the same `script_name` share
|
/// used to look it up. Two `ObjectDef`s with the same `script_name` share
|
||||||
/// source text but will run with independent Rhai scopes (independent local
|
/// source text but will run with independent Rhai scopes when the scripting
|
||||||
/// variables) when the scripting runtime is wired.
|
/// runtime is wired.
|
||||||
///
|
///
|
||||||
/// **Not yet runtime-wired.** Objects are loaded and stored but their scripts
|
/// **Not yet runtime-wired.** Objects are loaded and stored but their scripts
|
||||||
/// are not yet executed. Scripting dispatch is a future feature.
|
/// are not yet executed. Scripting dispatch is a future feature.
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct ObjectDef {
|
pub struct ObjectDef {
|
||||||
/// Column of this object on the board (0-indexed).
|
/// Column of this object on the board (0-indexed).
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
/// Row of this object on the board (0-indexed).
|
/// Row of this object on the board (0-indexed).
|
||||||
pub y: usize,
|
pub y: usize,
|
||||||
|
/// Visual representation of this object. Owned by the object (not derived
|
||||||
|
/// from the grid cell), so scripts can change tile, fg, and bg at runtime.
|
||||||
|
pub glyph: Glyph,
|
||||||
/// Name of the Rhai script in [`Board::scripts`] that drives this object.
|
/// Name of the Rhai script in [`Board::scripts`] that drives this object.
|
||||||
/// `None` means this object has no script yet.
|
/// `None` means this object has no script yet.
|
||||||
#[serde(default)]
|
|
||||||
pub script_name: Option<String>,
|
pub script_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,14 +323,21 @@ impl Board {
|
|||||||
&mut self.cells[y * self.width + x]
|
&mut self.cells[y * self.width + x]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the archetype at `(x, y)` allows entities to pass through.
|
/// Returns `true` if the cell at `(x, y)` allows entities to pass through.
|
||||||
///
|
///
|
||||||
|
/// Objects block movement before the grid cell archetype is consulted.
|
||||||
/// Panics if `x` or `y` are out of bounds.
|
/// Panics if `x` or `y` are out of bounds.
|
||||||
pub fn is_passable(&self, x: usize, y: usize) -> bool {
|
pub fn is_passable(&self, x: usize, y: usize) -> bool {
|
||||||
let (_, arch) = self.get(x, y);
|
// An object on this cell always blocks (until scripting overrides this).
|
||||||
// Derive passability from the archetype's behavior.
|
if self.object_at(x, y).is_some() {
|
||||||
// Object cells will need special handling here once scripts are wired.
|
return false;
|
||||||
arch.behavior().passable
|
}
|
||||||
|
self.get(x, y).1.behavior().passable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index into [`Board::objects`] of the object at `(x, y)`, if any.
|
||||||
|
pub fn object_at(&self, x: usize, y: usize) -> Option<usize> {
|
||||||
|
self.objects.iter().position(|o| o.x == x && o.y == y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-27
@@ -12,7 +12,7 @@ mod render;
|
|||||||
|
|
||||||
use editor::{EditorState, EditorTab, show_editor_panel};
|
use editor::{EditorState, EditorTab, show_editor_panel};
|
||||||
use font::BitmapFont;
|
use font::BitmapFont;
|
||||||
use game::{Archetype, FontSpec, GameState, ObjectDef};
|
use game::{Archetype, FontSpec, GameState, Glyph, ObjectDef};
|
||||||
use render::{
|
use render::{
|
||||||
DEFAULT_WINDOW_H, DEFAULT_WINDOW_W, MIN_WINDOW_H, MIN_WINDOW_W, board_origin, draw_board,
|
DEFAULT_WINDOW_H, DEFAULT_WINDOW_W, MIN_WINDOW_H, MIN_WINDOW_W, board_origin, draw_board,
|
||||||
draw_object_overlays, pos_to_cell,
|
draw_object_overlays, pos_to_cell,
|
||||||
@@ -234,37 +234,37 @@ impl eframe::App for App {
|
|||||||
let board = &mut self.state.board;
|
let board = &mut self.state.board;
|
||||||
if cx < board.width && cy < board.height {
|
if cx < board.width && cy < board.height {
|
||||||
if self.editor.tab == EditorTab::Objects {
|
if self.editor.tab == EditorTab::Objects {
|
||||||
// Objects tab: clicks select an object rather than paint.
|
if self.editor.placing_object {
|
||||||
let found = board
|
// Placement mode: create a new object here if none exists.
|
||||||
.objects
|
if board.object_at(cx, cy).is_none() {
|
||||||
.iter()
|
|
||||||
.position(|o| o.x == cx && o.y == cy);
|
|
||||||
self.editor.selected_object = found;
|
|
||||||
self.editor.object_glyph_picker_open = false;
|
|
||||||
if let Some(i) = found {
|
|
||||||
// Snapshot the object's cell glyph for the picker.
|
|
||||||
self.editor.object_editing_glyph =
|
|
||||||
board.get(board.objects[i].x, board.objects[i].y).0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Other tabs: paint the clicked cell and keep objects in sync.
|
|
||||||
let old_arch = board.get(cx, cy).1;
|
|
||||||
let arch = self.editor.selected;
|
|
||||||
*board.get_mut(cx, cy) = (self.editor.glyph, arch);
|
|
||||||
if arch == Archetype::Object {
|
|
||||||
// Add an ObjectDef if one doesn't already exist here.
|
|
||||||
if !board.objects.iter().any(|o| o.x == cx && o.y == cy) {
|
|
||||||
board.objects.push(ObjectDef {
|
board.objects.push(ObjectDef {
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
|
glyph: Glyph {
|
||||||
|
tile: Archetype::Object.default_glyph().tile,
|
||||||
|
fg: Archetype::Object.default_glyph().fg,
|
||||||
|
bg: Archetype::Object.default_glyph().bg,
|
||||||
|
},
|
||||||
script_name: None,
|
script_name: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if old_arch == Archetype::Object {
|
self.editor.placing_object = false;
|
||||||
// Overwriting an Object cell: remove its ObjectDef.
|
} else {
|
||||||
board.objects.retain(|o| !(o.x == cx && o.y == cy));
|
// Selection mode: click to select an existing object.
|
||||||
|
let found = board.object_at(cx, cy);
|
||||||
|
self.editor.selected_object = found;
|
||||||
|
self.editor.object_glyph_picker_open = false;
|
||||||
|
if let Some(i) = found {
|
||||||
|
// Snapshot the object's glyph for the picker.
|
||||||
|
self.editor.object_editing_glyph =
|
||||||
|
board.objects[i].glyph;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Other tabs: paint the grid cell (floor only).
|
||||||
|
// Objects on this cell are left untouched — they sit on top.
|
||||||
|
*board.get_mut(cx, cy) = (self.editor.glyph, self.editor.selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,9 +308,7 @@ impl eframe::App for App {
|
|||||||
// Any change the picker made to object_editing_glyph is flushed here.
|
// Any change the picker made to object_editing_glyph is flushed here.
|
||||||
if let Some(i) = self.editor.selected_object {
|
if let Some(i) = self.editor.selected_object {
|
||||||
if i < self.state.board.objects.len() {
|
if i < self.state.board.objects.len() {
|
||||||
let x = self.state.board.objects[i].x;
|
self.state.board.objects[i].glyph = self.editor.object_editing_glyph;
|
||||||
let y = self.state.board.objects[i].y;
|
|
||||||
self.state.board.get_mut(x, y).0 = self.editor.object_editing_glyph;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-5
@@ -26,7 +26,7 @@ pub struct MapFile {
|
|||||||
pub font: Option<FontHeader>,
|
pub font: Option<FontHeader>,
|
||||||
/// Any `[[objects]]` entries. Optional; defaults to empty.
|
/// Any `[[objects]]` entries. Optional; defaults to empty.
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub objects: Vec<ObjectDef>,
|
pub objects: Vec<MapFileObjectEntry>,
|
||||||
/// Any `[[portals]]` entries. Optional; defaults to empty.
|
/// Any `[[portals]]` entries. Optional; defaults to empty.
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub portals: Vec<PortalDef>,
|
pub portals: Vec<PortalDef>,
|
||||||
@@ -121,6 +121,28 @@ pub struct GridData {
|
|||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One `[[objects]]` entry in a map file.
|
||||||
|
///
|
||||||
|
/// Used only for TOML serialization/deserialization. Converted to/from
|
||||||
|
/// [`ObjectDef`] (which holds a [`Glyph`] directly) in the [`TryFrom`] /
|
||||||
|
/// [`From`] impls below.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub(crate) struct MapFileObjectEntry {
|
||||||
|
/// Column of this object on the board (0-indexed).
|
||||||
|
x: usize,
|
||||||
|
/// Row of this object on the board (0-indexed).
|
||||||
|
y: usize,
|
||||||
|
/// Tile index into the board's bitmap font. Accepts integer or single-char string.
|
||||||
|
tile: TileIndex,
|
||||||
|
/// Foreground color as an `"#RRGGBB"` hex string.
|
||||||
|
fg: String,
|
||||||
|
/// Background color as an `"#RRGGBB"` hex string.
|
||||||
|
bg: String,
|
||||||
|
/// Name of the Rhai script in the `[scripts]` table, if any.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
script_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses an `"#RRGGBB"` hex color string into a [`Color32`].
|
/// Parses an `"#RRGGBB"` hex color string into a [`Color32`].
|
||||||
/// Returns black on any parse failure.
|
/// Returns black on any parse failure.
|
||||||
fn parse_color(hex: &str) -> Color32 {
|
fn parse_color(hex: &str) -> Color32 {
|
||||||
@@ -222,6 +244,22 @@ impl TryFrom<MapFile> for Board {
|
|||||||
tile_h: f.tile_h,
|
tile_h: f.tile_h,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Convert MapFileObjectEntry → ObjectDef, resolving tile index and color strings.
|
||||||
|
let objects = mf
|
||||||
|
.objects
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| ObjectDef {
|
||||||
|
x: e.x,
|
||||||
|
y: e.y,
|
||||||
|
glyph: Glyph {
|
||||||
|
tile: e.tile.into_u32(),
|
||||||
|
fg: parse_color(&e.fg),
|
||||||
|
bg: parse_color(&e.bg),
|
||||||
|
},
|
||||||
|
script_name: e.script_name,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Board {
|
Ok(Board {
|
||||||
name: mf.map.name,
|
name: mf.map.name,
|
||||||
width: w,
|
width: w,
|
||||||
@@ -231,7 +269,7 @@ impl TryFrom<MapFile> for Board {
|
|||||||
x: mf.map.player_start[0],
|
x: mf.map.player_start[0],
|
||||||
y: mf.map.player_start[1],
|
y: mf.map.player_start[1],
|
||||||
},
|
},
|
||||||
objects: mf.objects,
|
objects,
|
||||||
portals: mf.portals,
|
portals: mf.portals,
|
||||||
font,
|
font,
|
||||||
scripts: mf.scripts,
|
scripts: mf.scripts,
|
||||||
@@ -317,13 +355,16 @@ impl From<&Board> for MapFile {
|
|||||||
tile_h: f.tile_h,
|
tile_h: f.tile_h,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reconstruct objects as plain structs (ObjectDef/PortalDef don't implement Clone).
|
// Convert ObjectDef → MapFileObjectEntry, encoding the glyph as TOML-compatible fields.
|
||||||
let objects = board
|
let objects: Vec<MapFileObjectEntry> = board
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| ObjectDef {
|
.map(|o| MapFileObjectEntry {
|
||||||
x: o.x,
|
x: o.x,
|
||||||
y: o.y,
|
y: o.y,
|
||||||
|
tile: TileIndex::Num(o.glyph.tile),
|
||||||
|
fg: color_to_hex(o.glyph.fg),
|
||||||
|
bg: color_to_hex(o.glyph.bg),
|
||||||
script_name: o.script_name.clone(),
|
script_name: o.script_name.clone(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -429,4 +470,78 @@ content = """
|
|||||||
assert!(msg.contains("3 characters"), "expected col count in error, got: {msg}");
|
assert!(msg.contains("3 characters"), "expected col count in error, got: {msg}");
|
||||||
assert!(msg.contains("width = 4"), "expected declared width in error, got: {msg}");
|
assert!(msg.contains("width = 4"), "expected declared width in error, got: {msg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_archetype_in_palette_produces_error_block() {
|
||||||
|
// "object" is no longer a valid palette archetype; it should produce ErrorBlock.
|
||||||
|
let toml = r##"
|
||||||
|
[map]
|
||||||
|
name = "Test"
|
||||||
|
width = 1
|
||||||
|
height = 1
|
||||||
|
player_start = [0, 0]
|
||||||
|
|
||||||
|
[palette]
|
||||||
|
"O" = { archetype = "object", tile = 64, fg = "#00FFFF", bg = "#000000" }
|
||||||
|
|
||||||
|
[grid]
|
||||||
|
content = """
|
||||||
|
O
|
||||||
|
"""
|
||||||
|
"##;
|
||||||
|
let mf: MapFile = toml::from_str(toml).unwrap();
|
||||||
|
let board = Board::try_from(mf).unwrap();
|
||||||
|
let (_, arch) = board.get(0, 0);
|
||||||
|
assert_eq!(*arch, crate::game::Archetype::ErrorBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_glyph_round_trips_through_toml() {
|
||||||
|
// Objects must survive a save→load cycle with their glyph intact.
|
||||||
|
use eframe::egui::Color32;
|
||||||
|
|
||||||
|
let toml = r##"
|
||||||
|
[map]
|
||||||
|
name = "Test"
|
||||||
|
width = 3
|
||||||
|
height = 3
|
||||||
|
player_start = [0, 0]
|
||||||
|
|
||||||
|
[palette]
|
||||||
|
"." = { archetype = "empty", tile = 46, fg = "#000000", bg = "#000000" }
|
||||||
|
|
||||||
|
[grid]
|
||||||
|
content = """
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[objects]]
|
||||||
|
x = 1
|
||||||
|
y = 1
|
||||||
|
tile = 64
|
||||||
|
fg = "#00FFFF"
|
||||||
|
bg = "#000000"
|
||||||
|
"##;
|
||||||
|
let mf: MapFile = toml::from_str(toml).unwrap();
|
||||||
|
let board = Board::try_from(mf).unwrap();
|
||||||
|
assert_eq!(board.objects.len(), 1);
|
||||||
|
let obj = &board.objects[0];
|
||||||
|
assert_eq!(obj.x, 1);
|
||||||
|
assert_eq!(obj.y, 1);
|
||||||
|
assert_eq!(obj.glyph.tile, 64);
|
||||||
|
assert_eq!(obj.glyph.fg, Color32::from_rgb(0x00, 0xFF, 0xFF));
|
||||||
|
assert_eq!(obj.glyph.bg, Color32::BLACK);
|
||||||
|
|
||||||
|
// Save back to TOML and reload; glyph must survive.
|
||||||
|
let map_file = MapFile::from(&board);
|
||||||
|
let toml_out = toml::to_string_pretty(&map_file).unwrap();
|
||||||
|
let mf2: MapFile = toml::from_str(&toml_out).unwrap();
|
||||||
|
let board2 = Board::try_from(mf2).unwrap();
|
||||||
|
let obj2 = &board2.objects[0];
|
||||||
|
assert_eq!(obj2.glyph.tile, obj.glyph.tile);
|
||||||
|
assert_eq!(obj2.glyph.fg, obj.glyph.fg);
|
||||||
|
assert_eq!(obj2.glyph.bg, obj.glyph.bg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -76,16 +76,23 @@ pub(crate) fn draw_glyph(
|
|||||||
paint_glyph(painter, rect, glyph, font);
|
paint_glyph(painter, rect, glyph, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws all board cells then the player overlay at `origin`.
|
/// Draws all board cells, then the object overlay, then the player at `origin`.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board, font: &BitmapFont) {
|
pub(crate) fn draw_board(painter: &egui::Painter, origin: Pos2, board: &Board, font: &BitmapFont) {
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The player is not a board cell; it is rendered on top as an overlay.
|
// Pass 2: objects drawn on top of the floor.
|
||||||
// This separate draw will be removed once the player becomes a scripted object.
|
for obj in &board.objects {
|
||||||
|
draw_glyph(painter, origin, obj.x, obj.y, &obj.glyph, font);
|
||||||
|
}
|
||||||
|
// Pass 3: player overlay. Will be removed once the player becomes a scripted object.
|
||||||
let player_glyph = Glyph::player();
|
let player_glyph = Glyph::player();
|
||||||
draw_glyph(
|
draw_glyph(
|
||||||
painter,
|
painter,
|
||||||
@@ -126,8 +133,7 @@ pub(crate) fn draw_object_overlays(
|
|||||||
let rect = Rect::from_min_size(tl, vec2(tw, th));
|
let rect = Rect::from_min_size(tl, vec2(tw, th));
|
||||||
|
|
||||||
// Redraw on top of the dim overlay so this cell remains bright.
|
// Redraw on top of the dim overlay so this cell remains bright.
|
||||||
let (glyph, _) = board.get(obj.x, obj.y);
|
paint_glyph(painter, rect, &obj.glyph, font);
|
||||||
paint_glyph(painter, rect, glyph, font);
|
|
||||||
|
|
||||||
// Yellow outline for the selected object, white for all others.
|
// Yellow outline for the selected object, white for all others.
|
||||||
let outline = if selected == Some(i) {
|
let outline = if selected == Some(i) {
|
||||||
|
|||||||
Reference in New Issue
Block a user