more layout refactor

This commit is contained in:
Dustin Carlino 2020-07-01 12:11:00 -07:00
parent 54b4bd318e
commit ef51a8b9b8
22 changed files with 300 additions and 400 deletions

View File

@ -61,26 +61,22 @@ impl CityPicker {
batch = batch.scale(zoom);
}
let mut other_cities = vec![Line("Other cities").draw(ctx).margin_below(10)];
let mut other_cities = vec![Line("Other cities").draw(ctx)];
let mut this_city = vec![];
for name in abstutil::list_all_objects(abstutil::path_all_maps()) {
if let Some((_, color, _)) = regions.iter().find(|(n, _, _)| &name == n) {
let btn = Btn::txt(&name, Text::from(Line(nice_map_name(&name)).fg(*color)))
.tooltip(Text::new());
this_city.push(
if &name == app.primary.map.get_name() {
btn.inactive(ctx)
} else {
btn.build_def(ctx, None)
}
.margin_below(5),
);
this_city.push(if &name == app.primary.map.get_name() {
btn.inactive(ctx)
} else {
btn.build_def(ctx, None)
});
} else {
other_cities.push(
Btn::txt(&name, Text::from(Line(nice_map_name(&name))))
.tooltip(Text::new())
.build_def(ctx, None)
.margin_below(5),
.build_def(ctx, None),
);
}
}
@ -90,22 +86,22 @@ impl CityPicker {
selected: None,
on_load,
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line("Select a region").small_heading().draw(ctx),
Btn::plaintext("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
]),
Widget::row(vec![
Widget::col(other_cities).centered_vert(),
Widget::row2(vec![
Widget::col2(other_cities).centered_vert(),
Widget::draw_batch(ctx, batch).named("picker"),
Widget::col(this_city).centered_vert(),
Widget::col2(this_city).centered_vert(),
]),
])
.bg(app.cs.panel_bg)
.outline(2.0, Color::WHITE)
.padding(10),
.padding(16),
)
.build(ctx),
})

View File

@ -71,12 +71,9 @@ impl BlockMap {
draw_all_blocks: ctx.upload(all_blocks),
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Commute map by block")
.small_heading()
.draw(ctx)
.margin_right(10),
Widget::col2(vec![
Widget::row2(vec![
Line("Commute map by block").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
@ -84,7 +81,7 @@ impl BlockMap {
Checkbox::text(ctx, "from / to this block", hotkey(Key::Space), true),
Checkbox::text(ctx, "arrows / heatmap", hotkey(Key::H), true),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)

View File

@ -48,7 +48,7 @@ impl PopularDestinations {
}
// TODO Er, the heatmap actually looks terrible.
let legend = make_heatmap(ctx, &mut batch, map.get_bounds(), pts, o);
Widget::col(o.to_controls(ctx, legend))
Widget::col2(o.to_controls(ctx, legend))
} else {
let max = per_bldg.max();
let gradient = colorous::REDS;
@ -92,12 +92,9 @@ impl PopularDestinations {
per_bldg,
draw: ctx.upload(batch),
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Most popular destinations")
.small_heading()
.draw(ctx)
.margin_right(10),
Widget::col2(vec![
Widget::row2(vec![
Line("Most popular destinations").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
@ -106,7 +103,7 @@ impl PopularDestinations {
controls,
breakdown.draw(ctx),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)

View File

@ -79,12 +79,9 @@ impl ViewKML {
Box::new(ViewKML {
draw: ctx.upload(batch),
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("KML viewer")
.small_heading()
.draw(ctx)
.margin_right(10),
Widget::col2(vec![
Widget::row2(vec![
Line("KML viewer").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
@ -95,13 +92,13 @@ impl ViewKML {
prettyprint_usize(objects.len())
)
.draw_text(ctx),
Widget::row(vec![
"Query:".draw_text(ctx).margin_right(10),
Widget::row2(vec![
"Query:".draw_text(ctx),
Widget::dropdown(ctx, "query", "None".to_string(), choices),
]),
"Query matches 0 objects".draw_text(ctx).named("matches"),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)

View File

@ -123,19 +123,15 @@ impl ParkingMapper {
draw_layer: ctx.upload(batch),
show,
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Parking mapper")
.small_heading()
.draw(ctx)
.margin_right(10),
Widget::col2(vec![
Widget::row2(vec![
Line("Parking mapper").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
])
.margin_below(5),
Widget::row(vec![
"Change map:".draw_text(ctx).margin_right(10),
]),
Widget::row2(vec![
"Change map:".draw_text(ctx),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name())))
.build(ctx, "change map", None),
]),
@ -145,9 +141,8 @@ impl ParkingMapper {
prettyprint_usize(done.len() + todo.len()),
data.len()
)
.draw_text(ctx)
.margin_below(5),
Widget::row(vec![
.draw_text(ctx),
Widget::row2(vec![
Widget::dropdown(
ctx,
"Show",
@ -165,8 +160,7 @@ impl ParkingMapper {
)
.tooltip("Roads often have the wrong number of lanes tagged"),
],
)
.margin_right(15),
),
ColorLegend::row(
ctx,
color,
@ -179,15 +173,12 @@ impl ParkingMapper {
}
},
),
])
.margin_below(5),
]),
Checkbox::text(ctx, "max 3 days parking (default in Seattle)", None, false),
Btn::text_fg("Generate OsmChange file")
.build_def(ctx, None)
.margin_below(30),
Btn::text_fg("Generate OsmChange file").build_def(ctx, None),
"Select a road".draw_text(ctx).named("info"),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)

View File

@ -25,24 +25,19 @@ impl DevToolsMode {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
Box::new(DevToolsMode {
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Internal dev tools")
.small_heading()
.draw(ctx)
.margin_right(10),
Widget::col2(vec![
Widget::row2(vec![
Line("Internal dev tools").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
])
.margin_below(5),
Widget::row(vec![
"Change map:".draw_text(ctx).margin_right(10),
]),
Widget::row2(vec![
"Change map:".draw_text(ctx),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name())))
.build(ctx, "change map", None),
])
.margin_below(5),
Widget::row(vec![
]),
Widget::custom_row(vec![
Btn::text_fg("edit a polygon").build_def(ctx, hotkey(Key::E)),
Btn::text_fg("draw a polygon").build_def(ctx, hotkey(Key::P)),
Btn::text_fg("load scenario").build_def(ctx, hotkey(Key::W)),
@ -51,7 +46,7 @@ impl DevToolsMode {
])
.flex_wrap(ctx, 60),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)

View File

@ -34,8 +34,8 @@ impl PolygonEditor {
points.pop();
Box::new(PolygonEditor {
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line("Polygon editor").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))

View File

@ -48,8 +48,8 @@ impl ScenarioManager {
let (unzoomed, zoomed, legend) = colorer.build(ctx);
ScenarioManager {
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line(format!("Scenario {}", scenario.scenario_name))
.small_heading()
.draw(ctx),

View File

@ -325,20 +325,14 @@ fn make_panel(
dirty: bool,
) -> Composite {
Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Story map editor")
.small_heading()
.draw(ctx)
.margin_right(5),
Widget::col2(vec![
Widget::row2(vec![
Line("Story map editor").small_heading().draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 30.0))]),
)
.margin_right(5),
Btn::text_fg(format!("{}", story.name))
.build(ctx, "load", lctrl(Key::L))
.margin_right(5),
),
Btn::text_fg(format!("{}", story.name)).build(ctx, "load", lctrl(Key::L)),
if dirty {
Btn::svg_def("../data/system/assets/tools/save.svg").build(
ctx,
@ -351,13 +345,12 @@ fn make_panel(
"../data/system/assets/tools/save.svg",
RewriteColor::ChangeAlpha(0.5),
)
}
.margin_right(5),
},
Btn::plaintext("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
]),
Widget::row(vec![
Widget::row2(vec![
if let Mode::PlacingMarker = mode {
Widget::draw_svg_transform(
ctx,
@ -550,8 +543,8 @@ impl Marker {
fn make_editor(&self, ctx: &mut EventCtx, app: &App) -> Composite {
Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line("Editing marker").small_heading().draw(ctx),
Btn::plaintext("X")
.build(ctx, "close", hotkey(Key::Escape))

View File

@ -27,15 +27,18 @@ impl TitleScreen {
let mut rng = app.primary.current_flags.sim_flags.make_rng();
TitleScreen {
composite: Composite::new(
Widget::col(vec![
Widget::draw_svg(ctx, "../data/system/assets/pregame/logo.svg").margin(5),
Widget::col2(vec![
Widget::draw_svg(ctx, "../data/system/assets/pregame/logo.svg"),
// TODO that nicer font
// TODO Any key
Btn::text_bg2("PLAY")
.build(ctx, "start game", hotkeys(vec![Key::Space, Key::Enter]))
.margin(5),
Btn::text_bg2("PLAY").build(
ctx,
"start game",
hotkeys(vec![Key::Space, Key::Enter]),
),
])
.bg(app.cs.grass)
.padding(16)
.outline(3.0, Color::BLACK)
.centered(),
)
@ -83,7 +86,7 @@ impl MainMenu {
txt.add(Line("Created by Dustin Carlino and Yuwen Li"));
txt.draw(ctx).centered_horiz()
},
Widget::row(vec![
Widget::row2(vec![
Btn::svg(
"../data/system/assets/pregame/tutorial.svg",
RewriteColor::Change(Color::WHITE, app.cs.hovering),
@ -116,7 +119,7 @@ impl MainMenu {
.build(ctx, "Challenges", hotkey(Key::C)),
])
.centered(),
Widget::row(vec![
Widget::row2(vec![
Btn::text_bg2("Community Proposals")
.tooltip({
let mut txt = Text::tooltip(ctx, hotkey(Key::P), "Community Proposals");
@ -138,9 +141,9 @@ impl MainMenu {
Btn::text_bg2("Internal Dev Tools").build_def(ctx, hotkey(Key::D)),
])
.centered(),
Widget::col(vec![
Widget::row(vec![
Btn::text_bg2("About").build_def(ctx, None).margin_right(20),
Widget::col2(vec![
Widget::row2(vec![
Btn::text_bg2("About").build_def(ctx, None),
Btn::text_bg2("Feedback").build_def(ctx, None),
]),
built_info::time().draw(ctx),
@ -149,7 +152,7 @@ impl MainMenu {
];
Box::new(MainMenu {
composite: Composite::new(Widget::col(col).evenly_spaced())
composite: Composite::new(Widget::col2(col).evenly_spaced())
.exact_size_percent(90, 85)
.build(ctx),
})
@ -276,7 +279,7 @@ impl About {
];
Box::new(About {
composite: Composite::new(Widget::col(col))
composite: Composite::new(Widget::custom_col(col))
.exact_size_percent(90, 85)
.build(ctx),
})
@ -375,18 +378,18 @@ impl Proposals {
txt.add(Line("Contact dabreegster@gmail.com to add your idea here!"));
txt.draw(ctx).centered_horiz().margin_below(20)
},
Widget::row(buttons).flex_wrap(ctx, 80),
Widget::custom_row(buttons).flex_wrap(ctx, 80),
];
col.extend(current_tab);
Box::new(Proposals {
proposals,
composite: Composite::new(Widget::col(vec![
composite: Composite::new(Widget::custom_col(vec![
Btn::svg_def("../data/system/assets/pregame/back.svg")
.build(ctx, "back", hotkey(Key::Escape))
.align_left()
.margin_below(20),
Widget::col(col).bg(app.cs.panel_bg).padding(16),
Widget::col2(col).bg(app.cs.panel_bg).padding(16),
]))
.exact_size_percent(90, 85)
.build(ctx),

View File

@ -37,7 +37,7 @@ impl DashTab {
row.push(Btn::text_bg2(name).build_def(ctx, None));
}
}
Widget::row(vec![
Widget::custom_row(vec![
// TODO Centered, but actually, we need to set the padding of each button to divide the
// available space evenly. Fancy fill rules... hmmm.
Widget::row(row).bg(Color::WHITE).margin_vert(16),

View File

@ -269,7 +269,7 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
let mut col = vec![DashTab::ParkingOverhead.picker(ctx, app)];
col.push(
Widget::row(vec![
Widget::row2(vec![
Text::from_multiline(vec![
Line(
"Trips taken by car also include time to walk between the building and \
@ -296,46 +296,37 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
))
.named("preview"),
])
.evenly_spaced()
.margin_below(10),
.evenly_spaced(),
);
col.push(
Widget::row(vec![
Checkbox::text(ctx, "starting off-map", None, opts.off_map_starts).margin_right(10),
Checkbox::text(ctx, "ending off-map", None, opts.off_map_ends),
])
.margin_below(5),
);
col.push(
Widget::row(vec![
if opts.skip > 0 {
Btn::text_fg("<").build(ctx, "previous trips", None)
col.push(Widget::row2(vec![
Checkbox::text(ctx, "starting off-map", None, opts.off_map_starts),
Checkbox::text(ctx, "ending off-map", None, opts.off_map_ends),
]));
col.push(Widget::row2(vec![
if opts.skip > 0 {
Btn::text_fg("<").build(ctx, "previous trips", None)
} else {
Btn::text_fg("<").inactive(ctx)
},
format!(
"{}-{} of {}",
if total_rows > 0 {
prettyprint_usize(opts.skip + 1)
} else {
Btn::text_fg("<").inactive(ctx)
}
.margin_right(10),
format!(
"{}-{} of {}",
if total_rows > 0 {
prettyprint_usize(opts.skip + 1)
} else {
"0".to_string()
},
prettyprint_usize((opts.skip + 1 + ROWS).min(total_rows)),
prettyprint_usize(total_rows)
)
.draw_text(ctx)
.margin_right(10),
if opts.skip + 1 + ROWS < total_rows {
Btn::text_fg(">").build(ctx, "next trips", None)
} else {
Btn::text_fg(">").inactive(ctx)
"0".to_string()
},
])
.margin_below(5),
);
prettyprint_usize((opts.skip + 1 + ROWS).min(total_rows)),
prettyprint_usize(total_rows)
)
.draw_text(ctx),
if opts.skip + 1 + ROWS < total_rows {
Btn::text_fg(">").build(ctx, "next trips", None)
} else {
Btn::text_fg(">").inactive(ctx)
},
]));
col.extend(make_table(
col.push(make_table(
ctx,
app,
headers,
@ -343,7 +334,7 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
0.88 * ctx.canvas.window_width,
));
Composite::new(Widget::col(col).bg(app.cs.panel_bg).padding(10))
Composite::new(Widget::col2(col).bg(app.cs.panel_bg).padding(16))
.exact_size_percent(90, 90)
.build(ctx)
}

View File

@ -29,27 +29,24 @@ impl TripSummaries {
Choice::new("at least 10% change", Some(0.1)),
Choice::new("at least 50% change", Some(0.5)),
],
)
.margin_right(10),
),
checkbox_per_mode(ctx, app, &filter.modes),
];
Box::new(TripSummaries {
composite: Composite::new(
Widget::col(vec![
Widget::col2(vec![
DashTab::TripSummaries.picker(ctx, app),
Widget::row(filters).centered_horiz().margin_below(10),
summary(ctx, app, &filter).margin_below(10),
Widget::row(vec![
contingency_table(ctx, app, &filter)
.centered_vert()
.margin_right(20),
Widget::row2(filters).centered_horiz(),
summary(ctx, app, &filter),
Widget::row2(vec![
contingency_table(ctx, app, &filter).centered_vert(),
scatter_plot(ctx, app, &filter),
])
.evenly_spaced(),
])
.bg(app.cs.panel_bg)
.padding(10),
.padding(16),
)
.exact_size_percent(90, 90)
.build(ctx),
@ -127,8 +124,8 @@ fn summary(ctx: &mut EventCtx, app: &App, filter: &Filter) -> Widget {
}
}
Widget::col(vec![Widget::row(vec![
Widget::col(vec![Text::from_multiline(vec![
Widget::col2(vec![Widget::row2(vec![
Widget::col2(vec![Text::from_multiline(vec![
Line(format!("{} trips faster", prettyprint_usize(num_faster))),
Line(format!("{} total time saved", sum_faster)),
Line(format!(
@ -146,10 +143,9 @@ fn summary(ctx: &mut EventCtx, app: &App, filter: &Filter) -> Widget {
Line(format!("{} trips unchanged", prettyprint_usize(num_same)))
.draw(ctx)
.centered_vert()
.margin_horiz(5)
.outline(2.0, Color::YELLOW)
.padding(10),
Widget::col(vec![Text::from_multiline(vec![
Widget::col2(vec![Text::from_multiline(vec![
Line(format!("{} trips slower", prettyprint_usize(num_slower))),
Line(format!("{} total time lost", sum_slower)),
Line(format!(
@ -317,7 +313,8 @@ fn contingency_table(ctx: &mut EventCtx, app: &App, filter: &Filter) -> Widget {
}
batch.extend(Color::BLACK, outlines);
Widget::row(vec![DrawWithTooltips::new(ctx, batch, tooltips)])
DrawWithTooltips::new(ctx, batch, tooltips)
.container()
.outline(2.0, Color::WHITE)
.padding(10)
}

View File

@ -15,7 +15,7 @@ use geom::{Distance, Duration, Polygon, Pt2D, Time};
use sim::{TripEndpoint, TripID, TripMode};
use std::collections::{BTreeSet, HashMap};
const ROWS: usize = 10;
const ROWS: usize = 8;
pub struct TripTable {
composite: Composite,
@ -326,14 +326,11 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
headers.push(btn(SortBy::PercentWaiting, "Percent waiting"));
let mut col = vec![DashTab::TripTable.picker(ctx, app)];
col.push(checkbox_per_mode(ctx, app, &opts.modes).margin_below(5));
col.push(
Widget::row(vec![
Checkbox::text(ctx, "starting off-map", None, opts.off_map_starts).margin_right(10),
Checkbox::text(ctx, "ending off-map", None, opts.off_map_ends),
])
.margin_below(5),
);
col.push(checkbox_per_mode(ctx, app, &opts.modes));
col.push(Widget::row2(vec![
Checkbox::text(ctx, "starting off-map", None, opts.off_map_starts),
Checkbox::text(ctx, "ending off-map", None, opts.off_map_ends),
]));
let (_, unfinished, _) = app.primary.sim.num_trips();
col.push(
Text::from_multiline(vec![
@ -346,40 +343,34 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
prettyprint_usize(unfinished)
)),
])
.draw(ctx)
.margin_below(10),
.draw(ctx),
);
col.push(
Widget::row(vec![
if opts.skip > 0 {
Btn::text_fg("<").build(ctx, "previous trips", None)
col.push(Widget::row2(vec![
if opts.skip > 0 {
Btn::text_fg("<").build(ctx, "previous trips", None)
} else {
Btn::text_fg("<").inactive(ctx)
},
format!(
"{}-{} of {}",
if total_rows > 0 {
prettyprint_usize(opts.skip + 1)
} else {
Btn::text_fg("<").inactive(ctx)
}
.margin_right(10),
format!(
"{}-{} of {}",
if total_rows > 0 {
prettyprint_usize(opts.skip + 1)
} else {
"0".to_string()
},
prettyprint_usize((opts.skip + 1 + ROWS).min(total_rows)),
prettyprint_usize(total_rows)
)
.draw_text(ctx)
.margin_right(10),
if opts.skip + 1 + ROWS < total_rows {
Btn::text_fg(">").build(ctx, "next trips", None)
} else {
Btn::text_fg(">").inactive(ctx)
"0".to_string()
},
])
.margin_below(5),
);
prettyprint_usize((opts.skip + 1 + ROWS).min(total_rows)),
prettyprint_usize(total_rows)
)
.draw_text(ctx),
if opts.skip + 1 + ROWS < total_rows {
Btn::text_fg(">").build(ctx, "next trips", None)
} else {
Btn::text_fg(">").inactive(ctx)
},
]));
col.extend(make_table(
col.push(make_table(
ctx,
app,
headers,
@ -392,11 +383,10 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Composite {
0.15 * ctx.canvas.window_width,
))
.named("preview")
.centered_horiz()
.margin_above(10),
.centered_horiz(),
);
Composite::new(Widget::col(col).bg(app.cs.panel_bg).padding(10))
Composite::new(Widget::col2(col).bg(app.cs.panel_bg).padding(16))
.exact_size_percent(90, 90)
.build(ctx)
}
@ -408,7 +398,7 @@ pub fn make_table(
headers: Vec<Widget>,
rows: Vec<(String, Vec<GeomBatch>)>,
total_width: f64,
) -> Vec<Widget> {
) -> Widget {
let total_width = total_width / ctx.get_scale_factor();
let mut width_per_col: Vec<f64> = headers
.iter()
@ -423,7 +413,7 @@ pub fn make_table(
/ (width_per_col.len() - 1) as f64)
.max(0.0);
let mut col = vec![Widget::row(
let mut col = vec![Widget::custom_row(
headers
.into_iter()
.enumerate()
@ -461,7 +451,7 @@ pub fn make_table(
);
}
col
Widget::custom_col(col)
}
pub fn preview_trip(g: &mut GfxCtx, app: &App, composite: &Composite) {

View File

@ -47,9 +47,9 @@ impl OptimizeCommute {
let trips = app.primary.sim.get_person(person).trips.clone();
Box::new(OptimizeCommute {
top_center: Composite::new(
Widget::col(vec![
Widget::col2(vec![
challenge_header(ctx, "Optimize the VIP's commute"),
Widget::row(vec![
Widget::row2(vec![
format!("Speed up the VIP's trips by {}", goal)
.draw_text(ctx)
.centered_vert(),
@ -252,7 +252,7 @@ fn make_meter(
txt.append(Line(")"));
Composite::new(
Widget::col(vec![
Widget::col2(vec![
// Separator
Widget::draw_batch(
ctx,
@ -261,15 +261,14 @@ fn make_meter(
Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0),
)]),
)
.margin(15)
.centered_horiz(),
Widget::row(vec![
Btn::svg_def("../data/system/assets/tools/location.svg")
.build(ctx, "locate VIP", None)
.margin_right(10),
format!("{}/{} trips done", done, trips)
.draw_text(ctx)
.margin_right(20),
Widget::row2(vec![
Btn::svg_def("../data/system/assets/tools/location.svg").build(
ctx,
"locate VIP",
None,
),
format!("{}/{} trips done", done, trips).draw_text(ctx),
txt.draw(ctx),
]),
])
@ -364,7 +363,7 @@ fn cutscene_task(mode: &GameplayMode) -> Box<dyn Fn(&mut EventCtx) -> Widget> {
};
Box::new(move |ctx| {
Widget::col(vec![
Widget::custom_col(vec![
Text::from_multiline(vec![
Line(format!("Speed up the VIP's trips by a total of {}", goal)).fg(Color::BLACK),
Line("Ignore the damage done to everyone else.").fg(Color::BLACK),
@ -372,45 +371,39 @@ fn cutscene_task(mode: &GameplayMode) -> Box<dyn Fn(&mut EventCtx) -> Widget> {
.draw(ctx)
.margin_below(30),
Widget::row(vec![
Widget::col(vec![
Widget::col2(vec![
Line("Time").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/time.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Text::from_multiline(vec![
Line("Until the VIP's").fg(Color::BLACK),
Line("last trip is done").fg(Color::BLACK),
])
.draw(ctx),
]),
Widget::col(vec![
Widget::col2(vec![
Line("Goal").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/location.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Text::from_multiline(vec![
Line("Speed up the VIP's trips").fg(Color::BLACK),
Line(format!("by at least {}", goal)).fg(Color::BLACK),
])
.draw(ctx),
]),
Widget::col(vec![
Widget::col2(vec![
Line("Score").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/star.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Text::from_multiline(vec![
Line("How much time").fg(Color::BLACK),
Line("the VIP saves").fg(Color::BLACK),

View File

@ -30,15 +30,14 @@ impl FixTrafficSignals {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn GameplayState> {
Box::new(FixTrafficSignals {
top_center: Composite::new(
Widget::col(vec![
Widget::col2(vec![
challenge_header(ctx, "Traffic signal survivor"),
Widget::row(vec![
Widget::row2(vec![
Line(format!(
"Keep delay at all intersections under {}",
THRESHOLD
))
.draw(ctx)
.margin_right(16),
.draw(ctx),
Btn::svg(
"../data/system/assets/tools/hint.svg",
RewriteColor::Change(Color::WHITE, app.cs.hovering),
@ -137,9 +136,9 @@ impl GameplayState for FixTrafficSignals {
if dt >= THRESHOLD {
self.done = true;
self.top_center = Composite::new(
Widget::col(vec![
Widget::col2(vec![
challenge_header(ctx, "Traffic signal survivor"),
Widget::row(vec![
Widget::row2(vec![
Line(format!(
"Delay exceeded {} at {}",
THRESHOLD,
@ -147,8 +146,7 @@ impl GameplayState for FixTrafficSignals {
))
.fg(Color::RED)
.draw(ctx)
.centered_vert()
.margin_right(10),
.centered_vert(),
Btn::text_fg("try again").build_def(ctx, None),
]),
])
@ -292,7 +290,7 @@ fn make_meter(
worst: Option<(IntersectionID, Duration)>,
) -> Composite {
Composite::new(
Widget::col(vec![
Widget::col2(vec![
// Separator
Widget::draw_batch(
ctx,
@ -301,10 +299,9 @@ fn make_meter(
Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0),
)]),
)
.margin(15)
.centered_horiz(),
if let Some((_, delay)) = worst {
Widget::row(vec![
Widget::row2(vec![
Text::from_all(vec![
Line("Worst delay: "),
Line(delay.to_string()).fg(if delay < Duration::minutes(5) {
@ -321,12 +318,11 @@ fn make_meter(
.align_right(),
])
} else {
Widget::row(vec![
Widget::row2(vec![
if app.primary.dirty_from_edits {
Btn::plaintext("(!)")
.pad(0)
.build(ctx, "explain score", None)
.margin_right(10)
} else {
Widget::nothing()
},
@ -342,7 +338,7 @@ fn make_meter(
},
])
.bg(app.cs.panel_bg)
.padding(20),
.padding(35),
)
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx)
@ -379,7 +375,7 @@ fn final_score(
// TODO Can we automatically transform text and SVG colors?
fn cutscene_pt1_task(ctx: &mut EventCtx) -> Widget {
Widget::col(vec![
Widget::custom_col(vec![
Text::from_multiline(vec![
Line(format!(
"Don't let anyone be delayed by one traffic signal more than {}!",
@ -391,42 +387,36 @@ fn cutscene_pt1_task(ctx: &mut EventCtx) -> Widget {
])
.draw(ctx)
.margin_below(30),
Widget::row(vec![
Widget::col(vec![
Widget::custom_row(vec![
Widget::col2(vec![
Line("Time").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/time.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Line("24 hours").fg(Color::BLACK).draw(ctx),
]),
Widget::col(vec![
Widget::col2(vec![
Line("Goal").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/location.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Text::from_multiline(vec![
Line("Keep delay at all intersections").fg(Color::BLACK),
Line(format!("under {}", THRESHOLD)).fg(Color::BLACK),
])
.draw(ctx),
]),
Widget::col(vec![
Widget::col2(vec![
Line("Score").fg(Color::BLACK).draw(ctx),
Widget::draw_svg_transform(
ctx,
"../data/system/assets/tools/star.svg",
RewriteColor::ChangeAll(Color::BLACK),
)
.margin_below(5)
.margin_above(5),
),
Line("How long you survive").fg(Color::BLACK).draw(ctx),
]),
])

View File

@ -82,24 +82,25 @@ impl GameplayState for Freeform {
fn make_top_center(ctx: &mut EventCtx, app: &App) -> Composite {
let rows = vec![
Widget::row(vec![
Line("Sandbox").small_heading().draw(ctx).margin(5),
Widget::row2(vec![
Line("Sandbox").small_heading().draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin(5),
"Map:".draw_text(ctx).margin(5),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name())))
.build(ctx, "change map", lctrl(Key::L))
.margin(5),
"Traffic:".draw_text(ctx).margin(5),
Btn::text_fg("none ↓")
.build(ctx, "change traffic", hotkey(Key::S))
.margin(5),
Btn::svg_def("../data/system/assets/tools/edit_map.svg")
.build(ctx, "edit map", lctrl(Key::E))
.margin(5),
),
"Map:".draw_text(ctx),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name()))).build(
ctx,
"change map",
lctrl(Key::L),
),
"Traffic:".draw_text(ctx),
Btn::text_fg("none ↓").build(ctx, "change traffic", hotkey(Key::S)),
Btn::svg_def("../data/system/assets/tools/edit_map.svg").build(
ctx,
"edit map",
lctrl(Key::E),
),
])
.centered(),
Btn::text_fg("Start a new trip")
@ -113,7 +114,7 @@ fn make_top_center(ctx: &mut EventCtx, app: &App) -> Composite {
.draw(ctx),
];
Composite::new(Widget::col(rows).bg(app.cs.panel_bg).padding(10))
Composite::new(Widget::col2(rows).bg(app.cs.panel_bg).padding(16))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}
@ -190,8 +191,8 @@ impl AgentSpawner {
goal: None,
confirmed: false,
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line("New trip").small_heading().draw(ctx),
Btn::plaintext("X")
.build(ctx, "close", hotkey(Key::Escape))
@ -199,10 +200,9 @@ impl AgentSpawner {
]),
"Click a building or border to specify start"
.draw_text(ctx)
.named("instructions")
.margin_below(10),
Widget::row(vec![
"Type of trip:".draw_text(ctx).margin_right(10),
.named("instructions"),
Widget::row2(vec![
"Type of trip:".draw_text(ctx),
Widget::dropdown(
ctx,
"mode",
@ -212,17 +212,15 @@ impl AgentSpawner {
.map(|m| Choice::new(m.ongoing_verb(), m))
.collect(),
),
])
.margin_below(10),
Widget::row(vec![
"Number of trips:".draw_text(ctx).margin_right(10),
]),
Widget::row2(vec![
"Number of trips:".draw_text(ctx),
Spinner::new(ctx, (1, 1000), 1).named("number"),
])
.margin_below(10),
]),
Btn::text_fg("Confirm").inactive(ctx).named("Confirm"),
])
.bg(app.cs.panel_bg)
.padding(10),
.padding(16),
)
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx),

View File

@ -276,21 +276,15 @@ impl ContextualActions for GameplayMode {
}
fn challenge_header(ctx: &mut EventCtx, title: &str) -> Widget {
Widget::row(vec![
Line(title)
.small_heading()
.draw(ctx)
.centered_vert()
.margin_right(10),
Widget::row2(vec![
Line(title).small_heading().draw(ctx).centered_vert(),
Btn::svg_def("../data/system/assets/tools/info.svg")
.build(ctx, "instructions", None)
.centered_vert()
.margin_right(10),
.centered_vert(),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin_right(10),
),
Btn::svg_def("../data/system/assets/tools/edit_map.svg")
.build(ctx, "edit map", lctrl(Key::E))
.centered_vert(),
@ -314,24 +308,18 @@ impl FinalScore {
) -> Box<dyn State> {
Box::new(FinalScore {
composite: Composite::new(
Widget::row(vec![
Widget::custom_row(vec![
Widget::draw_svg(ctx, "../data/system/assets/characters/boss.svg")
.container()
.outline(10.0, Color::BLACK)
.padding(10),
Widget::col(vec![
msg.draw_text(ctx).margin_below(5),
Widget::col2(vec![
msg.draw_text(ctx),
// TODO Adjust wording
Btn::text_bg2("Keep simulating")
.build_def(ctx, None)
.margin_below(5),
Btn::text_bg2("Try again")
.build_def(ctx, None)
.margin_below(5),
Btn::text_bg2("Keep simulating").build_def(ctx, None),
Btn::text_bg2("Try again").build_def(ctx, None),
if next_mode.is_some() {
Btn::text_bg2("Next challenge")
.build_def(ctx, None)
.margin_below(5)
Btn::text_bg2("Next challenge").build_def(ctx, None)
} else {
Widget::nothing()
},

View File

@ -101,31 +101,38 @@ fn make_top_center(
modifiers: &Vec<ScenarioModifier>,
) -> Composite {
let rows = vec![
Widget::row(vec![
Line("Sandbox").small_heading().draw(ctx).margin(5),
Widget::row2(vec![
Line("Sandbox").small_heading().draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin(5),
"Map:".draw_text(ctx).margin(5),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name())))
.build(ctx, "change map", lctrl(Key::L))
.margin(5),
"Traffic:".draw_text(ctx).margin(5),
Btn::text_fg(format!("{}", scenario_name))
.build(ctx, "change traffic", hotkey(Key::S))
.margin(5),
Btn::svg_def("../data/system/assets/tools/edit_map.svg")
.build(ctx, "edit map", lctrl(Key::E))
.margin(5),
),
"Map:".draw_text(ctx),
Btn::text_fg(format!("{}", nice_map_name(app.primary.map.get_name()))).build(
ctx,
"change map",
lctrl(Key::L),
),
"Traffic:".draw_text(ctx),
Btn::text_fg(format!("{}", scenario_name)).build(
ctx,
"change traffic",
hotkey(Key::S),
),
Btn::svg_def("../data/system/assets/tools/edit_map.svg").build(
ctx,
"edit map",
lctrl(Key::E),
),
])
.centered(),
if scenario_name == "weekday" {
Widget::row(vec![
Btn::svg_def("../data/system/assets/tools/pencil.svg")
.build(ctx, "edit traffic patterns", None)
.margin_right(15),
Widget::row2(vec![
Btn::svg_def("../data/system/assets/tools/pencil.svg").build(
ctx,
"edit traffic patterns",
None,
),
format!("{} modifications to traffic patterns", modifiers.len()).draw_text(ctx),
])
.centered_horiz()
@ -134,7 +141,7 @@ fn make_top_center(
},
];
Composite::new(Widget::col(rows).bg(app.cs.panel_bg).padding(10))
Composite::new(Widget::col2(rows).bg(app.cs.panel_bg).padding(16))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}
@ -153,10 +160,7 @@ impl EditScenarioModifiers {
modifiers: Vec<ScenarioModifier>,
) -> Box<dyn State> {
let mut rows = vec![
Line("Modify traffic patterns")
.small_heading()
.draw(ctx)
.margin_below(10),
Line("Modify traffic patterns").small_heading().draw(ctx),
Text::from_multiline(vec![
Line(
"Data for all of the people in this simulation comes from PSRC's 2014 \
@ -167,29 +171,23 @@ impl EditScenarioModifiers {
Line("You can modify those patterns here. The modifications apply in order."),
])
.wrap_to_pct(ctx, 50)
.draw(ctx)
.margin_below(10),
.draw(ctx),
];
for (idx, m) in modifiers.iter().enumerate() {
rows.push(
Widget::row(vec![
Widget::row2(vec![
m.describe().draw_text(ctx),
Btn::svg_def("../data/system/assets/tools/delete.svg")
.build(ctx, format!("delete modifier {}", idx + 1), None)
.align_right(),
])
.padding(10)
.outline(2.0, Color::WHITE)
.margin_below(10),
.outline(2.0, Color::WHITE),
);
}
rows.push(Btn::text_bg2("New modification").build_def(ctx, None));
rows.push(
Btn::text_bg2("New modification")
.build_def(ctx, None)
.margin_below(10),
);
rows.push(
Widget::row(vec![
Widget::row2(vec![
Btn::text_bg2("Apply").build_def(ctx, hotkey(Key::Enter)),
Btn::text_bg2("Discard changes").build_def(ctx, hotkey(Key::Escape)),
])
@ -199,7 +197,7 @@ impl EditScenarioModifiers {
Box::new(EditScenarioModifiers {
scenario_name,
modifiers,
composite: Composite::new(Widget::col(rows).padding(16).bg(app.cs.panel_bg))
composite: Composite::new(Widget::col2(rows).padding(16).bg(app.cs.panel_bg))
.exact_size_percent(80, 80)
.build(ctx),
})

View File

@ -719,38 +719,35 @@ impl TutorialState {
}
fn make_top_center(&self, ctx: &mut EventCtx, cs: &ColorScheme, edit_map: bool) -> Composite {
let mut col = vec![Widget::row(vec![
Line("Tutorial").small_heading().draw(ctx).margin(5),
let mut col = vec![Widget::row2(vec![
Line("Tutorial").small_heading().draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin(5),
),
if self.current.stage == 0 {
Btn::text_fg("<").inactive(ctx)
} else {
Btn::text_fg("<").build(ctx, "previous tutorial", None)
}
.margin(5),
},
{
let mut txt = Text::from(Line(format!("Task {}", self.current.stage + 1)));
// TODO Smaller font and use alpha for the "/9" part
txt.append(Line(format!("/{}", self.stages.len())).fg(Color::grey(0.7)));
txt.draw(ctx).margin(5)
txt.draw(ctx)
},
if self.current.stage == self.stages.len() - 1 {
Btn::text_fg(">").inactive(ctx)
} else {
Btn::text_fg(">").build(ctx, "next tutorial", None)
}
.margin(5),
Btn::text_fg("Quit").build_def(ctx, None).margin(5),
},
Btn::text_fg("Quit").build_def(ctx, None),
])
.centered()];
{
let task = self.interaction();
if task != Task::Nil {
col.push(Widget::row(vec![
col.push(Widget::row2(vec![
Text::from(
Line(format!(
"Task {}: {}",
@ -759,8 +756,7 @@ impl TutorialState {
))
.small_heading(),
)
.draw(ctx)
.margin_right(15),
.draw(ctx),
// TODO also text saying "instructions"... can we layout two things easily to
// make a button?
Btn::svg_def("../data/system/assets/tools/info.svg")
@ -768,18 +764,20 @@ impl TutorialState {
.centered_vert()
.align_right(),
]));
col.push(task.top_txt(self).draw(ctx).margin(5));
col.push(task.top_txt(self).draw(ctx));
}
}
if edit_map {
col.push(
Btn::svg_def("../data/system/assets/tools/edit_map.svg")
.build(ctx, "edit map", lctrl(Key::E))
.margin(5),
Btn::svg_def("../data/system/assets/tools/edit_map.svg").build(
ctx,
"edit map",
lctrl(Key::E),
),
);
}
Composite::new(Widget::col(col).bg(cs.panel_bg).padding(16))
Composite::new(Widget::col2(col).bg(cs.panel_bg).padding(16))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}
@ -817,7 +815,7 @@ impl TutorialState {
}
txt.wrap_to_pct(ctx, 30).draw(ctx)
}];
let mut controls = vec![Widget::row(vec![
let mut controls = vec![Widget::row2(vec![
if self.current.part > 0 {
Btn::svg(
"../data/system/assets/tools/prev.svg",
@ -834,12 +832,10 @@ impl TutorialState {
"../data/system/assets/tools/prev.svg",
RewriteColor::ChangeAll(Color::WHITE.alpha(0.5)),
)
}
.margin_right(15),
},
format!("{}/{}", self.current.part + 1, self.stage().messages.len())
.draw_text(ctx)
.centered_vert()
.margin_right(15),
.centered_vert(),
if self.current.part == self.stage().messages.len() - 1 {
Widget::draw_svg_transform(
ctx,
@ -862,15 +858,14 @@ impl TutorialState {
if self.current.part == self.stage().messages.len() - 1 {
controls.push(
Btn::text_bg2("Try it")
.build_def(ctx, hotkeys(vec![Key::RightArrow, Key::Space, Key::Enter]))
.margin_above(10),
.build_def(ctx, hotkeys(vec![Key::RightArrow, Key::Space, Key::Enter])),
);
}
col.push(Widget::col(controls).align_bottom());
col.push(Widget::col2(controls).align_bottom());
Some(
Composite::new(
Widget::col(col)
Widget::col2(col)
.bg(app.cs.panel_bg)
.outline(5.0, Color::WHITE)
.padding(16),

View File

@ -242,7 +242,7 @@ impl TurnExplorer {
fn make_panel(ctx: &mut EventCtx, app: &App, l: LaneID, idx: usize) -> Composite {
let num_turns = app.primary.map.get_turns_from_lane(l).len();
let mut col = vec![Widget::row(vec![
let mut col = vec![Widget::row2(vec![
Text::from(
Line(format!(
"Turns from {}",
@ -250,29 +250,24 @@ impl TurnExplorer {
))
.small_heading(),
)
.draw(ctx)
.margin(5),
.draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin(5),
),
if idx == 0 {
Btn::text_fg("<").inactive(ctx)
} else {
Btn::text_fg("<").build(ctx, "previous turn", hotkey(Key::LeftArrow))
}
.margin(5),
},
Text::from(Line(format!("{}/{}", idx, num_turns)).secondary())
.draw(ctx)
.margin(5)
.centered_vert(),
if idx == num_turns {
Btn::text_fg(">").inactive(ctx)
} else {
Btn::text_fg(">").build(ctx, "next turn", hotkey(Key::RightArrow))
}
.margin(5),
},
Btn::text_fg("X").build(ctx, "close", hotkey(Key::Escape)),
])];
if idx == 0 {
@ -319,7 +314,7 @@ impl TurnExplorer {
col.push(ColorLegend::row(ctx, CONFLICTING_TURN, "conflicting turn"));
}
Composite::new(Widget::col(col).bg(app.cs.panel_bg))
Composite::new(Widget::col2(col).bg(app.cs.panel_bg).padding(16))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
}

View File

@ -30,8 +30,8 @@ impl UberTurnPicker {
Box::new(UberTurnPicker {
members,
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Widget::col2(vec![
Widget::row2(vec![
Line("Select multiple intersections")
.small_heading()
.draw(ctx),
@ -42,7 +42,7 @@ impl UberTurnPicker {
Btn::text_fg("View uber-turns").build_def(ctx, hotkey(Key::Enter)),
Btn::text_fg("Edit").build_def(ctx, hotkey(Key::E)),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
@ -165,14 +165,13 @@ impl UberTurnViewer {
Box::new(UberTurnViewer {
draw: ctx.upload(batch),
composite: Composite::new(
Widget::col(vec![
Widget::row(vec![
Line("Uber-turn viewer").small_heading().draw(ctx).margin(5),
Widget::col2(vec![
Widget::row2(vec![
Line("Uber-turn viewer").small_heading().draw(ctx),
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
)
.margin(5),
),
if idx == 0 {
Btn::text_fg("<").inactive(ctx)
} else {
@ -181,23 +180,20 @@ impl UberTurnViewer {
"previous uber-turn",
hotkey(Key::LeftArrow),
)
}
.margin(5),
},
Text::from(Line(format!("{}/{}", idx, ic.uber_turns.len())).secondary())
.draw(ctx)
.margin(5)
.centered_vert(),
if ic.uber_turns.is_empty() || idx == ic.uber_turns.len() - 1 {
Btn::text_fg(">").inactive(ctx)
} else {
Btn::text_fg(">").build(ctx, "next uber-turn", hotkey(Key::RightArrow))
}
.margin(5),
},
Btn::text_fg("X").build(ctx, "close", hotkey(Key::Escape)),
]),
Checkbox::text(ctx, "legal / illegal movements", None, legal_turns),
])
.padding(10)
.padding(16)
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)