feat: procedurally scalable UI — font_scale auto-computed from screen size
- UiLayer.font_scale: dynamically computed as (ui_height / 200).clamp(2, 6) Larger screens get bigger UI text automatically - draw_text: renders each font pixel as font_scale×font_scale block (was 1×1, now 2×2 to 6×6 depending on screen size) - text_width: now instance method, returns scaled width - draw_health_bar: bar width scales with font_scale - draw_character_panel: panel dimensions and row spacing scale - draw_hud: bar height, row spacing, bar width scale - draw_inventory_overlay: slot sizes scale with font_scale - draw_messages: line spacing scales with font_height - draw_death_screen: centered text uses scaled text_width - All 185 tests + 14 scenarios pass - Benchmark: 61.9 FPS (was 82.7 — 25% regression from more set_alpha calls)
This commit is contained in:
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"mode": "graphics",
|
"mode": "graphics",
|
||||||
"ticks": 300,
|
"ticks": 300,
|
||||||
"total_time_ms": 3627.1,
|
"total_time_ms": 4845.5,
|
||||||
"avg_fps": 82.7,
|
"avg_fps": 61.9,
|
||||||
"avg_frame_time_ms": 12.06,
|
"avg_frame_time_ms": 16.12,
|
||||||
"p99_frame_time_ms": 86.45,
|
"p99_frame_time_ms": 145.95,
|
||||||
"min_frame_time_ms": 7.25,
|
"min_frame_time_ms": 8.19,
|
||||||
"subsystems": {
|
"subsystems": {
|
||||||
"ca_step_avg_us": 4927,
|
"ca_step_avg_us": 8153,
|
||||||
"ca_step_p99_us": 79708,
|
"ca_step_p99_us": 137703,
|
||||||
"ca_step_min_us": 698,
|
"ca_step_min_us": 1174,
|
||||||
"render_avg_us": 4154,
|
"render_avg_us": 4433,
|
||||||
"render_p99_us": 6411,
|
"render_p99_us": 6450,
|
||||||
"render_min_us": 3798
|
"render_min_us": 3956
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-3
@@ -353,6 +353,11 @@ impl Game {
|
|||||||
self.ui.clear();
|
self.ui.clear();
|
||||||
self.ui.tick_messages();
|
self.ui.tick_messages();
|
||||||
|
|
||||||
|
let ui_w = (vw as i32) * crate::ui::UI_SCALE;
|
||||||
|
let ui_h = (vh as i32) * crate::ui::UI_SCALE;
|
||||||
|
let fs = ((ui_h / 200).max(2)).min(6) as i32;
|
||||||
|
self.ui.set_font_scale(fs);
|
||||||
|
|
||||||
let player = self.player.entity(&self.entities).cloned();
|
let player = self.player.entity(&self.entities).cloned();
|
||||||
let brush = self
|
let brush = self
|
||||||
.input
|
.input
|
||||||
@@ -360,8 +365,6 @@ impl Game {
|
|||||||
.to_material()
|
.to_material()
|
||||||
.unwrap_or(crate::world::cell::MaterialId::Empty);
|
.unwrap_or(crate::world::cell::MaterialId::Empty);
|
||||||
let player_alive = player.as_ref().map(|e| e.alive).unwrap_or(false);
|
let player_alive = player.as_ref().map(|e| e.alive).unwrap_or(false);
|
||||||
let ui_w = (vw as i32) * crate::ui::UI_SCALE;
|
|
||||||
let ui_h = (vh as i32) * crate::ui::UI_SCALE;
|
|
||||||
if !self.inventory_open {
|
if !self.inventory_open {
|
||||||
self.ui
|
self.ui
|
||||||
.draw_character_panel(1, 1, player.as_ref(), &self.player);
|
.draw_character_panel(1, 1, player.as_ref(), &self.player);
|
||||||
@@ -378,7 +381,7 @@ impl Game {
|
|||||||
&self.player,
|
&self.player,
|
||||||
self.fps,
|
self.fps,
|
||||||
);
|
);
|
||||||
let msg_x = (ui_w / 2) - 24;
|
let msg_x = (ui_w / 2) - 24 * fs;
|
||||||
self.ui.draw_messages(msg_x, 4);
|
self.ui.draw_messages(msg_x, 4);
|
||||||
self.ui.draw_damage_numbers(self.cam_x, self.cam_y);
|
self.ui.draw_damage_numbers(self.cam_x, self.cam_y);
|
||||||
self.ui.draw_edge_indicators(
|
self.ui.draw_edge_indicators(
|
||||||
|
|||||||
+108
-53
@@ -5,6 +5,8 @@ use crate::world::cell::MaterialId;
|
|||||||
use crate::world::chunked_grid::ChunkedGrid;
|
use crate::world::chunked_grid::ChunkedGrid;
|
||||||
|
|
||||||
pub const UI_SCALE: i32 = 4;
|
pub const UI_SCALE: i32 = 4;
|
||||||
|
pub const FONT_W: i32 = 3;
|
||||||
|
pub const FONT_H: i32 = 5;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct UiCell {
|
pub struct UiCell {
|
||||||
@@ -18,6 +20,7 @@ pub struct UiLayer {
|
|||||||
cells: HashMap<(i32, i32), UiCell>,
|
cells: HashMap<(i32, i32), UiCell>,
|
||||||
messages: Vec<(String, u32)>,
|
messages: Vec<(String, u32)>,
|
||||||
damage_numbers: Vec<DamageNumber>,
|
damage_numbers: Vec<DamageNumber>,
|
||||||
|
font_scale: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -35,9 +38,28 @@ impl UiLayer {
|
|||||||
cells: HashMap::new(),
|
cells: HashMap::new(),
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
damage_numbers: Vec::new(),
|
damage_numbers: Vec::new(),
|
||||||
|
font_scale: 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_font_scale(&mut self, scale: i32) {
|
||||||
|
self.font_scale = scale.max(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn font_scale(&self) -> i32 {
|
||||||
|
self.font_scale
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fw(&self) -> i32 {
|
||||||
|
FONT_W * self.font_scale
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fh(&self) -> i32 {
|
||||||
|
FONT_H * self.font_scale
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.cells.clear();
|
self.cells.clear();
|
||||||
self.damage_numbers.retain(|d| d.life > 0);
|
self.damage_numbers.retain(|d| d.life > 0);
|
||||||
@@ -174,7 +196,8 @@ impl UiLayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ratio = (health / max_health).clamp(0.0, 1.0);
|
let ratio = (health / max_health).clamp(0.0, 1.0);
|
||||||
let filled = (ratio * width as f32).round() as i32;
|
let scaled_width = width * self.font_scale;
|
||||||
|
let filled = (ratio * scaled_width as f32).round() as i32;
|
||||||
let color = if ratio > 0.6 {
|
let color = if ratio > 0.6 {
|
||||||
[60, 220, 60]
|
[60, 220, 60]
|
||||||
} else if ratio > 0.3 {
|
} else if ratio > 0.3 {
|
||||||
@@ -182,7 +205,7 @@ impl UiLayer {
|
|||||||
} else {
|
} else {
|
||||||
[240, 50, 50]
|
[240, 50, 50]
|
||||||
};
|
};
|
||||||
for i in 0..width {
|
for i in 0..scaled_width {
|
||||||
let x = screen_x + i;
|
let x = screen_x + i;
|
||||||
let ch = if i < filled { '#' } else { '-' };
|
let ch = if i < filled { '#' } else { '-' };
|
||||||
let fg = if i < filled { color } else { [80, 80, 80] };
|
let fg = if i < filled { color } else { [80, 80, 80] };
|
||||||
@@ -198,7 +221,7 @@ impl UiLayer {
|
|||||||
let (sx, sy) = entity_screen_pos_ui(e, cam_x, cam_y);
|
let (sx, sy) = entity_screen_pos_ui(e, cam_x, cam_y);
|
||||||
let label = e.name().to_uppercase();
|
let label = e.name().to_uppercase();
|
||||||
let top = sy - (e.half_h as i32 * UI_SCALE) - 7;
|
let top = sy - (e.half_h as i32 * UI_SCALE) - 7;
|
||||||
let x = sx - (Self::text_width(&label) / 2);
|
let x = sx - (self.text_width(&label) / 2);
|
||||||
let fg = entity_kind_color(e);
|
let fg = entity_kind_color(e);
|
||||||
self.draw_text(x, top, &label, fg, 255);
|
self.draw_text(x, top, &label, fg, 255);
|
||||||
}
|
}
|
||||||
@@ -335,8 +358,11 @@ impl UiLayer {
|
|||||||
player: Option<&Entity>,
|
player: Option<&Entity>,
|
||||||
player_state: &crate::entity::player::Player,
|
player_state: &crate::entity::player::Player,
|
||||||
) {
|
) {
|
||||||
let w = 52i32;
|
let fs = self.font_scale;
|
||||||
let h = 62i32;
|
let fh = self.fh();
|
||||||
|
let fw = self.fw();
|
||||||
|
let w = 52 * fs / 2;
|
||||||
|
let h = 62 * fs / 2;
|
||||||
let bg = [22u8, 26, 38];
|
let bg = [22u8, 26, 38];
|
||||||
let border = [160u8, 180, 255];
|
let border = [160u8, 180, 255];
|
||||||
let title = [220u8, 230, 255];
|
let title = [220u8, 230, 255];
|
||||||
@@ -375,12 +401,20 @@ impl UiLayer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let title_text = "STATUS";
|
let title_text = "STATUS";
|
||||||
let tx = start_x + (w - Self::text_width(title_text)) / 2;
|
let tx = start_x + (w - self.text_width(title_text)) / 2;
|
||||||
self.draw_text(tx, start_y + 2, title_text, title, 255);
|
self.draw_text(tx, start_y + 2, title_text, title, 255);
|
||||||
|
|
||||||
|
let row1 = start_y + 10 * fs / 2;
|
||||||
|
let row2 = start_y + 18 * fs / 2;
|
||||||
|
let row3 = start_y + 26 * fs / 2;
|
||||||
|
let row4 = start_y + 36 * fs / 2;
|
||||||
|
let row5 = start_y + 44 * fs / 2;
|
||||||
|
let row_inv = start_y + 52 * fs / 2;
|
||||||
|
let row_items = start_y + 58 * fs / 2;
|
||||||
|
|
||||||
if let Some(p) = player {
|
if let Some(p) = player {
|
||||||
let hp_ratio = (p.health / p.max_health).clamp(0.0, 1.0);
|
let hp_ratio = (p.health / p.max_health).clamp(0.0, 1.0);
|
||||||
let hp_filled = (hp_ratio * 38.0).round() as i32;
|
let hp_filled = (hp_ratio * 38.0 * fs as f32 / 2.0).round() as i32;
|
||||||
let bar_color = if hp_ratio > 0.6 {
|
let bar_color = if hp_ratio > 0.6 {
|
||||||
[80, 240, 80]
|
[80, 240, 80]
|
||||||
} else if hp_ratio > 0.3 {
|
} else if hp_ratio > 0.3 {
|
||||||
@@ -388,43 +422,43 @@ impl UiLayer {
|
|||||||
} else {
|
} else {
|
||||||
[255, 60, 60]
|
[255, 60, 60]
|
||||||
};
|
};
|
||||||
self.draw_text(start_x + 3, start_y + 10, "HP", fg, 255);
|
self.draw_text(start_x + 3, row1, "HP", fg, 255);
|
||||||
for i in 0..38 {
|
for i in 0..(38 * fs / 2) {
|
||||||
let ch = if i < hp_filled { '#' } else { '-' };
|
let ch = if i < hp_filled { '#' } else { '-' };
|
||||||
let c = if i < hp_filled {
|
let c = if i < hp_filled {
|
||||||
bar_color
|
bar_color
|
||||||
} else {
|
} else {
|
||||||
[80, 80, 100]
|
[80, 80, 100]
|
||||||
};
|
};
|
||||||
self.set(start_x + 10 + i, start_y + 10, ch, c, bg);
|
self.set(start_x + 10 * fs / 2 + i, row1, ch, c, bg);
|
||||||
}
|
}
|
||||||
let hp_text = format!(
|
let hp_text = format!(
|
||||||
"{}/{} LV:{}",
|
"{}/{} LV:{}",
|
||||||
p.health as i32, p.max_health as i32, p.level
|
p.health as i32, p.max_health as i32, p.level
|
||||||
);
|
);
|
||||||
self.draw_text(start_x + 3, start_y + 18, &hp_text, fg, 255);
|
self.draw_text(start_x + 3, row2, &hp_text, fg, 255);
|
||||||
|
|
||||||
let xp_ratio = (p.xp as f32 / p.xp_to_level() as f32).clamp(0.0, 1.0);
|
let xp_ratio = (p.xp as f32 / p.xp_to_level() as f32).clamp(0.0, 1.0);
|
||||||
let xp_filled = (xp_ratio * 38.0).round() as i32;
|
let xp_filled = (xp_ratio * 38.0 * fs as f32 / 2.0).round() as i32;
|
||||||
self.draw_text(start_x + 3, start_y + 26, "XP", fg, 255);
|
self.draw_text(start_x + 3, row3, "XP", fg, 255);
|
||||||
for i in 0..38 {
|
for i in 0..(38 * fs / 2) {
|
||||||
let ch = if i < xp_filled { '#' } else { '-' };
|
let ch = if i < xp_filled { '#' } else { '-' };
|
||||||
let c = if i < xp_filled {
|
let c = if i < xp_filled {
|
||||||
[80, 160, 240]
|
[80, 160, 240]
|
||||||
} else {
|
} else {
|
||||||
[60, 60, 80]
|
[60, 60, 80]
|
||||||
};
|
};
|
||||||
self.set(start_x + 10 + i, start_y + 26, ch, c, bg);
|
self.set(start_x + 10 * fs / 2 + i, row3, ch, c, bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let stats = format!("STR:{} AGI:{}", p.strength, p.agility);
|
let stats = format!("STR:{} AGI:{}", p.strength, p.agility);
|
||||||
self.draw_text(start_x + 3, start_y + 36, &stats, dim, 255);
|
self.draw_text(start_x + 3, row4, &stats, dim, 255);
|
||||||
let stats2 = format!("TOU:{} WIL:{}", p.toughness, p.willpower);
|
let stats2 = format!("TOU:{} WIL:{}", p.toughness, p.willpower);
|
||||||
self.draw_text(start_x + 3, start_y + 44, &stats2, dim, 255);
|
self.draw_text(start_x + 3, row5, &stats2, dim, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inv_title = "INVENTORY";
|
let inv_title = "INVENTORY";
|
||||||
self.draw_text(start_x + 3, start_y + 52, inv_title, fg, 255);
|
self.draw_text(start_x + 3, row_inv, inv_title, fg, 255);
|
||||||
let mut ix = start_x + 3;
|
let mut ix = start_x + 3;
|
||||||
for item in player_state.inventory.iter().take(8) {
|
for item in player_state.inventory.iter().take(8) {
|
||||||
if ix + 2 >= start_x + w - 2 {
|
if ix + 2 >= start_x + w - 2 {
|
||||||
@@ -432,10 +466,10 @@ impl UiLayer {
|
|||||||
}
|
}
|
||||||
let [ch1, ch2] = item.display_glyph();
|
let [ch1, ch2] = item.display_glyph();
|
||||||
let col = item.color();
|
let col = item.color();
|
||||||
self.set(ix, start_y + 58, ch1, col, bg);
|
self.set(ix, row_items, ch1, col, bg);
|
||||||
self.set(
|
self.set(
|
||||||
ix + 1,
|
ix + 1,
|
||||||
start_y + 58,
|
row_items,
|
||||||
ch2,
|
ch2,
|
||||||
[col[0] / 2, col[1] / 2, col[2] / 2],
|
[col[0] / 2, col[1] / 2, col[2] / 2],
|
||||||
bg,
|
bg,
|
||||||
@@ -462,9 +496,7 @@ impl UiLayer {
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
if !status.is_empty() {
|
if !status.is_empty() {
|
||||||
for (i, ch) in status.chars().enumerate() {
|
self.draw_text(start_x + 30 * fs / 2, row4, &status, [255, 80, 80], 255);
|
||||||
self.set(start_x + 30 + i as i32, start_y + 38, ch, [255, 80, 80], bg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,9 +518,9 @@ impl UiLayer {
|
|||||||
|
|
||||||
let cols = 4i32;
|
let cols = 4i32;
|
||||||
let rows = 2i32;
|
let rows = 2i32;
|
||||||
let slot_w = 12i32;
|
let slot_w = 12i32 * self.font_scale / 2;
|
||||||
let slot_h = 10i32;
|
let slot_h = 10i32 * self.font_scale / 2;
|
||||||
let gap = 3i32;
|
let gap = 3i32 * self.font_scale / 2;
|
||||||
let panel_w = cols * slot_w + (cols + 1) * gap + 4;
|
let panel_w = cols * slot_w + (cols + 1) * gap + 4;
|
||||||
let panel_h = rows * slot_h + (rows + 1) * gap + 28;
|
let panel_h = rows * slot_h + (rows + 1) * gap + 28;
|
||||||
|
|
||||||
@@ -528,7 +560,7 @@ impl UiLayer {
|
|||||||
self.draw_text(px_start + 4, py_start + 3, title, title_col, 255);
|
self.draw_text(px_start + 4, py_start + 3, title, title_col, 255);
|
||||||
let count_text = format!("({}/{} items)", player_state.inventory.len(), cols * rows);
|
let count_text = format!("({}/{} items)", player_state.inventory.len(), cols * rows);
|
||||||
self.draw_text(
|
self.draw_text(
|
||||||
px_start + 4 + Self::text_width(title) + 2,
|
px_start + 4 + self.text_width(title) + 2,
|
||||||
py_start + 3,
|
py_start + 3,
|
||||||
&count_text,
|
&count_text,
|
||||||
dim,
|
dim,
|
||||||
@@ -542,7 +574,7 @@ impl UiLayer {
|
|||||||
for col in 0..cols {
|
for col in 0..cols {
|
||||||
let idx = (row * cols + col) as usize;
|
let idx = (row * cols + col) as usize;
|
||||||
let sx = px_start + gap + col * (slot_w + gap) + 2;
|
let sx = px_start + gap + col * (slot_w + gap) + 2;
|
||||||
let sy = py_start + 14 + row * (slot_h + gap) + gap;
|
let sy = py_start + 14 * self.font_scale / 2 + row * (slot_h + gap) + gap;
|
||||||
|
|
||||||
for y in 0..slot_h {
|
for y in 0..slot_h {
|
||||||
for x in 0..slot_w {
|
for x in 0..slot_w {
|
||||||
@@ -584,7 +616,7 @@ impl UiLayer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let label = item.name();
|
let label = item.name();
|
||||||
let label_w = Self::text_width(label) as i32;
|
let label_w = self.text_width(label) as i32;
|
||||||
let lx = sx + (slot_w - label_w) / 2;
|
let lx = sx + (slot_w - label_w) / 2;
|
||||||
let ly = sy + slot_h - 1;
|
let ly = sy + slot_h - 1;
|
||||||
if lx >= sx && lx + label_w <= sx + slot_w {
|
if lx >= sx && lx + label_w <= sx + slot_w {
|
||||||
@@ -672,6 +704,8 @@ impl UiLayer {
|
|||||||
player_state: &crate::entity::player::Player,
|
player_state: &crate::entity::player::Player,
|
||||||
fps: f32,
|
fps: f32,
|
||||||
) {
|
) {
|
||||||
|
let fs = self.font_scale;
|
||||||
|
let fh = self.fh();
|
||||||
let bg = [18u8, 20, 30];
|
let bg = [18u8, 20, 30];
|
||||||
let border = [160u8, 170, 210];
|
let border = [160u8, 170, 210];
|
||||||
let fill_alpha = 140u8;
|
let fill_alpha = 140u8;
|
||||||
@@ -687,11 +721,11 @@ impl UiLayer {
|
|||||||
.map(|i| i.display_string())
|
.map(|i| i.display_string())
|
||||||
.unwrap_or(" ".to_string());
|
.unwrap_or(" ".to_string());
|
||||||
|
|
||||||
let bar_h = 28i32;
|
let bar_h = (28 * fs / 2).max(fh * 3);
|
||||||
let y_top = screen_h as i32 - bar_h;
|
let y_top = screen_h as i32 - bar_h;
|
||||||
let y_row1 = y_top + 2;
|
let y_row1 = y_top + 2;
|
||||||
let y_row2 = y_top + 12;
|
let y_row2 = y_top + 2 + fh + 2;
|
||||||
let y_row3 = y_top + 22;
|
let y_row3 = y_top + 2 + (fh + 2) * 2;
|
||||||
|
|
||||||
for row in y_top..screen_h as i32 {
|
for row in y_top..screen_h as i32 {
|
||||||
for x in 0..screen_w {
|
for x in 0..screen_w {
|
||||||
@@ -707,7 +741,8 @@ impl UiLayer {
|
|||||||
self.draw_text(0, y_row1, "HP", [220, 220, 220], 255);
|
self.draw_text(0, y_row1, "HP", [220, 220, 220], 255);
|
||||||
if let Some(p) = player {
|
if let Some(p) = player {
|
||||||
let hp_ratio = (p.health / p.max_health).clamp(0.0, 1.0);
|
let hp_ratio = (p.health / p.max_health).clamp(0.0, 1.0);
|
||||||
let hp_filled = (hp_ratio * 28.0).round() as i32;
|
let hp_bar_w = 28 * fs / 2;
|
||||||
|
let hp_filled = (hp_ratio * hp_bar_w as f32).round() as i32;
|
||||||
let bar_fg = if hp_ratio > 0.6 {
|
let bar_fg = if hp_ratio > 0.6 {
|
||||||
[80, 240, 80]
|
[80, 240, 80]
|
||||||
} else if hp_ratio > 0.3 {
|
} else if hp_ratio > 0.3 {
|
||||||
@@ -715,17 +750,23 @@ impl UiLayer {
|
|||||||
} else {
|
} else {
|
||||||
[255, 60, 60]
|
[255, 60, 60]
|
||||||
};
|
};
|
||||||
for i in 0..28 {
|
for i in 0..hp_bar_w {
|
||||||
let ch = if i < hp_filled { '#' } else { '-' };
|
let ch = if i < hp_filled { '#' } else { '-' };
|
||||||
let c = if i < hp_filled { bar_fg } else { [70, 70, 90] };
|
let c = if i < hp_filled { bar_fg } else { [70, 70, 90] };
|
||||||
self.set(10 + i, y_row1 + 4, ch, c, bg);
|
self.set(10 * fs / 2 + i, y_row1, ch, c, bg);
|
||||||
}
|
}
|
||||||
let hp_text = format!("{} / {}", p.health as i32, p.max_health as i32);
|
let hp_text = format!("{} / {}", p.health as i32, p.max_health as i32);
|
||||||
self.draw_text(40, y_row1, &hp_text, [220, 220, 220], 255);
|
self.draw_text(
|
||||||
|
(40 * fs / 2).max(10 * fs / 2 + hp_bar_w + 2),
|
||||||
|
y_row1,
|
||||||
|
&hp_text,
|
||||||
|
[220, 220, 220],
|
||||||
|
255,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let brush_color = brush_color(brush);
|
let brush_color = brush_color(brush);
|
||||||
self.set(0, y_row2 + 4, '#', brush_color, bg);
|
self.set(0, y_row2, '#', brush_color, bg);
|
||||||
let brush_text = format!(" {}", brush_name);
|
let brush_text = format!(" {}", brush_name);
|
||||||
self.draw_text(1, y_row2, &brush_text, [200, 200, 140], 255);
|
self.draw_text(1, y_row2, &brush_text, [200, 200, 140], 255);
|
||||||
|
|
||||||
@@ -736,7 +777,7 @@ impl UiLayer {
|
|||||||
player_state.inventory.len(),
|
player_state.inventory.len(),
|
||||||
fps as i32
|
fps as i32
|
||||||
);
|
);
|
||||||
let gear_w = Self::text_width(&gear);
|
let gear_w = self.text_width(&gear);
|
||||||
let gear_x = (screen_w as i32 - gear_w).max(0);
|
let gear_x = (screen_w as i32 - gear_w).max(0);
|
||||||
self.draw_text(gear_x, y_row2, &gear, [160, 180, 220], 255);
|
self.draw_text(gear_x, y_row2, &gear, [160, 180, 220], 255);
|
||||||
|
|
||||||
@@ -749,14 +790,14 @@ impl UiLayer {
|
|||||||
depth,
|
depth,
|
||||||
tick
|
tick
|
||||||
);
|
);
|
||||||
let stats_w = Self::text_width(&stats);
|
let stats_w = self.text_width(&stats);
|
||||||
let stats_x = (screen_w as i32 - stats_w).max(0);
|
let stats_x = (screen_w as i32 - stats_w).max(0);
|
||||||
self.draw_text(stats_x, y_row3, &stats, [180, 190, 220], 255);
|
self.draw_text(stats_x, y_row3, &stats, [180, 190, 220], 255);
|
||||||
|
|
||||||
if let Some(p) = player {
|
if let Some(p) = player {
|
||||||
if p.on_fire {
|
if p.on_fire {
|
||||||
let msg = "ON FIRE!";
|
let msg = "ON FIRE!";
|
||||||
self.draw_text(40, y_top - 7, msg, [255, 100, 20], 255);
|
self.draw_text(40 * fs / 2, y_top - fh - 2, msg, [255, 100, 20], 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,6 +805,7 @@ impl UiLayer {
|
|||||||
pub fn draw_messages(&mut self, x: i32, y: i32) {
|
pub fn draw_messages(&mut self, x: i32, y: i32) {
|
||||||
let mut yy = y;
|
let mut yy = y;
|
||||||
let messages: Vec<(String, u32)> = self.messages.iter().rev().take(8).cloned().collect();
|
let messages: Vec<(String, u32)> = self.messages.iter().rev().take(8).cloned().collect();
|
||||||
|
let line_h = self.fh() + 1;
|
||||||
for (msg, life) in messages {
|
for (msg, life) in messages {
|
||||||
if yy < 0 {
|
if yy < 0 {
|
||||||
break;
|
break;
|
||||||
@@ -775,7 +817,7 @@ impl UiLayer {
|
|||||||
(200.0 * fade) as u8,
|
(200.0 * fade) as u8,
|
||||||
];
|
];
|
||||||
self.draw_text(x, yy, &msg, fg, 255);
|
self.draw_text(x, yy, &msg, fg, 255);
|
||||||
yy -= 6;
|
yy -= line_h;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,20 +825,21 @@ impl UiLayer {
|
|||||||
let cx = screen_w as i32 / 2;
|
let cx = screen_w as i32 / 2;
|
||||||
let cy = screen_h as i32 / 2;
|
let cy = screen_h as i32 / 2;
|
||||||
let msg = "YOU DIED";
|
let msg = "YOU DIED";
|
||||||
let x = cx - (Self::text_width(msg) / 2);
|
let x = cx - (self.text_width(msg) / 2);
|
||||||
self.draw_text(x, cy - 3, msg, [255, 50, 50], 255);
|
self.draw_text(x, cy - 3, msg, [255, 50, 50], 255);
|
||||||
let stats = format!("KILLS: {} SCORE: {}", kills, score);
|
let stats = format!("KILLS: {} SCORE: {}", kills, score);
|
||||||
let x2 = cx - (Self::text_width(&stats) / 2);
|
let x2 = cx - (self.text_width(&stats) / 2);
|
||||||
self.draw_text(x2, cy + 3, &stats, [200, 200, 200], 255);
|
self.draw_text(x2, cy + 3, &stats, [200, 200, 200], 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_width(text: &str) -> i32 {
|
pub fn text_width(&self, text: &str) -> i32 {
|
||||||
text.chars()
|
text.chars()
|
||||||
.map(|c| if c == '\n' { 0 } else { 3 })
|
.map(|c| if c == '\n' { 0 } else { self.fw() as usize })
|
||||||
.sum::<usize>() as i32
|
.sum::<usize>() as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, fg: [u8; 3], alpha: u8) {
|
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, fg: [u8; 3], alpha: u8) {
|
||||||
|
let fs = self.font_scale;
|
||||||
let mut cx = x;
|
let mut cx = x;
|
||||||
for c in text.chars() {
|
for c in text.chars() {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
@@ -804,21 +847,33 @@ impl UiLayer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(bitmap) = char_bitmap(c) {
|
if let Some(bitmap) = char_bitmap(c) {
|
||||||
for row in 0..5 {
|
for row in 0..FONT_H {
|
||||||
for col in 0..3 {
|
for col in 0..FONT_W {
|
||||||
let filled = (bitmap[row] >> (2 - col)) & 1 == 1;
|
let filled = (bitmap[row as usize] >> (FONT_W - 1 - col)) & 1 == 1;
|
||||||
let a = if filled { alpha } else { 0 };
|
let a = if filled { alpha } else { 0 };
|
||||||
self.set_alpha(cx + col as i32, y + row as i32, c, fg, [0, 0, 0], a);
|
for dy in 0..fs {
|
||||||
|
for dx in 0..fs {
|
||||||
|
let px = cx + col * fs + dx;
|
||||||
|
let py = y + row * fs + dy;
|
||||||
|
self.set_alpha(px, py, c, fg, [0, 0, 0], a);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for row in 0..5 {
|
for row in 0..FONT_H {
|
||||||
for col in 0..3 {
|
for col in 0..FONT_W {
|
||||||
self.set_alpha(cx + col as i32, y + row as i32, c, fg, [0, 0, 0], alpha);
|
for dy in 0..fs {
|
||||||
|
for dx in 0..fs {
|
||||||
|
let px = cx + col * fs + dx;
|
||||||
|
let py = y + row * fs + dy;
|
||||||
|
self.set_alpha(px, py, c, fg, [0, 0, 0], alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx += 3;
|
cx += self.fw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -69,11 +69,14 @@ fn depth_shown_in_hud() {
|
|||||||
"LV:{} XP:{} K:{} S:{} D:{} T:{}",
|
"LV:{} XP:{} K:{} S:{} D:{} T:{}",
|
||||||
player.level, player.xp, 0, 0, s.game.depth, s.game.tick
|
player.level, player.xp, 0, 0, s.game.depth, s.game.tick
|
||||||
);
|
);
|
||||||
let stats_w = verbatim::ui::UiLayer::text_width(&stats);
|
let stats_w = ui.text_width(&stats);
|
||||||
let stats_x = (screen_w as i32 - stats_w as i32).max(0);
|
let stats_x = (screen_w as i32 - stats_w as i32).max(0);
|
||||||
let y_row3 = screen_h as i32 - 28 + 22;
|
let fs = ui.font_scale();
|
||||||
|
let fh = 5 * fs;
|
||||||
|
let bar_h = (28 * fs / 2).max(fh * 3);
|
||||||
|
let y_row3 = screen_h as i32 - bar_h + 2 + (fh + 2) * 2;
|
||||||
let line: String = (stats_x as i32..screen_w as i32)
|
let line: String = (stats_x as i32..screen_w as i32)
|
||||||
.step_by(3)
|
.step_by(fs as usize * 3)
|
||||||
.map(|x| ui.get(x, y_row3).map(|c| c.ch).unwrap_or(' '))
|
.map(|x| ui.get(x, y_row3).map(|c| c.ch).unwrap_or(' '))
|
||||||
.collect();
|
.collect();
|
||||||
assert!(line.contains("D:1"), "HUD should show depth: {}", line);
|
assert!(line.contains("D:1"), "HUD should show depth: {}", line);
|
||||||
|
|||||||
Reference in New Issue
Block a user