nicer API for appending bits of text

This commit is contained in:
Dustin Carlino 2019-09-13 15:23:25 -07:00
parent 95545863fc
commit 1248dac270
15 changed files with 209 additions and 132 deletions

View File

@ -305,9 +305,11 @@ impl UserInput {
pub fn populate_osd(&mut self, osd: &mut Text) { pub fn populate_osd(&mut self, osd: &mut Text) {
for (key, a) in self.important_actions.drain(..) { for (key, a) in self.important_actions.drain(..) {
osd.add(Line("Press ")); osd.add_appended(vec![
osd.append(Line(key.describe()).fg(text::HOTKEY_COLOR)); Line("Press "),
osd.append(Line(format!(" to {}", a))); Line(key.describe()).fg(text::HOTKEY_COLOR),
Line(format!(" to {}", a)),
]);
} }
} }

View File

@ -120,6 +120,23 @@ impl Text {
self.lines.last_mut().unwrap().1.push(line); self.lines.last_mut().unwrap().1.push(line);
} }
pub fn add_appended(&mut self, lines: Vec<TextSpan>) {
assert!(lines.len() > 1);
for (idx, l) in lines.into_iter().enumerate() {
if idx == 0 {
self.add(l);
} else {
self.append(l);
}
}
}
pub fn append_all(&mut self, lines: Vec<TextSpan>) {
for l in lines {
self.append(l);
}
}
// TODO Ideally we'd wrap last-minute when drawing, but eh, start somewhere. // TODO Ideally we'd wrap last-minute when drawing, but eh, start somewhere.
pub fn add_wrapped_line(&mut self, canvas: &Canvas, line: String) { pub fn add_wrapped_line(&mut self, canvas: &Canvas, line: String) {
let wrap_to = canvas.window_width / MAX_CHAR_WIDTH; let wrap_to = canvas.window_width / MAX_CHAR_WIDTH;

View File

@ -59,9 +59,11 @@ impl<T: Clone + Hash + Eq> Autocomplete<T> {
txt.add(Line(&self.line[0..self.cursor_x])); txt.add(Line(&self.line[0..self.cursor_x]));
if self.cursor_x < self.line.len() { if self.cursor_x < self.line.len() {
// TODO This "cursor" looks awful! // TODO This "cursor" looks awful!
txt.append(Line("|").fg(text::SELECTED_COLOR)); txt.append_all(vec![
txt.append(Line(&self.line[self.cursor_x..=self.cursor_x])); Line("|").fg(text::SELECTED_COLOR),
txt.append(Line(&self.line[self.cursor_x + 1..])); Line(&self.line[self.cursor_x..=self.cursor_x]),
Line(&self.line[self.cursor_x + 1..]),
]);
} else { } else {
txt.append(Line("|").fg(text::SELECTED_COLOR)); txt.append(Line("|").fg(text::SELECTED_COLOR));
} }

View File

@ -338,8 +338,10 @@ impl<T: Clone> Menu<T> {
txt.override_width = Some(self.total_width); txt.override_width = Some(self.total_width);
if choice.active { if choice.active {
if let Some(key) = choice.hotkey { if let Some(key) = choice.hotkey {
txt.add(Line(key.describe()).fg(text::HOTKEY_COLOR)); txt.add_appended(vec![
txt.append(Line(format!(" - {}", choice.label))); Line(key.describe()).fg(text::HOTKEY_COLOR),
Line(format!(" - {}", choice.label)),
]);
} else { } else {
txt.add(Line(&choice.label)); txt.add(Line(&choice.label));
} }

View File

@ -26,9 +26,11 @@ impl TextBox {
txt.add(Line(&self.line[0..self.cursor_x])); txt.add(Line(&self.line[0..self.cursor_x]));
if self.cursor_x < self.line.len() { if self.cursor_x < self.line.len() {
// TODO This "cursor" looks awful! // TODO This "cursor" looks awful!
txt.append(Line("|").fg(text::SELECTED_COLOR)); txt.append_all(vec![
txt.append(Line(&self.line[self.cursor_x..=self.cursor_x])); Line("|").fg(text::SELECTED_COLOR),
txt.append(Line(&self.line[self.cursor_x + 1..])); Line(&self.line[self.cursor_x..=self.cursor_x]),
Line(&self.line[self.cursor_x + 1..]),
]);
} else { } else {
txt.append(Line("|").fg(text::SELECTED_COLOR)); txt.append(Line("|").fg(text::SELECTED_COLOR));
} }

View File

@ -82,32 +82,38 @@ impl GUI for UI {
let len = self.hints.hints.len(); let len = self.hints.hints.len();
let mut txt = Text::prompt("Fix Map Geometry"); let mut txt = Text::prompt("Fix Map Geometry");
txt.add(Line(len.to_string()).fg(Color::CYAN)); txt.add_appended(vec![
txt.append(Line(" hints, ")); Line(len.to_string()).fg(Color::CYAN),
txt.append( Line(" hints, "),
Line(self.hints.parking_overrides.len().to_string()).fg(Color::CYAN), Line(self.hints.parking_overrides.len().to_string()).fg(Color::CYAN),
); Line(" parking overrides"),
txt.append(Line(" parking overrides")); ]);
if let Some(ID::Road(r)) = self.world.get_selection() { if let Some(ID::Road(r)) = self.world.get_selection() {
txt.add(Line(r.to_string()).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(format!( Line(r.to_string()).fg(Color::RED),
" is {} long", Line(format!(
self.data.roads[&r].trimmed_center_pts.length() " is {} long",
))); self.data.roads[&r].trimmed_center_pts.length()
)),
]);
if self.data.roads[&r].has_parking() { if self.data.roads[&r].has_parking() {
txt.add(Line("Has parking")); txt.add(Line("Has parking"));
} else { } else {
txt.add(Line("No parking")); txt.add(Line("No parking"));
} }
for (k, v) in &self.raw.roads[&r].osm_tags { for (k, v) in &self.raw.roads[&r].osm_tags {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v).fg(Color::CYAN)); Line(" = "),
Line(v).fg(Color::CYAN),
]);
} }
} }
if let Some(ID::Intersection(i)) = self.world.get_selection() { if let Some(ID::Intersection(i)) = self.world.get_selection() {
txt.add(Line(i.to_string()).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" OSM tag diffs:")); Line(i.to_string()).fg(Color::RED),
Line(" OSM tag diffs:"),
]);
let roads = &self.data.intersections[&i].roads; let roads = &self.data.intersections[&i].roads;
if roads.len() == 2 { if roads.len() == 2 {
let mut iter = roads.iter(); let mut iter = roads.iter();
@ -117,27 +123,33 @@ impl GUI for UI {
for (k, v1) in r1_tags { for (k, v1) in r1_tags {
if let Some(v2) = r2_tags.get(k) { if let Some(v2) = r2_tags.get(k) {
if v1 != v2 { if v1 != v2 {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v1).fg(Color::CYAN)); Line(" = "),
txt.append(Line(" / ")); Line(v1).fg(Color::CYAN),
txt.append(Line(v2).fg(Color::CYAN)); Line(" / "),
Line(v2).fg(Color::CYAN),
]);
} }
} else { } else {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v1).fg(Color::CYAN)); Line(" = "),
txt.append(Line(" / ")); Line(v1).fg(Color::CYAN),
txt.append(Line("MISSING").fg(Color::CYAN)); Line(" / "),
Line("MISSING").fg(Color::CYAN),
]);
} }
} }
for (k, v2) in r2_tags { for (k, v2) in r2_tags {
if !r1_tags.contains_key(k) { if !r1_tags.contains_key(k) {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line("MISSING").fg(Color::CYAN)); Line(" = "),
txt.append(Line(" / ")); Line("MISSING").fg(Color::CYAN),
txt.append(Line(v2).fg(Color::CYAN)); Line(" / "),
Line(v2).fg(Color::CYAN),
]);
} }
} }
} }

View File

@ -30,17 +30,21 @@ impl Scoreboard {
let t2 = secondary.sim.get_finished_trips(); let t2 = secondary.sim.get_finished_trips();
let mut summary = Text::new(); let mut summary = Text::new();
summary.add(Line("Score at ")); summary.add_appended(vec![
summary.append(Line(primary.sim.time().to_string()).fg(Color::RED)); Line("Score at "),
summary.append(Line(format!( Line(primary.sim.time().to_string()).fg(Color::RED),
"... {} / {}", Line(format!(
primary.map.get_edits().edits_name, "... {} / {}",
secondary.map.get_edits().edits_name primary.map.get_edits().edits_name,
))); secondary.map.get_edits().edits_name
summary.add(Line(prettyprint_usize(t1.unfinished_trips)).fg(Color::CYAN)); )),
summary.append(Line(" | ")); ]);
summary.append(Line(prettyprint_usize(t2.unfinished_trips)).fg(Color::RED)); summary.add_appended(vec![
summary.append(Line(" unfinished trips")); Line(prettyprint_usize(t1.unfinished_trips)).fg(Color::CYAN),
Line(" | "),
Line(prettyprint_usize(t2.unfinished_trips)).fg(Color::RED),
Line(" unfinished trips"),
]);
let cmp = CompareTrips::new(t1, t2); let cmp = CompareTrips::new(t1, t2);
for (mode, trips) in &cmp for (mode, trips) in &cmp
@ -64,29 +68,33 @@ impl Scoreboard {
deltas.sort(); deltas.sort();
let len = deltas.len() as f64; let len = deltas.len() as f64;
summary.add(Line(format!("{:?}", mode)).fg(Color::CYAN)); summary.add_appended(vec![
summary.append(Line(format!( Line(format!("{:?}", mode)).fg(Color::CYAN),
" trips: {} same, {} different", Line(format!(
abstutil::prettyprint_usize(num_same), " trips: {} same, {} different",
abstutil::prettyprint_usize(deltas.len()) abstutil::prettyprint_usize(num_same),
))); abstutil::prettyprint_usize(deltas.len())
)),
]);
if !deltas.is_empty() { if !deltas.is_empty() {
summary.add(Line(" deltas: ")); summary.add_appended(vec![
summary.append(Line("50%ile").fg(Color::RED)); Line(" deltas: "),
summary.append(Line(format!( Line("50%ile").fg(Color::RED),
" {}, ", Line(format!(
handle_negative(deltas[(0.5 * len).floor() as usize]) " {}, ",
))); handle_negative(deltas[(0.5 * len).floor() as usize])
summary.append(Line("90%ile").fg(Color::RED)); )),
summary.append(Line(format!( Line("90%ile").fg(Color::RED),
" {}, ", Line(format!(
handle_negative(deltas[(0.9 * len).floor() as usize]) " {}, ",
))); handle_negative(deltas[(0.9 * len).floor() as usize])
summary.append(Line("99%ile").fg(Color::RED)); )),
summary.append(Line(format!( Line("99%ile").fg(Color::RED),
" {}", Line(format!(
handle_negative(deltas[(0.99 * len).floor() as usize]) " {}",
))); handle_negative(deltas[(0.99 * len).floor() as usize])
)),
]);
} }
} }

View File

@ -88,15 +88,19 @@ impl CommonState {
osd.append(Line("...")); osd.append(Line("..."));
} }
Some(ID::Lane(l)) => { Some(ID::Lane(l)) => {
osd.append(Line(l.to_string()).fg(id_color)); osd.append_all(vec![
osd.append(Line(" is ")); Line(l.to_string()).fg(id_color),
osd.append(Line(map.get_parent(*l).get_name()).fg(name_color)); Line(" is "),
Line(map.get_parent(*l).get_name()).fg(name_color),
]);
} }
Some(ID::Building(b)) => { Some(ID::Building(b)) => {
let bldg = map.get_b(*b); let bldg = map.get_b(*b);
osd.append(Line(b.to_string()).fg(id_color)); osd.append_all(vec![
osd.append(Line(" is ")); Line(b.to_string()).fg(id_color),
osd.append(Line(bldg.get_name()).fg(name_color)); Line(" is "),
Line(bldg.get_name()).fg(name_color),
]);
if let Some(ref p) = bldg.parking { if let Some(ref p) = bldg.parking {
osd.append(Line(format!( osd.append(Line(format!(
" ({} parking spots via {})", " ({} parking spots via {})",
@ -105,15 +109,16 @@ impl CommonState {
} }
} }
Some(ID::Turn(t)) => { Some(ID::Turn(t)) => {
osd.append(Line(format!("TurnID({})", map.get_t(*t).lookup_idx)).fg(id_color)); osd.append_all(vec![
osd.append(Line(" between ")); Line(format!("TurnID({})", map.get_t(*t).lookup_idx)).fg(id_color),
osd.append(Line(map.get_parent(t.src).get_name()).fg(name_color)); Line(" between "),
osd.append(Line(" and ")); Line(map.get_parent(t.src).get_name()).fg(name_color),
osd.append(Line(map.get_parent(t.dst).get_name()).fg(name_color)); Line(" and "),
Line(map.get_parent(t.dst).get_name()).fg(name_color),
]);
} }
Some(ID::Intersection(i)) => { Some(ID::Intersection(i)) => {
osd.append(Line(i.to_string()).fg(id_color)); osd.append_all(vec![Line(i.to_string()).fg(id_color), Line(" of ")]);
osd.append(Line(" of "));
let mut road_names = BTreeSet::new(); let mut road_names = BTreeSet::new();
for r in &map.get_i(*i).roads { for r in &map.get_i(*i).roads {
@ -130,13 +135,14 @@ impl CommonState {
Some(ID::Car(c)) => { Some(ID::Car(c)) => {
osd.append(Line(c.to_string()).fg(id_color)); osd.append(Line(c.to_string()).fg(id_color));
if let Some(r) = ui.primary.sim.bus_route_id(*c) { if let Some(r) = ui.primary.sim.bus_route_id(*c) {
osd.append(Line(" serving ")); osd.append_all(vec![
osd.append(Line(&map.get_br(r).name).fg(name_color)); Line(" serving "),
Line(&map.get_br(r).name).fg(name_color),
]);
} }
} }
Some(ID::BusStop(bs)) => { Some(ID::BusStop(bs)) => {
osd.append(Line(bs.to_string()).fg(id_color)); osd.append_all(vec![Line(bs.to_string()).fg(id_color), Line(" serving ")]);
osd.append(Line(" serving "));
let routes = map.get_routes_serving_stop(*bs); let routes = map.get_routes_serving_stop(*bs);
let len = routes.len(); let len = routes.len();

View File

@ -149,16 +149,20 @@ fn tooltip_lines(id: ID, g: &mut GfxCtx, ctx: &PerMapUI) -> Text {
match id { match id {
ID::Road(id) => { ID::Road(id) => {
let r = map.get_r(id); let r = map.get_r(id);
txt.add(Line(format!("{} (originally {}) is ", r.id, r.stable_id))); txt.add_appended(vec![
txt.append(Line(r.get_name()).fg(Color::CYAN)); Line(format!("{} (originally {}) is ", r.id, r.stable_id)),
Line(r.get_name()).fg(Color::CYAN),
]);
txt.add(Line(format!("From OSM way {}", r.osm_way_id))); txt.add(Line(format!("From OSM way {}", r.osm_way_id)));
} }
ID::Lane(id) => { ID::Lane(id) => {
let l = map.get_l(id); let l = map.get_l(id);
let r = map.get_r(l.parent); let r = map.get_r(l.parent);
txt.add(Line(format!("{} is ", l.id))); txt.add_appended(vec![
txt.append(Line(r.get_name()).fg(Color::CYAN)); Line(format!("{} is ", l.id)),
Line(r.get_name()).fg(Color::CYAN),
]);
txt.add(Line(format!("From OSM way {}", r.osm_way_id))); txt.add(Line(format!("From OSM way {}", r.osm_way_id)));
txt.add(Line(format!( txt.add(Line(format!(
"Parent {} (originally {}) points to {}", "Parent {} (originally {}) points to {}",
@ -258,8 +262,10 @@ fn tooltip_lines(id: ID, g: &mut GfxCtx, ctx: &PerMapUI) -> Text {
fn styled_kv(txt: &mut Text, tags: &BTreeMap<String, String>) { fn styled_kv(txt: &mut Text, tags: &BTreeMap<String, String>) {
for (k, v) in tags { for (k, v) in tags {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v).fg(Color::CYAN)); Line(" = "),
Line(v).fg(Color::CYAN),
]);
} }
} }

View File

@ -120,8 +120,10 @@ impl State for StopSignEditor {
self.menu.draw(g); self.menu.draw(g);
if let Some(r) = self.selected_sign { if let Some(r) = self.selected_sign {
let mut osd = Text::new(); let mut osd = Text::new();
osd.add(Line("Stop sign for ")); osd.add_appended(vec![
osd.append(Line(ui.primary.map.get_r(r).get_name()).fg(ui.cs.get("OSD name color"))); Line("Stop sign for "),
Line(ui.primary.map.get_r(r).get_name()).fg(ui.cs.get("OSD name color")),
]);
CommonState::draw_custom_osd(g, osd); CommonState::draw_custom_osd(g, osd);
} else { } else {
CommonState::draw_osd(g, ui, &None); CommonState::draw_osd(g, ui, &None);

View File

@ -66,8 +66,10 @@ impl State for DataVisualizer {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
let mut txt = Text::prompt("Data Visualizer"); let mut txt = Text::prompt("Data Visualizer");
if let Some(ref name) = self.current_tract { if let Some(ref name) = self.current_tract {
txt.add(Line("Census ")); txt.add_appended(vec![
txt.append(Line(name).fg(ui.cs.get("OSD name color"))); Line("Census "),
Line(name).fg(ui.cs.get("OSD name color")),
]);
let tract = &self.tracts[name]; let tract = &self.tracts[name];
txt.add(Line(format!( txt.add(Line(format!(
"{} buildings", "{} buildings",
@ -126,8 +128,10 @@ impl State for DataVisualizer {
self.menu.draw(g); self.menu.draw(g);
if let Some(ref name) = self.current_tract { if let Some(ref name) = self.current_tract {
let mut osd = Text::new(); let mut osd = Text::new();
osd.add(Line("Census ")); osd.add_appended(vec![
osd.append(Line(name).fg(ui.cs.get("OSD name color"))); Line("Census "),
Line(name).fg(ui.cs.get("OSD name color")),
]);
CommonState::draw_custom_osd(g, osd); CommonState::draw_custom_osd(g, osd);
} else { } else {
CommonState::draw_osd(g, ui, &None); CommonState::draw_osd(g, ui, &None);
@ -150,9 +154,11 @@ impl State for DataVisualizer {
} else { } else {
let mut txt = Text::new(); let mut txt = Text::new();
for (k, v) in kv { for (k, v) in kv {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v.to_string()).fg(Color::CYAN)); Line(" = "),
Line(v.to_string()).fg(Color::CYAN),
]);
} }
g.draw_blocking_text(&txt, (HorizontalAlignment::Left, VerticalAlignment::Top)); g.draw_blocking_text(&txt, (HorizontalAlignment::Left, VerticalAlignment::Top));
} }
@ -236,15 +242,15 @@ fn bar_chart(g: &mut GfxCtx, data: &BTreeMap<String, Estimate>) {
if name == "Total:" { if name == "Total:" {
continue; continue;
} }
labels.add(Line(format!("{} (", name)).size(40)); labels.add_appended(vec![
labels.append( Line(format!("{} (", name)).size(40),
Line(format!( Line(format!(
"{}%", "{}%",
((est.value as f64) / (sum as f64) * 100.0) as usize ((est.value as f64) / (sum as f64) * 100.0) as usize
)) ))
.fg(Color::RED), .fg(Color::RED),
); Line(")"),
labels.append(Line(")")); ]);
} }
let (txt_width, total_height) = g.text_dims(&labels); let (txt_width, total_height) = g.text_dims(&labels);
let line_height = total_height / ((data.len() as f64) - 1.0); let line_height = total_height / ((data.len() as f64) - 1.0);

View File

@ -108,9 +108,11 @@ impl State for TripsVisualizer {
self.slider.draw(g); self.slider.draw(g);
if let Some(ID::Building(b)) = ui.primary.current_selection { if let Some(ID::Building(b)) = ui.primary.current_selection {
let mut osd = Text::new(); let mut osd = Text::new();
osd.add(Line(b.to_string()).fg(ui.cs.get("OSD ID color"))); osd.add_appended(vec![
osd.append(Line(" is ")); Line(b.to_string()).fg(ui.cs.get("OSD ID color")),
osd.append(Line(ui.primary.map.get_b(b).get_name()).fg(ui.cs.get("OSD name color"))); Line(" is "),
Line(ui.primary.map.get_b(b).get_name()).fg(ui.cs.get("OSD name color")),
]);
if let Some(md) = self.bldgs.get(&b) { if let Some(md) = self.bldgs.get(&b) {
osd.append(Line(format!( osd.append(Line(format!(
". {} households, {} employees, {} offstreet parking spaces", ". {} households, {} employees, {} offstreet parking spaces",

View File

@ -213,17 +213,19 @@ impl State for ScenarioManager {
if let Some(ID::Building(b)) = ui.primary.current_selection { if let Some(ID::Building(b)) = ui.primary.current_selection {
let mut osd = Text::new(); let mut osd = Text::new();
osd.add(Line(b.to_string()).fg(ui.cs.get("OSD ID color")));
osd.append(Line(" is "));
osd.append(Line(ui.primary.map.get_b(b).get_name()).fg(ui.cs.get("OSD name color")));
let from = self.trips_from_bldg.get(b); let from = self.trips_from_bldg.get(b);
let to = self.trips_to_bldg.get(b); let to = self.trips_to_bldg.get(b);
osd.append(Line(format!( osd.add_appended(vec![
". {} trips from here, {} trips to here, {} parked cars needed", Line(b.to_string()).fg(ui.cs.get("OSD ID color")),
from.len(), Line(" is "),
to.len(), Line(ui.primary.map.get_b(b).get_name()).fg(ui.cs.get("OSD name color")),
self.cars_needed_per_bldg[&b] Line(format!(
))); ". {} trips from here, {} trips to here, {} parked cars needed",
from.len(),
to.len(),
self.cars_needed_per_bldg[&b]
)),
]);
CommonState::draw_custom_osd(g, osd); CommonState::draw_custom_osd(g, osd);
} else { } else {
CommonState::draw_osd(g, ui, &ui.primary.current_selection); CommonState::draw_osd(g, ui, &ui.primary.current_selection);

View File

@ -28,10 +28,14 @@ impl Scoreboard {
let t = ui.primary.sim.get_finished_trips(); let t = ui.primary.sim.get_finished_trips();
let mut summary = Text::new(); let mut summary = Text::new();
summary.add(Line("Score at ")); summary.add_appended(vec![
summary.append(Line(ui.primary.sim.time().to_string()).fg(Color::RED)); Line("Score at "),
summary.add(Line(prettyprint_usize(t.unfinished_trips)).fg(Color::CYAN)); Line(ui.primary.sim.time().to_string()).fg(Color::RED),
summary.append(Line(" unfinished trips")); ]);
summary.add_appended(vec![
Line(prettyprint_usize(t.unfinished_trips)).fg(Color::CYAN),
Line(" unfinished trips"),
]);
for (mode, trips) in &t for (mode, trips) in &t
.finished_trips .finished_trips
@ -43,8 +47,10 @@ impl Scoreboard {
for (_, _, dt) in trips { for (_, _, dt) in trips {
distrib.add(dt); distrib.add(dt);
} }
summary.add(Line(format!("{:?}", mode)).fg(Color::CYAN)); summary.add_appended(vec![
summary.append(Line(format!(" trips: {}", distrib.describe()))); Line(format!("{:?}", mode)).fg(Color::CYAN),
Line(format!(" trips: {}", distrib.describe())),
]);
} }
Scoreboard { menu, summary } Scoreboard { menu, summary }

View File

@ -213,9 +213,11 @@ impl GUI for UI {
if let Some(ID::Lane(id, _, _)) = self.model.get_selection() { if let Some(ID::Lane(id, _, _)) = self.model.get_selection() {
let mut txt = Text::new(); let mut txt = Text::new();
for (k, v) in self.model.get_tags(id) { for (k, v) in self.model.get_tags(id) {
txt.add(Line(k).fg(Color::RED)); txt.add_appended(vec![
txt.append(Line(" = ")); Line(k).fg(Color::RED),
txt.append(Line(v).fg(Color::CYAN)); Line(" = "),
Line(v).fg(Color::CYAN),
]);
} }
g.draw_blocking_text( g.draw_blocking_text(
&txt, &txt,