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) {
for (key, a) in self.important_actions.drain(..) {
osd.add(Line("Press "));
osd.append(Line(key.describe()).fg(text::HOTKEY_COLOR));
osd.append(Line(format!(" to {}", a)));
osd.add_appended(vec![
Line("Press "),
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);
}
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.
pub fn add_wrapped_line(&mut self, canvas: &Canvas, line: String) {
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]));
if self.cursor_x < self.line.len() {
// TODO This "cursor" looks awful!
txt.append(Line("|").fg(text::SELECTED_COLOR));
txt.append(Line(&self.line[self.cursor_x..=self.cursor_x]));
txt.append(Line(&self.line[self.cursor_x + 1..]));
txt.append_all(vec![
Line("|").fg(text::SELECTED_COLOR),
Line(&self.line[self.cursor_x..=self.cursor_x]),
Line(&self.line[self.cursor_x + 1..]),
]);
} else {
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);
if choice.active {
if let Some(key) = choice.hotkey {
txt.add(Line(key.describe()).fg(text::HOTKEY_COLOR));
txt.append(Line(format!(" - {}", choice.label)));
txt.add_appended(vec![
Line(key.describe()).fg(text::HOTKEY_COLOR),
Line(format!(" - {}", choice.label)),
]);
} else {
txt.add(Line(&choice.label));
}

View File

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

View File

@ -82,32 +82,38 @@ impl GUI for UI {
let len = self.hints.hints.len();
let mut txt = Text::prompt("Fix Map Geometry");
txt.add(Line(len.to_string()).fg(Color::CYAN));
txt.append(Line(" hints, "));
txt.append(
txt.add_appended(vec![
Line(len.to_string()).fg(Color::CYAN),
Line(" hints, "),
Line(self.hints.parking_overrides.len().to_string()).fg(Color::CYAN),
);
txt.append(Line(" parking overrides"));
Line(" parking overrides"),
]);
if let Some(ID::Road(r)) = self.world.get_selection() {
txt.add(Line(r.to_string()).fg(Color::RED));
txt.append(Line(format!(
" is {} long",
self.data.roads[&r].trimmed_center_pts.length()
)));
txt.add_appended(vec![
Line(r.to_string()).fg(Color::RED),
Line(format!(
" is {} long",
self.data.roads[&r].trimmed_center_pts.length()
)),
]);
if self.data.roads[&r].has_parking() {
txt.add(Line("Has parking"));
} else {
txt.add(Line("No parking"));
}
for (k, v) in &self.raw.roads[&r].osm_tags {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line(v).fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line(v).fg(Color::CYAN),
]);
}
}
if let Some(ID::Intersection(i)) = self.world.get_selection() {
txt.add(Line(i.to_string()).fg(Color::RED));
txt.append(Line(" OSM tag diffs:"));
txt.add_appended(vec![
Line(i.to_string()).fg(Color::RED),
Line(" OSM tag diffs:"),
]);
let roads = &self.data.intersections[&i].roads;
if roads.len() == 2 {
let mut iter = roads.iter();
@ -117,27 +123,33 @@ impl GUI for UI {
for (k, v1) in r1_tags {
if let Some(v2) = r2_tags.get(k) {
if v1 != v2 {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line(v1).fg(Color::CYAN));
txt.append(Line(" / "));
txt.append(Line(v2).fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line(v1).fg(Color::CYAN),
Line(" / "),
Line(v2).fg(Color::CYAN),
]);
}
} else {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line(v1).fg(Color::CYAN));
txt.append(Line(" / "));
txt.append(Line("MISSING").fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line(v1).fg(Color::CYAN),
Line(" / "),
Line("MISSING").fg(Color::CYAN),
]);
}
}
for (k, v2) in r2_tags {
if !r1_tags.contains_key(k) {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line("MISSING").fg(Color::CYAN));
txt.append(Line(" / "));
txt.append(Line(v2).fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line("MISSING").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 mut summary = Text::new();
summary.add(Line("Score at "));
summary.append(Line(primary.sim.time().to_string()).fg(Color::RED));
summary.append(Line(format!(
"... {} / {}",
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.append(Line(" unfinished trips"));
summary.add_appended(vec![
Line("Score at "),
Line(primary.sim.time().to_string()).fg(Color::RED),
Line(format!(
"... {} / {}",
primary.map.get_edits().edits_name,
secondary.map.get_edits().edits_name
)),
]);
summary.add_appended(vec![
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);
for (mode, trips) in &cmp
@ -64,29 +68,33 @@ impl Scoreboard {
deltas.sort();
let len = deltas.len() as f64;
summary.add(Line(format!("{:?}", mode)).fg(Color::CYAN));
summary.append(Line(format!(
" trips: {} same, {} different",
abstutil::prettyprint_usize(num_same),
abstutil::prettyprint_usize(deltas.len())
)));
summary.add_appended(vec![
Line(format!("{:?}", mode)).fg(Color::CYAN),
Line(format!(
" trips: {} same, {} different",
abstutil::prettyprint_usize(num_same),
abstutil::prettyprint_usize(deltas.len())
)),
]);
if !deltas.is_empty() {
summary.add(Line(" deltas: "));
summary.append(Line("50%ile").fg(Color::RED));
summary.append(Line(format!(
" {}, ",
handle_negative(deltas[(0.5 * len).floor() as usize])
)));
summary.append(Line("90%ile").fg(Color::RED));
summary.append(Line(format!(
" {}, ",
handle_negative(deltas[(0.9 * len).floor() as usize])
)));
summary.append(Line("99%ile").fg(Color::RED));
summary.append(Line(format!(
" {}",
handle_negative(deltas[(0.99 * len).floor() as usize])
)));
summary.add_appended(vec![
Line(" deltas: "),
Line("50%ile").fg(Color::RED),
Line(format!(
" {}, ",
handle_negative(deltas[(0.5 * len).floor() as usize])
)),
Line("90%ile").fg(Color::RED),
Line(format!(
" {}, ",
handle_negative(deltas[(0.9 * len).floor() as usize])
)),
Line("99%ile").fg(Color::RED),
Line(format!(
" {}",
handle_negative(deltas[(0.99 * len).floor() as usize])
)),
]);
}
}

View File

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

View File

@ -149,16 +149,20 @@ fn tooltip_lines(id: ID, g: &mut GfxCtx, ctx: &PerMapUI) -> Text {
match id {
ID::Road(id) => {
let r = map.get_r(id);
txt.add(Line(format!("{} (originally {}) is ", r.id, r.stable_id)));
txt.append(Line(r.get_name()).fg(Color::CYAN));
txt.add_appended(vec![
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)));
}
ID::Lane(id) => {
let l = map.get_l(id);
let r = map.get_r(l.parent);
txt.add(Line(format!("{} is ", l.id)));
txt.append(Line(r.get_name()).fg(Color::CYAN));
txt.add_appended(vec![
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!(
"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>) {
for (k, v) in tags {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line(v).fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line(v).fg(Color::CYAN),
]);
}
}

View File

@ -120,8 +120,10 @@ impl State for StopSignEditor {
self.menu.draw(g);
if let Some(r) = self.selected_sign {
let mut osd = Text::new();
osd.add(Line("Stop sign for "));
osd.append(Line(ui.primary.map.get_r(r).get_name()).fg(ui.cs.get("OSD name color")));
osd.add_appended(vec![
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);
} else {
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 {
let mut txt = Text::prompt("Data Visualizer");
if let Some(ref name) = self.current_tract {
txt.add(Line("Census "));
txt.append(Line(name).fg(ui.cs.get("OSD name color")));
txt.add_appended(vec![
Line("Census "),
Line(name).fg(ui.cs.get("OSD name color")),
]);
let tract = &self.tracts[name];
txt.add(Line(format!(
"{} buildings",
@ -126,8 +128,10 @@ impl State for DataVisualizer {
self.menu.draw(g);
if let Some(ref name) = self.current_tract {
let mut osd = Text::new();
osd.add(Line("Census "));
osd.append(Line(name).fg(ui.cs.get("OSD name color")));
osd.add_appended(vec![
Line("Census "),
Line(name).fg(ui.cs.get("OSD name color")),
]);
CommonState::draw_custom_osd(g, osd);
} else {
CommonState::draw_osd(g, ui, &None);
@ -150,9 +154,11 @@ impl State for DataVisualizer {
} else {
let mut txt = Text::new();
for (k, v) in kv {
txt.add(Line(k).fg(Color::RED));
txt.append(Line(" = "));
txt.append(Line(v.to_string()).fg(Color::CYAN));
txt.add_appended(vec![
Line(k).fg(Color::RED),
Line(" = "),
Line(v.to_string()).fg(Color::CYAN),
]);
}
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:" {
continue;
}
labels.add(Line(format!("{} (", name)).size(40));
labels.append(
labels.add_appended(vec![
Line(format!("{} (", name)).size(40),
Line(format!(
"{}%",
((est.value as f64) / (sum as f64) * 100.0) as usize
))
.fg(Color::RED),
);
labels.append(Line(")"));
Line(")"),
]);
}
let (txt_width, total_height) = g.text_dims(&labels);
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);
if let Some(ID::Building(b)) = ui.primary.current_selection {
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")));
osd.add_appended(vec![
Line(b.to_string()).fg(ui.cs.get("OSD ID 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) {
osd.append(Line(format!(
". {} 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 {
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 to = self.trips_to_bldg.get(b);
osd.append(Line(format!(
". {} trips from here, {} trips to here, {} parked cars needed",
from.len(),
to.len(),
self.cars_needed_per_bldg[&b]
)));
osd.add_appended(vec![
Line(b.to_string()).fg(ui.cs.get("OSD ID color")),
Line(" is "),
Line(ui.primary.map.get_b(b).get_name()).fg(ui.cs.get("OSD name color")),
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);
} else {
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 mut summary = Text::new();
summary.add(Line("Score at "));
summary.append(Line(ui.primary.sim.time().to_string()).fg(Color::RED));
summary.add(Line(prettyprint_usize(t.unfinished_trips)).fg(Color::CYAN));
summary.append(Line(" unfinished trips"));
summary.add_appended(vec![
Line("Score at "),
Line(ui.primary.sim.time().to_string()).fg(Color::RED),
]);
summary.add_appended(vec![
Line(prettyprint_usize(t.unfinished_trips)).fg(Color::CYAN),
Line(" unfinished trips"),
]);
for (mode, trips) in &t
.finished_trips
@ -43,8 +47,10 @@ impl Scoreboard {
for (_, _, dt) in trips {
distrib.add(dt);
}
summary.add(Line(format!("{:?}", mode)).fg(Color::CYAN));
summary.append(Line(format!(" trips: {}", distrib.describe())));
summary.add_appended(vec![
Line(format!("{:?}", mode)).fg(Color::CYAN),
Line(format!(" trips: {}", distrib.describe())),
]);
}
Scoreboard { menu, summary }

View File

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