refactor BulkSelect. use it for access-restricted zones too!

This commit is contained in:
Dustin Carlino 2020-07-14 14:20:40 -07:00
parent 2c27c89796
commit 5b7cf6d532
4 changed files with 210 additions and 186 deletions

View File

@ -1,4 +1,5 @@
use crate::app::App;
use crate::edit::select::RoadSelector;
use crate::edit::{apply_map_edits, change_speed_limit, try_change_lt};
use crate::game::{msg, State, Transition};
use ezgui::{
@ -7,15 +8,105 @@ use ezgui::{
};
use geom::Speed;
use map_model::{EditCmd, LaneType, RoadID};
use std::collections::BTreeSet;
pub struct BulkEdit {
pub struct BulkSelect {
composite: Composite,
selector: RoadSelector,
}
impl BulkSelect {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State> {
let selector = RoadSelector::new(app, BTreeSet::new());
let composite = make_select_composite(ctx, app, &selector);
Box::new(BulkSelect {
composite,
selector,
})
}
}
fn make_select_composite(ctx: &mut EventCtx, app: &App, selector: &RoadSelector) -> Composite {
Composite::new(Widget::col(vec![
Line("Edit many roads").small_heading().draw(ctx),
selector.make_controls(ctx),
Widget::row(vec![
if selector.roads.is_empty() {
Btn::text_fg("Edit 0 roads").inactive(ctx)
} else {
Btn::text_fg(format!("Edit {} roads", selector.roads.len())).build(
ctx,
"edit roads",
hotkey(Key::E),
)
},
if app.opts.dev {
Btn::text_fg(format!(
"Export {} roads to shared-row",
selector.roads.len()
))
.build(ctx, "export roads to shared-row", None)
} else {
Widget::nothing()
},
Btn::text_fg("Cancel").build_def(ctx, hotkey(Key::Escape)),
])
.evenly_spaced(),
]))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}
impl State for BulkSelect {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"Cancel" => {
return Transition::Pop;
}
"edit roads" => {
return Transition::Replace(crate::edit::bulk::BulkEdit::new(
ctx,
self.selector.roads.iter().cloned().collect(),
self.selector.preview.take().unwrap(),
));
}
"export roads to shared-row" => {
crate::debug::shared_row::export(
self.selector.roads.iter().cloned().collect(),
&app.primary.map,
);
}
x => {
if self.selector.event(ctx, app, Some(x)) {
self.composite = make_select_composite(ctx, app, &self.selector);
}
}
},
None => {
if self.selector.event(ctx, app, None) {
self.composite = make_select_composite(ctx, app, &self.selector);
}
}
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
self.composite.draw(g);
self.selector.draw(g, app, true);
}
}
struct BulkEdit {
composite: Composite,
roads: Vec<RoadID>,
preview: Drawable,
}
impl BulkEdit {
pub fn new(ctx: &mut EventCtx, roads: Vec<RoadID>, preview: Drawable) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, roads: Vec<RoadID>, preview: Drawable) -> Box<dyn State> {
Box::new(BulkEdit {
composite: Composite::new(Widget::col(vec![
Line(format!("Editing {} roads", roads.len()))

View File

@ -160,7 +160,7 @@ impl State for EditMode {
match self.top_center.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"bulk edit" => {
return Transition::Push(select::BulkSelect::new(ctx, app));
return Transition::Push(bulk::BulkSelect::new(ctx, app));
}
"finish editing" => {
return self.quit(ctx, app);

View File

@ -1,21 +1,16 @@
use crate::app::{App, ShowEverything};
use crate::common::CommonState;
use crate::game::{State, Transition};
use crate::helpers::{intersections_from_roads, ID};
use ezgui::{
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, RewriteColor, VerticalAlignment, Widget,
};
use ezgui::{hotkey, Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, RewriteColor, Widget};
use geom::Distance;
use map_model::{IntersectionID, Map, RoadID};
use petgraph::graphmap::UnGraphMap;
use sim::DontDrawAgents;
use std::collections::BTreeSet;
pub struct BulkSelect {
composite: Composite,
roads: BTreeSet<RoadID>,
preview: Option<Drawable>,
pub struct RoadSelector {
pub roads: BTreeSet<RoadID>,
pub preview: Option<Drawable>,
mode: Mode,
dragging: bool,
}
@ -30,16 +25,65 @@ pub enum Mode {
Erase,
}
impl BulkSelect {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State> {
impl RoadSelector {
pub fn new(app: &mut App, roads: BTreeSet<RoadID>) -> RoadSelector {
app.primary.current_selection = None;
Box::new(BulkSelect {
composite: make_composite(ctx, app, &Mode::Paint, &BTreeSet::new()),
roads: BTreeSet::new(),
RoadSelector {
roads,
preview: None,
mode: Mode::Paint,
dragging: false,
})
}
}
pub fn make_controls(&self, ctx: &mut EventCtx) -> Widget {
Widget::custom_row(vec![
if let Mode::Paint = self.mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/pencil.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/pencil.svg").build(ctx, "paint", hotkey(Key::P))
},
if let Mode::Erase = self.mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/eraser.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/eraser.svg").build(
ctx,
"erase",
hotkey(Key::Backspace),
)
},
if let Mode::Route { .. } = self.mode {
Widget::draw_svg_transform(
ctx,
"system/assets/timeline/start_pos.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/timeline/start_pos.svg").build(
ctx,
"select along route",
hotkey(Key::R),
)
},
if let Mode::Pan = self.mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/pan.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/pan.svg").build(ctx, "pan", hotkey(Key::Escape))
},
])
.evenly_spaced()
}
fn roads_changed(&mut self, ctx: &mut EventCtx, app: &App) {
@ -60,12 +104,10 @@ impl BulkSelect {
);
}
self.preview = Some(ctx.upload(batch));
self.composite = make_composite(ctx, app, &self.mode, &self.roads);
}
}
impl State for BulkSelect {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
// Pass it Outcome::Clicked. Returns true if anything changed.
pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App, clicked: Option<&str>) -> bool {
if ctx.redo_mouseover() {
app.primary.current_selection = app.calculate_current_selection(
ctx,
@ -138,27 +180,28 @@ impl State for BulkSelect {
};
if change {
self.roads_changed(ctx, app);
return true;
}
}
}
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
match clicked {
Some(x) => match x {
"paint" => {
self.dragging = false;
self.mode = Mode::Paint;
self.composite = make_composite(ctx, app, &self.mode, &self.roads);
return true;
}
"erase" => {
self.dragging = false;
self.mode = Mode::Erase;
self.composite = make_composite(ctx, app, &self.mode, &self.roads);
return true;
}
"pan" => {
app.primary.current_selection = None;
self.dragging = false;
self.mode = Mode::Pan;
self.composite = make_composite(ctx, app, &self.mode, &self.roads);
return true;
}
"select along route" => {
app.primary.current_selection = None;
@ -167,23 +210,7 @@ impl State for BulkSelect {
i1: None,
preview_path: None,
};
self.composite = make_composite(ctx, app, &self.mode, &self.roads);
}
"Cancel" => {
return Transition::Pop;
}
"edit roads" => {
return Transition::Replace(crate::edit::bulk::BulkEdit::new(
ctx,
self.roads.iter().cloned().collect(),
self.preview.take().unwrap(),
));
}
"export roads to shared-row" => {
crate::debug::shared_row::export(
self.roads.iter().cloned().collect(),
&app.primary.map,
);
return true;
}
_ => unreachable!(),
},
@ -243,6 +270,7 @@ impl State for BulkSelect {
self.roads.extend(roads);
self.mode = Mode::Pan;
self.roads_changed(ctx, app);
return true;
}
} else {
*preview_path = None;
@ -250,13 +278,14 @@ impl State for BulkSelect {
}
}
Transition::Keep
false
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
self.composite.draw(g);
if let Some(ref p) = self.preview {
g.redraw(p);
pub fn draw(&self, g: &mut GfxCtx, app: &App, show_preview: bool) {
if show_preview {
if let Some(ref p) = self.preview {
g.redraw(p);
}
}
if g.canvas.get_cursor_in_map_space().is_some() {
if let Some(cursor) = match self.mode {
@ -294,88 +323,6 @@ impl State for BulkSelect {
}
}
fn make_composite(
ctx: &mut EventCtx,
app: &App,
mode: &Mode,
roads: &BTreeSet<RoadID>,
) -> Composite {
Composite::new(Widget::col(vec![
Line("Edit many roads").small_heading().draw(ctx),
Widget::custom_row(vec![
if let Mode::Paint = mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/pencil.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/pencil.svg").build(ctx, "paint", hotkey(Key::P))
},
if let Mode::Erase = mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/eraser.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/eraser.svg").build(
ctx,
"erase",
hotkey(Key::Backspace),
)
},
if let Mode::Route { .. } = mode {
Widget::draw_svg_transform(
ctx,
"system/assets/timeline/start_pos.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/timeline/start_pos.svg").build(
ctx,
"select along route",
hotkey(Key::R),
)
},
if let Mode::Pan = mode {
Widget::draw_svg_transform(
ctx,
"system/assets/tools/pan.svg",
RewriteColor::ChangeAll(Color::hex("#4CA7E9")),
)
} else {
Btn::svg_def("system/assets/tools/pan.svg").build(ctx, "pan", hotkey(Key::Escape))
},
])
.evenly_spaced(),
Widget::row(vec![
if roads.is_empty() {
Btn::text_fg("Edit 0 roads").inactive(ctx)
} else {
Btn::text_fg(format!("Edit {} roads", roads.len())).build(
ctx,
"edit roads",
hotkey(Key::E),
)
},
if app.opts.dev {
Btn::text_fg(format!("Export {} roads to shared-row", roads.len())).build(
ctx,
"export roads to shared-row",
None,
)
} else {
Widget::nothing()
},
Btn::text_fg("Cancel").build_def(ctx, hotkey(Key::Escape)),
])
.evenly_spaced(),
]))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}
// Simple search along undirected roads
fn pathfind(map: &Map, i1: IntersectionID, i2: IntersectionID) -> Option<Vec<RoadID>> {
let mut graph: UnGraphMap<IntersectionID, RoadID> = UnGraphMap::new();

View File

@ -1,9 +1,10 @@
use crate::app::{App, ShowEverything};
use crate::app::App;
use crate::common::ColorDiscrete;
use crate::common::CommonState;
use crate::edit::apply_map_edits;
use crate::edit::select::RoadSelector;
use crate::game::{State, Transition};
use crate::helpers::{checkbox_per_mode, intersections_from_roads, ID};
use crate::helpers::{checkbox_per_mode, intersections_from_roads};
use enumset::EnumSet;
use ezgui::{
hotkey, Btn, Color, Composite, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line,
@ -11,12 +12,12 @@ use ezgui::{
};
use map_model::{EditCmd, RoadID};
use maplit::btreeset;
use sim::{DontDrawAgents, TripMode};
use sim::TripMode;
use std::collections::BTreeSet;
pub struct ZoneEditor {
composite: Composite,
members: BTreeSet<RoadID>,
selector: RoadSelector,
allow_through_traffic: BTreeSet<TripMode>,
unzoomed: Drawable,
zoomed: Drawable,
@ -25,7 +26,7 @@ pub struct ZoneEditor {
}
impl ZoneEditor {
pub fn new(ctx: &mut EventCtx, app: &App, start: RoadID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State> {
let start = app.primary.map.get_r(start);
let members = if let Some(z) = start.get_zone(&app.primary.map) {
z.members.clone()
@ -40,12 +41,15 @@ impl ZoneEditor {
.collect();
let (unzoomed, zoomed, legend) = draw_zone(ctx, app, &members);
let orig_members = members.clone();
let selector = RoadSelector::new(app, members);
Box::new(ZoneEditor {
composite: Composite::new(Widget::col(vec![
Line("Editing restricted access zone")
.small_heading()
.draw(ctx),
selector.make_controls(ctx).named("selector"),
legend,
make_instructions(ctx, &allow_through_traffic),
checkbox_per_mode(ctx, app, &allow_through_traffic),
@ -57,8 +61,8 @@ impl ZoneEditor {
]))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx),
orig_members: members.clone(),
members,
orig_members,
selector,
allow_through_traffic,
unzoomed,
zoomed,
@ -66,57 +70,14 @@ impl ZoneEditor {
}
}
// TODO Handle splitting/merging zones.
impl State for ZoneEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
// TODO Share with PaintSelect.
if ctx.redo_mouseover() {
app.primary.current_selection = app.calculate_current_selection(
ctx,
&DontDrawAgents {},
&ShowEverything::new(),
false,
true,
false,
);
if let Some(ID::Road(_)) = app.primary.current_selection {
} else if let Some(ID::Lane(l)) = app.primary.current_selection {
app.primary.current_selection = Some(ID::Road(app.primary.map.get_l(l).parent));
} else {
app.primary.current_selection = None;
}
if let Some(ID::Road(r)) = app.primary.current_selection {
if app.primary.map.get_r(r).is_light_rail() {
app.primary.current_selection = None;
}
}
}
// TODO Need to figure out merging/splitting zones.
if let Some(ID::Road(r)) = app.primary.current_selection {
if self.members.contains(&r) {
if app.per_obj.left_click(ctx, "remove road from zone") {
self.members.remove(&r);
let (unzoomed, zoomed, _) = draw_zone(ctx, app, &self.members);
self.unzoomed = unzoomed;
self.zoomed = zoomed;
}
} else {
if app.per_obj.left_click(ctx, "add road to zone") {
self.members.insert(r);
let (unzoomed, zoomed, _) = draw_zone(ctx, app, &self.members);
self.unzoomed = unzoomed;
self.zoomed = zoomed;
}
}
}
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"Apply" => {
let mut edits = app.primary.map.get_edits().clone();
for r in self.orig_members.difference(&self.members) {
for r in self.orig_members.difference(&self.selector.roads) {
edits.commands.push(EditCmd::ChangeAccessRestrictions {
id: *r,
old_allow_through_traffic: app
@ -134,7 +95,7 @@ impl State for ZoneEditor {
.iter()
.map(|m| m.to_constraints())
.collect::<EnumSet<_>>();
for r in &self.members {
for r in &self.selector.roads {
let old_allow_through_traffic =
app.primary.map.get_r(*r).allow_through_traffic.clone();
if old_allow_through_traffic != new_allow_through_traffic {
@ -152,9 +113,33 @@ impl State for ZoneEditor {
"Cancel" => {
return Transition::Pop;
}
_ => unreachable!(),
x => {
if self.selector.event(ctx, app, Some(x)) {
let new_controls = self
.selector
.make_controls(ctx)
.named("selector")
.margin_below(10);
self.composite.replace(ctx, "selector", new_controls);
let (unzoomed, zoomed, _) = draw_zone(ctx, app, &self.selector.roads);
self.unzoomed = unzoomed;
self.zoomed = zoomed;
}
}
},
None => {}
None => {
if self.selector.event(ctx, app, None) {
let new_controls = self
.selector
.make_controls(ctx)
.named("selector")
.margin_below(10);
self.composite.replace(ctx, "selector", new_controls);
let (unzoomed, zoomed, _) = draw_zone(ctx, app, &self.selector.roads);
self.unzoomed = unzoomed;
self.zoomed = zoomed;
}
}
}
let mut new_allow_through_traffic = BTreeSet::new();
@ -180,6 +165,7 @@ impl State for ZoneEditor {
g.redraw(&self.zoomed);
}
self.composite.draw(g);
self.selector.draw(g, app, false);
CommonState::draw_osd(g, app);
}
}