fix outline for spinner (without crashing!) and cache drawable

This commit is contained in:
Michael Kirk 2021-02-27 15:01:03 -08:00 committed by Dustin Carlino
parent 088e434a0a
commit 87a1a3f027
15 changed files with 87 additions and 49 deletions

View File

@ -194,7 +194,7 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams
if mode == TripMode::Drive || mode == TripMode::Bike {
rows.push(Widget::row(vec![
"Unprotected turn penalty:".draw_text(ctx).margin_right(20),
Spinner::new(
Spinner::widget(
ctx,
(1, 100),
(params.unprotected_turn_penalty * 10.0) as isize,
@ -206,17 +206,17 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams
// TODO Spinners that natively understand a floating point range with a given precision
rows.push(Widget::row(vec![
"Bike lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (params.bike_lane_penalty * 10.0) as isize)
Spinner::widget(ctx, (0, 20), (params.bike_lane_penalty * 10.0) as isize)
.named("bike lane penalty"),
]));
rows.push(Widget::row(vec![
"Bus lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (params.bus_lane_penalty * 10.0) as isize)
Spinner::widget(ctx, (0, 20), (params.bus_lane_penalty * 10.0) as isize)
.named("bus lane penalty"),
]));
rows.push(Widget::row(vec![
"Driving lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (params.driving_lane_penalty * 10.0) as isize)
Spinner::widget(ctx, (0, 20), (params.driving_lane_penalty * 10.0) as isize)
.named("driving lane penalty"),
]));
}

View File

@ -29,7 +29,7 @@ impl RouteEditor {
// TODO This UI needs design, just something to start plumbing the edits
Widget::row(vec![
"Frequency in minutes".draw_text(ctx),
Spinner::new(ctx, (1, 120), 60).named("freq_mins"),
Spinner::widget(ctx, (1, 120), 60).named("freq_mins"),
]),
ctx.style()
.btn_solid_primary_text("Apply")

View File

@ -32,7 +32,7 @@ impl ChangeDuration {
]),
Widget::row(vec![
"Seconds:".draw_text(ctx).centered_vert(),
Spinner::new(
Spinner::widget(
ctx,
(
signal.get_min_crossing_time(idx).inner_seconds() as isize,
@ -64,7 +64,7 @@ impl ChangeDuration {
.draw(ctx)]),
Widget::row(vec![
"Seconds:".draw_text(ctx).centered_vert(),
Spinner::new(
Spinner::widget(
ctx,
(1, 300),
match signal.stages[idx].stage_type {
@ -81,7 +81,7 @@ impl ChangeDuration {
.draw(ctx)]),
Widget::row(vec![
"Seconds:".draw_text(ctx).centered_vert(),
Spinner::new(
Spinner::widget(
ctx,
(1, 300),
match signal.stages[idx].stage_type {

View File

@ -249,7 +249,7 @@ impl TuneRelative {
.draw(ctx),
Widget::row(vec![
"Offset (seconds):".draw_text(ctx),
Spinner::new(ctx, (0, 90), (offset2 - offset1).inner_seconds() as isize)
Spinner::widget(ctx, (0, 90), (offset2 - offset1).inner_seconds() as isize)
.named("offset"),
]),
ctx.style()

View File

@ -59,7 +59,7 @@ impl ZoneEditor {
Widget::row(vec![
"Limit the number of vehicles passing through per hour (0 = unlimited):"
.draw_text(ctx),
Spinner::new(ctx, (0, 1000), cap_vehicles_per_hour.unwrap_or(0) as isize)
Spinner::widget(ctx, (0, 1000), cap_vehicles_per_hour.unwrap_or(0) as isize)
.named("cap_vehicles"),
]),
Widget::custom_row(vec![

View File

@ -56,7 +56,7 @@ impl TrafficSignalDemand {
.draw(ctx),
Widget::row(vec![
"Hour:".draw_text(ctx),
Spinner::new(ctx, (0, 24), 7).named("hour"),
Spinner::widget(ctx, (0, 24), 7).named("hour"),
]),
]))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
@ -111,11 +111,11 @@ impl State<App> for TrafficSignalDemand {
_ => {}
}
if ctx.input.pressed(Key::LeftArrow) {
self.panel.modify_spinner("hour", -1);
self.panel.modify_spinner(ctx, "hour", -1);
changed = true;
}
if ctx.input.pressed(Key::RightArrow) {
self.panel.modify_spinner("hour", 1);
self.panel.modify_spinner(ctx, "hour", 1);
changed = true;
}
if changed {

View File

@ -320,7 +320,7 @@ impl AgentSpawner {
]),
Widget::row(vec![
"Number of trips:".draw_text(ctx),
Spinner::new(ctx, (1, 1000), 1).named("number"),
Spinner::widget(ctx, (1, 1000), 1).named("number"),
]),
ctx.style()
.btn_solid_primary_text("Confirm")

View File

@ -218,7 +218,7 @@ impl EditScenarioModifiers {
.build_def(ctx),
);
rows.push(Widget::row(vec![
Spinner::new(ctx, (2, 14), 2).named("repeat_days"),
Spinner::widget(ctx, (2, 14), 2).named("repeat_days"),
ctx.style()
.btn_outline_text("Repeat schedule multiple days")
.build_def(ctx),
@ -364,7 +364,7 @@ impl ChangeMode {
"Percent of people to modify:"
.draw_text(ctx)
.centered_vert(),
Spinner::new(ctx, (1, 100), 50).named("pct_ppl"),
Spinner::widget(ctx, (1, 100), 50).named("pct_ppl"),
]),
"Types of trips to convert:".draw_text(ctx),
checkbox_per_mode(ctx, app, &btreeset! { TripMode::Drive }),

View File

@ -34,7 +34,7 @@ impl EditRoad {
let controls = Widget::col(vec![
Widget::row(vec![
"lanes:forward".draw_text(ctx).margin_right(20),
Spinner::new(
Spinner::widget(
ctx,
(1, 5),
road.osm_tags
@ -46,7 +46,7 @@ impl EditRoad {
]),
Widget::row(vec![
"lanes:backward".draw_text(ctx).margin_right(20),
Spinner::new(
Spinner::widget(
ctx,
(0, 5),
road.osm_tags

View File

@ -160,7 +160,7 @@ impl OptionsPanel {
),
Widget::row(vec![
"Scroll speed for menus".draw_text(ctx).centered_vert(),
Spinner::new(ctx, (1, 50), ctx.canvas.gui_scroll_speed as isize)
Spinner::widget(ctx, (1, 50), ctx.canvas.gui_scroll_speed as isize)
.named("gui_scroll_speed"),
]),
])

View File

@ -41,7 +41,7 @@ impl HeatmapOptions {
// TODO Display the value...
Widget::row(vec![
"Resolution (meters)".draw_text(ctx).centered_vert(),
Spinner::new(ctx, (1, 100), self.resolution as isize)
Spinner::widget(ctx, (1, 100), self.resolution as isize)
.named("resolution")
.align_right(),
]),
@ -49,7 +49,7 @@ impl HeatmapOptions {
"Radius (resolution multiplier)"
.draw_text(ctx)
.centered_vert(),
Spinner::new(ctx, (0, 10), self.radius as isize)
Spinner::widget(ctx, (0, 10), self.radius as isize)
.named("radius")
.align_right(),
]),

View File

@ -144,8 +144,12 @@ impl<A: AppLike + 'static, T: MinimapControls<A>> Minimap<A, T> {
if self.controls.has_zorder(app) {
Widget::col(vec![
Line("Z-order:").small().draw(ctx),
Spinner::new(ctx, app.draw_map().zorder_range, app.draw_map().show_zorder)
.named("zorder"),
Spinner::widget(
ctx,
app.draw_map().zorder_range,
app.draw_map().show_zorder,
)
.named("zorder"),
])
.margin_above(10)
} else {

View File

@ -377,8 +377,8 @@ impl Panel {
pub fn spinner(&self, name: &str) -> isize {
self.find::<Spinner>(name).current
}
pub fn modify_spinner(&mut self, name: &str, delta: isize) {
self.find_mut::<Spinner>(name).modify(delta)
pub fn modify_spinner(&mut self, ctx: &EventCtx, name: &str, delta: isize) {
self.find_mut::<Spinner>(name).modify(ctx, delta)
}
pub fn dropdown_value<T: 'static + PartialEq + Clone, I: Into<String>>(&self, name: I) -> T {

View File

@ -1,8 +1,9 @@
use geom::{Polygon, Pt2D};
use geom::{CornerRadii, Distance, Polygon, Pt2D};
use crate::{
include_labeled_bytes, text, Button, EdgeInsets, EventCtx, GeomBatch, GfxCtx, Line, Outcome,
ScreenDims, ScreenPt, ScreenRectangle, StyledButtons, Text, Widget, WidgetImpl, WidgetOutput,
include_labeled_bytes, text, Button, Drawable, EdgeInsets, EventCtx, GeomBatch, GfxCtx, Line,
Outcome, OutlineStyle, Prerender, ScreenDims, ScreenPt, ScreenRectangle, Style, StyledButtons,
Text, Widget, WidgetImpl, WidgetOutput,
};
// TODO MAX_CHAR_WIDTH is a hardcoded nonsense value
@ -18,13 +19,19 @@ pub struct Spinner {
up: Button,
down: Button,
outline: OutlineStyle,
drawable: Drawable,
top_left: ScreenPt,
dims: ScreenDims,
}
impl Spinner {
pub fn new(ctx: &EventCtx, (low, high): (isize, isize), mut current: isize) -> Widget {
pub fn widget(ctx: &EventCtx, (low, high): (isize, isize), current: isize) -> Widget {
Widget::new(Box::new(Self::new(ctx, (low, high), current)))
}
pub fn new(ctx: &EventCtx, (low, high): (isize, isize), mut current: isize) -> Self {
let button_builder = ctx
.style()
.btn_plain()
@ -39,12 +46,25 @@ impl Spinner {
let up = button_builder
.clone()
.image_bytes(include_labeled_bytes!("../../icons/arrow_up.svg"))
.corner_rounding(CornerRadii {
top_left: 0.0,
top_right: 5.0,
bottom_right: 0.0,
bottom_left: 5.0,
})
.build(ctx, "increase value");
let down = button_builder
.image_bytes(include_labeled_bytes!("../../icons/arrow_down.svg"))
.corner_rounding(CornerRadii {
top_left: 5.0,
top_right: 0.0,
bottom_right: 5.0,
bottom_left: 0.0,
})
.build(ctx, "decrease value");
let outline = ctx.style().btn_outline.outline;
let dims = ScreenDims::new(
TEXT_WIDTH + up.get_dims().width,
up.get_dims().height + down.get_dims().height + 1.0,
@ -57,25 +77,46 @@ impl Spinner {
warn!("Spinner current value is out of bounds!");
}
let outline = ctx.style().btn_outline.outline;
Widget::new(Box::new(Spinner {
let mut spinner = Spinner {
low,
high,
current,
up,
down,
drawable: Drawable::empty(ctx),
outline,
top_left: ScreenPt::new(0.0, 0.0),
dims,
}))
.outline(outline)
};
spinner.drawable = spinner.drawable(ctx.prerender, ctx.style());
spinner
}
pub fn modify(&mut self, delta: isize) {
pub fn modify(&mut self, ctx: &EventCtx, delta: isize) {
self.current += delta;
self.current = self.current.min(self.high);
self.current = self.current.max(self.low);
self.drawable = self.drawable(ctx.prerender, ctx.style());
}
fn drawable(&self, prerender: &Prerender, style: &Style) -> Drawable {
let mut batch = GeomBatch::from(vec![(
style.field_bg,
Polygon::rounded_rectangle(self.dims.width, self.dims.height, 5.0),
)]);
batch.append(
Text::from(Line(self.current.to_string()))
.render_autocropped(prerender)
.centered_on(Pt2D::new(TEXT_WIDTH / 2.0, self.dims.height / 2.0)),
);
batch.push(
self.outline.1,
Polygon::rounded_rectangle(self.dims.width, self.dims.height, 5.0)
.to_outline(Distance::meters(self.outline.0))
.unwrap(),
);
prerender.upload(batch)
}
}
@ -101,6 +142,7 @@ impl WidgetImpl for Spinner {
if let Outcome::Clicked(_) = output.outcome {
output.outcome = Outcome::Changed;
self.current = (self.current + 1).min(self.high);
self.drawable = self.drawable(&ctx.prerender, ctx.style());
ctx.no_op_event(true, |ctx| self.up.event(ctx, output));
return;
}
@ -109,6 +151,7 @@ impl WidgetImpl for Spinner {
if let Outcome::Clicked(_) = output.outcome {
output.outcome = Outcome::Changed;
self.current = (self.current - 1).max(self.low);
self.drawable = self.drawable(&ctx.prerender, ctx.style());
ctx.no_op_event(true, |ctx| self.down.event(ctx, output));
return;
}
@ -119,10 +162,12 @@ impl WidgetImpl for Spinner {
if dy > 0.0 && self.current != self.high {
self.current += 1;
output.outcome = Outcome::Changed;
self.drawable = self.drawable(&ctx.prerender, ctx.style());
}
if dy < 0.0 && self.current != self.low {
self.current -= 1;
output.outcome = Outcome::Changed;
self.drawable = self.drawable(&ctx.prerender, ctx.style());
}
}
}
@ -130,18 +175,7 @@ impl WidgetImpl for Spinner {
}
fn draw(&self, g: &mut GfxCtx) {
// TODO Cache
let mut batch = GeomBatch::from(vec![(
g.style().panel_bg,
Polygon::rounded_rectangle(self.dims.width, self.dims.height, 5.0),
)]);
batch.append(
Text::from(Line(self.current.to_string()))
.render_autocropped(g)
.centered_on(Pt2D::new(TEXT_WIDTH / 2.0, self.dims.height / 2.0)),
);
let draw = g.upload(batch);
g.redraw_at(self.top_left, &draw);
g.redraw_at(self.top_left, &self.drawable);
self.up.draw(g);
self.down.draw(g);

View File

@ -344,7 +344,7 @@ fn make_controls(ctx: &mut EventCtx) -> Panel {
.build_widget(ctx, "btn_outline_icon_text"),
])]),
Text::from(Line("Spinner").big_heading_styled().size(18)).draw(ctx),
widgetry::Spinner::new(ctx, (0, 11), 1),
widgetry::Spinner::widget(ctx, (0, 11), 1),
Widget::row(vec![
ctx.style()
.btn_outline_text("New faces")
@ -474,7 +474,7 @@ fn make_controls(ctx: &mut EventCtx) -> Panel {
),
Widget::col(
(0..row_height)
.map(|_| widgetry::Spinner::new(ctx, (0, 11), 1))
.map(|_| widgetry::Spinner::widget(ctx, (0, 11), 1))
.collect::<Vec<_>>(),
),
Widget::col(