mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-29 12:43:38 +03:00
create a spinner widget, replace some bad heatmap sliders with it
This commit is contained in:
parent
87cc45752d
commit
a31d3baf1d
@ -196,7 +196,6 @@ impl<'a> GfxCtx<'a> {
|
||||
self.redraw_at(top_left, &draw);
|
||||
}
|
||||
|
||||
// TODO Rename these draw_nonblocking_text_*
|
||||
// TODO Super close to deleting this.
|
||||
pub fn draw_text_at(&mut self, txt: Text, map_pt: Pt2D) {
|
||||
let batch = txt.render_g(self);
|
||||
|
@ -14,6 +14,7 @@
|
||||
//! * [`Menu`] - select something from a menu, with keybindings
|
||||
//! * [`Plot`] - visualize 2 variables with a line plot
|
||||
//! * [`Slider`] - horizontal and vertical sliders
|
||||
//! * [`Spinner`] - numeric input with up/down buttons
|
||||
//! * [`TexBox`] - single line text entry
|
||||
|
||||
mod assets;
|
||||
@ -63,6 +64,7 @@ pub(crate) use crate::widgets::just_draw::JustDraw;
|
||||
pub(crate) use crate::widgets::menu::Menu;
|
||||
pub use crate::widgets::plot::{Plot, PlotOptions, Series};
|
||||
pub use crate::widgets::slider::Slider;
|
||||
pub use crate::widgets::spinner::Spinner;
|
||||
pub(crate) use crate::widgets::text_box::TextBox;
|
||||
pub use crate::widgets::WidgetImpl;
|
||||
|
||||
|
@ -2,7 +2,7 @@ use crate::widgets::containers::{Container, Nothing};
|
||||
use crate::{
|
||||
Btn, Button, Checkbox, Choice, Color, Drawable, Dropdown, EventCtx, Filler, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, JustDraw, Menu, MultiKey, RewriteColor, ScreenDims, ScreenPt,
|
||||
ScreenRectangle, Slider, TextBox, VerticalAlignment, WidgetImpl,
|
||||
ScreenRectangle, Slider, Spinner, TextBox, VerticalAlignment, WidgetImpl,
|
||||
};
|
||||
use geom::{Distance, Polygon};
|
||||
use std::collections::HashSet;
|
||||
@ -681,6 +681,10 @@ impl Composite {
|
||||
self.find::<TextBox>(name).get_line()
|
||||
}
|
||||
|
||||
pub fn spinner(&self, name: &str) -> usize {
|
||||
self.find::<Spinner>(name).current
|
||||
}
|
||||
|
||||
pub fn dropdown_value<T: 'static + PartialEq + Clone>(&self, name: &str) -> T {
|
||||
self.find::<Dropdown<T>>(name).current_value()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ pub mod just_draw;
|
||||
pub mod menu;
|
||||
pub mod plot;
|
||||
pub mod slider;
|
||||
pub mod spinner;
|
||||
pub mod text_box;
|
||||
|
||||
use crate::{EventCtx, GfxCtx, Outcome, ScreenDims, ScreenPt};
|
||||
|
103
ezgui/src/widgets/spinner.rs
Normal file
103
ezgui/src/widgets/spinner.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::{
|
||||
text, Btn, Button, EventCtx, GeomBatch, GfxCtx, Line, Outcome, ScreenDims, ScreenPt, Text,
|
||||
Widget, WidgetImpl,
|
||||
};
|
||||
use geom::Polygon;
|
||||
|
||||
const TEXT_WIDTH: f64 = 5.0 * text::MAX_CHAR_WIDTH;
|
||||
|
||||
// TODO Allow text entry
|
||||
// TODO Allow click and hold
|
||||
// TODO Grey out the buttons when we're maxed out
|
||||
pub struct Spinner {
|
||||
low: usize,
|
||||
high: usize,
|
||||
pub current: usize,
|
||||
|
||||
up: Button,
|
||||
down: Button,
|
||||
|
||||
top_left: ScreenPt,
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
impl Spinner {
|
||||
pub fn new(ctx: &EventCtx, (low, high): (usize, usize), current: usize) -> Widget {
|
||||
let up = Btn::text_fg("▲")
|
||||
.build(ctx, "increase value", None)
|
||||
.take_btn();
|
||||
let down = Btn::text_fg("▼")
|
||||
.build(ctx, "decrease value", None)
|
||||
.take_btn();
|
||||
|
||||
let dims = ScreenDims::new(
|
||||
TEXT_WIDTH + up.get_dims().width,
|
||||
up.get_dims().height + down.get_dims().height,
|
||||
);
|
||||
|
||||
Widget::new(Box::new(Spinner {
|
||||
low,
|
||||
high,
|
||||
current,
|
||||
|
||||
up,
|
||||
down,
|
||||
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
dims,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for Spinner {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
// TODO This works, but it'd be kind of cool if we could construct a tiny little Composite
|
||||
// here and use that. Wait, why can't we? ...
|
||||
self.top_left = top_left;
|
||||
self.up
|
||||
.set_pos(ScreenPt::new(top_left.x + TEXT_WIDTH, top_left.y));
|
||||
self.down.set_pos(ScreenPt::new(
|
||||
top_left.x + TEXT_WIDTH,
|
||||
top_left.y + self.up.get_dims().height,
|
||||
));
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, redo_layout: &mut bool) -> Option<Outcome> {
|
||||
if self.up.event(ctx, redo_layout).is_some() {
|
||||
if self.current != self.high {
|
||||
self.current += 1;
|
||||
}
|
||||
} else if self.down.event(ctx, redo_layout).is_some() {
|
||||
if self.current != self.low {
|
||||
self.current -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
// TODO Cache
|
||||
let mut batch = GeomBatch::from(vec![(
|
||||
text::BG_COLOR,
|
||||
Polygon::rectangle(self.dims.width, self.dims.height),
|
||||
)]);
|
||||
let txt_batch = Text::from(Line(self.current.to_string())).render_to_batch(g.prerender);
|
||||
// TODO Don't we have some centering methods somewhere?
|
||||
let dims = txt_batch.get_dims();
|
||||
batch.add_translated(
|
||||
txt_batch,
|
||||
(TEXT_WIDTH - dims.width) / 2.0,
|
||||
(self.dims.height - dims.height) / 2.0,
|
||||
);
|
||||
let draw = g.upload(batch);
|
||||
g.redraw_at(self.top_left, &draw);
|
||||
|
||||
self.up.draw(g);
|
||||
self.down.draw(g);
|
||||
}
|
||||
}
|
@ -113,6 +113,7 @@ impl WidgetImpl for TextBox {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
// TODO Cache
|
||||
let mut batch = GeomBatch::from(vec![(
|
||||
if self.has_focus || self.autofocus {
|
||||
Color::ORANGE
|
||||
|
@ -4,7 +4,7 @@ use geom::{Bounds, Histogram, Polygon, Pt2D};
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct HeatmapOptions {
|
||||
// In meters
|
||||
pub resolution: f64,
|
||||
pub resolution: usize,
|
||||
pub radius: usize,
|
||||
pub colors: HeatmapColors,
|
||||
}
|
||||
@ -12,7 +12,7 @@ pub struct HeatmapOptions {
|
||||
impl HeatmapOptions {
|
||||
pub fn new() -> HeatmapOptions {
|
||||
HeatmapOptions {
|
||||
resolution: 10.0,
|
||||
resolution: 10,
|
||||
radius: 3,
|
||||
colors: HeatmapColors::FullSpectral,
|
||||
}
|
||||
@ -46,15 +46,15 @@ pub fn make_heatmap(
|
||||
}
|
||||
|
||||
let mut grid: Grid<f64> = Grid::new(
|
||||
(bounds.width() / opts.resolution).ceil() as usize,
|
||||
(bounds.height() / opts.resolution).ceil() as usize,
|
||||
(bounds.width() / opts.resolution as f64).ceil() as usize,
|
||||
(bounds.height() / opts.resolution as f64).ceil() as usize,
|
||||
0.0,
|
||||
);
|
||||
|
||||
// At each point, add a 2D Gaussian kernel centered at the point.
|
||||
for pt in pts {
|
||||
let base_x = ((pt.x() - bounds.min_x) / opts.resolution) as isize;
|
||||
let base_y = ((pt.y() - bounds.min_y) / opts.resolution) as isize;
|
||||
let base_x = ((pt.x() - bounds.min_x) / opts.resolution as f64) as isize;
|
||||
let base_y = ((pt.y() - bounds.min_y) / opts.resolution as f64) as isize;
|
||||
let denom = 2.0 * (opts.radius as f64).powi(2);
|
||||
|
||||
let r = opts.radius as isize;
|
||||
@ -111,7 +111,7 @@ pub fn make_heatmap(
|
||||
.collect();
|
||||
|
||||
// Now draw rectangles
|
||||
let square = Polygon::rectangle(opts.resolution, opts.resolution);
|
||||
let square = Polygon::rectangle(opts.resolution as f64, opts.resolution as f64);
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
let idx = grid.idx(x, y);
|
||||
@ -128,7 +128,7 @@ pub fn make_heatmap(
|
||||
|
||||
batch.push(
|
||||
color,
|
||||
square.translate((x as f64) * opts.resolution, (y as f64) * opts.resolution),
|
||||
square.translate((x * opts.resolution) as f64, (y * opts.resolution) as f64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::render::MIN_ZOOM_FOR_DETAIL;
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use ezgui::{
|
||||
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, Histogram,
|
||||
HorizontalAlignment, Key, Line, Outcome, Slider, Text, TextExt, VerticalAlignment, Widget,
|
||||
HorizontalAlignment, Key, Line, Outcome, Spinner, Text, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
use geom::{Circle, Distance, Duration, PolyLine, Pt2D, Time};
|
||||
use map_model::{BusRouteID, IntersectionID};
|
||||
@ -163,6 +163,11 @@ impl Overlays {
|
||||
let new_opts = population_options(c);
|
||||
if *opts != new_opts {
|
||||
app.overlay = Overlays::population_map(ctx, app, new_opts);
|
||||
// Immediately fix the alignment. TODO Do this for all of them, in a
|
||||
// more uniform way
|
||||
if let Overlays::PopulationMap(_, _, _, ref mut c) = app.overlay {
|
||||
c.align_above(ctx, minimap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -971,16 +976,14 @@ fn population_controls(
|
||||
// TODO Display the value...
|
||||
col.push(Widget::row(vec![
|
||||
"Resolution (meters)".draw_text(ctx).margin(5),
|
||||
// 1 to 100m
|
||||
Slider::horizontal(ctx, 100.0, 25.0, (o.resolution - 1.0) / 99.0)
|
||||
Spinner::new(ctx, (1, 100), o.resolution)
|
||||
.named("resolution")
|
||||
.align_right()
|
||||
.centered_vert(),
|
||||
]));
|
||||
col.push(Widget::row(vec![
|
||||
"Radius (0 to 10 * resolution)".draw_text(ctx).margin(5),
|
||||
// 0 to 10
|
||||
Slider::horizontal(ctx, 100.0, 25.0, (o.radius as f64) / 10.0)
|
||||
"Radius (resolution multiplier)".draw_text(ctx).margin(5),
|
||||
Spinner::new(ctx, (0, 10), o.radius)
|
||||
.named("radius")
|
||||
.align_right()
|
||||
.centered_vert(),
|
||||
@ -1007,8 +1010,8 @@ fn population_options(c: &mut Composite) -> PopulationOptions {
|
||||
// Did we just change?
|
||||
if c.has_widget("resolution") {
|
||||
Some(HeatmapOptions {
|
||||
resolution: 1.0 + c.slider("resolution").get_percent() * 99.0,
|
||||
radius: (c.slider("radius").get_percent() * 10.0) as usize,
|
||||
resolution: c.spinner("resolution"),
|
||||
radius: c.spinner("radius"),
|
||||
colors: c.dropdown_value("Colors"),
|
||||
})
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user