mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 20:29:04 +03:00
adding categories to unimportant actions, arranging them in a tree
This commit is contained in:
parent
0a50fbd8bd
commit
f6ddd8aeaa
@ -198,3 +198,62 @@ as the calls to the wizard are deterministic.
|
||||
Menus are super awkward -- drawing extra effects, mainly.
|
||||
|
||||
cursive crate is good inspiration for the API
|
||||
|
||||
## Menus
|
||||
|
||||
Dynamically populating the TreeMenu every frame while possible input keys are collected has problems.
|
||||
|
||||
- How do we remember the permanent state between frames?
|
||||
- What if the possible actions change between frames, screwing up that state anyway?
|
||||
- stop handing events to the game entirely?
|
||||
|
||||
Rethink assumptions. Is it nice to dynamically populate the menu in a bunch of different places?
|
||||
|
||||
- Won't have any control whatsoever for order of entries, and I'll definitely want that.
|
||||
- Hard to understand all the things that could happen; it'd be nice to see them in one place
|
||||
- Lots of plugins have boilerplate code for state management. Even if they keep
|
||||
it, might be cool to at least peel out the code to activate the plugin.
|
||||
- all plugins become Optional; dont have to represent the nil state
|
||||
- or more extreme: enum of active plugin
|
||||
- Similarly, might be nice to have kind of a static list of context-sensitive, right click menu actions for each type of object?
|
||||
|
||||
|
||||
|
||||
|
||||
current TreeMenu:
|
||||
- Debug ()
|
||||
- Show extra ()
|
||||
- hide Intersection(IntersectionID(59)) (H)
|
||||
- start searching (/)
|
||||
- to show OSM classifications (6)
|
||||
- unhide everything (K)
|
||||
- visualize steepness (5)
|
||||
- Show layers ()
|
||||
- toggle buildings (1)
|
||||
- toggle debug mode (G)
|
||||
- toggle extra KML shapes (7)
|
||||
- toggle intersections (2)
|
||||
- toggle lanes (3)
|
||||
- toggle parcels (4)
|
||||
- toggle turn icons (9)
|
||||
- Validate map geometry (I)
|
||||
- start searching for something to warp to (J)
|
||||
- Edit map ()
|
||||
- start drawing a polygon (N)
|
||||
- Settings ()
|
||||
- configure colors (8)
|
||||
- Sim ()
|
||||
- Seed the map with agents (S)
|
||||
- Setup ()
|
||||
- spawn some agents for a scenario (W)
|
||||
- load sim state (P)
|
||||
- run one step (M)
|
||||
- run sim (Space)
|
||||
- save sim state (O)
|
||||
- slow down sim ([)
|
||||
- speed up sim (])
|
||||
- quit (Escape)
|
||||
|
||||
|
||||
|
||||
Back up and think about ideal for these background controls...
|
||||
|
@ -70,3 +70,14 @@ pub struct Ctx<'a> {
|
||||
pub canvas: &'a Canvas,
|
||||
pub sim: &'a Sim,
|
||||
}
|
||||
|
||||
// TODO not the right module for this, totally temp
|
||||
|
||||
pub const ROOT_MENU: &str = "";
|
||||
pub const DEBUG: &str = "Debug";
|
||||
pub const DEBUG_EXTRA: &str = "Debug/Show extra";
|
||||
pub const DEBUG_LAYERS: &str = "Debug/Show layers";
|
||||
pub const EDIT_MAP: &str = "Edit map";
|
||||
pub const SETTINGS: &str = "Settings";
|
||||
pub const SIM: &str = "Sim";
|
||||
pub const SIM_SETUP: &str = "Sim/Setup";
|
||||
|
@ -3,7 +3,7 @@
|
||||
use colors::Colors;
|
||||
use ezgui::UserInput;
|
||||
use graphics::types::Color;
|
||||
use objects::{Ctx, ID};
|
||||
use objects::{Ctx, DEBUG_EXTRA, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
|
||||
@ -23,7 +23,7 @@ impl OsmClassifier {
|
||||
} else {
|
||||
"to show OSM classifications"
|
||||
};
|
||||
if input.unimportant_key_pressed(Key::D6, msg) {
|
||||
if input.unimportant_key_pressed(Key::D6, DEBUG_EXTRA, msg) {
|
||||
self.active = !self.active;
|
||||
}
|
||||
self.active
|
||||
|
@ -3,6 +3,7 @@
|
||||
use colors::{ColorScheme, Colors};
|
||||
use ezgui::{Canvas, GfxCtx, InputResult, Menu, UserInput};
|
||||
use graphics;
|
||||
use objects::SETTINGS;
|
||||
use piston::input::{Key, MouseCursorEvent};
|
||||
use plugins::Colorizer;
|
||||
use std::str::FromStr;
|
||||
@ -31,7 +32,7 @@ impl ColorPicker {
|
||||
let mut new_state: Option<ColorPicker> = None;
|
||||
match self {
|
||||
ColorPicker::Inactive => {
|
||||
if input.unimportant_key_pressed(Key::D8, "configure colors") {
|
||||
if input.unimportant_key_pressed(Key::D8, SETTINGS, "configure colors") {
|
||||
new_state = Some(ColorPicker::Choosing(Menu::new(
|
||||
"Pick a color to change",
|
||||
Colors::iter().map(|c| c.to_string()).collect(),
|
||||
|
@ -2,6 +2,7 @@ use abstutil;
|
||||
use ezgui::{Canvas, GfxCtx, InputResult, Menu, TextBox, TextOSD, UserInput};
|
||||
use geom::{Circle, Line, Polygon, Pt2D};
|
||||
use map_model::Map;
|
||||
use objects::EDIT_MAP;
|
||||
use piston::input::{Button, Key, ReleaseEvent};
|
||||
use plugins::Colorizer;
|
||||
use polygons;
|
||||
@ -35,7 +36,7 @@ impl DrawPolygonState {
|
||||
let mut new_state: Option<DrawPolygonState> = None;
|
||||
match self {
|
||||
DrawPolygonState::Empty => {
|
||||
if input.unimportant_key_pressed(Key::N, "start drawing a polygon") {
|
||||
if input.unimportant_key_pressed(Key::N, EDIT_MAP, "start drawing a polygon") {
|
||||
new_state = Some(DrawPolygonState::DrawingPoints(
|
||||
Vec::new(),
|
||||
None,
|
||||
|
@ -4,6 +4,7 @@ use geo;
|
||||
use geo::prelude::Intersects;
|
||||
use geom::{Polygon, Pt2D};
|
||||
use map_model::{BuildingID, IntersectionID, LaneID, Map, ParcelID};
|
||||
use objects::DEBUG;
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use render::DrawMap;
|
||||
@ -99,7 +100,7 @@ impl Validator {
|
||||
let mut new_state: Option<Validator> = None;
|
||||
match self {
|
||||
Validator::Inactive => {
|
||||
if input.unimportant_key_pressed(Key::I, "Validate map geometry") {
|
||||
if input.unimportant_key_pressed(Key::I, DEBUG, "Validate map geometry") {
|
||||
new_state = Some(Validator::start(draw_map));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
use ezgui::UserInput;
|
||||
use objects::ID;
|
||||
use objects::{DEBUG_EXTRA, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use std::collections::HashSet;
|
||||
@ -18,7 +18,7 @@ impl Hider {
|
||||
}
|
||||
|
||||
pub fn event(&mut self, input: &mut UserInput, selected: &mut Option<ID>) -> bool {
|
||||
if input.unimportant_key_pressed(Key::K, "unhide everything") {
|
||||
if input.unimportant_key_pressed(Key::K, DEBUG_EXTRA, "unhide everything") {
|
||||
println!("Unhiding {} things", self.items.len());
|
||||
self.items.clear();
|
||||
return true;
|
||||
@ -37,7 +37,7 @@ impl Hider {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if input.unimportant_key_pressed(Key::H, &format!("hide {:?}", item)) {
|
||||
if input.unimportant_key_pressed(Key::H, DEBUG_EXTRA, &format!("hide {:?}", item)) {
|
||||
self.items.insert(item);
|
||||
println!("Hiding {:?}", item);
|
||||
*selected = None;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use control::ControlMap;
|
||||
use ezgui::UserInput;
|
||||
use map_model::{EditReason, Edits, LaneID, LaneType, Map};
|
||||
use objects::ID;
|
||||
use objects::{EDIT_MAP, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use render::DrawMap;
|
||||
@ -33,7 +33,7 @@ impl RoadEditor {
|
||||
match self {
|
||||
RoadEditor::Inactive(edits) => match selected {
|
||||
None => {
|
||||
if input.unimportant_key_pressed(Key::E, "Start editing roads") {
|
||||
if input.unimportant_key_pressed(Key::E, EDIT_MAP, "Start editing roads") {
|
||||
// TODO cloning edits sucks! want to consume self
|
||||
new_state = Some(RoadEditor::Active(edits.clone()));
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
use colors::{ColorScheme, Colors};
|
||||
use ezgui::{Canvas, GfxCtx, InputResult, TextBox, UserInput};
|
||||
use graphics::types::Color;
|
||||
use objects::{Ctx, ID};
|
||||
use objects::{Ctx, DEBUG_EXTRA, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use std::collections::BTreeMap;
|
||||
@ -30,7 +30,7 @@ impl SearchState {
|
||||
let mut new_state: Option<SearchState> = None;
|
||||
match self {
|
||||
SearchState::Empty => {
|
||||
if input.unimportant_key_pressed(Key::Slash, "start searching") {
|
||||
if input.unimportant_key_pressed(Key::Slash, DEBUG_EXTRA, "start searching") {
|
||||
new_state = Some(SearchState::EnteringSearch(TextBox::new(
|
||||
"Search for what?",
|
||||
)));
|
||||
|
@ -3,7 +3,7 @@
|
||||
use control::ControlMap;
|
||||
use ezgui::{EventLoopMode, TextOSD, UserInput};
|
||||
use map_model::Map;
|
||||
use objects::ID;
|
||||
use objects::{ID, SIM};
|
||||
use piston::input::{Key, UpdateEvent};
|
||||
use sim::{Benchmark, Sim, TIMESTEP};
|
||||
use std::time::{Duration, Instant};
|
||||
@ -37,20 +37,20 @@ impl SimController {
|
||||
selected: Option<ID>,
|
||||
osd: &mut TextOSD,
|
||||
) -> EventLoopMode {
|
||||
if input.unimportant_key_pressed(Key::S, "Seed the map with agents") {
|
||||
if input.unimportant_key_pressed(Key::S, SIM, "Seed the map with agents") {
|
||||
sim.small_spawn(map);
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::LeftBracket, "slow down sim") {
|
||||
if input.unimportant_key_pressed(Key::LeftBracket, SIM, "slow down sim") {
|
||||
self.desired_speed -= ADJUST_SPEED;
|
||||
self.desired_speed = self.desired_speed.max(0.0);
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::RightBracket, "speed up sim") {
|
||||
if input.unimportant_key_pressed(Key::RightBracket, SIM, "speed up sim") {
|
||||
self.desired_speed += ADJUST_SPEED;
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::O, "save sim state") {
|
||||
if input.unimportant_key_pressed(Key::O, SIM, "save sim state") {
|
||||
sim.save();
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::P, "load sim state") {
|
||||
if input.unimportant_key_pressed(Key::P, SIM, "load sim state") {
|
||||
match sim.load_most_recent() {
|
||||
Ok(new_sim) => {
|
||||
*sim = new_sim;
|
||||
@ -60,16 +60,16 @@ impl SimController {
|
||||
};
|
||||
}
|
||||
if self.last_step.is_some() {
|
||||
if input.unimportant_key_pressed(Key::Space, "pause sim") {
|
||||
if input.unimportant_key_pressed(Key::Space, SIM, "pause sim") {
|
||||
self.last_step = None;
|
||||
self.benchmark = None;
|
||||
self.sim_speed = String::from("paused");
|
||||
}
|
||||
} else {
|
||||
if input.unimportant_key_pressed(Key::Space, "run sim") {
|
||||
if input.unimportant_key_pressed(Key::Space, SIM, "run sim") {
|
||||
self.last_step = Some(Instant::now());
|
||||
self.benchmark = Some(sim.start_benchmark());
|
||||
} else if input.unimportant_key_pressed(Key::M, "run one step") {
|
||||
} else if input.unimportant_key_pressed(Key::M, SIM, "run one step") {
|
||||
sim.step(map, control_map);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
use ezgui::UserInput;
|
||||
use graphics::types::Color;
|
||||
use map_model::{Lane, Map};
|
||||
use objects::{Ctx, ID};
|
||||
use objects::{Ctx, DEBUG_EXTRA, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use std::f64;
|
||||
@ -41,7 +41,7 @@ impl SteepnessVisualizer {
|
||||
} else {
|
||||
"visualize steepness"
|
||||
};
|
||||
if input.unimportant_key_pressed(Key::D5, msg) {
|
||||
if input.unimportant_key_pressed(Key::D5, DEBUG_EXTRA, msg) {
|
||||
self.active = !self.active;
|
||||
}
|
||||
self.active
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ezgui::{Canvas, GfxCtx, InputResult, TextBox, UserInput};
|
||||
use geom::Pt2D;
|
||||
use map_model::{AreaID, BuildingID, IntersectionID, LaneID, Map, ParcelID, RoadID};
|
||||
use objects::ID;
|
||||
use objects::{DEBUG, ID};
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use sim::{CarID, PedestrianID, Sim};
|
||||
@ -24,8 +24,11 @@ impl WarpState {
|
||||
let mut new_state: Option<WarpState> = None;
|
||||
match self {
|
||||
WarpState::Empty => {
|
||||
if input.unimportant_key_pressed(Key::J, "start searching for something to warp to")
|
||||
{
|
||||
if input.unimportant_key_pressed(
|
||||
Key::J,
|
||||
DEBUG,
|
||||
"start searching for something to warp to",
|
||||
) {
|
||||
new_state = Some(WarpState::EnteringSearch(TextBox::new("Warp to what?")));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use ezgui::{Canvas, GfxCtx, InputResult, Menu, TextBox, UserInput};
|
||||
use geom::Polygon;
|
||||
use map_model::Map;
|
||||
use objects::SIM_SETUP;
|
||||
use piston::input::Key;
|
||||
use plugins::Colorizer;
|
||||
use polygons;
|
||||
@ -36,7 +37,11 @@ impl WizardSample {
|
||||
let mut new_state: Option<WizardSample> = None;
|
||||
match self {
|
||||
WizardSample::Inactive => {
|
||||
if input.unimportant_key_pressed(Key::W, "spawn some agents for a scenario") {
|
||||
if input.unimportant_key_pressed(
|
||||
Key::W,
|
||||
SIM_SETUP,
|
||||
"spawn some agents for a scenario",
|
||||
) {
|
||||
new_state = Some(WizardSample::Active(Wizard::new()));
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use graphics::types::Color;
|
||||
use kml;
|
||||
use map_model;
|
||||
use map_model::IntersectionID;
|
||||
use objects::{Ctx, ID};
|
||||
use objects::{Ctx, DEBUG_LAYERS, ID, ROOT_MENU};
|
||||
use piston::input::{Key, MouseCursorEvent};
|
||||
use piston::window::Size;
|
||||
use plugins::classification::OsmClassifier;
|
||||
@ -369,7 +369,7 @@ impl UI {
|
||||
}
|
||||
}
|
||||
|
||||
if input.unimportant_key_pressed(Key::Escape, "quit") {
|
||||
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
|
||||
let state = EditorState {
|
||||
map_name: self.map.get_name().clone(),
|
||||
cam_x: self.canvas.cam_x,
|
||||
@ -544,21 +544,33 @@ pub struct ToggleableLayers {
|
||||
impl ToggleableLayers {
|
||||
fn new() -> ToggleableLayers {
|
||||
ToggleableLayers {
|
||||
show_lanes: ToggleableLayer::new("lanes", Key::D3, Some(MIN_ZOOM_FOR_LANES)),
|
||||
show_buildings: ToggleableLayer::new("buildings", Key::D1, Some(0.0)),
|
||||
show_lanes: ToggleableLayer::new(
|
||||
DEBUG_LAYERS,
|
||||
"lanes",
|
||||
Key::D3,
|
||||
Some(MIN_ZOOM_FOR_LANES),
|
||||
),
|
||||
show_buildings: ToggleableLayer::new(DEBUG_LAYERS, "buildings", Key::D1, Some(0.0)),
|
||||
show_intersections: ToggleableLayer::new(
|
||||
DEBUG_LAYERS,
|
||||
"intersections",
|
||||
Key::D2,
|
||||
Some(MIN_ZOOM_FOR_LANES),
|
||||
),
|
||||
show_parcels: ToggleableLayer::new("parcels", Key::D4, Some(MIN_ZOOM_FOR_PARCELS)),
|
||||
show_parcels: ToggleableLayer::new(
|
||||
DEBUG_LAYERS,
|
||||
"parcels",
|
||||
Key::D4,
|
||||
Some(MIN_ZOOM_FOR_PARCELS),
|
||||
),
|
||||
show_extra_shapes: ToggleableLayer::new(
|
||||
DEBUG_LAYERS,
|
||||
"extra KML shapes",
|
||||
Key::D7,
|
||||
Some(MIN_ZOOM_FOR_LANES),
|
||||
),
|
||||
show_all_turn_icons: ToggleableLayer::new("turn icons", Key::D9, None),
|
||||
debug_mode: ToggleableLayer::new("debug mode", Key::G, None),
|
||||
show_all_turn_icons: ToggleableLayer::new(DEBUG_LAYERS, "turn icons", Key::D9, None),
|
||||
debug_mode: ToggleableLayer::new(DEBUG_LAYERS, "debug mode", Key::G, None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
use keys::describe_key;
|
||||
use piston::input::{Button, Event, IdleArgs, Key, PressEvent};
|
||||
use std::collections::HashMap;
|
||||
use tree_menu::TreeMenu;
|
||||
use TextOSD;
|
||||
|
||||
// As we check for user input, record the input and the thing that would happen. This will let us
|
||||
@ -18,6 +19,8 @@ pub struct UserInput {
|
||||
|
||||
// TODO hack :(
|
||||
empty_event: Event,
|
||||
|
||||
unimportant_actions_tree: TreeMenu,
|
||||
}
|
||||
|
||||
// TODO it'd be nice to automatically detect cases where two callers are trying to check for the
|
||||
@ -32,6 +35,7 @@ impl UserInput {
|
||||
important_actions: Vec::new(),
|
||||
reserved_keys: HashMap::new(),
|
||||
empty_event: Event::from(IdleArgs { dt: 0.0 }),
|
||||
unimportant_actions_tree: TreeMenu::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +121,7 @@ impl UserInput {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn unimportant_key_pressed(&mut self, key: Key, action: &str) -> bool {
|
||||
pub fn unimportant_key_pressed(&mut self, key: Key, category: &str, action: &str) -> bool {
|
||||
self.reserve_key(key, action);
|
||||
|
||||
if self.event_consumed {
|
||||
@ -132,6 +136,8 @@ impl UserInput {
|
||||
}
|
||||
self.unimportant_actions
|
||||
.push(format!("Press {} to {}", describe_key(key), action));
|
||||
self.unimportant_actions_tree
|
||||
.add_action(Some(key), category, action);
|
||||
false
|
||||
}
|
||||
|
||||
@ -166,6 +172,8 @@ impl UserInput {
|
||||
for a in self.important_actions.into_iter() {
|
||||
osd.add_line(a);
|
||||
}
|
||||
|
||||
println!("{}", self.unimportant_actions_tree);
|
||||
}
|
||||
|
||||
fn reserve_key(&mut self, key: Key, action: &str) {
|
||||
|
@ -15,6 +15,7 @@ mod menu;
|
||||
mod runner;
|
||||
mod text;
|
||||
mod text_box;
|
||||
mod tree_menu;
|
||||
|
||||
pub use canvas::Canvas;
|
||||
use graphics::character::CharacterCache;
|
||||
@ -136,6 +137,7 @@ impl<'a> GfxCtx<'a> {
|
||||
}
|
||||
|
||||
pub struct ToggleableLayer {
|
||||
category: String,
|
||||
layer_name: String,
|
||||
key: Key,
|
||||
// If None, never automatically enable at a certain zoom level.
|
||||
@ -145,11 +147,17 @@ pub struct ToggleableLayer {
|
||||
}
|
||||
|
||||
impl ToggleableLayer {
|
||||
pub fn new(layer_name: &str, key: Key, min_zoom: Option<f64>) -> ToggleableLayer {
|
||||
pub fn new(
|
||||
category: &str,
|
||||
layer_name: &str,
|
||||
key: Key,
|
||||
min_zoom: Option<f64>,
|
||||
) -> ToggleableLayer {
|
||||
ToggleableLayer {
|
||||
key,
|
||||
min_zoom,
|
||||
layer_name: String::from(layer_name),
|
||||
category: category.to_string(),
|
||||
layer_name: layer_name.to_string(),
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
@ -172,11 +180,8 @@ impl ToggleableLayer {
|
||||
pub fn event(&mut self, input: &mut input::UserInput) -> bool {
|
||||
if input.unimportant_key_pressed(
|
||||
self.key,
|
||||
&format!(
|
||||
"Press {} to toggle {}",
|
||||
keys::describe_key(self.key),
|
||||
self.layer_name
|
||||
),
|
||||
&self.category,
|
||||
&format!("toggle {}", self.layer_name),
|
||||
) {
|
||||
self.enabled = !self.enabled;
|
||||
return true;
|
||||
|
91
ezgui/src/tree_menu.rs
Normal file
91
ezgui/src/tree_menu.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use keys::describe_key;
|
||||
use piston::input::Key;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::fmt;
|
||||
|
||||
pub struct TreeMenu {
|
||||
root: BTreeMap<String, Item>,
|
||||
}
|
||||
|
||||
impl TreeMenu {
|
||||
pub fn new() -> TreeMenu {
|
||||
TreeMenu {
|
||||
root: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_action(&mut self, hotkey: Option<Key>, path: &str, action: &str) {
|
||||
// Split returns something for an empty string
|
||||
if path == "" {
|
||||
populate_tree(VecDeque::new(), &mut self.root, hotkey, action);
|
||||
return;
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = path.split("/").collect();
|
||||
populate_tree(VecDeque::from(parts), &mut self.root, hotkey, action);
|
||||
}
|
||||
}
|
||||
|
||||
enum Item {
|
||||
Action(Option<Key>),
|
||||
Tree(Option<Key>, BTreeMap<String, Item>),
|
||||
}
|
||||
|
||||
impl fmt::Display for TreeMenu {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TreeMenu:\n")?;
|
||||
print(0, &self.root, f)
|
||||
}
|
||||
}
|
||||
|
||||
fn print(depth: usize, tree: &BTreeMap<String, Item>, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let pad = String::from_utf8(vec![b' '; 4 * depth]).unwrap();
|
||||
for (name, item) in tree {
|
||||
match item {
|
||||
Item::Action(key) => {
|
||||
write!(f, "{}- {} ({})\n", pad, name, describe_maybe_key(key))?;
|
||||
}
|
||||
Item::Tree(key, subtree) => {
|
||||
write!(f, "{}- {} ({})\n", pad, name, describe_maybe_key(key))?;
|
||||
print(depth + 1, subtree, f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn describe_maybe_key(key: &Option<Key>) -> String {
|
||||
match key {
|
||||
Some(k) => describe_key(*k),
|
||||
None => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_tree(
|
||||
mut path_parts: VecDeque<&str>,
|
||||
tree: &mut BTreeMap<String, Item>,
|
||||
hotkey: Option<Key>,
|
||||
action: &str,
|
||||
) {
|
||||
let part = match path_parts.pop_front() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
assert!(!tree.contains_key(action));
|
||||
tree.insert(action.to_string(), Item::Action(hotkey));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !tree.contains_key(part) {
|
||||
tree.insert(part.to_string(), Item::Tree(None, BTreeMap::new()));
|
||||
}
|
||||
|
||||
match tree.get_mut(part).unwrap() {
|
||||
Item::Action(_) => {
|
||||
panic!("add_action specifies a path that's an action, not a subtree");
|
||||
}
|
||||
Item::Tree(_, subtree) => {
|
||||
populate_tree(path_parts, subtree, hotkey, action);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user