create a spinner widget, replace some bad heatmap sliders with it

This commit is contained in:
Dustin Carlino 2020-04-02 11:03:27 -07:00
parent 87cc45752d
commit a31d3baf1d
8 changed files with 131 additions and 18 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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()
}

View File

@ -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};

View 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);
}
}

View File

@ -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

View File

@ -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),
);
}
}

View File

@ -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 {