making a View mode, with mostly ambient/stackable plugins

This commit is contained in:
Dustin Carlino 2018-12-05 20:00:58 -08:00
parent 7cac74457c
commit 5b6cdf24bd
11 changed files with 164 additions and 164 deletions

View File

@ -136,10 +136,25 @@ and probably step 2...
- each of the editors can stop having inactive state. have new() that returns option
- the permanent ones (hider and toggleable layers) shouldnt even implement Plugin; theyre custom weirdness
- make a single 'Mode' for normal exploration
- the blocking ones: warp
- the ambient ones: debug objects, follow, neighborhood summary, show activity, show owner, show route, turn cycler
- still represent the inactive state? for now, sure
- have to solve the problem of overlapping keys to quit
- what is search? should it be ambient or not?
- dont forget neighborhood summary
- this has to be completely per UI or completely per map
- let a bunch of plugins run non-exclusively there, as relevant
- AmbientPlugin trait, maybe? or maybe just explicitly call on each field in order
- and still have a single blocking plugin possible, like warp
- rewrite turn_cycler; i dont understand it. also it used to block input after starting to tab through stuff. weird?
thursday pick-up:
- neighborhood summary
- search (sometimes ambient, sometimes blocking)
- warp (blocking)
- overlapping keys to quit stuff...
and step 3...
- dismantle the plugin abstraction in UI and probably also the trait. do something different for modes.
@ -147,3 +162,5 @@ and step 3...
- use Escape to quit most plugins, since it'll only be callable normally from some modes
- make it more clear that keys cant overlap... in each mode, specify the trigger key it uses?
- except some of them are more conditional and that makes overlap fine
- can we get rid of PluginsPerUI almost? since we'll likely get rid of plugins entirely... yeah?
- view and debug mode can coexist!

View File

@ -1,20 +1,16 @@
pub mod debug;
pub mod debug_mode;
pub mod debug_objects;
pub mod diff_all;
pub mod diff_worlds;
pub mod edit;
pub mod edit_mode;
pub mod follow;
pub mod logs;
pub mod neighborhood_summary;
pub mod search;
pub mod show_activity;
pub mod show_owner;
pub mod show_route;
pub mod sim_controls;
pub mod time_travel;
pub mod turn_cycler;
pub mod view;
pub mod view_mode;
pub mod warp;
use abstutil;
@ -30,8 +26,12 @@ pub trait Plugin: Any {
fn color_for(&self, _obj: ID, _ctx: Ctx) -> Option<Color> {
None
}
fn new_color_for(&self, _obj: ID, _ctx: &mut Ctx) -> Option<Color> {
None
}
fn draw(&self, _g: &mut GfxCtx, _ctx: Ctx) {}
fn new_draw(&self, _g: &mut GfxCtx, _ctx: &mut Ctx) {}
// True if active
fn event(&mut self, _ctx: PluginCtx) -> bool {
@ -42,6 +42,8 @@ pub trait Plugin: Any {
fn new_event(&mut self, _ctx: &mut PluginCtx) -> bool {
false
}
fn ambient_event(&mut self, _ctx: &mut PluginCtx) {}
}
downcast!(Plugin);

View File

@ -1,84 +1,57 @@
use ezgui::{Color, GfxCtx, Text, TEXT_FG_COLOR};
use map_model::Map;
use objects::{Ctx, ID};
use objects::{Ctx, DEBUG, ID};
use piston::input::Key;
use plugins::{Plugin, PluginCtx};
use render::DrawMap;
use sim::Sim;
pub enum DebugObjectsState {
Empty,
Selected(ID),
Tooltip(ID),
pub struct DebugObjectsState {
control_held: bool,
selected: Option<ID>,
}
impl DebugObjectsState {
pub fn new() -> DebugObjectsState {
DebugObjectsState::Empty
DebugObjectsState {
control_held: false,
selected: None,
}
}
}
impl Plugin for DebugObjectsState {
fn event(&mut self, ctx: PluginCtx) -> bool {
let new_state = if let Some(id) = ctx.primary.current_selection {
// Don't break out of the tooltip state
if let DebugObjectsState::Tooltip(_) = self {
DebugObjectsState::Tooltip(id)
} else {
DebugObjectsState::Selected(id)
}
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
self.selected = ctx.primary.current_selection;
if self.control_held {
self.control_held = !ctx.input.key_released(Key::LCtrl);
} else {
DebugObjectsState::Empty
};
*self = new_state;
let mut new_state: Option<DebugObjectsState> = None;
match self {
DebugObjectsState::Empty => {}
DebugObjectsState::Selected(id) => {
if ctx
.input
.key_pressed(Key::LCtrl, &format!("Hold Ctrl to show {:?}'s tooltip", id))
{
new_state = Some(DebugObjectsState::Tooltip(*id));
} else if ctx.input.key_pressed(Key::D, "debug") {
id.debug(
&ctx.primary.map,
&mut ctx.primary.sim,
&ctx.primary.draw_map,
);
}
}
DebugObjectsState::Tooltip(id) => {
if ctx.input.key_released(Key::LCtrl) {
new_state = Some(DebugObjectsState::Selected(*id));
}
}
};
if let Some(s) = new_state {
*self = s;
// TODO Can't really display an OSD action if we're not currently selecting something.
// Could only activate sometimes, but that seems a bit harder to use.
self.control_held =
ctx.input
.unimportant_key_pressed(Key::LCtrl, DEBUG, "hold Ctrl to show tooltips");
}
match self {
DebugObjectsState::Empty => false,
// TODO hmm, but when we press D to debug, we don't want other stuff to happen...
DebugObjectsState::Selected(_) => false,
DebugObjectsState::Tooltip(_) => true,
if let Some(id) = self.selected {
if ctx.input.key_pressed(Key::D, "debug") {
id.debug(
&ctx.primary.map,
&mut ctx.primary.sim,
&ctx.primary.draw_map,
);
}
}
}
fn draw(&self, g: &mut GfxCtx, ctx: Ctx) {
match *self {
DebugObjectsState::Empty => {}
DebugObjectsState::Selected(_) => {}
DebugObjectsState::Tooltip(id) => {
ctx.canvas
.draw_mouse_tooltip(g, tooltip_lines(id, ctx.map, ctx.sim, ctx.draw_map));
fn new_draw(&self, g: &mut GfxCtx, ctx: &mut Ctx) {
if self.control_held {
if let Some(id) = self.selected {
ctx.canvas.draw_mouse_tooltip(g, tooltip_lines(id, ctx));
}
}
}
}
fn tooltip_lines(obj: ID, map: &Map, sim: &Sim, draw_map: &DrawMap) -> Text {
fn tooltip_lines(obj: ID, ctx: &Ctx) -> Text {
let (map, sim, draw_map) = (&ctx.map, &ctx.sim, &ctx.draw_map);
let mut txt = Text::new();
match obj {
ID::Lane(id) => {

View File

@ -2,34 +2,29 @@ use piston::input::Key;
use plugins::{Plugin, PluginCtx};
use sim::TripID;
// TODO woops, all the plugins that work off of trips now don't work for buses. :(
#[derive(PartialEq)]
pub enum FollowState {
Empty,
Active(TripID),
pub struct FollowState {
trip: Option<TripID>,
}
impl FollowState {
pub fn new() -> FollowState {
FollowState::Empty
FollowState { trip: None }
}
}
impl Plugin for FollowState {
fn event(&mut self, ctx: PluginCtx) -> bool {
if *self == FollowState::Empty {
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
if self.trip.is_none() {
if let Some(agent) = ctx.primary.current_selection.and_then(|id| id.agent_id()) {
if let Some(trip) = ctx.primary.sim.agent_to_trip(agent) {
if ctx.input.key_pressed(Key::F, &format!("follow {}", agent)) {
*self = FollowState::Active(trip);
return true;
self.trip = Some(trip);
}
}
}
}
let mut quit = false;
if let FollowState::Active(trip) = self {
if let Some(trip) = self.trip {
if let Some(pt) = ctx.primary.sim.get_stats().canonical_pt_per_trip.get(&trip) {
ctx.canvas.center_on_map_pt(*pt);
} else {
@ -37,14 +32,9 @@ impl Plugin for FollowState {
// get_canonical_point_for_trip
warn!("{} is gone... temporarily or not?", trip);
}
quit = ctx.input.key_pressed(Key::Return, "stop following");
};
if quit {
*self = FollowState::Empty;
}
match self {
FollowState::Empty => false,
_ => true,
if ctx.input.key_pressed(Key::Return, "stop following") {
self.trip = None;
}
}
}
}

View File

@ -0,0 +1,6 @@
pub mod debug_objects;
pub mod follow;
pub mod show_activity;
pub mod show_owner;
pub mod show_route;
pub mod turn_cycler;

View File

@ -17,7 +17,7 @@ impl ShowActivityState {
}
impl Plugin for ShowActivityState {
fn event(&mut self, ctx: PluginCtx) -> bool {
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
let mut new_state: Option<ShowActivityState> = None;
match self {
ShowActivityState::Inactive => {
@ -51,13 +51,9 @@ impl Plugin for ShowActivityState {
if let Some(s) = new_state {
*self = s;
}
match self {
ShowActivityState::Inactive => false,
_ => true,
}
}
fn draw(&self, g: &mut GfxCtx, _ctx: Ctx) {
fn new_draw(&self, g: &mut GfxCtx, _ctx: &mut Ctx) {
if let ShowActivityState::Active(_, ref heatmap) = self {
heatmap.draw(g);
}

View File

@ -21,7 +21,7 @@ impl ShowOwnerState {
}
impl Plugin for ShowOwnerState {
fn event(&mut self, ctx: PluginCtx) -> bool {
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
let (selected, sim) = (ctx.primary.current_selection, &ctx.primary.sim);
// Reset to Inactive when appropriate
@ -70,12 +70,9 @@ impl Plugin for ShowOwnerState {
if let Some(s) = new_state {
*self = s;
}
// TODO This is a weird exception -- this plugin doesn't consume input, so never treat it
// as active for blocking input
false
}
fn color_for(&self, obj: ID, ctx: Ctx) -> Option<Color> {
fn new_color_for(&self, obj: ID, ctx: &mut Ctx) -> Option<Color> {
let color = ctx.cs.get("car/building owner", Color::PURPLE);
match (self, obj) {
(ShowOwnerState::BuildingSelected(_, cars), ID::Car(id)) => {

View File

@ -20,7 +20,7 @@ impl ShowRouteState {
}
impl Plugin for ShowRouteState {
fn event(&mut self, ctx: PluginCtx) -> bool {
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
let mut new_state: Option<ShowRouteState> = None;
match self {
@ -62,14 +62,9 @@ impl Plugin for ShowRouteState {
if let Some(s) = new_state {
*self = s;
}
match self {
ShowRouteState::Inactive => false,
_ => true,
}
}
fn draw(&self, g: &mut GfxCtx, ctx: Ctx) {
fn new_draw(&self, g: &mut GfxCtx, ctx: &mut Ctx) {
match self {
ShowRouteState::Active(_, _, Some(trace)) => {
g.draw_polygon(
@ -90,7 +85,7 @@ impl Plugin for ShowRouteState {
}
}
fn show_route(trip: TripID, ctx: PluginCtx) -> ShowRouteState {
fn show_route(trip: TripID, ctx: &mut PluginCtx) -> ShowRouteState {
let time = ctx.primary.sim.time;
if let Some(agent) = ctx.primary.sim.trip_to_agent(trip) {
// Trace along the entire route by passing in max distance
@ -113,7 +108,7 @@ fn show_route(trip: TripID, ctx: PluginCtx) -> ShowRouteState {
}
}
fn debug_all_routes(ctx: PluginCtx) -> ShowRouteState {
fn debug_all_routes(ctx: &mut PluginCtx) -> ShowRouteState {
let sim = &ctx.primary.sim;
let mut traces: Vec<Trace> = Vec::new();
for trip in sim.get_stats().canonical_pt_per_trip.keys() {

View File

@ -8,6 +8,7 @@ use render::{draw_signal_cycle, draw_stop_sign, stop_sign_rendering_hints, DrawT
#[derive(Clone, Debug)]
pub enum TurnCyclerState {
// TODO Can probably simplify this?
Inactive,
Active(LaneID, Option<usize>),
Intersection(IntersectionID),
@ -20,10 +21,8 @@ impl TurnCyclerState {
}
impl Plugin for TurnCyclerState {
fn event(&mut self, mut ctx: PluginCtx) -> bool {
let (input, selected) = (ctx.input, ctx.primary.current_selection);
let current_id = match selected {
fn ambient_event(&mut self, ctx: &mut PluginCtx) {
let current_id = match ctx.primary.current_selection {
Some(ID::Lane(id)) => id,
Some(ID::Intersection(id)) => {
*self = TurnCyclerState::Intersection(id);
@ -38,11 +37,11 @@ impl Plugin for TurnCyclerState {
} else if let Some(sign) = ctx.primary.map.maybe_get_stop_sign(id) {
stop_sign_rendering_hints(&mut ctx.hints, sign, &ctx.primary.map, ctx.cs);
}
return false;
return;
}
_ => {
*self = TurnCyclerState::Inactive;
return false;
return;
}
};
@ -54,7 +53,10 @@ impl Plugin for TurnCyclerState {
TurnCyclerState::Active(old_id, current_turn_index) => {
if current_id != *old_id {
new_state = Some(TurnCyclerState::Inactive);
} else if input.key_pressed(Key::Tab, "cycle through this lane's turns") {
} else if ctx
.input
.key_pressed(Key::Tab, "cycle through this lane's turns")
{
let idx = match *current_turn_index {
Some(i) => i + 1,
None => 0,
@ -66,15 +68,9 @@ impl Plugin for TurnCyclerState {
if let Some(s) = new_state {
*self = s;
}
match self {
TurnCyclerState::Inactive => false,
// Only once they start tabbing through turns does this plugin block other input.
TurnCyclerState::Active(_, current_turn_index) => current_turn_index.is_some(),
TurnCyclerState::Intersection(_) => false,
}
}
fn draw(&self, g: &mut GfxCtx, ctx: Ctx) {
fn new_draw(&self, g: &mut GfxCtx, ctx: &mut Ctx) {
match self {
TurnCyclerState::Inactive => {}
TurnCyclerState::Active(l, current_turn_index) => {
@ -145,7 +141,7 @@ impl Plugin for TurnCyclerState {
}
}
fn color_for(&self, obj: ID, ctx: Ctx) -> Option<Color> {
fn new_color_for(&self, obj: ID, ctx: &mut Ctx) -> Option<Color> {
match (self, obj) {
(TurnCyclerState::Active(l, Some(idx)), ID::Turn(t)) => {
// Quickly prune irrelevant lanes

View File

@ -0,0 +1,49 @@
use ezgui::{Color, GfxCtx};
use objects::{Ctx, ID};
use plugins;
use plugins::{Plugin, PluginCtx};
pub struct ViewMode {
ambient_plugins: Vec<Box<Plugin>>,
}
impl ViewMode {
pub fn new() -> ViewMode {
ViewMode {
ambient_plugins: vec![
Box::new(plugins::view::follow::FollowState::new()),
Box::new(plugins::view::debug_objects::DebugObjectsState::new()),
Box::new(plugins::view::show_activity::ShowActivityState::new()),
Box::new(plugins::view::show_owner::ShowOwnerState::new()),
Box::new(plugins::view::show_route::ShowRouteState::new()),
Box::new(plugins::view::turn_cycler::TurnCyclerState::new()),
],
}
}
}
impl Plugin for ViewMode {
fn event(&mut self, mut ctx: PluginCtx) -> bool {
for p in self.ambient_plugins.iter_mut() {
p.ambient_event(&mut ctx);
}
false
}
fn draw(&self, g: &mut GfxCtx, mut ctx: Ctx) {
for p in &self.ambient_plugins {
p.new_draw(g, &mut ctx);
}
}
fn color_for(&self, obj: ID, mut ctx: Ctx) -> Option<Color> {
// First one arbitrarily wins.
// TODO Maybe none of these actually do this?
for p in &self.ambient_plugins {
if let Some(c) = p.new_color_for(obj, &mut ctx) {
return Some(c);
}
}
None
}
}

View File

@ -116,33 +116,22 @@ impl GUI<RenderingHints> for UI {
);
}
if let Some(p) = self.get_active_plugin() {
p.draw(
g,
Ctx {
cs: &mut self.cs.borrow_mut(),
map: &self.primary.map,
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
},
);
} else {
// TODO Ew, this is a weird ambient plugin that doesn't consume input but might want to
// draw stuff... only if another plugin isn't already active (aka, this is a hack to
// turn this off when traffic signal editor is on.)
self.primary_plugins.turn_cycler().draw(
g,
Ctx {
cs: &mut self.cs.borrow_mut(),
map: &self.primary.map,
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
},
);
// TODO nll
{
let ctx = Ctx {
cs: &mut self.cs.borrow_mut(),
map: &self.primary.map,
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
};
if let Some(p) = self.get_active_plugin() {
p.draw(g, ctx);
} else {
// If no other mode was active, give the ambient plugins in ViewMode a chance.
self.primary_plugins.view_mode().draw(g, ctx);
}
}
self.canvas.draw_text(g, hints.osd, BOTTOM_LEFT);
@ -172,16 +161,12 @@ impl PluginsPerMap {
self.list[0].downcast_ref::<DebugMode>().unwrap()
}
fn show_owner(&self) -> &Box<Plugin> {
fn view_mode(&self) -> &Box<Plugin> {
&self.list[1]
}
fn turn_cycler(&self) -> &Box<Plugin> {
&self.list[2]
}
fn time_travel(&self) -> &TimeTravel {
self.list[3].downcast_ref::<TimeTravel>().unwrap()
self.list[2].downcast_ref::<TimeTravel>().unwrap()
}
fn layers(&self) -> &ToggleableLayers {
@ -234,13 +219,8 @@ impl PerMapUI {
let plugins = PluginsPerMap {
list: vec![
Box::new(debug_mode),
Box::new(plugins::show_owner::ShowOwnerState::new()),
Box::new(plugins::turn_cycler::TurnCyclerState::new()),
Box::new(plugins::view_mode::ViewMode::new()),
Box::new(plugins::time_travel::TimeTravel::new()),
Box::new(plugins::debug_objects::DebugObjectsState::new()),
Box::new(plugins::follow::FollowState::new()),
Box::new(plugins::show_route::ShowRouteState::new()),
Box::new(plugins::show_activity::ShowActivityState::new()),
Box::new(neighborhood_summary),
],
};
@ -424,12 +404,11 @@ impl UI {
hints,
};
if let Some(p) = self.get_active_plugin() {
return p.color_for(id, ctx);
p.color_for(id, ctx)
} else {
// If no other mode was active, give the ambient plugins in ViewMode a chance.
self.primary_plugins.view_mode().color_for(id, ctx)
}
// TODO Ew, this is a weird ambient plugin that doesn't consume input but has an opinion on
// color.
self.primary_plugins.show_owner().color_for(id, ctx)
}
fn get_active_plugin(&self) -> Option<&Box<Plugin>> {