UI in device pixels on the character grid with fixed integer font sizes, ASCII bars; 3D render scale for a pixel look
- text only at 1x/2x/4x times an integer interface factor (auto or 1-4 in the settings) - high DPI rendering, own 2D camera in device pixels, mouse converted - toolbar buttons 6 columns, integer icon scale, minimap integer pixels per tile - all bars (top bar, unit/tower/gate HP) as CP437 block characters - 3D world optionally rendered at 1/2, 1/3, 1/4 and upscaled with nearest filtering Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
889ca66dbf
commit
50d013bb28
8 files changed
+344
-213
No files matched your search
@@ -9,18 +9,58 @@ use crate::render3d::{minimap_color, Cam};
|
||||
use crate::settings::{key_name, Action, Settings};
|
||||
use macroquad::prelude::*;
|
||||
|
||||
// UI font scale (setting: 2 = normal, 3 = large); character cell size follows it
|
||||
// ---- interface scaling
|
||||
// The interface is drawn in device pixels (see begin_ui). Every size is an integer multiple
|
||||
// of the font's 6x8 pixel glyphs: small text = 1*F, normal text = 2*F, titles = 4*F, where F
|
||||
// is the interface factor (automatic from the resolution or fixed in the settings).
|
||||
thread_local! {
|
||||
static UI_SCALE: std::cell::Cell<f32> = std::cell::Cell::new(2.0);
|
||||
static UI_FACTOR_SETTING: std::cell::Cell<u8> = std::cell::Cell::new(0);
|
||||
}
|
||||
pub fn set_ui_scale(s: f32) {
|
||||
UI_SCALE.with(|c| c.set(s));
|
||||
|
||||
pub fn set_ui_factor(f: u8) {
|
||||
UI_FACTOR_SETTING.with(|c| c.set(f));
|
||||
}
|
||||
|
||||
// device pixels per CSS pixel
|
||||
pub fn dpr() -> f32 {
|
||||
screen_dpi_scale()
|
||||
}
|
||||
// screen size and mouse in device pixels
|
||||
pub fn sw() -> f32 {
|
||||
(screen_width() * dpr()).round()
|
||||
}
|
||||
pub fn sh() -> f32 {
|
||||
(screen_height() * dpr()).round()
|
||||
}
|
||||
pub fn mouse() -> (f32, f32) {
|
||||
let (x, y) = mouse_position();
|
||||
(x * dpr(), y * dpr())
|
||||
}
|
||||
|
||||
// interface factor (integer)
|
||||
#[allow(non_snake_case)]
|
||||
pub fn F() -> u32 {
|
||||
let set = UI_FACTOR_SETTING.with(|c| c.get());
|
||||
if set > 0 {
|
||||
return set as u32;
|
||||
}
|
||||
((sw() / 1280.0).min(sh() / 760.0).floor() as u32).clamp(1, 4)
|
||||
}
|
||||
// text scales: 1x, 2x and 4x the glyph size, times F
|
||||
#[allow(non_snake_case)]
|
||||
pub fn SMALL() -> f32 {
|
||||
F() as f32
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
pub fn S() -> f32 {
|
||||
UI_SCALE.with(|c| c.get())
|
||||
2.0 * F() as f32
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TITLE() -> f32 {
|
||||
4.0 * F() as f32
|
||||
}
|
||||
// character cell of normal text = the layout grid
|
||||
#[allow(non_snake_case)]
|
||||
pub fn CW() -> f32 {
|
||||
GW * S()
|
||||
}
|
||||
@@ -28,10 +68,44 @@ pub fn CW() -> f32 {
|
||||
pub fn CH() -> f32 {
|
||||
GH * S()
|
||||
}
|
||||
// factor relative to the normal scale, for pixel sizes such as buttons and icons
|
||||
// pixel factor for icons and other pixel art
|
||||
#[allow(non_snake_case)]
|
||||
pub fn K() -> f32 {
|
||||
S() / 2.0
|
||||
F() as f32
|
||||
}
|
||||
|
||||
// x position that centres a width on the character grid
|
||||
pub fn grid_center(total: f32, w: f32) -> f32 {
|
||||
(((total - w) * 0.5) / CW()).floor() * CW()
|
||||
}
|
||||
|
||||
pub fn grid_center_y(total: f32, h: f32) -> f32 {
|
||||
(((total - h) * 0.5) / CH()).floor() * CH()
|
||||
}
|
||||
|
||||
// 2D camera mapping one unit to one device pixel (top-left origin)
|
||||
pub fn begin_ui() {
|
||||
let (w, h) = (sw(), sh());
|
||||
set_camera(&Camera2D { target: vec2(w * 0.5, h * 0.5), zoom: vec2(2.0 / w, 2.0 / h), ..Default::default() });
|
||||
}
|
||||
|
||||
// bar made of CP437 block characters: full blocks, a half block, shaded rest
|
||||
pub fn ascii_bar(gfx: &Gfx, x: f32, y: f32, cols: usize, frac: f32, col: Color, scale: f32) {
|
||||
let f = frac.clamp(0.0, 1.0) * cols as f32;
|
||||
let full = f.floor() as usize;
|
||||
let half = f - full as f32 >= 0.5;
|
||||
let dim = Color::new(col.r * 0.35, col.g * 0.35, col.b * 0.35, col.a);
|
||||
for k in 0..cols {
|
||||
let px = x + k as f32 * GW * scale;
|
||||
if k < full {
|
||||
gfx.glyph(0xDB, px, y, scale, col);
|
||||
} else if k == full && half {
|
||||
gfx.glyph(0xB0, px, y, scale, dim);
|
||||
gfx.glyph(0xDD, px, y, scale, col);
|
||||
} else {
|
||||
gfx.glyph(0xB0, px, y, scale, dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const FRAME: Color = Color::new(0.62, 0.52, 0.36, 1.0);
|
||||
@@ -176,12 +250,6 @@ fn wrap(s: &str, cols: usize) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
fn bar(x: f32, y: f32, w: f32, h: f32, frac: f32, col: Color) {
|
||||
draw_rectangle(x - 1.0, y - 1.0, w + 2.0, h + 2.0, Color::new(0.0, 0.0, 0.0, 1.0));
|
||||
draw_rectangle(x, y, w, h, Color::new(col.r * 0.25, col.g * 0.25, col.b * 0.25, 1.0));
|
||||
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), h, col);
|
||||
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), (h * 0.3).max(1.0), Color::new(1.0, 1.0, 1.0, 0.25));
|
||||
}
|
||||
|
||||
fn mmss(t: f32) -> String {
|
||||
let t = t.max(0.0) as i32;
|
||||
@@ -259,64 +327,86 @@ impl Ui {
|
||||
pub fn draw(&mut self, g: &Game, gfx: &Gfx, cam: &Cam, settings: &Settings, info: &[(String, Color)], now: f64) {
|
||||
self.rects.clear();
|
||||
let keys: Vec<String> = (0..TOOLS.len()).map(|i| key_name(settings.key(Action::Tool(i)))).collect();
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
let (mx, my) = mouse_position();
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
let (mx, my) = mouse();
|
||||
|
||||
// ---------------- top bar
|
||||
let cols = (sw / CW()).ceil() as i32;
|
||||
// ---------------- top bar (everything on the character grid)
|
||||
let cols = (sw / CW()).floor() as i32;
|
||||
gfx.window(0.0, 0.0, cols, 3, S(), false, FRAME, BG);
|
||||
self.rects.push(Rect::new(0.0, 0.0, sw, 3.0 * CH()));
|
||||
let y = CH();
|
||||
let mut x = CW() * 2.0;
|
||||
let seg = |label: &str, lc: Color, value: &str, vc: Color, x: &mut f32| {
|
||||
gfx.text(label, *x, y, S(), lc);
|
||||
*x += gfx.measure(label, S()) + CW();
|
||||
gfx.text(value, *x, y, S(), vc);
|
||||
*x += gfx.measure(value, S()) + CW() * 2.0;
|
||||
let mut c = 2usize; // current column
|
||||
let col_x = |c: usize| c as f32 * CW();
|
||||
let put = |s: &str, color: Color, c: &mut usize| {
|
||||
gfx.text(s, col_x(*c), y, S(), color);
|
||||
*c += s.chars().count();
|
||||
};
|
||||
seg(&tr("hud.gold"), GOLD, &format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut x);
|
||||
let mana = tr("hud.mana");
|
||||
gfx.text(&mana, x, y, S(), MANA);
|
||||
x += gfx.measure(&mana, S()) + CW() * 0.5;
|
||||
bar(x, y + 3.0, 70.0, 10.0, g.mana / MANA_MAX, MANA);
|
||||
x += 70.0 + CW() * 0.5;
|
||||
gfx.text(&format!("{}", g.mana as i32), x, y, S(), TXT);
|
||||
x += CW() * 5.0;
|
||||
let core = tr("hud.core");
|
||||
gfx.text(&core, x, y, S(), RED);
|
||||
x += gfx.measure(&core, S()) + CW() * 0.5;
|
||||
bar(x, y + 3.0, 70.0, 10.0, g.core_hp / CORE_HP, RED);
|
||||
x += 70.0 + CW() * 1.5;
|
||||
let gate = tr("hud.gate");
|
||||
gfx.text(&gate, x, y, S(), BLUE);
|
||||
x += gfx.measure(&gate, S()) + CW() * 0.5;
|
||||
bar(x, y + 3.0, 70.0, 10.0, g.gate_hp / GATE_HP, BLUE);
|
||||
x += 70.0 + CW() * 1.5;
|
||||
let bar = |frac: f32, color: Color, c: &mut usize| {
|
||||
ascii_bar(gfx, col_x(*c), y, 6, frac, color, S());
|
||||
*c += 6;
|
||||
};
|
||||
put(&tr("hud.gold"), GOLD, &mut c);
|
||||
c += 1;
|
||||
put(&format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut c);
|
||||
c += 2;
|
||||
put(&tr("hud.mana"), MANA, &mut c);
|
||||
c += 1;
|
||||
bar(g.mana / MANA_MAX, MANA, &mut c);
|
||||
c += 1;
|
||||
put(&format!("{:<3}", g.mana as i32), TXT, &mut c);
|
||||
c += 2;
|
||||
put(&tr("hud.core"), RED, &mut c);
|
||||
c += 1;
|
||||
bar(g.core_hp / CORE_HP, RED, &mut c);
|
||||
c += 2;
|
||||
put(&tr("hud.gate"), BLUE, &mut c);
|
||||
c += 1;
|
||||
bar(g.gate_hp / GATE_HP, BLUE, &mut c);
|
||||
c += 2;
|
||||
let cap = g.counts[1].min(30);
|
||||
seg(&tr("hud.creatures"), TXT, &format!("{}/{}", g.creature_count(), cap), TXT, &mut x);
|
||||
seg(&tr("hud.workers"), TXT, &format!("{}", g.worker_count()), TXT, &mut x);
|
||||
let mut segs: Vec<(String, Color, String)> = vec![
|
||||
(tr("hud.creatures"), TXT, format!("{}/{}", g.creature_count(), cap)),
|
||||
(tr("hud.workers"), TXT, g.worker_count().to_string()),
|
||||
];
|
||||
let wave_col = if g.wave_timer < 30.0 { RED } else { TXT };
|
||||
seg(&trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col, &mmss(g.wave_timer), wave_col, &mut x);
|
||||
seg(&tr("hud.army"), TXT, &format!("{}", g.army_value() as i32), TXT, &mut x);
|
||||
segs.push((trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col, mmss(g.wave_timer)));
|
||||
segs.push((tr("hud.army"), TXT, (g.army_value() as i32).to_string()));
|
||||
if !g.hand.is_empty() {
|
||||
segs.push((tr("hud.hand"), FRAME_HI, format!("{}/8", g.hand.len())));
|
||||
}
|
||||
let vol = if settings.muted { tr("hud.off") } else { format!("{}%", (settings.volume * 100.0).round() as i32) };
|
||||
// volume only if it still fits into the bar
|
||||
if x + gfx.measure(&format!("♪ {}", vol), S()) < sw - CW() * 2.0 {
|
||||
seg("♪", DIM, &vol, DIM, &mut x);
|
||||
segs.push(("\u{266a}".to_string(), DIM, vol));
|
||||
for (label, lc, value) in segs {
|
||||
// only what still fits into the bar
|
||||
if c + label.chars().count() + value.chars().count() + 2 > cols as usize - 1 {
|
||||
break;
|
||||
}
|
||||
put(&label, lc, &mut c);
|
||||
c += 1;
|
||||
put(&value, if lc == DIM { DIM } else { TXT }, &mut c);
|
||||
c += 2;
|
||||
}
|
||||
|
||||
// ---------------- toolbar
|
||||
// ---------------- toolbar: buttons of 6 x 3 characters, icons and labels on the grid
|
||||
let tb_rows = 5;
|
||||
let tb_y = sh - tb_rows as f32 * CH();
|
||||
let tb_y = (tb_y / CH()).floor() * CH();
|
||||
let tb_h = sh - tb_y;
|
||||
let tb_rows = (tb_h / CH()).floor() as i32;
|
||||
gfx.window(0.0, tb_y, cols, tb_rows, S(), false, FRAME, BG);
|
||||
self.rects.push(Rect::new(0.0, tb_y, sw, tb_rows as f32 * CH()));
|
||||
self.rects.push(Rect::new(0.0, tb_y, sw, tb_h));
|
||||
self.tool_rects.clear();
|
||||
let bw = (80.0 * K()).min(((sw - CW() * 2.0 - 150.0) / TOOLS.len() as f32 - 4.0 * K()).max(48.0));
|
||||
let mut hover_tool = None;
|
||||
let bw = 6.0 * CW();
|
||||
let px = K(); // one pixel of the font, times the factor
|
||||
for (i, &t) in TOOLS.iter().enumerate() {
|
||||
let bx = CW() + i as f32 * (bw + 4.0 * K());
|
||||
let by = tb_y + CH() * 0.75;
|
||||
let r = Rect::new(bx, by, bw, CH() * 3.5);
|
||||
let bx = CW() + i as f32 * bw;
|
||||
let by = tb_y + CH();
|
||||
if bx + bw > sw - CW() {
|
||||
break;
|
||||
}
|
||||
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
|
||||
self.tool_rects.push(r);
|
||||
let hovered = r.contains(vec2(mx, my));
|
||||
if hovered {
|
||||
@@ -324,43 +414,27 @@ impl Ui {
|
||||
}
|
||||
let sel = self.tool == t;
|
||||
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.35, 0.22, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
|
||||
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
|
||||
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
|
||||
let ti = tool_info(t);
|
||||
let icon = 32.0 * K();
|
||||
let small = (S() / 2.0).floor().max(1.0);
|
||||
gfx.draw_cell(ti.icon, bx + (bw - icon) * 0.5, by + 4.0 * K(), icon, icon, WHITE);
|
||||
gfx.text(&keys[i], bx + 3.0, by + 3.0, small, if sel { FRAME_HI } else { DIM });
|
||||
let lw = gfx.measure(&ti.label, small);
|
||||
gfx.text(&ti.label, bx + (bw - lw) * 0.5, by + 42.0 * K(), small, if sel { FRAME_HI } else { TXT });
|
||||
}
|
||||
// hand contents
|
||||
let hx = CW() + TOOLS.len() as f32 * (bw + 4.0 * K()) + CW();
|
||||
if hx + 120.0 < sw - 200.0 {
|
||||
gfx.text(&tr("hud.hand"), hx, tb_y + CH(), S(), TXT);
|
||||
for (k, id) in g.hand.iter().enumerate() {
|
||||
if let Some(e) = g.ents.iter().find(|e| e.id == *id) {
|
||||
let sx = hx + (k % 4) as f32 * 26.0;
|
||||
let sy = tb_y + CH() * 2.2 + (k / 4) as f32 * 24.0;
|
||||
draw_texture_ex(
|
||||
&gfx.sprites,
|
||||
sx,
|
||||
sy,
|
||||
WHITE,
|
||||
DrawTextureParams { dest_size: Some(vec2(20.0, 20.0)), source: Some(Gfx::sprite_rect(e.kind, false)), ..Default::default() },
|
||||
);
|
||||
}
|
||||
}
|
||||
if g.hand.is_empty() {
|
||||
gfx.text(&tr("hud.empty"), hx, tb_y + CH() * 2.5, 1.0, DIM);
|
||||
}
|
||||
// icon: the 16 px atlas cell at 2*F, centred on the grid
|
||||
let icon = 32.0 * px;
|
||||
gfx.draw_cell(ti.icon, bx + (bw - icon) * 0.5, by + 4.0 * px, icon, icon, WHITE);
|
||||
gfx.text(&keys[i], bx + 3.0 * px, by + 3.0 * px, SMALL(), if sel { FRAME_HI } else { DIM });
|
||||
// label in small text, centred on whole small-character cells
|
||||
let label: String = ti.label.chars().take(11).collect();
|
||||
let lw = gfx.measure(&label, SMALL());
|
||||
let lx = bx + ((bw - lw) / (GW * SMALL()) / 2.0).floor() * GW * SMALL();
|
||||
gfx.text(&label, lx, by + 2.0 * CH() + 4.0 * px, SMALL(), if sel { FRAME_HI } else { TXT });
|
||||
}
|
||||
|
||||
// ---------------- minimap
|
||||
let mm_cols = 16;
|
||||
let mm_rows = 12;
|
||||
// ---------------- minimap: whole device pixels per tile
|
||||
let ms = ((14.0 * CW()) / mw().max(mh()) as f32).floor().max(1.0);
|
||||
let (map_w, map_h) = (mw() as f32 * ms, mh() as f32 * ms);
|
||||
let mm_cols = ((map_w / CW()).ceil() as i32 + 2).max(10);
|
||||
let mm_rows = (map_h / CH()).ceil() as i32 + 2;
|
||||
let mm_w = mm_cols as f32 * CW();
|
||||
let mm_h = mm_rows as f32 * CH();
|
||||
let mm_x = sw - mm_w;
|
||||
let mm_x = ((sw - mm_w) / CW()).floor() * CW();
|
||||
let mm_y = tb_y - mm_h;
|
||||
gfx.window_titled(mm_x, mm_y, mm_cols, mm_rows, S(), false, FRAME, BG, &tr("ui.map"), FRAME_HI);
|
||||
self.rects.push(Rect::new(mm_x, mm_y, mm_w, mm_h));
|
||||
@@ -383,39 +457,37 @@ impl Ui {
|
||||
}
|
||||
self.minimap_tex.update(&self.minimap_img);
|
||||
}
|
||||
// keep the map's aspect ratio inside the window
|
||||
let avail = (mm_w - CW() * 2.0).min(mm_h - CH() * 2.0);
|
||||
let ms = avail / mw().max(mh()) as f32;
|
||||
let (map_w, map_h) = (mw() as f32 * ms, mh() as f32 * ms);
|
||||
let mrx = mm_x + (mm_w - map_w) * 0.5;
|
||||
let mry = mm_y + CH() + (mm_h - CH() * 2.0 - map_h) * 0.5;
|
||||
let mrx = (mm_x + (mm_w - map_w) * 0.5).floor();
|
||||
let mry = (mm_y + (mm_h - map_h) * 0.5).floor();
|
||||
self.minimap_rect = Rect::new(mrx, mry, map_w, map_h);
|
||||
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_w, map_h)), ..Default::default() });
|
||||
let dot = (2.0 * px).max(ms);
|
||||
for e in &g.ents {
|
||||
if e.alive && !e.held {
|
||||
let c = if e.hero { Color::new(0.6, 0.85, 1.0, 1.0) } else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
|
||||
draw_rectangle(mrx + e.x * ms - 1.0, mry + e.y * ms - 1.0, 3.0, 3.0, c);
|
||||
draw_rectangle((mrx + e.x.floor() * ms).floor(), (mry + e.y.floor() * ms).floor(), dot, dot, c);
|
||||
}
|
||||
}
|
||||
if let Some(f) = g.flag {
|
||||
let (fx, fy) = xy(f);
|
||||
draw_rectangle(mrx + fx as f32 * ms - 2.0, mry + fy as f32 * ms - 2.0, 5.0, 5.0, RED);
|
||||
draw_rectangle(mrx + fx as f32 * ms - px, mry + fy as f32 * ms - px, dot + 2.0 * px, dot + 2.0 * px, RED);
|
||||
}
|
||||
// expected entry points of the hero wave (blinking)
|
||||
if (now * 3.0) as i64 % 2 == 0 {
|
||||
for &(ex, ey) in &g.planned_entries {
|
||||
let (px, py) = (mrx + ex as f32 * ms, mry + ey as f32 * ms);
|
||||
draw_rectangle_lines(px - 4.0, py - 4.0, 9.0, 9.0, 2.0, Color::new(0.6, 0.85, 1.0, 1.0));
|
||||
let (ppx, ppy) = (mrx + ex as f32 * ms, mry + ey as f32 * ms);
|
||||
draw_rectangle_lines(ppx - 4.0 * px, ppy - 4.0 * px, 9.0 * px, 9.0 * px, 2.0 * px, Color::new(0.6, 0.85, 1.0, 1.0));
|
||||
}
|
||||
}
|
||||
// camera footprint on the ground
|
||||
let corners = [cam.ground_point(0.0, 0.0), cam.ground_point(sw, 0.0), cam.ground_point(sw, sh), cam.ground_point(0.0, sh)];
|
||||
// camera footprint on the ground (the 3D camera works in CSS pixels)
|
||||
let (lw, lh) = (screen_width(), screen_height());
|
||||
let corners = [cam.ground_point(0.0, 0.0), cam.ground_point(lw, 0.0), cam.ground_point(lw, lh), cam.ground_point(0.0, lh)];
|
||||
for k in 0..4 {
|
||||
let a = corners[k];
|
||||
let b = corners[(k + 1) % 4];
|
||||
let clampp = |p: Vec2| vec2(mrx + p.x.clamp(0.0, mw() as f32) * ms, mry + p.y.clamp(0.0, mh() as f32) * ms);
|
||||
let (pa, pb) = (clampp(a), clampp(b));
|
||||
draw_line(pa.x, pa.y, pb.x, pb.y, 1.5, WHITE);
|
||||
draw_line(pa.x, pa.y, pb.x, pb.y, px, WHITE);
|
||||
}
|
||||
|
||||
// ---------------- info panel
|
||||
@@ -471,8 +543,8 @@ impl Ui {
|
||||
}
|
||||
lines.push((trf("ui.key", &[keys[i].clone()]), DIM));
|
||||
let rows = lines.len() as i32 + 2;
|
||||
let tx = (mx - tcols as f32 * CW() * 0.5).clamp(0.0, sw - tcols as f32 * CW());
|
||||
let ty = tb_y - rows as f32 * CH() - 4.0;
|
||||
let tx = (((mx - tcols as f32 * CW() * 0.5) / CW()).floor() * CW()).clamp(0.0, sw - tcols as f32 * CW());
|
||||
let ty = tb_y - rows as f32 * CH();
|
||||
gfx.window_titled(tx, ty, tcols as i32, rows, S(), false, FRAME_HI, BG, &ti.name, FRAME_HI);
|
||||
for (k, (l, c)) in lines.iter().enumerate() {
|
||||
gfx.text(l, tx + CW() * 2.0, ty + CH() * (k as f32 + 1.0), S(), *c);
|
||||
@@ -483,8 +555,8 @@ impl Ui {
|
||||
|
||||
// Dialogs on top of everything (help, language selection, end screen, pause)
|
||||
pub fn draw_dialogs(&mut self, g: &Game, gfx: &Gfx) {
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
if self.show_langsel {
|
||||
self.draw_langsel(gfx);
|
||||
} else if g.state != 0 {
|
||||
@@ -493,13 +565,13 @@ impl Ui {
|
||||
self.draw_help(gfx);
|
||||
} else if g.paused {
|
||||
let w = 24;
|
||||
let x = (sw - w as f32 * CW()) * 0.5;
|
||||
let x = grid_center(sw, w as f32 * CW());
|
||||
let y = sh * 0.35;
|
||||
gfx.window(x, y, w, 5, S(), true, FRAME_HI, BG);
|
||||
let t = tr("ui.pause");
|
||||
gfx.text(&t, (sw - gfx.measure(&t, 3.0)) * 0.5, y + CH(), 3.0, FRAME_HI);
|
||||
gfx.text(&t, grid_center(sw, gfx.measure(&t, TITLE())), y + CH(), TITLE(), FRAME_HI);
|
||||
let t2 = tr("ui.pause_hint");
|
||||
gfx.text(&t2, (sw - gfx.measure(&t2, S())) * 0.5, y + CH() * 3.0, S(), DIM);
|
||||
gfx.text(&t2, grid_center(sw, gfx.measure(&t2, S())), y + CH() * 3.0, S(), DIM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,10 +601,10 @@ impl Ui {
|
||||
let lines: Vec<(String, Color)> = keys.iter().map(|(k, c)| (if k.is_empty() { String::new() } else { tr(k) }, *c)).collect();
|
||||
let cols = 74;
|
||||
let rows = lines.len() as i32 + 4;
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), "DungeonCraft", FRAME_HI);
|
||||
for (k, (l, c)) in lines.iter().enumerate() {
|
||||
gfx.text(l, x + CW() * 3.0, y + CH() * (k as f32 + 2.0), S(), *c);
|
||||
@@ -542,22 +614,22 @@ impl Ui {
|
||||
|
||||
// First start: choose the language (names shown in their own language)
|
||||
fn draw_langsel(&mut self, gfx: &Gfx) {
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.6));
|
||||
let langs = languages();
|
||||
let cols = 50;
|
||||
let rows = 6 + langs.len() as i32 * 3;
|
||||
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
||||
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||
// the title shows all language names, since no language is chosen yet
|
||||
let title = langs.iter().map(|(c, _)| crate::i18n::tr_in(c, "langsel.title")).collect::<Vec<_>>().join(" / ");
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
|
||||
self.lang_rects.clear();
|
||||
let (mx, my) = mouse_position();
|
||||
let (mx, my) = mouse();
|
||||
for (k, (code, name)) in langs.iter().enumerate() {
|
||||
let bw = 24.0 * CW();
|
||||
let bx = (sw - bw) * 0.5;
|
||||
let bx = grid_center(sw, bw);
|
||||
let by = y + CH() * (2.0 + k as f32 * 3.0);
|
||||
let r = Rect::new(bx, by, bw, CH() * 2.0);
|
||||
let hov = r.contains(vec2(mx, my));
|
||||
@@ -569,35 +641,35 @@ impl Ui {
|
||||
let hint_y = y + CH() * (2.0 + langs.len() as f32 * 3.0 + 0.5);
|
||||
for (k, (c, _)) in langs.iter().enumerate() {
|
||||
let h = crate::i18n::tr_in(c, "langsel.hint");
|
||||
gfx.text(&h, x + CW() * 2.0, hint_y + CH() * k as f32, 1.0, DIM);
|
||||
gfx.text(&h, x + CW() * 2.0, hint_y + CH() * k as f32, SMALL(), DIM);
|
||||
}
|
||||
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
|
||||
}
|
||||
|
||||
fn draw_end(&mut self, g: &Game, gfx: &Gfx) {
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.5));
|
||||
let win = g.state == 1;
|
||||
let cols = 44;
|
||||
let rows = 11;
|
||||
let x = (sw - cols as f32 * CW()) * 0.5;
|
||||
let y = (sh - rows as f32 * CH()) * 0.5;
|
||||
let x = grid_center(sw, cols as f32 * CW());
|
||||
let y = grid_center_y(sh, rows as f32 * CH());
|
||||
let col = if win { GOLD } else { RED };
|
||||
gfx.window(x, y, cols, rows, S(), true, col, Color::new(0.05, 0.03, 0.04, 0.97));
|
||||
let title = tr(if win { "end.victory" } else { "end.defeat" });
|
||||
gfx.text(&title, (sw - gfx.measure(&title, 4.0)) * 0.5, y + CH() * 1.5, 4.0, col);
|
||||
gfx.text(&title, grid_center(sw, gfx.measure(&title, TITLE())), y + CH(), TITLE(), col);
|
||||
let sub = tr(if win { "end.victory_sub" } else { "end.defeat_sub" });
|
||||
gfx.text(&sub, (sw - gfx.measure(&sub, S())) * 0.5, y + CH() * 4.5, S(), TXT);
|
||||
gfx.text(&sub, grid_center(sw, gfx.measure(&sub, S())), y + CH() * 4.5, S(), TXT);
|
||||
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
|
||||
gfx.text(&st, (sw - gfx.measure(&st, S())) * 0.5, y + CH() * 6.0, S(), DIM);
|
||||
gfx.text(&st, grid_center(sw, gfx.measure(&st, S())), y + CH() * 6.0, S(), DIM);
|
||||
let b = format!(" {} ", tr("end.new"));
|
||||
let b = b.as_str();
|
||||
let bw = gfx.measure(b, S()) + CW() * 2.0;
|
||||
let bx = (sw - bw) * 0.5;
|
||||
let bx = grid_center(sw, bw);
|
||||
let by = y + CH() * 7.75;
|
||||
self.end_button = Rect::new(bx, by, bw, CH() * 2.0);
|
||||
let (mx, my) = mouse_position();
|
||||
let (mx, my) = mouse();
|
||||
let hov = self.end_button.contains(vec2(mx, my));
|
||||
draw_rectangle(bx, by, bw, CH() * 2.0, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
|
||||
draw_rectangle_lines(bx, by, bw, CH() * 2.0, 2.0, col);
|
||||
|
||||
Reference in new issue
Block a user