mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +03:00
Individual hitboxes for multiple signals in the sidebar. #331
This commit is contained in:
parent
3c11dca9ba
commit
bb3342de27
@ -4,7 +4,7 @@ mod picker;
|
||||
mod preview;
|
||||
|
||||
use crate::app::{App, ShowEverything};
|
||||
use crate::common::CommonState;
|
||||
use crate::common::{CommonState, Warping};
|
||||
use crate::edit::{apply_map_edits, ConfirmDiscard};
|
||||
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
|
||||
use crate::options::TrafficSignalStyle;
|
||||
@ -19,7 +19,7 @@ use map_model::{
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use widgetry::{
|
||||
lctrl, Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
|
||||
Outcome, Panel, RewriteColor, Text, TextExt, VerticalAlignment, Widget,
|
||||
MultiButton, Outcome, Panel, RewriteColor, Text, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
// Welcome to one of the most overwhelmingly complicated parts of the UI...
|
||||
@ -247,9 +247,25 @@ impl State for TrafficSignalEditor {
|
||||
return Transition::Keep;
|
||||
}
|
||||
if let Some(x) = x.strip_prefix("stage ") {
|
||||
let idx = x.parse::<usize>().unwrap() - 1;
|
||||
// 123, Intersection #456
|
||||
let parts = x.split(", Intersection #").collect::<Vec<_>>();
|
||||
let idx = parts[0].parse::<usize>().unwrap() - 1;
|
||||
let i = IntersectionID(parts[1].parse::<usize>().unwrap());
|
||||
self.change_stage(ctx, app, idx);
|
||||
return Transition::Keep;
|
||||
let center = app.primary.map.get_i(i).polygon.center();
|
||||
// Constantly warping is really annoying, only do it if the intersection is
|
||||
// offscreen
|
||||
if ctx.canvas.get_screen_bounds().contains(center) {
|
||||
return Transition::Keep;
|
||||
} else {
|
||||
return Transition::Push(Warping::new(
|
||||
ctx,
|
||||
center,
|
||||
Some(15.0),
|
||||
None,
|
||||
&mut app.primary,
|
||||
));
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
@ -604,15 +620,7 @@ fn make_side_panel(
|
||||
);
|
||||
|
||||
for (idx, canonical_stage) in canonical_signal.stages.iter().enumerate() {
|
||||
let unselected_btn = draw_multiple_signals(ctx, app, members, idx, &translations);
|
||||
let mut selected_btn = unselected_btn.clone();
|
||||
let bbox = unselected_btn.get_bounds().get_rectangle();
|
||||
selected_btn.push(Color::RED, bbox.to_outline(Distance::meters(5.0)).unwrap());
|
||||
let stage_btn = Btn::custom(unselected_btn, selected_btn, bbox).build(
|
||||
ctx,
|
||||
format!("stage {}", idx + 1),
|
||||
None,
|
||||
);
|
||||
let stage_btn = draw_multiple_signals(ctx, app, members, idx, &translations);
|
||||
|
||||
let stage_controls = Widget::row(vec![
|
||||
Widget::col(vec![
|
||||
@ -769,7 +777,7 @@ fn draw_multiple_signals(
|
||||
members: &BTreeSet<IntersectionID>,
|
||||
idx: usize,
|
||||
translations: &Vec<(f64, f64)>,
|
||||
) -> GeomBatch {
|
||||
) -> Widget {
|
||||
let mut batch = GeomBatch::new();
|
||||
for (i, (dx, dy)) in members.iter().zip(translations) {
|
||||
let mut piece = GeomBatch::new();
|
||||
@ -791,10 +799,27 @@ fn draw_multiple_signals(
|
||||
}
|
||||
|
||||
// Make the whole thing fit a fixed width
|
||||
let bounds_before = batch.get_bounds();
|
||||
batch = batch.autocrop();
|
||||
let bounds = batch.get_bounds();
|
||||
let zoom = (300.0 / bounds.width()).min(300.0 / bounds.height());
|
||||
batch.scale(zoom)
|
||||
let batch = batch.scale(zoom);
|
||||
|
||||
// Figure out the hitboxes per intersection, after all of these transformations
|
||||
let mut hitboxes = Vec::new();
|
||||
for (i, (dx, dy)) in members.iter().zip(translations) {
|
||||
hitboxes.push((
|
||||
app.primary
|
||||
.map
|
||||
.get_i(*i)
|
||||
.polygon
|
||||
.clone()
|
||||
.translate(*dx - bounds_before.min_x, *dy - bounds_before.min_y)
|
||||
.scale(zoom),
|
||||
format!("stage {}, {}", idx + 1, i),
|
||||
));
|
||||
}
|
||||
MultiButton::new(ctx, batch, hitboxes).named(format!("stage {}", idx + 1))
|
||||
}
|
||||
|
||||
// TODO Move to geom?
|
||||
|
@ -254,7 +254,7 @@ impl Canvas {
|
||||
)
|
||||
}
|
||||
|
||||
//the inner bound tells us whether auto-panning should or should not take place
|
||||
// the inner bound tells us whether auto-panning should or should not take place
|
||||
fn get_inner_bounds(&self) -> Bounds {
|
||||
let mut b = Bounds::new();
|
||||
b.update(ScreenPt::new(PANNING_THRESHOLD, PANNING_THRESHOLD).to_pt());
|
||||
|
@ -16,6 +16,7 @@
|
||||
//! * [`JustDraw`] (argh private) - just draw text, `GeomBatch`es, SVGs
|
||||
//! * [`LinePlot`] - visualize 2 variables with a line plot
|
||||
//! * [`Menu`] - select something from a menu, with keybindings
|
||||
//! * [`MultiButton`] - clickable regions in one batch of geometry
|
||||
//! * [`PersistentSplit`] - a button with a dropdown to change its state
|
||||
//! * [`ScatterPlot`] - visualize 2 variables with a scatter plot
|
||||
//! * [`Slider`] - horizontal and vertical sliders
|
||||
@ -68,8 +69,8 @@ pub use crate::style::Style;
|
||||
pub use crate::text::{Line, Text, TextExt, TextSpan};
|
||||
pub use crate::tools::warper::Warper;
|
||||
pub use crate::widgets::autocomplete::Autocomplete;
|
||||
pub use crate::widgets::button::Btn;
|
||||
pub(crate) use crate::widgets::button::Button;
|
||||
pub use crate::widgets::button::{Btn, MultiButton};
|
||||
pub use crate::widgets::checkbox::Checkbox;
|
||||
pub use crate::widgets::compare_times::CompareTimes;
|
||||
pub(crate) use crate::widgets::dropdown::Dropdown;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
svg, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, MultiKey, Outcome, RewriteColor,
|
||||
ScreenDims, ScreenPt, Text, Widget, WidgetImpl, WidgetOutput,
|
||||
ScreenDims, ScreenPt, ScreenRectangle, Text, Widget, WidgetImpl, WidgetOutput,
|
||||
};
|
||||
use geom::Polygon;
|
||||
use geom::{Distance, Polygon};
|
||||
|
||||
pub struct Button {
|
||||
pub action: String,
|
||||
@ -413,3 +413,71 @@ impl BtnBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Like an image map from the old HTML days
|
||||
pub struct MultiButton {
|
||||
draw: Drawable,
|
||||
hitboxes: Vec<(Polygon, String)>,
|
||||
hovering: Option<usize>,
|
||||
|
||||
top_left: ScreenPt,
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
impl MultiButton {
|
||||
pub fn new(ctx: &EventCtx, batch: GeomBatch, hitboxes: Vec<(Polygon, String)>) -> Widget {
|
||||
Widget::new(Box::new(MultiButton {
|
||||
dims: batch.get_dims(),
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
draw: ctx.upload(batch),
|
||||
hitboxes,
|
||||
hovering: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MultiButton {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
self.top_left = top_left;
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
|
||||
if ctx.redo_mouseover() {
|
||||
self.hovering = None;
|
||||
if let Some(cursor) = ctx.canvas.get_cursor_in_screen_space() {
|
||||
if !ScreenRectangle::top_left(self.top_left, self.dims).contains(cursor) {
|
||||
return;
|
||||
}
|
||||
let translated =
|
||||
ScreenPt::new(cursor.x - self.top_left.x, cursor.y - self.top_left.y).to_pt();
|
||||
// TODO Assume regions are non-overlapping
|
||||
for (idx, (region, _)) in self.hitboxes.iter().enumerate() {
|
||||
if region.contains_pt(translated) {
|
||||
self.hovering = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = self.hovering {
|
||||
if ctx.normal_left_click() {
|
||||
self.hovering = None;
|
||||
output.outcome = Outcome::Clicked(self.hitboxes[idx].1.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
g.redraw_at(self.top_left, &self.draw);
|
||||
if let Some(idx) = self.hovering {
|
||||
if let Ok(p) = self.hitboxes[idx].0.to_outline(Distance::meters(1.0)) {
|
||||
let draw = g.upload(GeomBatch::from(vec![(Color::YELLOW, p)]));
|
||||
g.redraw_at(self.top_left, &draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user