tie together a popup menu and a button in a really simple way. move some

info and location things there
This commit is contained in:
Dustin Carlino 2019-10-29 15:14:17 -07:00
parent 00253d2783
commit 7574869798
17 changed files with 246 additions and 56 deletions

View File

@ -13,6 +13,8 @@ pub enum ContainerOrientation {
TopLeft,
TopRight,
Centered,
// Place the widget this percentage along the width of the screen
Top(f64),
}
pub fn stack_vertically(
@ -39,6 +41,7 @@ pub fn stack_vertically(
pt.y -= total_height / 2.0;
pt
}
ContainerOrientation::Top(percent) => ScreenPt::new(canvas.window_width * percent, 0.0),
};
for (w, dims) in widgets.into_iter().zip(dims_per_widget) {
w.set_pos(top_left, total_width);

View File

@ -20,8 +20,8 @@ pub use crate::runner::{run, EventLoopMode, Settings, GUI};
pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
pub use crate::text::{Line, Text, TextSpan, HOTKEY_COLOR};
pub use crate::widgets::{
Autocomplete, Choice, ItemSlider, ModalMenu, NewScroller, Scroller, Slider, SliderWithTextBox,
Warper, WarpingItemSlider, Wizard, WrappedWizard,
Autocomplete, Choice, ItemSlider, MenuUnderButton, ModalMenu, NewScroller, Scroller, Slider,
SliderWithTextBox, Warper, WarpingItemSlider, Wizard, WrappedWizard,
};
pub enum InputResult<T: Clone> {

View File

@ -110,8 +110,13 @@ const ICON_BACKGROUND: Color = Color::grey(0.5);
const ICON_BACKGROUND_SELECTED: Color = Color::YELLOW;
impl Button {
fn show_hide_btn(icon: &str, tooltip: &str, ctx: &EventCtx) -> Button {
let radius = ctx.canvas.line_height / 2.0;
pub fn icon_btn(
icon: &str,
radius: f64,
tooltip: &str,
key: Option<MultiKey>,
ctx: &EventCtx,
) -> Button {
let circle = Circle::new(Pt2D::new(radius, radius), Distance::meters(radius));
let mut normal = GeomBatch::new();
@ -122,15 +127,27 @@ impl Button {
hovered.push(ICON_BACKGROUND_SELECTED, circle.to_polygon());
hovered.push(ctx.canvas.texture(icon), circle.to_polygon());
// TODO Arbitrarilyish the first user to be event()'d will eat this key.
Button::new(normal, hovered, hotkey(Key::Tab), tooltip, ctx)
Button::new(normal, hovered, key, tooltip, ctx)
}
pub fn show_btn(ctx: &EventCtx, tooltip: &str) -> Button {
Button::show_hide_btn("assets/ui/show.png", tooltip, ctx)
// TODO Arbitrarilyish the first user to be event()'d will eat this key.
Button::icon_btn(
"assets/ui/show.png",
ctx.canvas.line_height / 2.0,
tooltip,
hotkey(Key::Tab),
ctx,
)
}
pub fn hide_btn(ctx: &EventCtx, tooltip: &str) -> Button {
Button::show_hide_btn("assets/ui/hide.png", tooltip, ctx)
Button::icon_btn(
"assets/ui/hide.png",
ctx.canvas.line_height / 2.0,
tooltip,
hotkey(Key::Tab),
ctx,
)
}
}

View File

@ -0,0 +1,122 @@
use crate::layout::{ContainerOrientation, Widget};
use crate::widgets::{Button, PopupMenu};
use crate::{layout, Choice, EventCtx, GfxCtx, InputResult, MultiKey, ScreenDims, ScreenPt, Text};
// TODO Ideally:
// - Pause sim while this is active
// - Grey out inactive items like ModalMenu
// Right now the uses of this don't really need this.
pub struct MenuUnderButton {
button: Button,
menu: PopupMenu<()>,
expanded: bool,
chosen_action: Option<String>,
// TODO Hackish. While unexpanded.
unexpanded_choices: Vec<(MultiKey, String)>,
standalone_layout: ContainerOrientation,
}
impl MenuUnderButton {
pub fn new(
icon: &str,
title: &str,
choices: Vec<(Option<MultiKey>, &str)>,
percent_along_top_of_screen: f64,
ctx: &EventCtx,
) -> MenuUnderButton {
let mut m = MenuUnderButton {
button: Button::icon_btn(icon, 32.0, title, None, ctx),
menu: PopupMenu::new(
Text::prompt(title),
choices
.iter()
.map(|(mk, name)| Choice::new(*name, ()).multikey(*mk))
.collect(),
ctx,
),
expanded: false,
chosen_action: None,
unexpanded_choices: choices
.into_iter()
.filter_map(|(mk, name)| {
if let Some(key) = mk {
Some((key, name.to_string()))
} else {
None
}
})
.collect(),
standalone_layout: ContainerOrientation::Top(percent_along_top_of_screen),
};
m.menu.disable_standalone_layout();
m
}
pub fn event(&mut self, ctx: &mut EventCtx) {
if let Some(ref c) = self.chosen_action {
panic!("Nothing consumed action {}", c);
}
layout::stack_vertically(self.standalone_layout, ctx.canvas, vec![self]);
self.button.event(ctx);
if self.expanded {
match self.menu.event(ctx) {
InputResult::StillActive => {}
InputResult::Done(name, _) => {
self.chosen_action = Some(name);
self.expanded = false;
}
InputResult::Canceled => {
self.expanded = false;
}
}
} else {
if self.button.clicked() {
self.expanded = true;
} else {
for (mk, name) in &self.unexpanded_choices {
if ctx.input.new_was_pressed(*mk) {
self.chosen_action = Some(name.to_string());
break;
}
}
}
}
}
pub fn draw(&self, g: &mut GfxCtx) {
self.button.draw(g);
if self.expanded {
self.menu.draw(g);
}
}
pub fn action(&mut self, label: &str) -> bool {
if let Some(ref action) = self.chosen_action {
if label == action {
self.chosen_action = None;
return true;
}
}
false
}
}
impl Widget for MenuUnderButton {
fn get_dims(&self) -> ScreenDims {
self.button.get_dims()
}
fn set_pos(&mut self, top_left: ScreenPt, total_width: f64) {
self.button.set_pos(top_left, total_width);
// TODO Brittle centering depends where these buttons are being placed right now
self.menu.set_pos(
ScreenPt::new(
top_left.x - self.menu.get_dims().width / 2.0,
top_left.y + self.get_dims().height,
),
total_width,
);
}
}

View File

@ -2,6 +2,7 @@ mod autocomplete;
mod button;
mod context_menu;
mod log_scroller;
mod menu_under_button;
mod modal_menu;
mod popup_menu;
mod screenshot;
@ -14,6 +15,7 @@ mod wizard;
pub use self::autocomplete::Autocomplete;
pub use self::button::Button;
pub(crate) use self::context_menu::ContextMenu;
pub use self::menu_under_button::MenuUnderButton;
pub use self::modal_menu::ModalMenu;
pub(crate) use self::popup_menu::PopupMenu;
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};

View File

@ -32,6 +32,12 @@ impl<T: Clone> PopupMenu<T> {
m
}
// It's part of something bigger
pub fn disable_standalone_layout(&mut self) {
assert!(self.standalone_layout.is_some());
self.standalone_layout = None;
}
pub fn event(&mut self, ctx: &mut EventCtx) -> InputResult<T> {
if let Some(o) = self.standalone_layout {
layout::stack_vertically(o, ctx.canvas, vec![self]);
@ -59,8 +65,12 @@ impl<T: Clone> PopupMenu<T> {
}
{
let choice = &self.choices[self.current_idx];
if ctx.input.left_mouse_button_pressed() && choice.active {
return InputResult::Done(choice.label.clone(), choice.data.clone());
if ctx.input.left_mouse_button_pressed() {
if choice.active && ctx.canvas.get_cursor_in_map_space().is_none() {
return InputResult::Done(choice.label.clone(), choice.data.clone());
} else {
return InputResult::Canceled;
}
}
}

View File

@ -407,6 +407,11 @@ impl<T: Clone> Choice<T> {
self
}
pub fn multikey(mut self, mk: Option<MultiKey>) -> Choice<T> {
self.hotkey = mk;
self
}
pub fn active(mut self, active: bool) -> Choice<T> {
self.active = active;
self

BIN
game/assets/ui/location.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -8,7 +8,8 @@ use crate::render::MIN_ZOOM_FOR_DETAIL;
use crate::ui::{PerMapUI, UI};
use abstutil::Timer;
use ezgui::{
hotkey, lctrl, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line, ModalMenu, Text,
hotkey, lctrl, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line, MenuUnderButton,
ModalMenu, Text,
};
use geom::{Circle, Distance, Line, PolyLine};
use map_model::{Map, LANE_THICKNESS};
@ -18,6 +19,7 @@ use sim::{Sim, SimOptions, TripID};
pub struct ABTestMode {
menu: ModalMenu,
speed: SpeedControls,
info_tools: MenuUnderButton,
primary_agent_tools: AgentTools,
secondary_agent_tools: AgentTools,
diff_trip: Option<DiffOneTrip>,
@ -39,28 +41,33 @@ impl ABTestMode {
(hotkey(Key::S), "swap"),
(hotkey(Key::D), "diff all trips"),
(hotkey(Key::A), "stop diffing trips"),
(hotkey(Key::Q), "scoreboard"),
(hotkey(Key::O), "save state"),
// TODO load arbitrary savestate
],
vec![
(hotkey(Key::Escape), "quit"),
(lctrl(Key::D), "debug mode"),
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::Semicolon), "change agent colorscheme"),
(hotkey(Key::SingleQuote), "shortcuts"),
(hotkey(Key::F1), "take a screenshot"),
],
],
ctx,
),
speed: SpeedControls::new(ctx, true),
info_tools: MenuUnderButton::new(
"assets/ui/info.png",
"Info",
vec![
(hotkey(Key::Q), "scoreboard"),
(hotkey(Key::Semicolon), "change agent colorscheme"),
],
0.5,
ctx,
),
primary_agent_tools: AgentTools::new(),
secondary_agent_tools: AgentTools::new(),
diff_trip: None,
diff_all: None,
common: CommonState::new(),
common: CommonState::new(ctx),
test_name: test_name.to_string(),
flipped: false,
}
@ -95,6 +102,7 @@ impl State for ABTestMode {
self.menu.set_info(ctx, txt);
}
self.menu.event(ctx);
self.info_tools.event(ctx);
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
@ -125,7 +133,7 @@ impl State for ABTestMode {
self.flipped = !self.flipped;
}
if self.menu.action("scoreboard") {
if self.info_tools.action("scoreboard") {
return Transition::Push(Box::new(score::Scoreboard::new(
ctx,
&ui.primary,
@ -133,7 +141,10 @@ impl State for ABTestMode {
)));
}
if let Some(t) = self.primary_agent_tools.event(ctx, ui, &mut self.menu) {
if let Some(t) =
self.primary_agent_tools
.event(ctx, ui, &mut self.menu, &mut self.info_tools)
{
return t;
}
@ -212,6 +223,7 @@ impl State for ABTestMode {
self.menu.draw(g);
self.speed.draw(g);
self.primary_agent_tools.draw(g, ui);
self.info_tools.draw(g);
}
fn on_suspend(&mut self, ctx: &mut EventCtx, _: &mut UI) {

View File

@ -3,7 +3,7 @@ use crate::common::{ColorLegend, RouteExplorer, TripExplorer};
use crate::game::{Transition, WizardState};
use crate::render::{AgentColorScheme, MIN_ZOOM_FOR_DETAIL};
use crate::ui::UI;
use ezgui::{hotkey, Choice, EventCtx, GfxCtx, Key, ModalMenu};
use ezgui::{hotkey, Choice, EventCtx, GfxCtx, Key, MenuUnderButton, ModalMenu};
use geom::{Duration, Pt2D};
use sim::{TripID, TripResult};
use std::cell::RefCell;
@ -29,6 +29,7 @@ impl AgentTools {
ctx: &mut EventCtx,
ui: &UI,
menu: &mut ModalMenu,
info_menu: &mut MenuUnderButton,
) -> Option<Transition> {
if self.following.is_none() {
if let Some(agent) = ui
@ -87,7 +88,7 @@ impl AgentTools {
}
self.route_viewer.event(ctx, ui, menu);
if menu.action("change agent colorscheme") {
if info_menu.action("change agent colorscheme") {
return Some(Transition::Push(WizardState::new(Box::new(
|wiz, ctx, ui| {
let (_, acs) = wiz.wrap(ctx).choose("Which colorscheme for agents?", || {

View File

@ -26,21 +26,33 @@ use crate::helpers::ID;
use crate::render::DrawOptions;
use crate::ui::UI;
use ezgui::{
Color, EventCtx, EventLoopMode, GfxCtx, HorizontalAlignment, Key, Line, ModalMenu, Text,
VerticalAlignment,
hotkey, Color, EventCtx, EventLoopMode, GfxCtx, HorizontalAlignment, Key, Line,
MenuUnderButton, ModalMenu, Text, VerticalAlignment,
};
use std::collections::BTreeSet;
pub struct CommonState {
associated: associated::ShowAssociatedState,
turn_cycler: turn_cycler::TurnCyclerState,
location_tools: MenuUnderButton,
}
impl CommonState {
pub fn new() -> CommonState {
pub fn new(ctx: &EventCtx) -> CommonState {
CommonState {
associated: associated::ShowAssociatedState::Inactive,
turn_cycler: turn_cycler::TurnCyclerState::Inactive,
location_tools: MenuUnderButton::new(
"assets/ui/location.png",
"Location",
vec![
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
],
0.4,
ctx,
),
}
}
@ -50,13 +62,15 @@ impl CommonState {
ui: &mut UI,
menu: &mut ModalMenu,
) -> Option<Transition> {
if menu.action("warp") {
self.location_tools.event(ctx);
if self.location_tools.action("warp") {
return Some(Transition::Push(warp::EnteringWarp::new()));
}
if menu.action("navigate") {
if self.location_tools.action("navigate") {
return Some(Transition::Push(Box::new(navigate::Navigator::new(ui))));
}
if menu.action("shortcuts") {
if self.location_tools.action("shortcuts") {
return Some(Transition::Push(shortcuts::ChoosingShortcut::new()));
}
@ -85,6 +99,7 @@ impl CommonState {
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.turn_cycler.draw(g, ui);
self.location_tools.draw(g);
CommonState::draw_osd(g, ui, &ui.primary.current_selection);
}

View File

@ -58,15 +58,12 @@ impl DebugMode {
],
vec![
(hotkey(Key::Escape), "return to previous mode"),
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
(hotkey(Key::F1), "take a screenshot"),
],
],
ctx,
),
common: CommonState::new(),
common: CommonState::new(ctx),
connected_roads: connected_roads::ShowConnectedRoads::new(),
objects: objects::ObjectDebugger::new(),
hidden: HashSet::new(),

View File

@ -29,7 +29,7 @@ impl EditMode {
ui.primary.reset_sim();
EditMode {
common: CommonState::new(),
common: CommonState::new(ctx),
menu: ModalMenu::new(
"Map Edit Mode",
vec![
@ -41,9 +41,6 @@ impl EditMode {
(hotkey(Key::Escape), "quit"),
(lctrl(Key::S), "sandbox mode"),
(lctrl(Key::D), "debug mode"),
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
(hotkey(Key::F1), "take a screenshot"),
],
],

View File

@ -123,15 +123,12 @@ impl ScenarioManager {
],
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
(hotkey(Key::F1), "take a screenshot"),
],
],
ctx,
),
common: CommonState::new(),
common: CommonState::new(ctx),
scenario,
trips_from_bldg,
trips_to_bldg,
@ -282,7 +279,8 @@ impl State for ScenarioManager {
}
self.menu.draw(g);
// TODO Weird to not draw common (turn cycler), but we want the custom OSD...
self.common.draw(g, ui);
// TODO Just cover up common's OSD with ours...
if let Some(ID::Building(b)) = ui.primary.current_selection {
let mut osd = CommonState::default_osd(ID::Building(b), ui);

View File

@ -6,7 +6,7 @@ use crate::render::DrawOptions;
use crate::sandbox::SandboxMode;
use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Counter};
use ezgui::{Choice, Color, EventCtx, GfxCtx, Line, ModalMenu, Text};
use ezgui::{Choice, Color, EventCtx, GfxCtx, Line, MenuUnderButton, Text};
use geom::Duration;
use map_model::PathStep;
use sim::ParkingSpot;
@ -27,7 +27,7 @@ impl Analytics {
&mut self,
ctx: &mut EventCtx,
ui: &UI,
menu: &mut ModalMenu,
menu: &mut MenuUnderButton,
trip_stats: &TripStats,
) -> Option<Transition> {
if menu.action("change analytics overlay") {

View File

@ -11,13 +11,15 @@ use crate::game::{State, Transition, WizardState};
use crate::helpers::ID;
use crate::ui::{ShowEverything, UI};
use ezgui::{
hotkey, lctrl, Choice, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, Text, Wizard,
hotkey, lctrl, Choice, EventCtx, EventLoopMode, GfxCtx, Key, Line, MenuUnderButton, ModalMenu,
Text, Wizard,
};
use geom::Duration;
use sim::Sim;
pub struct SandboxMode {
speed: SpeedControls,
info_tools: MenuUnderButton,
agent_tools: AgentTools,
pub time_travel: time_travel::InactiveTimeTravel,
trip_stats: trip_stats::TripStats,
@ -30,13 +32,24 @@ impl SandboxMode {
pub fn new(ctx: &mut EventCtx, ui: &UI) -> SandboxMode {
SandboxMode {
speed: SpeedControls::new(ctx, true),
info_tools: MenuUnderButton::new(
"assets/ui/info.png",
"Info",
vec![
(hotkey(Key::Q), "scoreboard"),
(hotkey(Key::L), "change analytics overlay"),
(hotkey(Key::Semicolon), "change agent colorscheme"),
],
0.5,
ctx,
),
agent_tools: AgentTools::new(),
time_travel: time_travel::InactiveTimeTravel::new(),
trip_stats: trip_stats::TripStats::new(
ui.primary.current_flags.sim_flags.opts.record_stats,
),
analytics: analytics::Analytics::Inactive,
common: CommonState::new(),
common: CommonState::new(ctx),
menu: ModalMenu::new(
"Sandbox Mode",
vec![
@ -48,19 +61,11 @@ impl SandboxMode {
(hotkey(Key::X), "reset sim"),
(hotkey(Key::S), "start a scenario"),
],
vec![
(hotkey(Key::T), "start time traveling"),
(hotkey(Key::Q), "scoreboard"),
(hotkey(Key::L), "change analytics overlay"),
(hotkey(Key::Semicolon), "change agent colorscheme"),
],
vec![(hotkey(Key::T), "start time traveling")],
vec![
(hotkey(Key::Escape), "quit"),
(lctrl(Key::D), "debug mode"),
(lctrl(Key::E), "edit mode"),
(hotkey(Key::J), "warp"),
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
(hotkey(Key::F1), "take a screenshot"),
],
],
@ -84,6 +89,7 @@ impl State for SandboxMode {
self.menu.set_info(ctx, txt);
}
self.menu.event(ctx);
self.info_tools.event(ctx);
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
@ -94,7 +100,7 @@ impl State for SandboxMode {
}
if let Some(t) = self
.analytics
.event(ctx, ui, &mut self.menu, &self.trip_stats)
.event(ctx, ui, &mut self.info_tools, &self.trip_stats)
{
return t;
}
@ -103,13 +109,16 @@ impl State for SandboxMode {
return Transition::Push(new_state);
}
if let Some(t) = self.agent_tools.event(ctx, ui, &mut self.menu) {
if let Some(t) = self
.agent_tools
.event(ctx, ui, &mut self.menu, &mut self.info_tools)
{
return t;
}
if ui.primary.current_selection.is_none() && self.menu.action("start time traveling") {
return self.time_travel.start(ctx, ui);
}
if self.menu.action("scoreboard") {
if self.info_tools.action("scoreboard") {
return Transition::Push(Box::new(score::Scoreboard::new(ctx, ui)));
}
@ -233,6 +242,7 @@ impl State for SandboxMode {
self.common.draw(g, ui);
self.menu.draw(g);
self.speed.draw(g);
self.info_tools.draw(g);
}
fn on_suspend(&mut self, ctx: &mut EventCtx, _: &mut UI) {

View File

@ -28,6 +28,7 @@ impl UI {
("assets/ui/hamburger.png", TextureType::Stretch),
("assets/ui/hide.png", TextureType::Stretch),
("assets/ui/info.png", TextureType::Stretch),
("assets/ui/location.png", TextureType::Stretch),
("assets/ui/show.png", TextureType::Stretch),
];
let skip_textures = if flags.textures {