489 lines
20 KiB
Rust
489 lines
20 KiB
Rust
// Menus: title screen, pause menu and the settings dialog (tabs, values, key bindings).
|
|
// Drawn as CP437 windows on top of the (paused) game world.
|
|
|
|
use crate::gfx::*;
|
|
use crate::i18n::{languages, tr, trf};
|
|
use crate::map::{MAX_SIZE, MIN_SIZE};
|
|
use crate::settings::{all_actions, key_name, Action, Settings};
|
|
use crate::ui::{tool_info, CH, CW, DIM, FRAME, FRAME_HI, GOLD, S, TOOLS, TXT};
|
|
use macroquad::prelude::*;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
pub enum Page {
|
|
Title,
|
|
Pause,
|
|
Settings { tab: usize, from_title: bool },
|
|
NewGame,
|
|
KeyCapture { action: usize, from_title: bool },
|
|
}
|
|
|
|
pub const TABS: [&str; 5] = ["set.tab.sound", "set.tab.camera", "set.tab.graphics", "set.tab.game", "set.tab.keys"];
|
|
|
|
// One adjustable value in the settings dialog
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
pub enum Item {
|
|
Language,
|
|
Volume,
|
|
MusicVolume,
|
|
Mute,
|
|
RotateSpeed,
|
|
ZoomSpeed,
|
|
EdgeScroll,
|
|
InvertMouse,
|
|
Outlines,
|
|
WallDetail,
|
|
Lights,
|
|
UiScale,
|
|
GameSpeed,
|
|
Hints,
|
|
}
|
|
|
|
fn tab_items(tab: usize) -> &'static [Item] {
|
|
use Item::*;
|
|
match tab {
|
|
0 => &[Language, Volume, MusicVolume, Mute],
|
|
1 => &[RotateSpeed, ZoomSpeed, EdgeScroll, InvertMouse],
|
|
2 => &[Outlines, WallDetail, Lights, UiScale],
|
|
3 => &[GameSpeed, Hints],
|
|
_ => &[],
|
|
}
|
|
}
|
|
|
|
fn item_label(it: Item) -> String {
|
|
tr(match it {
|
|
Item::Language => "set.language",
|
|
Item::Volume => "set.volume",
|
|
Item::MusicVolume => "set.music",
|
|
Item::Mute => "set.mute",
|
|
Item::RotateSpeed => "set.rotate_speed",
|
|
Item::ZoomSpeed => "set.zoom_speed",
|
|
Item::EdgeScroll => "set.edge_scroll",
|
|
Item::InvertMouse => "set.invert",
|
|
Item::Outlines => "set.outlines",
|
|
Item::WallDetail => "set.wall_detail",
|
|
Item::Lights => "set.lights",
|
|
Item::UiScale => "set.ui_scale",
|
|
Item::GameSpeed => "set.game_speed",
|
|
Item::Hints => "set.hints",
|
|
})
|
|
}
|
|
|
|
fn onoff(b: bool) -> String {
|
|
tr(if b { "val.on" } else { "val.off" })
|
|
}
|
|
|
|
fn pct(v: f32) -> String {
|
|
format!("{}%", (v * 100.0).round() as i32)
|
|
}
|
|
|
|
fn item_value(it: Item, s: &Settings) -> String {
|
|
match it {
|
|
Item::Language => languages().into_iter().find(|(c, _)| *c == s.language).map(|(_, n)| n).unwrap_or_else(|| s.language.clone()),
|
|
Item::Volume => pct(s.volume),
|
|
Item::MusicVolume => pct(s.music_volume),
|
|
Item::Mute => onoff(s.muted),
|
|
Item::RotateSpeed => pct(s.rotate_speed),
|
|
Item::ZoomSpeed => pct(s.zoom_speed),
|
|
Item::EdgeScroll => onoff(s.edge_scroll),
|
|
Item::InvertMouse => onoff(s.invert_mouse),
|
|
Item::Outlines => onoff(s.outlines),
|
|
Item::WallDetail => tr(match s.wall_detail {
|
|
1 => "val.low",
|
|
3 => "val.high",
|
|
_ => "val.medium",
|
|
}),
|
|
Item::Lights => s.max_lights.to_string(),
|
|
Item::UiScale => tr(if s.ui_scale >= 3 { "val.large" } else { "val.normal" }),
|
|
Item::GameSpeed => format!("{}x", s.game_speed as i32),
|
|
Item::Hints => onoff(s.hints),
|
|
}
|
|
}
|
|
|
|
fn cycle<T: PartialEq + Copy>(opts: &[T], cur: T, d: i32) -> T {
|
|
let i = opts.iter().position(|&o| o == cur).unwrap_or(0) as i32;
|
|
opts[(i + d).rem_euclid(opts.len() as i32) as usize]
|
|
}
|
|
|
|
fn step(v: f32, d: i32, lo: f32, hi: f32, st: f32) -> f32 {
|
|
((v + d as f32 * st) * 100.0).round().clamp(lo * 100.0, hi * 100.0) / 100.0
|
|
}
|
|
|
|
// Change a value; d = -1 / +1 (toggles flip either way)
|
|
fn item_change(it: Item, s: &mut Settings, d: i32) {
|
|
match it {
|
|
Item::Language => {
|
|
let codes: Vec<String> = languages().into_iter().map(|(c, _)| c).collect();
|
|
let i = codes.iter().position(|c| *c == s.language).unwrap_or(0) as i32;
|
|
s.language = codes[(i + d).rem_euclid(codes.len() as i32) as usize].clone();
|
|
}
|
|
Item::Volume => s.volume = step(s.volume, d, 0.0, 1.0, 0.1),
|
|
Item::MusicVolume => s.music_volume = step(s.music_volume, d, 0.0, 1.0, 0.1),
|
|
Item::Mute => s.muted = !s.muted,
|
|
Item::RotateSpeed => s.rotate_speed = step(s.rotate_speed, d, 0.25, 2.5, 0.25),
|
|
Item::ZoomSpeed => s.zoom_speed = step(s.zoom_speed, d, 0.25, 2.5, 0.25),
|
|
Item::EdgeScroll => s.edge_scroll = !s.edge_scroll,
|
|
Item::InvertMouse => s.invert_mouse = !s.invert_mouse,
|
|
Item::Outlines => s.outlines = !s.outlines,
|
|
Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d),
|
|
Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d),
|
|
Item::UiScale => s.ui_scale = cycle(&[2u8, 3], s.ui_scale, d),
|
|
Item::GameSpeed => s.game_speed = cycle(&[1.0f32, 2.0], s.game_speed, d),
|
|
Item::Hints => s.hints = !s.hints,
|
|
}
|
|
}
|
|
|
|
pub fn action_label(a: Action) -> String {
|
|
match a {
|
|
Action::Tool(i) => tool_info(TOOLS[i]).name,
|
|
other => tr(&format!("key.{}", other.id())),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
enum Btn {
|
|
Continue,
|
|
Resume,
|
|
New,
|
|
Load,
|
|
Save,
|
|
Settings,
|
|
Help,
|
|
MainMenu,
|
|
Back,
|
|
Tab(usize),
|
|
Change(usize, i32),
|
|
MapW(i32),
|
|
MapH(i32),
|
|
Start,
|
|
Key(usize),
|
|
ResetKeys,
|
|
}
|
|
|
|
pub enum MenuAction {
|
|
None,
|
|
Consumed,
|
|
Continue, // load the autosave
|
|
Resume,
|
|
NewGame, // start a new game with the chosen map size
|
|
Load,
|
|
Save,
|
|
Help,
|
|
MainMenu,
|
|
SettingsChanged,
|
|
}
|
|
|
|
pub struct Menu {
|
|
pub page: Option<Page>,
|
|
pub has_autosave: bool,
|
|
buttons: Vec<(Rect, Btn)>,
|
|
}
|
|
|
|
fn button(gfx: &Gfx, r: Rect, label: &str, hot: bool, accent: Color) {
|
|
draw_rectangle(r.x, r.y, r.w, r.h, if hot { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.13, 0.09, 0.08, 1.0) });
|
|
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, if hot { FRAME_HI } else { accent });
|
|
let s = S();
|
|
gfx.text(label, r.x + (r.w - gfx.measure(label, s)) * 0.5, r.y + (r.h - GH * s) * 0.5, s, if hot { FRAME_HI } else { TXT });
|
|
}
|
|
|
|
impl Menu {
|
|
pub fn new() -> Menu {
|
|
Menu { page: Some(Page::Title), has_autosave: false, buttons: Vec::new() }
|
|
}
|
|
|
|
pub fn open(&self) -> bool {
|
|
self.page.is_some()
|
|
}
|
|
|
|
// Esc: go one level back
|
|
pub fn back(&mut self) {
|
|
self.page = match self.page {
|
|
Some(Page::KeyCapture { from_title, .. }) => Some(Page::Settings { tab: 4, from_title }),
|
|
Some(Page::Settings { from_title: true, .. }) => Some(Page::Title),
|
|
Some(Page::Settings { from_title: false, .. }) => Some(Page::Pause),
|
|
Some(Page::NewGame) => Some(Page::Title),
|
|
Some(Page::Pause) => None,
|
|
Some(Page::Title) => Some(Page::Title),
|
|
None => Some(Page::Pause),
|
|
};
|
|
}
|
|
|
|
pub fn capturing(&self) -> Option<Action> {
|
|
match self.page {
|
|
Some(Page::KeyCapture { action, .. }) => all_actions().get(action).copied(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn finish_capture(&mut self) {
|
|
if let Some(Page::KeyCapture { from_title, .. }) = self.page {
|
|
self.page = Some(Page::Settings { tab: 4, from_title });
|
|
}
|
|
}
|
|
|
|
pub fn click(&mut self, x: f32, y: f32, settings: &mut Settings) -> MenuAction {
|
|
if self.page.is_none() {
|
|
return MenuAction::None;
|
|
}
|
|
let hit = self.buttons.iter().find(|(r, _)| r.contains(vec2(x, y))).map(|(_, b)| *b);
|
|
let from_title = matches!(self.page, Some(Page::Title) | Some(Page::Settings { from_title: true, .. }) | Some(Page::KeyCapture { from_title: true, .. }));
|
|
match hit {
|
|
None => MenuAction::Consumed,
|
|
Some(Btn::Continue) => MenuAction::Continue,
|
|
Some(Btn::Resume) => {
|
|
self.page = None;
|
|
MenuAction::Resume
|
|
}
|
|
Some(Btn::New) => {
|
|
self.page = Some(Page::NewGame);
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::Start) => MenuAction::NewGame,
|
|
Some(Btn::MapW(d)) => {
|
|
settings.map_w = (settings.map_w as i32 + d * 32).clamp(MIN_SIZE as i32, MAX_SIZE as i32) as usize;
|
|
MenuAction::SettingsChanged
|
|
}
|
|
Some(Btn::MapH(d)) => {
|
|
settings.map_h = (settings.map_h as i32 + d * 32).clamp(MIN_SIZE as i32, MAX_SIZE as i32) as usize;
|
|
MenuAction::SettingsChanged
|
|
}
|
|
Some(Btn::Load) => MenuAction::Load,
|
|
Some(Btn::Save) => MenuAction::Save,
|
|
Some(Btn::Help) => MenuAction::Help,
|
|
Some(Btn::MainMenu) => MenuAction::MainMenu,
|
|
Some(Btn::Settings) => {
|
|
self.page = Some(Page::Settings { tab: 0, from_title });
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::Back) => {
|
|
self.back();
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::Tab(t)) => {
|
|
self.page = Some(Page::Settings { tab: t, from_title });
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::Change(i, d)) => {
|
|
if let Some(Page::Settings { tab, .. }) = self.page {
|
|
if let Some(&it) = tab_items(tab).get(i) {
|
|
item_change(it, settings, d);
|
|
return MenuAction::SettingsChanged;
|
|
}
|
|
}
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::Key(a)) => {
|
|
self.page = Some(Page::KeyCapture { action: a, from_title });
|
|
MenuAction::Consumed
|
|
}
|
|
Some(Btn::ResetKeys) => {
|
|
settings.reset_keys();
|
|
MenuAction::SettingsChanged
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn draw(&mut self, gfx: &Gfx, settings: &Settings, status: Option<&str>) {
|
|
self.buttons.clear();
|
|
let Some(page) = self.page else { return };
|
|
let sw = screen_width();
|
|
let sh = screen_height();
|
|
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, if page == Page::Title { 0.55 } else { 0.45 }));
|
|
match page {
|
|
Page::Title => self.draw_title(gfx),
|
|
Page::Pause => self.draw_pause(gfx),
|
|
Page::NewGame => self.draw_new_game(gfx, settings),
|
|
Page::Settings { tab, .. } => self.draw_settings(gfx, settings, tab),
|
|
Page::KeyCapture { action, .. } => {
|
|
self.draw_settings(gfx, settings, 4);
|
|
self.buttons.clear();
|
|
let a = all_actions()[action];
|
|
let text = trf("set.press_key", &[action_label(a)]);
|
|
let cols = (text.chars().count() as i32 + 8).max(36);
|
|
let x = (sw - cols as f32 * CW()) * 0.5;
|
|
let y = sh * 0.4;
|
|
gfx.window(x, y, cols, 5, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.98));
|
|
gfx.text(&text, x + CW() * 4.0, y + CH() * 1.5, S(), TXT);
|
|
let esc = tr("set.esc_cancel");
|
|
gfx.text(&esc, x + CW() * 4.0, y + CH() * 3.0, S(), DIM);
|
|
}
|
|
}
|
|
if let Some(st) = status {
|
|
let w = gfx.measure(st, S()) + CW() * 4.0;
|
|
let x = (sw - w) * 0.5;
|
|
let y = sh - CH() * 9.0;
|
|
draw_rectangle(x, y, w, CH() * 2.0, Color::new(0.05, 0.03, 0.04, 0.95));
|
|
draw_rectangle_lines(x, y, w, CH() * 2.0, 2.0, GOLD);
|
|
gfx.text(st, x + CW() * 2.0, y + CH() * 0.5, S(), GOLD);
|
|
}
|
|
}
|
|
|
|
fn column(&mut self, gfx: &Gfx, x: f32, y: f32, w: f32, items: &[(Btn, String)]) {
|
|
let (mx, my) = mouse_position();
|
|
for (k, (b, label)) in items.iter().enumerate() {
|
|
let r = Rect::new(x, y + k as f32 * CH() * 3.0, w, CH() * 2.2);
|
|
button(gfx, r, label, r.contains(vec2(mx, my)), FRAME);
|
|
self.buttons.push((r, *b));
|
|
}
|
|
}
|
|
|
|
fn draw_title(&mut self, gfx: &Gfx) {
|
|
let sw = screen_width();
|
|
let sh = screen_height();
|
|
let big = (S() * 4.0).min((sw / (GW * 14.0)).floor()).max(S());
|
|
let title = "DungeonCraft";
|
|
let ty = sh * 0.12;
|
|
gfx.text(title, (sw - gfx.measure(title, big)) * 0.5 + big, ty + big, big, Color::new(0.0, 0.0, 0.0, 0.8));
|
|
gfx.text(title, (sw - gfx.measure(title, big)) * 0.5, ty, big, FRAME_HI);
|
|
let sub = tr("menu.subtitle");
|
|
gfx.text(&sub, (sw - gfx.measure(&sub, S())) * 0.5, ty + GH * big + CH(), S(), TXT);
|
|
let mut items = Vec::new();
|
|
if self.has_autosave {
|
|
items.push((Btn::Continue, tr("menu.continue")));
|
|
}
|
|
items.push((Btn::New, tr("menu.new")));
|
|
items.push((Btn::Load, tr("menu.load")));
|
|
items.push((Btn::Settings, tr("menu.settings")));
|
|
items.push((Btn::Help, tr("menu.help")));
|
|
let w = 26.0 * CW();
|
|
let y = ty + GH * big + CH() * 4.0;
|
|
self.column(gfx, (sw - w) * 0.5, y, w, &items);
|
|
let hint = tr("menu.drop_hint");
|
|
gfx.text(&hint, (sw - gfx.measure(&hint, (S() / 2.0).max(1.0))) * 0.5, sh - CH() * 7.5, (S() / 2.0).max(1.0), DIM);
|
|
let credit = tr("help.font");
|
|
gfx.text(&credit, (sw - gfx.measure(&credit, (S() / 2.0).max(1.0))) * 0.5, sh - CH() * 6.5, (S() / 2.0).max(1.0), DIM);
|
|
}
|
|
|
|
// New game: choose the map size (width and height separately)
|
|
fn draw_new_game(&mut self, gfx: &Gfx, s: &Settings) {
|
|
let sw = screen_width();
|
|
let sh = screen_height();
|
|
let (mx, my) = mouse_position();
|
|
let cols = 44;
|
|
let rows = 17;
|
|
let x = (sw - cols as f32 * CW()) * 0.5;
|
|
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.new"), FRAME_HI);
|
|
let lines = [("newgame.width", s.map_w, 0), ("newgame.height", s.map_h, 1)];
|
|
for (k, &(key, val, which)) in lines.iter().enumerate() {
|
|
let by = y + CH() * (2.0 + k as f32 * 2.5);
|
|
gfx.text(&tr(key), x + CW() * 3.0, by + CH() * 0.4, S(), TXT);
|
|
let vw = 14.0 * CW();
|
|
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
|
let left = Rect::new(vx, by, CW() * 2.5, CH() * 1.8);
|
|
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 1.8);
|
|
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
|
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
|
let v = val.to_string();
|
|
gfx.text(&v, vx + (vw - gfx.measure(&v, S())) * 0.5, by + CH() * 0.4, S(), GOLD);
|
|
let (l, r) = if which == 0 { (Btn::MapW(-1), Btn::MapW(1)) } else { (Btn::MapH(-1), Btn::MapH(1)) };
|
|
self.buttons.push((left, l));
|
|
self.buttons.push((right, r));
|
|
}
|
|
// what the size means for the game
|
|
let area = (s.map_w * s.map_h) as f32 / 4096.0;
|
|
let portals = (area.round() as usize).max(1);
|
|
let info = trf("newgame.info", &[portals.to_string(), (portals - 1).to_string()]);
|
|
gfx.text(&info, x + CW() * 3.0, y + CH() * 7.5, S(), DIM);
|
|
let w = (cols - 6) as f32 * CW();
|
|
self.column(gfx, x + CW() * 3.0, y + CH() * 9.5, w, &[(Btn::Start, tr("newgame.start")), (Btn::Back, tr("menu.back"))]);
|
|
}
|
|
|
|
fn draw_pause(&mut self, gfx: &Gfx) {
|
|
let sw = screen_width();
|
|
let sh = screen_height();
|
|
let items = vec![
|
|
(Btn::Resume, tr("menu.resume")),
|
|
(Btn::Save, tr("menu.save")),
|
|
(Btn::Load, tr("menu.load")),
|
|
(Btn::Settings, tr("menu.settings")),
|
|
(Btn::Help, tr("menu.help")),
|
|
(Btn::MainMenu, tr("menu.main")),
|
|
];
|
|
let cols = 32;
|
|
let rows = items.len() as i32 * 3 + 4;
|
|
let x = (sw - cols as f32 * CW()) * 0.5;
|
|
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI);
|
|
let w = (cols - 6) as f32 * CW();
|
|
self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items);
|
|
}
|
|
|
|
fn draw_settings(&mut self, gfx: &Gfx, s: &Settings, tab: usize) {
|
|
let sw = screen_width();
|
|
let sh = screen_height();
|
|
let (mx, my) = mouse_position();
|
|
let cols = ((sw / CW()) as i32 - 2).min(66);
|
|
let rows = ((sh / CH()) as i32 - 2).min(34);
|
|
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
|
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.settings"), FRAME_HI);
|
|
// tabs
|
|
let mut tx = x + CW() * 2.0;
|
|
let ty = y + CH() * 1.5;
|
|
for (i, key) in TABS.iter().enumerate() {
|
|
let label = tr(key);
|
|
let w = gfx.measure(&label, S()) + CW() * 2.0;
|
|
let r = Rect::new(tx, ty, w, CH() * 2.0);
|
|
let hot = r.contains(vec2(mx, my));
|
|
draw_rectangle(r.x, r.y, r.w, r.h, if i == tab { Color::new(0.35, 0.22, 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 i == tab || hot { FRAME_HI } else { FRAME });
|
|
gfx.text(&label, r.x + CW(), r.y + CH() * 0.5, S(), if i == tab { FRAME_HI } else { TXT });
|
|
self.buttons.push((r, Btn::Tab(i)));
|
|
tx += w + CW();
|
|
}
|
|
let top = ty + CH() * 3.5;
|
|
if tab == 4 {
|
|
// key bindings in two columns
|
|
let acts = all_actions();
|
|
let half = (acts.len() + 1) / 2;
|
|
let colw = (cols as f32 - 6.0) * CW() * 0.5;
|
|
let rowh = CH() * 1.35;
|
|
for (k, a) in acts.iter().enumerate() {
|
|
let c = k / half;
|
|
let r = k % half;
|
|
let bx = x + CW() * 2.0 + c as f32 * (colw + CW() * 2.0);
|
|
let by = top + r as f32 * rowh;
|
|
let rect = Rect::new(bx, by - 2.0, colw, rowh - 2.0);
|
|
let hot = rect.contains(vec2(mx, my));
|
|
if hot {
|
|
draw_rectangle(rect.x, rect.y, rect.w, rect.h, Color::new(0.25, 0.16, 0.08, 1.0));
|
|
}
|
|
let key = format!("[{}]", key_name(s.key(*a)));
|
|
let maxc = ((colw - gfx.measure(&key, S()) - CW()) / CW()).max(4.0) as usize;
|
|
let label: String = action_label(*a).chars().take(maxc).collect();
|
|
gfx.text(&label, bx + CW() * 0.5, by, S(), TXT);
|
|
gfx.text(&key, bx + colw - gfx.measure(&key, S()) - CW() * 0.5, by, S(), GOLD);
|
|
self.buttons.push((rect, Btn::Key(k)));
|
|
}
|
|
let hint = tr("set.keys_hint");
|
|
gfx.text(&hint, x + CW() * 2.0, top + half as f32 * rowh + CH() * 0.5, (S() / 2.0).max(1.0), DIM);
|
|
let rw = gfx.measure(&tr("set.reset_keys"), S()) + CW() * 4.0;
|
|
let rr = Rect::new(x + cols as f32 * CW() - rw - CW() * 2.0, y + (rows - 4) as f32 * CH(), rw, CH() * 2.2);
|
|
button(gfx, rr, &tr("set.reset_keys"), rr.contains(vec2(mx, my)), FRAME);
|
|
self.buttons.push((rr, Btn::ResetKeys));
|
|
} else {
|
|
for (i, &it) in tab_items(tab).iter().enumerate() {
|
|
let by = top + i as f32 * CH() * 2.5;
|
|
gfx.text(&item_label(it), x + CW() * 3.0, by + CH() * 0.4, S(), TXT);
|
|
// value with < > buttons
|
|
let vw = 18.0 * CW();
|
|
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
|
let left = Rect::new(vx, by, CW() * 2.5, CH() * 1.8);
|
|
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 1.8);
|
|
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
|
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
|
let val = item_value(it, s);
|
|
gfx.text(&val, vx + (vw - gfx.measure(&val, S())) * 0.5, by + CH() * 0.4, S(), GOLD);
|
|
let mid = Rect::new(vx + CW() * 2.5, by, vw - CW() * 5.0, CH() * 1.8);
|
|
self.buttons.push((left, Btn::Change(i, -1)));
|
|
self.buttons.push((right, Btn::Change(i, 1)));
|
|
self.buttons.push((mid, Btn::Change(i, 1)));
|
|
}
|
|
}
|
|
let bw = gfx.measure(&tr("menu.back"), S()) + CW() * 6.0;
|
|
let br = Rect::new(x + CW() * 2.0, y + (rows - 4) as f32 * CH(), bw, CH() * 2.2);
|
|
button(gfx, br, &tr("menu.back"), br.contains(vec2(mx, my)), FRAME);
|
|
self.buttons.push((br, Btn::Back));
|
|
}
|
|
}
|