mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-18 03:41:52 +03:00
Lift the freehand filter tool as a main edit mode.
We need a way to reset EditMode when we leave the state, no matter how it happens. There are some bugs now with fiddling with proposals in the middle of drawing!
This commit is contained in:
parent
ad5ce6c088
commit
8580485a36
@ -31,6 +31,9 @@ impl BrowseNeighbourhoods {
|
||||
if let EditMode::Shortcuts(ref mut maybe_focus) = app.session.edit_mode {
|
||||
*maybe_focus = None;
|
||||
}
|
||||
if let EditMode::FreehandFilters(_) = app.session.edit_mode {
|
||||
app.session.edit_mode = EditMode::Filters;
|
||||
}
|
||||
|
||||
let (world, draw_over_roads) =
|
||||
ctx.loading_screen("calculate neighbourhoods", |ctx, timer| {
|
||||
|
@ -1,95 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use geom::PolyLine;
|
||||
use map_model::{IntersectionID, Perimeter};
|
||||
use widgetry::tools::PolyLineLasso;
|
||||
use widgetry::{DrawBaselayer, EventCtx, GfxCtx, Key, Line, ScreenPt, State, Text, Widget};
|
||||
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighbourhood, Transition};
|
||||
|
||||
pub struct FreehandFilters {
|
||||
lasso: PolyLineLasso,
|
||||
perimeter: Perimeter,
|
||||
interior_intersections: BTreeSet<IntersectionID>,
|
||||
instructions: Text,
|
||||
instructions_at: ScreenPt,
|
||||
}
|
||||
|
||||
impl FreehandFilters {
|
||||
pub fn new_state(
|
||||
ctx: &EventCtx,
|
||||
neighbourhood: &Neighbourhood,
|
||||
instructions_at: ScreenPt,
|
||||
) -> Box<dyn State<App>> {
|
||||
Box::new(Self {
|
||||
lasso: PolyLineLasso::new(),
|
||||
perimeter: neighbourhood.orig_perimeter.clone(),
|
||||
interior_intersections: neighbourhood.interior_intersections.clone(),
|
||||
instructions_at,
|
||||
instructions: Text::from_all(vec![
|
||||
Line("Click and drag").fg(ctx.style().text_hotkey_color),
|
||||
Line(" across the roads you want to filter"),
|
||||
]),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn button(ctx: &EventCtx) -> Widget {
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.icon_text(
|
||||
"system/assets/tools/select.svg",
|
||||
"Create filters along a shape",
|
||||
)
|
||||
.hotkey(Key::F)
|
||||
.build_def(ctx)
|
||||
}
|
||||
|
||||
fn make_filters_along_path(&self, ctx: &mut EventCtx, app: &mut App, path: PolyLine) {
|
||||
app.session.modal_filters.before_edit();
|
||||
for r in &self.perimeter.interior {
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
let road = app.map.get_r(*r);
|
||||
// Don't show error messages
|
||||
if road.oneway_for_driving().is_some() || road.is_deadend_for_driving(&app.map) {
|
||||
continue;
|
||||
}
|
||||
if let Some((pt, _)) = road.center_pts.intersection(&path) {
|
||||
let dist = road
|
||||
.center_pts
|
||||
.dist_along_of_point(pt)
|
||||
.map(|pair| pair.0)
|
||||
.unwrap_or(road.center_pts.length() / 2.0);
|
||||
app.session.modal_filters.roads.insert(*r, dist);
|
||||
}
|
||||
}
|
||||
for i in &self.interior_intersections {
|
||||
if app.map.get_i(*i).polygon.intersects_polyline(&path) {
|
||||
// We probably won't guess the right one, but make an attempt
|
||||
DiagonalFilter::cycle_through_alternatives(app, *i);
|
||||
}
|
||||
}
|
||||
after_edit(ctx, app);
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for FreehandFilters {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
if let Some(pl) = self.lasso.event(ctx) {
|
||||
self.make_filters_along_path(ctx, app, pl);
|
||||
return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.lasso.draw(g);
|
||||
// Hacky, but just draw instructions over the other panel
|
||||
g.draw_tooltip_at(self.instructions.clone(), self.instructions_at);
|
||||
}
|
||||
|
||||
fn draw_baselayer(&self) -> DrawBaselayer {
|
||||
DrawBaselayer::PreviousState
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
mod about;
|
||||
mod freehand_filters;
|
||||
mod left_panel;
|
||||
mod top_panel;
|
||||
|
||||
pub use freehand_filters::FreehandFilters;
|
||||
pub use left_panel::LeftPanel;
|
||||
pub use top_panel::TopPanel;
|
||||
|
@ -144,13 +144,10 @@ impl State<App> for Viewer {
|
||||
return Transition::Keep;
|
||||
}
|
||||
|
||||
match self.edit.handle_panel_action(
|
||||
ctx,
|
||||
app,
|
||||
x.as_ref(),
|
||||
&self.neighbourhood,
|
||||
&self.left_panel,
|
||||
) {
|
||||
match self
|
||||
.edit
|
||||
.handle_panel_action(ctx, app, x.as_ref(), &self.neighbourhood)
|
||||
{
|
||||
// Fall through to AltProposals
|
||||
EditOutcome::Nothing => {}
|
||||
EditOutcome::UpdatePanelAndWorld => {
|
||||
@ -235,6 +232,10 @@ impl State<App> for Viewer {
|
||||
if self.left_panel.currently_hovering() == Some(&"warning".to_string()) {
|
||||
g.redraw(&self.show_error);
|
||||
}
|
||||
|
||||
if let EditMode::FreehandFilters(ref lasso) = app.session.edit_mode {
|
||||
lasso.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
|
@ -7,19 +7,16 @@ use super::{EditOutcome, Obj};
|
||||
use crate::{after_edit, colors, App, DiagonalFilter, Neighbourhood};
|
||||
|
||||
pub fn widget(ctx: &mut EventCtx) -> Widget {
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Image::from_path("system/assets/tools/pencil.svg")
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
Text::from(Line(
|
||||
"Click a road or intersection to add or remove a modal filter",
|
||||
))
|
||||
.wrap_to_pct(ctx, 15)
|
||||
.into_widget(ctx),
|
||||
]),
|
||||
crate::components::FreehandFilters::button(ctx),
|
||||
])
|
||||
Widget::col(vec![Widget::row(vec![
|
||||
Image::from_path("system/assets/tools/pencil.svg")
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
Text::from(Line(
|
||||
"Click a road or intersection to add or remove a modal filter",
|
||||
))
|
||||
.wrap_to_pct(ctx, 15)
|
||||
.into_widget(ctx),
|
||||
])])
|
||||
}
|
||||
|
||||
/// Creates clickable objects for managing filters on roads and intersections. Everything is
|
||||
|
63
apps/ltn/src/edit/freehand_filters.rs
Normal file
63
apps/ltn/src/edit/freehand_filters.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use geom::PolyLine;
|
||||
use widgetry::{EventCtx, Line, Text, Widget};
|
||||
|
||||
use crate::edit::{EditMode, EditOutcome};
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighbourhood, Transition};
|
||||
|
||||
pub fn widget(ctx: &mut EventCtx) -> Widget {
|
||||
Text::from_all(vec![
|
||||
Line("Click and drag").fg(ctx.style().text_hotkey_color),
|
||||
Line(" across the roads you want to filter"),
|
||||
])
|
||||
.into_widget(ctx)
|
||||
}
|
||||
|
||||
pub fn event(ctx: &mut EventCtx, app: &mut App, neighbourhood: &Neighbourhood) -> EditOutcome {
|
||||
if let EditMode::FreehandFilters(ref mut lasso) = app.session.edit_mode {
|
||||
if let Some(pl) = lasso.event(ctx) {
|
||||
make_filters_along_path(ctx, app, neighbourhood, pl);
|
||||
// Reset the tool
|
||||
app.session.edit_mode = EditMode::Filters;
|
||||
EditOutcome::Transition(Transition::Recreate)
|
||||
} else {
|
||||
// Do this instead of EditOutcome::Nothing to interrupt other processing
|
||||
EditOutcome::Transition(Transition::Keep)
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_filters_along_path(
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
neighbourhood: &Neighbourhood,
|
||||
path: PolyLine,
|
||||
) {
|
||||
app.session.modal_filters.before_edit();
|
||||
for r in &neighbourhood.orig_perimeter.interior {
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
let road = app.map.get_r(*r);
|
||||
// Don't show error messages
|
||||
if road.oneway_for_driving().is_some() || road.is_deadend_for_driving(&app.map) {
|
||||
continue;
|
||||
}
|
||||
if let Some((pt, _)) = road.center_pts.intersection(&path) {
|
||||
let dist = road
|
||||
.center_pts
|
||||
.dist_along_of_point(pt)
|
||||
.map(|pair| pair.0)
|
||||
.unwrap_or(road.center_pts.length() / 2.0);
|
||||
app.session.modal_filters.roads.insert(*r, dist);
|
||||
}
|
||||
}
|
||||
for i in &neighbourhood.interior_intersections {
|
||||
if app.map.get_i(*i).polygon.intersects_polyline(&path) {
|
||||
// We probably won't guess the right one, but make an attempt
|
||||
DiagonalFilter::cycle_through_alternatives(app, *i);
|
||||
}
|
||||
}
|
||||
after_edit(ctx, app);
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
mod filters;
|
||||
mod freehand_filters;
|
||||
mod one_ways;
|
||||
mod shortcuts;
|
||||
|
||||
use map_model::{IntersectionID, RoadID};
|
||||
use widgetry::mapspace::{ObjectID, World};
|
||||
use widgetry::tools::PopupMsg;
|
||||
use widgetry::tools::{PolyLineLasso, PopupMsg};
|
||||
use widgetry::{lctrl, EventCtx, Key, Line, Panel, PanelBuilder, TextExt, Widget};
|
||||
|
||||
use crate::{after_edit, App, BrowseNeighbourhoods, Neighbourhood, Transition};
|
||||
|
||||
pub enum EditMode {
|
||||
Filters,
|
||||
FreehandFilters(PolyLineLasso),
|
||||
Oneways,
|
||||
// Is a road clicked on right now?
|
||||
Shortcuts(Option<shortcuts::FocusedRoad>),
|
||||
@ -58,6 +60,7 @@ impl EditNeighbourhood {
|
||||
Self {
|
||||
world: match &app.session.edit_mode {
|
||||
EditMode::Filters => filters::make_world(ctx, app, neighbourhood),
|
||||
EditMode::FreehandFilters(_) => World::unbounded(),
|
||||
EditMode::Oneways => one_ways::make_world(ctx, app, neighbourhood),
|
||||
EditMode::Shortcuts(focus) => shortcuts::make_world(ctx, app, neighbourhood, focus),
|
||||
},
|
||||
@ -81,6 +84,7 @@ impl EditNeighbourhood {
|
||||
edit_mode(ctx, &app.session.edit_mode),
|
||||
match app.session.edit_mode {
|
||||
EditMode::Filters => filters::widget(ctx),
|
||||
EditMode::FreehandFilters(_) => freehand_filters::widget(ctx),
|
||||
EditMode::Oneways => one_ways::widget(ctx),
|
||||
EditMode::Shortcuts(ref focus) => shortcuts::widget(ctx, app, focus.as_ref()),
|
||||
},
|
||||
@ -126,9 +130,14 @@ impl EditNeighbourhood {
|
||||
app: &mut App,
|
||||
neighbourhood: &Neighbourhood,
|
||||
) -> EditOutcome {
|
||||
if let EditMode::FreehandFilters(_) = app.session.edit_mode {
|
||||
return freehand_filters::event(ctx, app, neighbourhood);
|
||||
}
|
||||
|
||||
let outcome = self.world.event(ctx);
|
||||
let outcome = match app.session.edit_mode {
|
||||
EditMode::Filters => filters::handle_world_outcome(ctx, app, outcome),
|
||||
EditMode::FreehandFilters(_) => unreachable!(),
|
||||
EditMode::Oneways => one_ways::handle_world_outcome(ctx, app, outcome),
|
||||
EditMode::Shortcuts(_) => shortcuts::handle_world_outcome(app, outcome, neighbourhood),
|
||||
};
|
||||
@ -144,7 +153,6 @@ impl EditNeighbourhood {
|
||||
app: &mut App,
|
||||
action: &str,
|
||||
neighbourhood: &Neighbourhood,
|
||||
panel: &Panel,
|
||||
) -> EditOutcome {
|
||||
let id = neighbourhood.id;
|
||||
match action {
|
||||
@ -157,14 +165,6 @@ impl EditNeighbourhood {
|
||||
"Adjust boundary" => EditOutcome::Transition(Transition::Replace(
|
||||
crate::select_boundary::SelectBoundary::new_state(ctx, app, id),
|
||||
)),
|
||||
// Overkill to force all mode-specific code into the module
|
||||
"Create filters along a shape" => EditOutcome::Transition(Transition::Push(
|
||||
crate::components::FreehandFilters::new_state(
|
||||
ctx,
|
||||
neighbourhood,
|
||||
panel.center_of("Create filters along a shape"),
|
||||
),
|
||||
)),
|
||||
"undo" => {
|
||||
let prev = app.session.modal_filters.previous_version.take().unwrap();
|
||||
app.session.modal_filters = prev;
|
||||
@ -173,6 +173,9 @@ impl EditNeighbourhood {
|
||||
if let EditMode::Shortcuts(ref mut maybe_focus) = app.session.edit_mode {
|
||||
*maybe_focus = None;
|
||||
}
|
||||
if let EditMode::FreehandFilters(_) = app.session.edit_mode {
|
||||
app.session.edit_mode = EditMode::Filters;
|
||||
}
|
||||
EditOutcome::Transition(Transition::Recreate)
|
||||
}
|
||||
"Plan a route" => EditOutcome::Transition(Transition::Push(
|
||||
@ -182,6 +185,10 @@ impl EditNeighbourhood {
|
||||
app.session.edit_mode = EditMode::Filters;
|
||||
EditOutcome::UpdatePanelAndWorld
|
||||
}
|
||||
"Freehand filters" => {
|
||||
app.session.edit_mode = EditMode::FreehandFilters(PolyLineLasso::new());
|
||||
EditOutcome::UpdatePanelAndWorld
|
||||
}
|
||||
"One-ways" => {
|
||||
app.session.edit_mode = EditMode::Oneways;
|
||||
EditOutcome::UpdatePanelAndWorld
|
||||
@ -211,10 +218,16 @@ fn edit_mode(ctx: &mut EventCtx, edit_mode: &EditMode) -> Widget {
|
||||
let mut row = Vec::new();
|
||||
for (label, key, is_current) in [
|
||||
("Filters", Key::F1, matches!(edit_mode, EditMode::Filters)),
|
||||
("One-ways", Key::F2, matches!(edit_mode, EditMode::Oneways)),
|
||||
// system/assets/tools/select.svg when we have icons
|
||||
(
|
||||
"Freehand filters",
|
||||
Key::F2,
|
||||
matches!(edit_mode, EditMode::FreehandFilters(_)),
|
||||
),
|
||||
("One-ways", Key::F3, matches!(edit_mode, EditMode::Oneways)),
|
||||
(
|
||||
"Shortcuts",
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
matches!(edit_mode, EditMode::Shortcuts(_)),
|
||||
),
|
||||
] {
|
||||
|
@ -45,6 +45,9 @@ impl SelectBoundary {
|
||||
if let EditMode::Shortcuts(ref mut maybe_focus) = app.session.edit_mode {
|
||||
*maybe_focus = None;
|
||||
}
|
||||
if let EditMode::FreehandFilters(_) = app.session.edit_mode {
|
||||
app.session.edit_mode = EditMode::Filters;
|
||||
}
|
||||
|
||||
let top_panel = crate::components::TopPanel::panel(ctx, app);
|
||||
let left_panel = make_panel(ctx, app, id, &top_panel);
|
||||
|
Loading…
Reference in New Issue
Block a user