mirror of
https://github.com/a-b-street/abstreet.git
synced 2025-01-07 06:57:25 +03:00
expose some settings for interactively tuning the dot / heat map of people. this way is buggy (controls constantly being recreated), but a start.
This commit is contained in:
parent
4ad75f99a1
commit
acbeb2b499
@ -33,7 +33,8 @@ enum WidgetType {
|
|||||||
Slider(String),
|
Slider(String),
|
||||||
Menu(String),
|
Menu(String),
|
||||||
Filler(String),
|
Filler(String),
|
||||||
// TODO Sadness. Can't have some kind of wildcard generic here?
|
// TODO Sadness. Can't have some kind of wildcard generic here? I think this goes away when
|
||||||
|
// WidgetType becomes a trait.
|
||||||
DurationPlot(Plot<Duration>),
|
DurationPlot(Plot<Duration>),
|
||||||
UsizePlot(Plot<usize>),
|
UsizePlot(Plot<usize>),
|
||||||
Histogram(Histogram),
|
Histogram(Histogram),
|
||||||
@ -1007,6 +1008,9 @@ impl Composite {
|
|||||||
pub fn slider(&self, name: &str) -> &Slider {
|
pub fn slider(&self, name: &str) -> &Slider {
|
||||||
&self.sliders[name]
|
&self.sliders[name]
|
||||||
}
|
}
|
||||||
|
pub fn maybe_slider(&self, name: &str) -> Option<&Slider> {
|
||||||
|
self.sliders.get(name)
|
||||||
|
}
|
||||||
pub fn slider_mut(&mut self, name: &str) -> &mut Slider {
|
pub fn slider_mut(&mut self, name: &str) -> &mut Slider {
|
||||||
self.sliders.get_mut(name).unwrap()
|
self.sliders.get_mut(name).unwrap()
|
||||||
}
|
}
|
||||||
@ -1029,10 +1033,16 @@ impl Composite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This invalidates the dropdown!
|
pub fn dropdown_value<T: 'static + Clone>(&mut self, name: &str) -> T {
|
||||||
pub fn dropdown_value<T: 'static>(&mut self, name: &str) -> T {
|
|
||||||
match self.find_mut(name).widget {
|
match self.find_mut(name).widget {
|
||||||
WidgetType::Dropdown(ref mut dropdown) => dropdown.take_value(),
|
WidgetType::Dropdown(ref mut dropdown) => {
|
||||||
|
// Amusing little pattern here.
|
||||||
|
// TODO I think this entire hack goes away when WidgetImpl is just a trait.
|
||||||
|
let choice: Choice<T> = dropdown.take_value();
|
||||||
|
let value = choice.data.clone();
|
||||||
|
dropdown.return_value(choice);
|
||||||
|
value
|
||||||
|
}
|
||||||
_ => panic!("{} isn't a dropdown", name),
|
_ => panic!("{} isn't a dropdown", name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,11 +105,31 @@ impl Dropdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This invalidates the entire widget!
|
// TODO This invalidates the entire widget! Have to call return_value
|
||||||
pub fn take_value<T: 'static>(&mut self) -> T {
|
pub fn take_value<T: 'static>(&mut self) -> Choice<T> {
|
||||||
let data: Box<dyn Any> = self.choices.remove(self.current_idx).data;
|
let c = self.choices.remove(self.current_idx);
|
||||||
|
let data: Box<dyn Any> = c.data;
|
||||||
let boxed: Box<T> = data.downcast().unwrap();
|
let boxed: Box<T> = data.downcast().unwrap();
|
||||||
*boxed
|
Choice {
|
||||||
|
label: c.label,
|
||||||
|
data: *boxed,
|
||||||
|
hotkey: c.hotkey,
|
||||||
|
active: c.active,
|
||||||
|
tooltip: c.tooltip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn return_value<T: 'static>(&mut self, c: Choice<T>) {
|
||||||
|
let data: Box<dyn Any> = Box::new(c.data);
|
||||||
|
self.choices.insert(
|
||||||
|
self.current_idx,
|
||||||
|
Choice {
|
||||||
|
label: c.label,
|
||||||
|
data,
|
||||||
|
hotkey: c.hotkey,
|
||||||
|
active: c.active,
|
||||||
|
tooltip: c.tooltip,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,58 @@
|
|||||||
use ezgui::{Color, GeomBatch};
|
use ezgui::{Choice, Color, GeomBatch};
|
||||||
use geom::{Bounds, Polygon, Pt2D};
|
use geom::{Bounds, Polygon, Pt2D};
|
||||||
|
|
||||||
pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>) {
|
#[derive(Clone, PartialEq)]
|
||||||
// Meters
|
pub struct HeatmapOptions {
|
||||||
let resolution = 10.0;
|
// In meters
|
||||||
|
pub resolution: f64,
|
||||||
|
pub num_passes: usize,
|
||||||
|
pub colors: HeatmapColors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeatmapOptions {
|
||||||
|
pub fn new() -> HeatmapOptions {
|
||||||
|
HeatmapOptions {
|
||||||
|
resolution: 10.0,
|
||||||
|
num_passes: 5,
|
||||||
|
colors: HeatmapColors::FullSpectral,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum HeatmapColors {
|
||||||
|
FullSpectral,
|
||||||
|
SingleHue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeatmapColors {
|
||||||
|
pub fn choices() -> Vec<Choice<HeatmapColors>> {
|
||||||
|
vec![
|
||||||
|
Choice::new("full spectral", HeatmapColors::FullSpectral),
|
||||||
|
Choice::new("single hue", HeatmapColors::SingleHue),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>, opts: &HeatmapOptions) {
|
||||||
// u8 is not quite enough -- one building could totally have more than 256 people.
|
// u8 is not quite enough -- one building could totally have more than 256 people.
|
||||||
let mut counts: Grid<u16> = Grid::new(
|
let mut counts: Grid<u16> = Grid::new(
|
||||||
(bounds.width() / resolution).ceil() as usize,
|
(bounds.width() / opts.resolution).ceil() as usize,
|
||||||
(bounds.height() / resolution).ceil() as usize,
|
(bounds.height() / opts.resolution).ceil() as usize,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
for pt in pts {
|
for pt in pts {
|
||||||
// TODO more careful rounding
|
// TODO more careful rounding
|
||||||
let idx = counts.idx(
|
let idx = counts.idx(
|
||||||
((pt.x() - bounds.min_x) / resolution) as usize,
|
((pt.x() - bounds.min_x) / opts.resolution) as usize,
|
||||||
((pt.y() - bounds.min_y) / resolution) as usize,
|
((pt.y() - bounds.min_y) / opts.resolution) as usize,
|
||||||
);
|
);
|
||||||
counts.data[idx] += 1;
|
counts.data[idx] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diffusion
|
// Diffusion
|
||||||
let num_passes = 5;
|
for _ in 0..opts.num_passes {
|
||||||
for _ in 0..num_passes {
|
|
||||||
// Have to hot-swap! Urgh
|
// Have to hot-swap! Urgh
|
||||||
let mut copy = counts.data.clone();
|
let mut copy = counts.data.clone();
|
||||||
for y in 0..counts.height {
|
for y in 0..counts.height {
|
||||||
@ -41,9 +71,9 @@ pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>) {
|
|||||||
|
|
||||||
// Now draw rectangles
|
// Now draw rectangles
|
||||||
let max = *counts.data.iter().max().unwrap();
|
let max = *counts.data.iter().max().unwrap();
|
||||||
// TODO Full spectral progression isn't recommended anymore!
|
|
||||||
// This is in order from low density to high.
|
// This is in order from low density to high.
|
||||||
let colors = vec![
|
let colors = match opts.colors {
|
||||||
|
HeatmapColors::FullSpectral => vec![
|
||||||
Color::hex("#0b2c7a"),
|
Color::hex("#0b2c7a"),
|
||||||
Color::hex("#1e9094"),
|
Color::hex("#1e9094"),
|
||||||
Color::hex("#0ec441"),
|
Color::hex("#0ec441"),
|
||||||
@ -51,14 +81,24 @@ pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>) {
|
|||||||
Color::hex("#f7d707"),
|
Color::hex("#f7d707"),
|
||||||
Color::hex("#e68e1c"),
|
Color::hex("#e68e1c"),
|
||||||
Color::hex("#c2523c"),
|
Color::hex("#c2523c"),
|
||||||
];
|
],
|
||||||
|
HeatmapColors::SingleHue => vec![
|
||||||
|
Color::hex("#FFEBD6"),
|
||||||
|
Color::hex("#F5CBAE"),
|
||||||
|
Color::hex("#EBA988"),
|
||||||
|
Color::hex("#E08465"),
|
||||||
|
Color::hex("#D65D45"),
|
||||||
|
Color::hex("#CC3527"),
|
||||||
|
Color::hex("#C40A0A"),
|
||||||
|
],
|
||||||
|
};
|
||||||
// TODO Off by 1?
|
// TODO Off by 1?
|
||||||
let range = max / ((colors.len() - 1) as u16);
|
let range = max / ((colors.len() - 1) as u16);
|
||||||
if range == 0 {
|
if range == 0 {
|
||||||
// Max is too low, use less colors?
|
// Max is too low, use less colors?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let square = Polygon::rectangle(resolution, resolution);
|
let square = Polygon::rectangle(opts.resolution, opts.resolution);
|
||||||
for y in 0..counts.height {
|
for y in 0..counts.height {
|
||||||
for x in 0..counts.width {
|
for x in 0..counts.width {
|
||||||
let idx = counts.idx(x, y);
|
let idx = counts.idx(x, y);
|
||||||
@ -68,7 +108,7 @@ pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>) {
|
|||||||
let color = colors[((cnt / range) as usize).min(colors.len() - 1)];
|
let color = colors[((cnt / range) as usize).min(colors.len() - 1)];
|
||||||
batch.push(
|
batch.push(
|
||||||
color,
|
color,
|
||||||
square.translate((x as f64) * resolution, (y as f64) * resolution),
|
square.translate((x as f64) * opts.resolution, (y as f64) * opts.resolution),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ mod warp;
|
|||||||
|
|
||||||
pub use self::bus_explorer::ShowBusRoute;
|
pub use self::bus_explorer::ShowBusRoute;
|
||||||
pub use self::colors::{ColorLegend, Colorer};
|
pub use self::colors::{ColorLegend, Colorer};
|
||||||
pub use self::heatmap::make_heatmap;
|
pub use self::heatmap::{make_heatmap, HeatmapColors, HeatmapOptions};
|
||||||
pub use self::minimap::Minimap;
|
pub use self::minimap::Minimap;
|
||||||
pub use self::overlays::Overlays;
|
pub use self::overlays::Overlays;
|
||||||
pub use self::panels::tool_panel;
|
pub use self::panels::tool_panel;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::colors;
|
use crate::colors;
|
||||||
use crate::common::{make_heatmap, ColorLegend, Colorer, ShowBusRoute, Warping};
|
use crate::common::{
|
||||||
|
make_heatmap, ColorLegend, Colorer, HeatmapColors, HeatmapOptions, ShowBusRoute, Warping,
|
||||||
|
};
|
||||||
use crate::game::Transition;
|
use crate::game::Transition;
|
||||||
use crate::helpers::rotating_color_map;
|
use crate::helpers::rotating_color_map;
|
||||||
use crate::helpers::ID;
|
use crate::helpers::ID;
|
||||||
@ -10,7 +12,7 @@ use abstutil::{prettyprint_usize, Counter};
|
|||||||
use ezgui::{
|
use ezgui::{
|
||||||
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, Histogram,
|
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, Histogram,
|
||||||
HorizontalAlignment, JustDraw, Key, Line, Outcome, Plot, PlotOptions, RewriteColor, Series,
|
HorizontalAlignment, JustDraw, Key, Line, Outcome, Plot, PlotOptions, RewriteColor, Series,
|
||||||
Text, TextExt, VerticalAlignment, Widget,
|
Slider, Text, TextExt, VerticalAlignment, Widget,
|
||||||
};
|
};
|
||||||
use geom::{Circle, Distance, Duration, PolyLine, Polygon, Pt2D, Statistic, Time};
|
use geom::{Circle, Distance, Duration, PolyLine, Polygon, Pt2D, Statistic, Time};
|
||||||
use map_model::{BusRouteID, IntersectionID};
|
use map_model::{BusRouteID, IntersectionID};
|
||||||
@ -28,7 +30,7 @@ pub enum Overlays {
|
|||||||
Elevation(Colorer, Drawable),
|
Elevation(Colorer, Drawable),
|
||||||
Edits(Colorer),
|
Edits(Colorer),
|
||||||
TripsHistogram(Time, Composite),
|
TripsHistogram(Time, Composite),
|
||||||
PersonDotMap(Time, Drawable),
|
PopulationMap(Time, Option<HeatmapOptions>, Drawable, Composite),
|
||||||
|
|
||||||
// These aren't selectable from the main picker
|
// These aren't selectable from the main picker
|
||||||
IntersectionDemand(Time, IntersectionID, Drawable, Composite),
|
IntersectionDemand(Time, IntersectionID, Drawable, Composite),
|
||||||
@ -94,9 +96,9 @@ impl Overlays {
|
|||||||
app.overlay = Overlays::bus_passengers(id, ctx, app);
|
app.overlay = Overlays::bus_passengers(id, ctx, app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Overlays::PersonDotMap(t, _) => {
|
Overlays::PopulationMap(t, ref opts, _, _) => {
|
||||||
if now != t {
|
if now != t {
|
||||||
app.overlay = Overlays::person_dot_map(ctx, app);
|
app.overlay = Overlays::population_map(ctx, app, opts.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No updates needed
|
// No updates needed
|
||||||
@ -193,10 +195,25 @@ impl Overlays {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Overlays::PersonDotMap(_, _) => {
|
Overlays::PopulationMap(_, ref mut opts, _, ref mut c) => {
|
||||||
// TODO No controls or legend at all?
|
c.align_above(ctx, minimap);
|
||||||
|
match c.event(ctx) {
|
||||||
|
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||||
|
"X" => {
|
||||||
|
app.overlay = Overlays::Inactive;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let new_opts = heatmap_options(c);
|
||||||
|
if *opts != new_opts {
|
||||||
|
app.overlay = Overlays::population_map(ctx, app, new_opts);
|
||||||
|
} else {
|
||||||
app.overlay = orig_overlay;
|
app.overlay = orig_overlay;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Overlays::Inactive => {}
|
Overlays::Inactive => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,9 +241,10 @@ impl Overlays {
|
|||||||
g.redraw(draw);
|
g.redraw(draw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Overlays::PersonDotMap(_, ref draw) => {
|
Overlays::PopulationMap(_, _, ref draw, ref composite) => {
|
||||||
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
|
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
|
||||||
g.redraw(draw);
|
g.redraw(draw);
|
||||||
|
composite.draw(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// All of these shouldn't care about zoom
|
// All of these shouldn't care about zoom
|
||||||
@ -274,10 +292,8 @@ impl Overlays {
|
|||||||
Btn::text_fg("throughput").build_def(ctx, hotkey(Key::T)),
|
Btn::text_fg("throughput").build_def(ctx, hotkey(Key::T)),
|
||||||
Btn::text_fg("bike network").build_def(ctx, hotkey(Key::B)),
|
Btn::text_fg("bike network").build_def(ctx, hotkey(Key::B)),
|
||||||
Btn::text_fg("bus network").build_def(ctx, hotkey(Key::U)),
|
Btn::text_fg("bus network").build_def(ctx, hotkey(Key::U)),
|
||||||
|
Btn::text_fg("population map").build_def(ctx, hotkey(Key::X)),
|
||||||
];
|
];
|
||||||
if app.opts.dev {
|
|
||||||
choices.push(Btn::text_fg("dot map of people").build_def(ctx, hotkey(Key::X)));
|
|
||||||
}
|
|
||||||
// TODO Grey out the inactive SVGs, and add the green checkmark
|
// TODO Grey out the inactive SVGs, and add the green checkmark
|
||||||
if let Some(name) = match app.overlay {
|
if let Some(name) = match app.overlay {
|
||||||
Overlays::Inactive => Some("None"),
|
Overlays::Inactive => Some("None"),
|
||||||
@ -289,7 +305,7 @@ impl Overlays {
|
|||||||
Overlays::BusNetwork(_) => Some("bus network"),
|
Overlays::BusNetwork(_) => Some("bus network"),
|
||||||
Overlays::Elevation(_, _) => Some("elevation"),
|
Overlays::Elevation(_, _) => Some("elevation"),
|
||||||
Overlays::Edits(_) => Some("map edits"),
|
Overlays::Edits(_) => Some("map edits"),
|
||||||
Overlays::PersonDotMap(_, _) => Some("dot map of people"),
|
Overlays::PopulationMap(_, _, _, _) => Some("population map"),
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
} {
|
||||||
for btn in &mut choices {
|
for btn in &mut choices {
|
||||||
@ -384,9 +400,9 @@ impl Overlays {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.maybe_cb(
|
.maybe_cb(
|
||||||
"dot map of people",
|
"population map",
|
||||||
Box::new(|ctx, app| {
|
Box::new(|ctx, app| {
|
||||||
app.overlay = Overlays::person_dot_map(ctx, app);
|
app.overlay = Overlays::population_map(ctx, app, None);
|
||||||
Some(maybe_unzoom(ctx, app))
|
Some(maybe_unzoom(ctx, app))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -405,7 +421,7 @@ impl Overlays {
|
|||||||
Overlays::BusNetwork(_) => Some("bus network"),
|
Overlays::BusNetwork(_) => Some("bus network"),
|
||||||
Overlays::Elevation(_, _) => Some("elevation"),
|
Overlays::Elevation(_, _) => Some("elevation"),
|
||||||
Overlays::Edits(_) => Some("map edits"),
|
Overlays::Edits(_) => Some("map edits"),
|
||||||
Overlays::PersonDotMap(_, _) => Some("dot map of people"),
|
Overlays::PopulationMap(_, _, _, _) => Some("population map"),
|
||||||
Overlays::TripsHistogram(_, _) => None,
|
Overlays::TripsHistogram(_, _) => None,
|
||||||
Overlays::IntersectionDemand(_, _, _, _) => None,
|
Overlays::IntersectionDemand(_, _, _, _) => None,
|
||||||
Overlays::BusRoute(_, _, _) => None,
|
Overlays::BusRoute(_, _, _) => None,
|
||||||
@ -981,7 +997,7 @@ impl Overlays {
|
|||||||
|
|
||||||
// TODO Disable drawing unzoomed agents... or alternatively, implement this by asking Sim to
|
// TODO Disable drawing unzoomed agents... or alternatively, implement this by asking Sim to
|
||||||
// return this kind of data instead!
|
// return this kind of data instead!
|
||||||
fn person_dot_map(ctx: &EventCtx, app: &App) -> Overlays {
|
fn population_map(ctx: &mut EventCtx, app: &App, opts: Option<HeatmapOptions>) -> Overlays {
|
||||||
let mut pts = Vec::new();
|
let mut pts = Vec::new();
|
||||||
// Faster to grab all agent positions than individually map trips to agent positions.
|
// Faster to grab all agent positions than individually map trips to agent positions.
|
||||||
for a in app.primary.sim.get_unzoomed_agents(&app.primary.map) {
|
for a in app.primary.sim.get_unzoomed_agents(&app.primary.map) {
|
||||||
@ -1007,14 +1023,15 @@ impl Overlays {
|
|||||||
// It's quite silly to produce triangles for the same circle over and over again. ;)
|
// It's quite silly to produce triangles for the same circle over and over again. ;)
|
||||||
let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(10.0)).to_polygon();
|
let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(10.0)).to_polygon();
|
||||||
let mut batch = GeomBatch::new();
|
let mut batch = GeomBatch::new();
|
||||||
if true {
|
if let Some(ref o) = opts {
|
||||||
make_heatmap(&mut batch, app.primary.map.get_bounds(), pts);
|
make_heatmap(&mut batch, app.primary.map.get_bounds(), pts, o);
|
||||||
} else {
|
} else {
|
||||||
for pt in pts {
|
for pt in pts {
|
||||||
batch.push(Color::RED.alpha(0.8), circle.translate(pt.x(), pt.y()));
|
batch.push(Color::RED.alpha(0.8), circle.translate(pt.x(), pt.y()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Overlays::PersonDotMap(app.primary.sim.time(), ctx.upload(batch))
|
let controls = population_controls(ctx, opts.as_ref());
|
||||||
|
Overlays::PopulationMap(app.primary.sim.time(), opts, ctx.upload(batch), controls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1030,3 +1047,67 @@ fn maybe_unzoom(ctx: &EventCtx, app: &mut App) -> Transition {
|
|||||||
&mut app.primary,
|
&mut app.primary,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function sounds more ominous than it should.
|
||||||
|
fn population_controls(ctx: &mut EventCtx, opts: Option<&HeatmapOptions>) -> Composite {
|
||||||
|
let mut col = vec![
|
||||||
|
Widget::row(vec![
|
||||||
|
Line("Population").roboto_bold().draw(ctx),
|
||||||
|
Btn::text_fg("X")
|
||||||
|
.build(ctx, "close", hotkey(Key::Escape))
|
||||||
|
.align_right(),
|
||||||
|
]),
|
||||||
|
Widget::checkbox(ctx, "Show heatmap", None, opts.is_some()),
|
||||||
|
];
|
||||||
|
if let Some(ref o) = opts {
|
||||||
|
// TODO Display the value...
|
||||||
|
col.push(Widget::row(vec![
|
||||||
|
"Resolution (meters)".draw_text(ctx),
|
||||||
|
Widget::slider("resolution"),
|
||||||
|
]));
|
||||||
|
col.push(Widget::row(vec![
|
||||||
|
"Diffusion (num of passes)".draw_text(ctx),
|
||||||
|
Widget::slider("passes"),
|
||||||
|
]));
|
||||||
|
let mut resolution = Slider::horizontal(ctx, 100.0, 25.0);
|
||||||
|
// 1 to 100m
|
||||||
|
resolution.set_percent(ctx, (o.resolution - 1.0) / 99.0);
|
||||||
|
let mut passes = Slider::horizontal(ctx, 100.0, 25.0);
|
||||||
|
// 0 to 10
|
||||||
|
passes.set_percent(ctx, (o.num_passes as f64) / 10.0);
|
||||||
|
|
||||||
|
col.push(Widget::dropdown(
|
||||||
|
ctx,
|
||||||
|
"Colors",
|
||||||
|
o.colors,
|
||||||
|
HeatmapColors::choices(),
|
||||||
|
));
|
||||||
|
|
||||||
|
Composite::new(Widget::col(col).bg(colors::PANEL_BG))
|
||||||
|
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
|
||||||
|
.slider("resolution", resolution)
|
||||||
|
.slider("passes", passes)
|
||||||
|
.build(ctx)
|
||||||
|
} else {
|
||||||
|
Composite::new(Widget::col(col).bg(colors::PANEL_BG))
|
||||||
|
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
|
||||||
|
.build(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn heatmap_options(c: &mut Composite) -> Option<HeatmapOptions> {
|
||||||
|
if c.is_checked("Show heatmap") {
|
||||||
|
// Did we just change?
|
||||||
|
if c.maybe_slider("resolution").is_some() {
|
||||||
|
Some(HeatmapOptions {
|
||||||
|
resolution: 1.0 + c.slider("resolution").get_percent() * 99.0,
|
||||||
|
num_passes: (c.slider("passes").get_percent() * 10.0) as usize,
|
||||||
|
colors: c.dropdown_value("Colors"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Some(HeatmapOptions::new())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user