multiple hotkeys

This commit is contained in:
Dustin Carlino 2020-02-25 14:17:49 -08:00
parent 06759dc58c
commit 64bde1e4a3
16 changed files with 60 additions and 52 deletions

View File

@ -366,27 +366,36 @@ impl Key {
} }
// TODO This is not an ideal representation at all. // TODO This is not an ideal representation at all.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct MultiKey { pub enum MultiKey {
pub key: Key, Normal(Key),
pub lctrl: bool, LCtrl(Key),
Any(Vec<Key>),
} }
impl MultiKey { impl MultiKey {
pub fn describe(self) -> String { pub fn describe(&self) -> String {
if self.lctrl { match self {
format!("Ctrl+{}", self.key.describe()) MultiKey::Normal(key) => key.describe(),
} else { MultiKey::LCtrl(key) => format!("Ctrl+{}", key.describe()),
self.key.describe() MultiKey::Any(ref keys) => keys
.iter()
.map(|k| k.describe())
.collect::<Vec<_>>()
.join(", "),
} }
} }
} }
// For easy ModalMenu construction // For easy ModalMenu construction
pub fn hotkey(key: Key) -> Option<MultiKey> { pub fn hotkey(key: Key) -> Option<MultiKey> {
Some(MultiKey { key, lctrl: false }) Some(MultiKey::Normal(key))
} }
pub fn lctrl(key: Key) -> Option<MultiKey> { pub fn lctrl(key: Key) -> Option<MultiKey> {
Some(MultiKey { key, lctrl: true }) Some(MultiKey::LCtrl(key))
}
pub fn hotkeys(keys: Vec<Key>) -> Option<MultiKey> {
Some(MultiKey::Any(keys))
} }

View File

@ -1,4 +1,4 @@
use crate::{hotkey, lctrl, text, Canvas, Event, Key, Line, MultiKey, ScreenPt, Text}; use crate::{text, Canvas, Event, Key, Line, MultiKey, ScreenPt, Text};
use geom::Duration; use geom::Duration;
use std::collections::HashMap; use std::collections::HashMap;
@ -55,7 +55,7 @@ impl UserInput {
false false
} }
pub fn new_was_pressed(&mut self, multikey: MultiKey) -> bool { pub fn new_was_pressed(&mut self, multikey: &MultiKey) -> bool {
// TODO Reserve? // TODO Reserve?
if self.event_consumed { if self.event_consumed {
@ -63,13 +63,12 @@ impl UserInput {
} }
if let Event::KeyPress(pressed) = self.event { if let Event::KeyPress(pressed) = self.event {
let mk = if self.lctrl_held { let same = match multikey {
lctrl(pressed) MultiKey::Normal(key) => pressed == *key && !self.lctrl_held,
} else { MultiKey::LCtrl(key) => pressed == *key && self.lctrl_held,
hotkey(pressed) MultiKey::Any(ref keys) => !self.lctrl_held && keys.contains(&pressed),
} };
.unwrap(); if same {
if mk == multikey {
self.consume_event(); self.consume_event();
return true; return true;
} }

View File

@ -23,7 +23,7 @@ pub use crate::backend::Drawable;
pub use crate::canvas::{Canvas, HorizontalAlignment, VerticalAlignment}; pub use crate::canvas::{Canvas, HorizontalAlignment, VerticalAlignment};
pub use crate::color::Color; pub use crate::color::Color;
pub use crate::drawing::{GeomBatch, GfxCtx, Prerender, RewriteColor}; pub use crate::drawing::{GeomBatch, GfxCtx, Prerender, RewriteColor};
pub use crate::event::{hotkey, lctrl, Event, Key, MultiKey}; pub use crate::event::{hotkey, hotkeys, lctrl, Event, Key, MultiKey};
pub use crate::event_ctx::EventCtx; pub use crate::event_ctx::EventCtx;
pub use crate::input::UserInput; pub use crate::input::UserInput;
pub use crate::managed::{Composite, ManagedWidget, Outcome}; pub use crate::managed::{Composite, ManagedWidget, Outcome};

View File

@ -44,8 +44,7 @@ impl Button {
draw_normal: ctx.upload(normal), draw_normal: ctx.upload(normal),
draw_hovered: ctx.upload(hovered), draw_hovered: ctx.upload(hovered),
hotkey, tooltip: if let Some(ref key) = hotkey {
tooltip: if let Some(key) = hotkey {
let mut txt = let mut txt =
Text::from(Line(key.describe()).fg(text::HOTKEY_COLOR).size(20)).with_bg(); Text::from(Line(key.describe()).fg(text::HOTKEY_COLOR).size(20)).with_bg();
txt.append(Line(format!(" - {}", tooltip))); txt.append(Line(format!(" - {}", tooltip)));
@ -53,6 +52,7 @@ impl Button {
} else { } else {
Text::from(Line(tooltip).size(20)).with_bg() Text::from(Line(tooltip).size(20)).with_bg()
}, },
hotkey,
hitbox, hitbox,
hovering: false, hovering: false,
@ -89,7 +89,7 @@ impl Button {
self.hovering = false; self.hovering = false;
} }
if let Some(hotkey) = self.hotkey { if let Some(ref hotkey) = self.hotkey {
if ctx.input.new_was_pressed(hotkey) { if ctx.input.new_was_pressed(hotkey) {
self.clicked = true; self.clicked = true;
self.hovering = false; self.hovering = false;

View File

@ -113,7 +113,7 @@ impl ModalMenu {
if !choice.active { if !choice.active {
continue; continue;
} }
if let Some(hotkey) = choice.hotkey { if let Some(ref hotkey) = choice.hotkey {
if ctx.input.new_was_pressed(hotkey) { if ctx.input.new_was_pressed(hotkey) {
self.chosen_action = Some(choice.label.clone()); self.chosen_action = Some(choice.label.clone());
break; break;
@ -214,7 +214,7 @@ impl ModalMenu {
for (idx, choice) in self.choices.iter().enumerate() { for (idx, choice) in self.choices.iter().enumerate() {
if choice.active { if choice.active {
if let Some(key) = choice.hotkey { if let Some(ref key) = choice.hotkey {
txt.add_appended(vec![ txt.add_appended(vec![
Line(key.describe()).fg(text::HOTKEY_COLOR), Line(key.describe()).fg(text::HOTKEY_COLOR),
Line(format!(" - {}", choice.label)), Line(format!(" - {}", choice.label)),
@ -228,7 +228,7 @@ impl ModalMenu {
txt.highlight_last_line(text::SELECTED_COLOR); txt.highlight_last_line(text::SELECTED_COLOR);
} }
} else { } else {
if let Some(key) = choice.hotkey { if let Some(ref key) = choice.hotkey {
txt.add( txt.add(
Line(format!("{} - {}", key.describe(), choice.label)) Line(format!("{} - {}", key.describe(), choice.label))
.fg(text::INACTIVE_CHOICE_COLOR), .fg(text::INACTIVE_CHOICE_COLOR),

View File

@ -89,7 +89,7 @@ impl<T: Clone> PopupMenu<T> {
if !choice.active { if !choice.active {
continue; continue;
} }
if let Some(hotkey) = choice.hotkey { if let Some(ref hotkey) = choice.hotkey {
if ctx.input.new_was_pressed(hotkey) { if ctx.input.new_was_pressed(hotkey) {
self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); self.state = InputResult::Done(choice.label.clone(), choice.data.clone());
return; return;
@ -98,7 +98,7 @@ impl<T: Clone> PopupMenu<T> {
} }
// Handle nav keys // Handle nav keys
if ctx.input.new_was_pressed(hotkey(Key::Enter).unwrap()) { if ctx.input.new_was_pressed(&hotkey(Key::Enter).unwrap()) {
let choice = &self.choices[self.current_idx]; let choice = &self.choices[self.current_idx];
if choice.active { if choice.active {
self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); self.state = InputResult::Done(choice.label.clone(), choice.data.clone());
@ -106,11 +106,11 @@ impl<T: Clone> PopupMenu<T> {
} else { } else {
return; return;
} }
} else if ctx.input.new_was_pressed(hotkey(Key::UpArrow).unwrap()) { } else if ctx.input.new_was_pressed(&hotkey(Key::UpArrow).unwrap()) {
if self.current_idx > 0 { if self.current_idx > 0 {
self.current_idx -= 1; self.current_idx -= 1;
} }
} else if ctx.input.new_was_pressed(hotkey(Key::DownArrow).unwrap()) { } else if ctx.input.new_was_pressed(&hotkey(Key::DownArrow).unwrap()) {
if self.current_idx < self.choices.len() - 1 { if self.current_idx < self.choices.len() - 1 {
self.current_idx += 1; self.current_idx += 1;
} }
@ -149,7 +149,7 @@ impl<T: Clone> PopupMenu<T> {
for (idx, choice) in self.choices.iter().enumerate() { for (idx, choice) in self.choices.iter().enumerate() {
if choice.active { if choice.active {
if let Some(key) = choice.hotkey { if let Some(ref key) = choice.hotkey {
txt.add_appended(vec![ txt.add_appended(vec![
Line(key.describe()).fg(text::HOTKEY_COLOR), Line(key.describe()).fg(text::HOTKEY_COLOR),
Line(format!(" - {}", choice.label)), Line(format!(" - {}", choice.label)),
@ -158,7 +158,7 @@ impl<T: Clone> PopupMenu<T> {
txt.add(Line(&choice.label)); txt.add(Line(&choice.label));
} }
} else { } else {
if let Some(key) = choice.hotkey { if let Some(ref key) = choice.hotkey {
txt.add( txt.add(
Line(format!("{} - {}", key.describe(), choice.label)) Line(format!("{} - {}", key.describe(), choice.label))
.fg(text::INACTIVE_CHOICE_COLOR), .fg(text::INACTIVE_CHOICE_COLOR),

View File

@ -487,7 +487,7 @@ impl<T: Clone> Choice<T> {
pub fn key(mut self, key: Key) -> Choice<T> { pub fn key(mut self, key: Key) -> Choice<T> {
assert_eq!(self.hotkey, None); assert_eq!(self.hotkey, None);
self.hotkey = Some(MultiKey { key, lctrl: false }); self.hotkey = hotkey(key);
self self
} }

View File

@ -91,7 +91,7 @@ impl State for ABTestMode {
ui.recalculate_current_selection(ctx); ui.recalculate_current_selection(ctx);
} }
if ui.opts.dev && ctx.input.new_was_pressed(lctrl(Key::D).unwrap()) { if ui.opts.dev && ctx.input.new_was_pressed(&lctrl(Key::D).unwrap()) {
return Transition::Push(Box::new(DebugMode::new(ctx))); return Transition::Push(Box::new(DebugMode::new(ctx)));
} }

View File

@ -48,10 +48,10 @@ impl CommonState {
ui: &mut UI, ui: &mut UI,
maybe_speed: Option<&mut SpeedControls>, maybe_speed: Option<&mut SpeedControls>,
) -> Option<Transition> { ) -> Option<Transition> {
if ctx.input.new_was_pressed(lctrl(Key::S).unwrap()) { if ctx.input.new_was_pressed(&lctrl(Key::S).unwrap()) {
ui.opts.dev = !ui.opts.dev; ui.opts.dev = !ui.opts.dev;
} }
if ui.opts.dev && ctx.input.new_was_pressed(lctrl(Key::J).unwrap()) { if ui.opts.dev && ctx.input.new_was_pressed(&lctrl(Key::J).unwrap()) {
return Some(Transition::Push(warp::EnteringWarp::new())); return Some(Transition::Push(warp::EnteringWarp::new()));
} }

View File

@ -110,7 +110,7 @@ impl State for EditMode {
} }
} }
if ui.opts.dev && ctx.input.new_was_pressed(lctrl(Key::D).unwrap()) { if ui.opts.dev && ctx.input.new_was_pressed(&lctrl(Key::D).unwrap()) {
return Transition::Push(Box::new(DebugMode::new(ctx))); return Transition::Push(Box::new(DebugMode::new(ctx)));
} }

View File

@ -79,12 +79,12 @@ impl State for TrafficSignalEditor {
ctx.canvas_movement(); ctx.canvas_movement();
// TODO Buttons for these... // TODO Buttons for these...
if self.current_phase != 0 && ctx.input.new_was_pressed(hotkey(Key::UpArrow).unwrap()) { if self.current_phase != 0 && ctx.input.new_was_pressed(&hotkey(Key::UpArrow).unwrap()) {
self.change_phase(self.current_phase - 1, ui, ctx); self.change_phase(self.current_phase - 1, ui, ctx);
} }
if self.current_phase != ui.primary.map.get_traffic_signal(self.i).phases.len() - 1 if self.current_phase != ui.primary.map.get_traffic_signal(self.i).phases.len() - 1
&& ctx.input.new_was_pressed(hotkey(Key::DownArrow).unwrap()) && ctx.input.new_was_pressed(&hotkey(Key::DownArrow).unwrap())
{ {
self.change_phase(self.current_phase + 1, ui, ctx); self.change_phase(self.current_phase + 1, ui, ctx);
} }

View File

@ -30,7 +30,7 @@ impl PerObjectActions {
if !(key == Key::I && lbl == "show info") { if !(key == Key::I && lbl == "show info") {
self.actions.borrow_mut().push((key, lbl)); self.actions.borrow_mut().push((key, lbl));
} }
!self.info_panel_open && ctx.input.new_was_pressed(hotkey(key).unwrap()) !self.info_panel_open && ctx.input.new_was_pressed(&hotkey(key).unwrap())
} }
pub fn consume(&mut self) -> Vec<(Key, String)> { pub fn consume(&mut self) -> Vec<(Key, String)> {

View File

@ -7,8 +7,8 @@ use crate::mission::MissionEditMode;
use crate::sandbox::{GameplayMode, SandboxMode, TutorialPointer}; use crate::sandbox::{GameplayMode, SandboxMode, TutorialPointer};
use crate::ui::UI; use crate::ui::UI;
use ezgui::{ use ezgui::{
hotkey, Button, Color, Composite, EventCtx, EventLoopMode, GfxCtx, JustDraw, Key, Line, hotkey, hotkeys, Button, Color, Composite, EventCtx, EventLoopMode, GfxCtx, JustDraw, Key,
ManagedWidget, Text, Line, ManagedWidget, Text,
}; };
use geom::{Duration, Line, Pt2D, Speed}; use geom::{Duration, Line, Pt2D, Speed};
use instant::Instant; use instant::Instant;
@ -37,7 +37,7 @@ impl TitleScreen {
Text::from(Line("PLAY")), Text::from(Line("PLAY")),
Color::BLUE, Color::BLUE,
colors::HOVERING, colors::HOVERING,
hotkey(Key::Space), hotkeys(vec![Key::Space, Key::Enter]),
"start game", "start game",
ctx, ctx,
)), )),

View File

@ -12,8 +12,9 @@ use crate::sandbox::{
use crate::ui::UI; use crate::ui::UI;
use abstutil::Timer; use abstutil::Timer;
use ezgui::{ use ezgui::{
hotkey, lctrl, Button, Color, Composite, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, hotkey, hotkeys, lctrl, Button, Color, Composite, EventCtx, GeomBatch, GfxCtx,
Line, ManagedWidget, Outcome, RewriteColor, ScreenPt, Text, VerticalAlignment, HorizontalAlignment, Key, Line, ManagedWidget, Outcome, RewriteColor, ScreenPt, Text,
VerticalAlignment,
}; };
use geom::{Distance, Duration, PolyLine, Polygon, Pt2D, Statistic, Time}; use geom::{Distance, Duration, PolyLine, Polygon, Pt2D, Statistic, Time};
use map_model::{BuildingID, IntersectionID, IntersectionType, LaneType, Map, RoadID}; use map_model::{BuildingID, IntersectionID, IntersectionType, LaneType, Map, RoadID};
@ -1027,8 +1028,7 @@ impl TutorialState {
ctx, ctx,
"../data/system/assets/tools/next.svg", "../data/system/assets/tools/next.svg",
"next message", "next message",
// TODO Or space or enter hotkeys(vec![Key::RightArrow, Key::Space, Key::Enter]),
hotkey(Key::RightArrow),
) )
} }
.margin(5), .margin(5),
@ -1038,7 +1038,7 @@ impl TutorialState {
col.push(WrappedComposite::text_bg_button( col.push(WrappedComposite::text_bg_button(
ctx, ctx,
"Try it", "Try it",
hotkey(Key::RightArrow), hotkeys(vec![Key::RightArrow, Key::Space, Key::Enter]),
)); ));
} }

View File

@ -86,7 +86,7 @@ impl SandboxMode {
} }
fn examine_objects(&self, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> { fn examine_objects(&self, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
if ui.opts.dev && ctx.input.new_was_pressed(lctrl(Key::D).unwrap()) { if ui.opts.dev && ctx.input.new_was_pressed(&lctrl(Key::D).unwrap()) {
return Some(Transition::Push(Box::new(DebugMode::new(ctx)))); return Some(Transition::Push(Box::new(DebugMode::new(ctx))));
} }

View File

@ -245,7 +245,7 @@ impl SpeedControls {
None => {} None => {}
} }
if ctx.input.new_was_pressed(hotkey(Key::LeftArrow).unwrap()) { if ctx.input.new_was_pressed(&hotkey(Key::LeftArrow).unwrap()) {
match self.setting { match self.setting {
SpeedSetting::Realtime => self.pause(ctx), SpeedSetting::Realtime => self.pause(ctx),
SpeedSetting::Fast => { SpeedSetting::Fast => {
@ -262,7 +262,7 @@ impl SpeedControls {
} }
} }
} }
if ctx.input.new_was_pressed(hotkey(Key::RightArrow).unwrap()) { if ctx.input.new_was_pressed(&hotkey(Key::RightArrow).unwrap()) {
match self.setting { match self.setting {
SpeedSetting::Realtime => { SpeedSetting::Realtime => {
if self.paused { if self.paused {