From 898e036c7e1b578a6d769929e5d85fb570f2ebcf Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 24 Oct 2018 15:59:53 -0700 Subject: [PATCH] the great color scheme refactor! --- docs/design/gui.md | 19 ++ editor/Cargo.toml | 2 - editor/color_scheme | 321 +------------------- editor/src/colors.rs | 159 ++++------ editor/src/main.rs | 3 - editor/src/objects.rs | 2 +- editor/src/plugins/a_b_tests.rs | 6 +- editor/src/plugins/chokepoints.rs | 8 +- editor/src/plugins/classification.rs | 10 +- editor/src/plugins/color_picker.rs | 35 +-- editor/src/plugins/diff_all.rs | 11 +- editor/src/plugins/diff_worlds.rs | 17 +- editor/src/plugins/draw_neighborhoods.rs | 35 ++- editor/src/plugins/floodfill.rs | 7 +- editor/src/plugins/map_edits.rs | 5 +- editor/src/plugins/neighborhood_summary.rs | 6 +- editor/src/plugins/road_editor.rs | 2 +- editor/src/plugins/scenarios.rs | 8 +- editor/src/plugins/search.rs | 10 +- editor/src/plugins/show_owner.rs | 7 +- editor/src/plugins/show_route.rs | 5 +- editor/src/plugins/sim_controls.rs | 10 +- editor/src/plugins/steep.rs | 2 +- editor/src/plugins/stop_sign_editor.rs | 11 +- editor/src/plugins/traffic_signal_editor.rs | 18 +- editor/src/plugins/turn_cycler.rs | 18 +- editor/src/render/area.rs | 22 +- editor/src/render/building.rs | 14 +- editor/src/render/bus_stop.rs | 8 +- editor/src/render/car.rs | 27 +- editor/src/render/extra_shape.rs | 5 +- editor/src/render/intersection.rs | 20 +- editor/src/render/lane.rs | 68 +++-- editor/src/render/map.rs | 27 +- editor/src/render/parcel.rs | 35 ++- editor/src/render/pedestrian.rs | 18 +- editor/src/render/turn.rs | 9 +- editor/src/ui.rs | 35 ++- ezgui/Cargo.toml | 2 + ezgui/src/lib.rs | 112 +++++-- ezgui/src/text.rs | 14 +- 41 files changed, 475 insertions(+), 678 deletions(-) diff --git a/docs/design/gui.md b/docs/design/gui.md index 8e8c46809a..a5f9951a84 100644 --- a/docs/design/gui.md +++ b/docs/design/gui.md @@ -370,3 +370,22 @@ Let's start with two concrete things: - sometimes call draw() alright, the boilerplate is all gone! \o/ I'm happy now. + +## Colors + +It's too tedious to make new colors, so I find myself hacking in temporary +hardcoded values. Declaring something far from its use sucks, because there's +no context. So how about this for an alternative: + +- allow either [0.0, 1.0] or [0, 255] formats. helper rgb(r, g, b) implicit 1.0 alpha and rgba(r, g, b, a) +- cs.get("name", default) +- the colorscheme object lazily accumulates the list of colors, so the color picker plugin still works + - but some are in plugins that dont get called often +- what gets serialized? just marked changes from the color picker? yeah, and + those overrides are loaded in at first. colorscheme obj has a set() fxn that + remembers it's changed from default in code. +- can still later support different colorschemes with different json files. +- shared colors? + - should those be defined in one of the places arbitrarily, that we know will be invoked early? + - could a macro help with registration? +- get() being mutable means we have to use RefCell or propogate mutability in lots of sad places diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 599a48c9d7..dd3b536b1f 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -27,5 +27,3 @@ serde = "1.0" serde_derive = "1.0" sim = { path = "../sim" } structopt = "0.2" -strum = "0.9.0" -strum_macros = "0.9.0" diff --git a/editor/color_scheme b/editor/color_scheme index f3b6ccd526..12f7fcf778 100644 --- a/editor/color_scheme +++ b/editor/color_scheme @@ -1,322 +1,3 @@ { - "map": { - "Background": [ - 1.0, - 1.0, - 1.0, - 1.0 - ], - "Debug": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "BrightDebug": [ - 0.8, - 0.1, - 0.1, - 1.0 - ], - "Broken": [ - 1.0, - 0.0, - 0.565, - 1.0 - ], - "Road": [ - 0.0, - 0.0, - 0.0, - 1.0 - ], - "DrivingLaneMarking": [ - 1.0, - 1.0, - 1.0, - 1.0 - ], - "Parking": [ - 0.2, - 0.2, - 0.2, - 1.0 - ], - "ParkingMarking": [ - 1.0, - 1.0, - 1.0, - 1.0 - ], - "Sidewalk": [ - 0.8, - 0.8, - 0.8, - 1.0 - ], - "SidewalkMarking": [ - 0.7, - 0.7, - 0.7, - 1.0 - ], - "Crosswalk": [ - 1.0, - 1.0, - 1.0, - 1.0 - ], - "StopSignMarking": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "Biking": [ - 0.059, - 0.494, - 0.294, - 1.0 - ], - "BusStopMarking": [ - 0.867, - 0.627, - 0.867, - 0.8 - ], - "UnchangedIntersection": [ - 0.6, - 0.6, - 0.6, - 1.0 - ], - "ChangedIntersection": [ - 0.8, - 0.6, - 0.6, - 1.0 - ], - "Selected": [ - 0.0, - 0.0, - 1.0, - 1.0 - ], - "Turn": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "ConflictingTurn": [ - 1.0, - 0.0, - 0.0, - 0.5 - ], - "Building": [ - 0.7, - 0.7, - 0.7, - 0.8 - ], - "BuildingPath": [ - 0.6, - 0.6, - 0.6, - 1.0 - ], - "BuildingBoundary": [ - 0.0, - 0.395, - 0.0, - 1.0 - ], - "ParcelBoundary": [ - 0.3, - 0.3, - 0.3, - 1.0 - ], - "RoadOrientation": [ - 1.0, - 1.0, - 0.0, - 1.0 - ], - "SearchResult": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "Visited": [ - 0.0, - 0.0, - 1.0, - 1.0 - ], - "Queued": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "NextQueued": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "TurnIconCircle": [ - 0.3, - 0.3, - 0.3, - 1.0 - ], - "TurnIconInactive": [ - 0.7, - 0.7, - 0.7, - 1.0 - ], - "ExtraShape": [ - 0.0, - 1.0, - 1.0, - 1.0 - ], - "MatchClassification": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "DontMatchClassification": [ - 0.0, - 0.0, - 0.0, - 0.1 - ], - "TurnIrrelevant": [ - 0.3, - 0.3, - 0.3, - 1.0 - ], - "SignalEditorTurnInCurrentCycle": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "SignalEditorTurnCompatibleWithCurrentCycle": [ - 0.0, - 1.0, - 0.0, - 0.2 - ], - "SignalEditorTurnConflictsWithCurrentCycle": [ - 1.0, - 0.0, - 0.0, - 0.0 - ], - "PriorityTurn": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "YieldTurn": [ - 1.0, - 1.0, - 0.0, - 1.0 - ], - "StopTurn": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "DebugCar": [ - 0.0, - 0.0, - 1.0, - 0.8 - ], - "MovingCar": [ - 0.0, - 1.0, - 1.0, - 1.0 - ], - "StuckCar": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "ParkedCar": [ - 0.7062618, - 0.91600245, - 0.29857337, - 1.0 - ], - "Pedestrian": [ - 0.2, - 0.7, - 0.7, - 1.0 - ], - "TrafficSignalBox": [ - 0.0, - 0.0, - 0.0, - 1.0 - ], - "TrafficSignalGreen": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "TrafficSignalYellow": [ - 1.0, - 1.0, - 0.0, - 1.0 - ], - "TrafficSignalRed": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "StopSignBackground": [ - 1.0, - 0.0, - 0.0, - 1.0 - ], - "ParkArea": [ - 0.0, - 1.0, - 0.0, - 1.0 - ], - "SwampArea": [ - 0.0, - 1.0, - 0.6, - 1.0 - ], - "WaterArea": [ - 0.0, - 0.0, - 1.0, - 1.0 - ] - } + "map": {} } \ No newline at end of file diff --git a/editor/src/colors.rs b/editor/src/colors.rs index 16582e7176..dee6cdafb9 100644 --- a/editor/src/colors.rs +++ b/editor/src/colors.rs @@ -1,122 +1,75 @@ -// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 - use abstutil; use ezgui::Color; -use rand; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::io::Error; -use strum::IntoEnumIterator; -#[derive( - Debug, - PartialEq, - Eq, - Serialize, - Deserialize, - EnumIter, - EnumString, - ToString, - PartialOrd, - Ord, - Clone, - Copy, -)] -pub enum Colors { - Background, - Debug, - BrightDebug, - Broken, - Road, - DrivingLaneMarking, - Parking, - ParkingMarking, - Sidewalk, - SidewalkMarking, - Crosswalk, - StopSignMarking, - Biking, - BusStopMarking, +pub struct ColorScheme { + // Filled out by lazy calls to get() + map: HashMap, - UnchangedIntersection, - ChangedIntersection, - - Selected, - Turn, - ConflictingTurn, - Building, - BuildingPath, - BuildingBoundary, - ParcelBoundary, - RoadOrientation, - SearchResult, - Visited, - Queued, - NextQueued, - TurnIconCircle, - TurnIconInactive, - ExtraShape, - - MatchClassification, - DontMatchClassification, - - TurnIrrelevant, - SignalEditorTurnInCurrentCycle, - SignalEditorTurnCompatibleWithCurrentCycle, - SignalEditorTurnConflictsWithCurrentCycle, - - PriorityTurn, - YieldTurn, - StopTurn, - - DebugCar, - MovingCar, - StuckCar, - ParkedCar, - - Pedestrian, - - TrafficSignalBox, - TrafficSignalGreen, - TrafficSignalYellow, - TrafficSignalRed, - - StopSignBackground, - - ParkArea, - SwampArea, - WaterArea, + // A subset of map + modified: ModifiedColors, } #[derive(Serialize, Deserialize)] -pub struct ColorScheme { - map: BTreeMap, +struct ModifiedColors { + map: BTreeMap, } impl ColorScheme { - pub fn load(path: &str) -> Result { - let mut scheme: ColorScheme = abstutil::read_json(path)?; - - for color in Colors::iter() { - if !scheme.map.contains_key(&color) { - warn!( - "No color for {:?} defined, initializing with a random one", - color - ); - scheme - .map - .insert(color, [rand::random(), rand::random(), rand::random(), 1.0]); - } + pub fn load() -> Result { + let modified: ModifiedColors = abstutil::read_json("color_scheme")?; + let mut map: HashMap = HashMap::new(); + for (name, c) in &modified.map { + map.insert(name.clone(), *c); } - Ok(scheme) + Ok(ColorScheme { map, modified }) } - pub fn get(&self, c: Colors) -> Color { - // TODO make sure this isn't slow; maybe back this with an array - *self.map.get(&c).unwrap() + pub fn save(&self) { + abstutil::write_json("color_scheme", &self.modified).expect("Saving color_scheme failed"); } - pub fn set(&mut self, c: Colors, value: Color) { - self.map.insert(c, value); + pub fn get(&mut self, name: &str, default: Color) -> Color { + if let Some(existing) = self.map.get(name) { + if default != *existing && !self.modified.map.contains_key(name) { + panic!( + "Two colors defined for {}: {} and {}", + name, existing, default + ); + } + return *existing; + } + + self.map.insert(name.to_string(), default); + default + } + + // Just for the color picker plugin, that's why the funky return value + pub fn color_names(&self) -> Vec<(String, ())> { + let mut names: Vec<(String, ())> = self.map.keys().map(|n| (n.clone(), ())).collect(); + names.sort(); + names + } + + pub fn override_color(&mut self, name: &str, value: Color) { + self.modified.map.insert(name.to_string(), value); + self.map.insert(name.to_string(), value); + } + + pub fn get_modified(&self, name: &str) -> Option { + self.modified.map.get(name).map(|c| *c) + } + + pub fn reset_modified(&mut self, name: &str, orig: Option) { + if let Some(c) = orig { + self.modified.map.insert(name.to_string(), c); + self.map.insert(name.to_string(), c); + } else { + self.modified.map.remove(name); + // Just, uh, wait for the default to be populated next time. :P + self.map.remove(name); + } } } diff --git a/editor/src/main.rs b/editor/src/main.rs index c9d988f100..62469a3e65 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -29,9 +29,6 @@ extern crate serde_derive; extern crate sim; #[macro_use] extern crate structopt; -extern crate strum; -#[macro_use] -extern crate strum_macros; #[macro_use] mod macros; diff --git a/editor/src/objects.rs b/editor/src/objects.rs index d1ecce60cd..9327ac71fa 100644 --- a/editor/src/objects.rs +++ b/editor/src/objects.rs @@ -103,7 +103,7 @@ impl ID { // For plugins and rendering. Not sure what module this should live in, here seems fine. pub struct Ctx<'a> { - pub cs: &'a ColorScheme, + pub cs: &'a mut ColorScheme, pub map: &'a Map, pub control_map: &'a ControlMap, pub draw_map: &'a DrawMap, diff --git a/editor/src/plugins/a_b_tests.rs b/editor/src/plugins/a_b_tests.rs index 8413292ecf..bea9cd0b80 100644 --- a/editor/src/plugins/a_b_tests.rs +++ b/editor/src/plugins/a_b_tests.rs @@ -1,3 +1,4 @@ +use colors::ColorScheme; use ezgui::{GfxCtx, LogScroller, Wizard, WrappedWizard}; use map_model::Map; use objects::{Ctx, SIM_SETUP}; @@ -45,7 +46,7 @@ impl Plugin for ABTestManager { ABTestManager::ManageABTest(test, ref mut scroller) => { if ctx.input.key_pressed(Key::R, "run this A/B test") { let ((new_primary, new_primary_plugins), new_secondary) = - launch_test(test, ctx.kml, &ctx.primary.current_flags); + launch_test(test, ctx.kml, &ctx.primary.current_flags, ctx.cs); *ctx.primary = new_primary; *ctx.new_primary_plugins = Some(new_primary_plugins); *ctx.secondary = Some(new_secondary); @@ -103,6 +104,7 @@ fn launch_test( test: &ABTest, kml: &Option, current_flags: &SimFlags, + cs: &mut ColorScheme, ) -> ((PerMapUI, PluginsPerMap), (PerMapUI, PluginsPerMap)) { info!("Launching A/B test {}...", test.test_name); let load = format!( @@ -123,6 +125,7 @@ fn launch_test( edits_name: test.edits1_name.clone(), }, kml, + cs, ); let secondary = PerMapUI::new( SimFlags { @@ -132,6 +135,7 @@ fn launch_test( edits_name: test.edits2_name.clone(), }, kml, + cs, ); // That's all! The scenario will be instantiated. (primary, secondary) diff --git a/editor/src/plugins/chokepoints.rs b/editor/src/plugins/chokepoints.rs index 1ae8ecb65c..969079dadd 100644 --- a/editor/src/plugins/chokepoints.rs +++ b/editor/src/plugins/chokepoints.rs @@ -1,4 +1,3 @@ -use colors::Colors; use counter::Counter; use dimensioned::si; use ezgui::Color; @@ -55,13 +54,12 @@ impl Plugin for ChokepointsFinder { } fn color_for(&self, obj: ID, ctx: Ctx) -> Option { + let color = ctx.cs.get("chokepoint", Color::RED); match self { ChokepointsFinder::Inactive => None, ChokepointsFinder::Active(lanes, intersections) => match obj { - ID::Lane(l) if lanes.contains(&l) => Some(ctx.cs.get(Colors::MatchClassification)), - ID::Intersection(i) if intersections.contains(&i) => { - Some(ctx.cs.get(Colors::MatchClassification)) - } + ID::Lane(l) if lanes.contains(&l) => Some(color), + ID::Intersection(i) if intersections.contains(&i) => Some(color), _ => None, }, } diff --git a/editor/src/plugins/classification.rs b/editor/src/plugins/classification.rs index a0467469ba..f104c0c6d2 100644 --- a/editor/src/plugins/classification.rs +++ b/editor/src/plugins/classification.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use ezgui::Color; use objects::{Ctx, DEBUG_EXTRA, ID}; use piston::input::Key; @@ -41,13 +40,16 @@ impl Plugin for OsmClassifier { Some(hwy) => hwy == "primary" || hwy == "secondary" || hwy == "tertiary", None => false, } { - Some(ctx.cs.get(Colors::MatchClassification)) + Some(ctx.cs.get("matches OSM classification", Color::GREEN)) } else { - Some(ctx.cs.get(Colors::DontMatchClassification)) + Some(ctx.cs.get( + "doesn't match OSM classification", + Color::rgba(0, 0, 0, 0.1), + )) } } ID::Building(b) => if ctx.map.get_b(b).osm_tags.contains_key("addr:housenumber") { - Some(ctx.cs.get(Colors::MatchClassification)) + Some(ctx.cs.get("matches OSM classification", Color::GREEN)) } else { None }, diff --git a/editor/src/plugins/color_picker.rs b/editor/src/plugins/color_picker.rs index f8e6233c01..636251ed81 100644 --- a/editor/src/plugins/color_picker.rs +++ b/editor/src/plugins/color_picker.rs @@ -1,12 +1,9 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use ezgui::{Canvas, Color, GfxCtx, InputResult, Menu}; use objects::{Ctx, SETTINGS}; use piston::input::Key; use plugins::{Plugin, PluginCtx}; -use std::string::ToString; -use strum::IntoEnumIterator; // TODO assumes minimum screen size const WIDTH: u32 = 255; @@ -16,9 +13,9 @@ const TILE_DIMS: u32 = 2; // TODO parts of this should be in ezgui pub enum ColorPicker { Inactive, - Choosing(Menu), - // Remember the original color, in case we revert - PickingColor(Colors, Color), + Choosing(Menu<()>), + // Remember the original modified color in case we revert. + ChangingColor(String, Option), } impl ColorPicker { @@ -37,7 +34,7 @@ impl Plugin for ColorPicker { if input.unimportant_key_pressed(Key::D8, SETTINGS, "configure colors") { new_state = Some(ColorPicker::Choosing(Menu::new( "Pick a color to change", - Colors::iter().map(|c| (c.to_string(), c)).collect(), + cs.color_names(), ))); } } @@ -47,21 +44,25 @@ impl Plugin for ColorPicker { new_state = Some(ColorPicker::Inactive); } InputResult::StillActive => {} - InputResult::Done(_, color) => { - new_state = Some(ColorPicker::PickingColor(color, cs.get(color))); + InputResult::Done(name, _) => { + new_state = Some(ColorPicker::ChangingColor( + name.clone(), + cs.get_modified(&name), + )); } }; } - ColorPicker::PickingColor(c, orig_color) => { + ColorPicker::ChangingColor(name, orig) => { if input.key_pressed( Key::Escape, - &format!("stop configuring color for {:?} and revert", c), + &format!("stop changing color for {} and revert", name), ) { - cs.set(*c, *orig_color); + cs.reset_modified(name, *orig); new_state = Some(ColorPicker::Inactive); - } else if input.key_pressed(Key::Return, &format!("finalize new color for {:?}", c)) + } else if input + .key_pressed(Key::Return, &format!("finalize new color for {}", name)) { - info!("Setting color for {:?}", c); + info!("Setting color for {}", name); new_state = Some(ColorPicker::Inactive); } @@ -71,7 +72,7 @@ impl Plugin for ColorPicker { let x = (m_x - (start_x as f64)) / (TILE_DIMS as f64) / 255.0; let y = (m_y - (start_y as f64)) / (TILE_DIMS as f64) / 255.0; if x >= 0.0 && x <= 1.0 && y >= 0.0 && y <= 1.0 { - cs.set(*c, get_color(x as f32, y as f32)); + cs.override_color(name, get_color(x as f32, y as f32)); } } } @@ -91,7 +92,7 @@ impl Plugin for ColorPicker { ColorPicker::Choosing(menu) => { menu.draw(g, ctx.canvas); } - ColorPicker::PickingColor(_, _) => { + ColorPicker::ChangingColor(_, _) => { let (start_x, start_y) = get_screen_offset(ctx.canvas); for x in 0..WIDTH { @@ -123,5 +124,5 @@ fn get_screen_offset(canvas: &Canvas) -> (u32, u32) { fn get_color(x: f32, y: f32) -> Color { assert!(x >= 0.0 && x <= 1.0); assert!(y >= 0.0 && y <= 1.0); - [x, y, (x + y) / 2.0, 1.0] + Color::rgb_f(x, y, (x + y) / 2.0) } diff --git a/editor/src/plugins/diff_all.rs b/editor/src/plugins/diff_all.rs index 45cb1ddc11..1a0f3d21a8 100644 --- a/editor/src/plugins/diff_all.rs +++ b/editor/src/plugins/diff_all.rs @@ -1,4 +1,4 @@ -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::Line; use map_model::LANE_THICKNESS; use objects::Ctx; @@ -74,11 +74,14 @@ impl Plugin for DiffAllState { active } - fn draw(&self, g: &mut GfxCtx, _ctx: Ctx) { + fn draw(&self, g: &mut GfxCtx, ctx: Ctx) { if let DiffAllState::Active(ref lines) = self { for line in lines { - // TODO move constants - g.draw_line([1.0, 1.0, 0.0, 1.0], LANE_THICKNESS, line); + g.draw_line( + ctx.cs.get("diff agents line", Color::YELLOW), + LANE_THICKNESS, + line, + ); } } } diff --git a/editor/src/plugins/diff_worlds.rs b/editor/src/plugins/diff_worlds.rs index ada5e0c066..fe456dff78 100644 --- a/editor/src/plugins/diff_worlds.rs +++ b/editor/src/plugins/diff_worlds.rs @@ -1,5 +1,5 @@ use dimensioned::si; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::Line; use map_model::{Trace, LANE_THICKNESS}; use objects::Ctx; @@ -98,7 +98,7 @@ impl Plugin for DiffWorldsState { } } - fn draw(&self, g: &mut GfxCtx, _ctx: Ctx) { + fn draw(&self, g: &mut GfxCtx, ctx: Ctx) { if let DiffWorldsState::Active { line, primary_route, @@ -107,18 +107,23 @@ impl Plugin for DiffWorldsState { } = self { if let Some(l) = line { - // TODO move constants - g.draw_line([1.0, 1.0, 0.0, 1.0], LANE_THICKNESS, l); + g.draw_line( + ctx.cs.get("diff agents line", Color::YELLOW), + LANE_THICKNESS, + l, + ); } if let Some(t) = primary_route { g.draw_polygon( - [1.0, 0.0, 0.0, 0.5], + ctx.cs + .get("primary agent route", Color::rgba(255, 0, 0, 0.5)), &t.get_polyline().make_polygons_blindly(LANE_THICKNESS), ); } if let Some(t) = secondary_route { g.draw_polygon( - [0.0, 0.0, 1.0, 0.5], + ctx.cs + .get("secondary agent route", Color::rgba(0, 0, 255, 0.5)), &t.get_polyline().make_polygons_blindly(LANE_THICKNESS), ); } diff --git a/editor/src/plugins/draw_neighborhoods.rs b/editor/src/plugins/draw_neighborhoods.rs index 0568bb9230..ac2f6726bd 100644 --- a/editor/src/plugins/draw_neighborhoods.rs +++ b/editor/src/plugins/draw_neighborhoods.rs @@ -1,4 +1,4 @@ -use ezgui::{GfxCtx, Wizard, WrappedWizard}; +use ezgui::{Color, GfxCtx, Wizard, WrappedWizard}; use geom::{Circle, Line, Polygon}; use map_model::Map; use objects::{Ctx, EDIT_MAP}; @@ -94,12 +94,6 @@ impl Plugin for DrawNeighborhoodState { } fn draw(&self, g: &mut GfxCtx, ctx: Ctx) { - // TODO add colorscheme entries - let red = [1.0, 0.0, 0.0, 1.0]; - let green = [0.0, 1.0, 0.0, 1.0]; - let blue = [0.0, 0.0, 1.0, 0.6]; - let cyan = [0.0, 1.0, 1.0, 1.0]; - let (pts, current_idx) = match self { DrawNeighborhoodState::Inactive => { return; @@ -118,19 +112,36 @@ impl Plugin for DrawNeighborhoodState { }; if pts.len() == 2 { - g.draw_line(red, POINT_RADIUS / 2.0, &Line::new(pts[0], pts[1])); + g.draw_line( + ctx.cs.get("neighborhood point", Color::RED), + POINT_RADIUS / 2.0, + &Line::new(pts[0], pts[1]), + ); } if pts.len() >= 3 { - g.draw_polygon(blue, &Polygon::new(pts)); + g.draw_polygon( + ctx.cs + .get("neighborhood polygon", Color::rgba(0, 0, 255, 0.6)), + &Polygon::new(pts), + ); } for pt in pts { - g.draw_circle(red, &Circle::new(*pt, POINT_RADIUS)); + g.draw_circle( + ctx.cs.get("neighborhood point", Color::RED), + &Circle::new(*pt, POINT_RADIUS), + ); } if let Some(last) = pts.last() { - g.draw_circle(green, &Circle::new(*last, POINT_RADIUS)); + g.draw_circle( + ctx.cs.get("neighborhood last placed point", Color::GREEN), + &Circle::new(*last, POINT_RADIUS), + ); } if let Some(idx) = current_idx { - g.draw_circle(cyan, &Circle::new(pts[idx], POINT_RADIUS)); + g.draw_circle( + ctx.cs.get("neighborhood point to move", Color::CYAN), + &Circle::new(pts[idx], POINT_RADIUS), + ); } } } diff --git a/editor/src/plugins/floodfill.rs b/editor/src/plugins/floodfill.rs index 2c103b58f9..9312ffe713 100644 --- a/editor/src/plugins/floodfill.rs +++ b/editor/src/plugins/floodfill.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use ezgui::Color; use map_model::{LaneID, Map}; use objects::{Ctx, ID}; @@ -87,14 +86,14 @@ impl Plugin for Floodfiller { match (self, obj) { (Floodfiller::Active { visited, queue }, ID::Lane(l)) => { if visited.contains(&l) { - return Some(ctx.cs.get(Colors::Visited)); + return Some(ctx.cs.get("visited in floodfill", Color::BLUE)); } if !queue.is_empty() && *queue.front().unwrap() == l { - return Some(ctx.cs.get(Colors::NextQueued)); + return Some(ctx.cs.get("next to visit in floodfill", Color::GREEN)); } // TODO linear search shouldnt suck too much for interactive mode if queue.contains(&l) { - return Some(ctx.cs.get(Colors::Queued)); + return Some(ctx.cs.get("queued in floodfill", Color::RED)); } None } diff --git a/editor/src/plugins/map_edits.rs b/editor/src/plugins/map_edits.rs index 5ffc1d7466..f7eb52b3df 100644 --- a/editor/src/plugins/map_edits.rs +++ b/editor/src/plugins/map_edits.rs @@ -1,3 +1,4 @@ +use colors::ColorScheme; use control::ControlMap; use ezgui::{GfxCtx, Wizard, WrappedWizard}; use map_model::Map; @@ -39,6 +40,7 @@ impl Plugin for EditsManager { &ctx.primary.control_map, ctx.kml, &mut new_primary, + ctx.cs, wizard.wrap(ctx.input), ).is_some() { @@ -77,6 +79,7 @@ fn manage_edits( control_map: &ControlMap, kml: &Option, new_primary: &mut Option<(PerMapUI, PluginsPerMap)>, + cs: &mut ColorScheme, mut wizard: WrappedWizard, ) -> Option<()> { // TODO Indicate how many edits are there / if there are any unsaved edits @@ -122,7 +125,7 @@ fn manage_edits( flags.edits_name = load_name; info!("Reloading everything..."); - *new_primary = Some(PerMapUI::new(flags, kml)); + *new_primary = Some(PerMapUI::new(flags, kml, cs)); Some(()) } _ => unreachable!(), diff --git a/editor/src/plugins/neighborhood_summary.rs b/editor/src/plugins/neighborhood_summary.rs index d7f3351747..7b19bc1bc8 100644 --- a/editor/src/plugins/neighborhood_summary.rs +++ b/editor/src/plugins/neighborhood_summary.rs @@ -136,7 +136,7 @@ impl Region { const COLORS: [Color; 3] = [ // TODO these are awful choices - [1.0, 0.0, 0.0, 0.8], - [0.0, 1.0, 0.0, 0.8], - [0.0, 0.0, 1.0, 0.8], + Color([1.0, 0.0, 0.0, 0.8]), + Color([0.0, 1.0, 0.0, 0.8]), + Color([0.0, 0.0, 1.0, 0.8]), ]; diff --git a/editor/src/plugins/road_editor.rs b/editor/src/plugins/road_editor.rs index f50bb312e1..7a55717d6f 100644 --- a/editor/src/plugins/road_editor.rs +++ b/editor/src/plugins/road_editor.rs @@ -89,7 +89,7 @@ impl Plugin for RoadEditor { // TODO Pretty sure control map needs to recalculate based on the new turns let old_type = map.get_l(id).lane_type; map.edit_lane_type(id, new_type); - draw_map.edit_lane_type(id, map, control_map); + draw_map.edit_lane_type(id, map, control_map, ctx.cs); sim.edit_lane_type(id, old_type, map); // Add turns back diff --git a/editor/src/plugins/scenarios.rs b/editor/src/plugins/scenarios.rs index 16dfa0cb2c..efa404b924 100644 --- a/editor/src/plugins/scenarios.rs +++ b/editor/src/plugins/scenarios.rs @@ -1,4 +1,4 @@ -use ezgui::{GfxCtx, LogScroller, Wizard, WrappedWizard}; +use ezgui::{Color, GfxCtx, LogScroller, Wizard, WrappedWizard}; use geom::Polygon; use map_model::Map; use objects::{Ctx, SIM_SETUP}; @@ -88,7 +88,11 @@ impl Plugin for ScenarioManager { } ScenarioManager::EditScenario(_, wizard) => { if let Some(neighborhood) = wizard.current_menu_choice::() { - g.draw_polygon([0.0, 0.0, 1.0, 0.6], &Polygon::new(&neighborhood.points)); + g.draw_polygon( + ctx.cs + .get("neighborhood polygon", Color::rgba(0, 0, 255, 0.6)), + &Polygon::new(&neighborhood.points), + ); } wizard.draw(g, ctx.canvas); } diff --git a/editor/src/plugins/search.rs b/editor/src/plugins/search.rs index 9f78c47a94..7847466049 100644 --- a/editor/src/plugins/search.rs +++ b/editor/src/plugins/search.rs @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::{ColorScheme, Colors}; +use colors::ColorScheme; use ezgui::{Color, GfxCtx, InputResult, TextBox}; use objects::{Ctx, DEBUG_EXTRA, ID}; use piston::input::Key; @@ -14,11 +14,15 @@ pub enum SearchState { } impl SearchState { - fn choose_color(&self, osm_tags: &BTreeMap, cs: &ColorScheme) -> Option { + fn choose_color( + &self, + osm_tags: &BTreeMap, + cs: &mut ColorScheme, + ) -> Option { if let SearchState::FilterOSM(filter) = self { for (k, v) in osm_tags { if format!("{}={}", k, v).contains(filter) { - return Some(cs.get(Colors::SearchResult)); + return Some(cs.get("search result", Color::RED)); } } } diff --git a/editor/src/plugins/show_owner.rs b/editor/src/plugins/show_owner.rs index b9195eea8f..29a5a365af 100644 --- a/editor/src/plugins/show_owner.rs +++ b/editor/src/plugins/show_owner.rs @@ -1,4 +1,3 @@ -use colors::Colors; use ezgui::Color; use map_model::BuildingID; use objects::{Ctx, ID}; @@ -65,16 +64,16 @@ impl Plugin for ShowOwnerState { } fn color_for(&self, obj: ID, ctx: Ctx) -> Option { + let color = ctx.cs.get("car/building owner", Color::PURPLE); match (self, obj) { (ShowOwnerState::BuildingSelected(_, cars), ID::Car(id)) => { if cars.contains(&id) { - // TODO really got lazy defining colors - return Some(ctx.cs.get(Colors::SearchResult)); + return Some(color); } } (ShowOwnerState::CarSelected(_, Some(id1)), ID::Building(id2)) => { if *id1 == id2 { - return Some(ctx.cs.get(Colors::SearchResult)); + return Some(color); } } _ => {} diff --git a/editor/src/plugins/show_route.rs b/editor/src/plugins/show_route.rs index 73509d35e1..8deffdc910 100644 --- a/editor/src/plugins/show_route.rs +++ b/editor/src/plugins/show_route.rs @@ -1,6 +1,5 @@ -use colors::Colors; use dimensioned::si; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use map_model::{Trace, LANE_THICKNESS}; use objects::Ctx; use piston::input::Key; @@ -70,7 +69,7 @@ impl Plugin for ShowRouteState { fn draw(&self, g: &mut GfxCtx, ctx: Ctx) { if let ShowRouteState::Active(_, trace) = self { g.draw_polygon( - ctx.cs.get(Colors::Queued), + ctx.cs.get("route", Color::rgba(255, 0, 0, 0.8)), &trace.get_polyline().make_polygons_blindly(LANE_THICKNESS), ); } diff --git a/editor/src/plugins/sim_controls.rs b/editor/src/plugins/sim_controls.rs index fa490532e9..f96c2fc55c 100644 --- a/editor/src/plugins/sim_controls.rs +++ b/editor/src/plugins/sim_controls.rs @@ -1,5 +1,5 @@ use abstutil::elapsed_seconds; -use ezgui::{Canvas, EventLoopMode, GfxCtx, Text, UserInput, TOP_RIGHT}; +use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Text, UserInput, TOP_RIGHT}; use objects::SIM; use piston::input::Key; use sim::{Benchmark, ScoreSummary, TIMESTEP}; @@ -180,8 +180,8 @@ impl SimController { fn summarize(txt: &mut Text, summary: ScoreSummary) { txt.add_styled_line( "Walking".to_string(), - [0.0, 0.0, 0.0, 1.0], - Some([1.0, 0.0, 0.0, 0.8]), + Color::BLACK, + Some(Color::rgba(255, 0, 0, 0.8)), ); txt.add_line(format!( " {}/{} trips done", @@ -192,8 +192,8 @@ fn summarize(txt: &mut Text, summary: ScoreSummary) { txt.add_styled_line( "Driving".to_string(), - [0.0, 0.0, 0.0, 1.0], - Some([0.0, 0.0, 1.0, 0.8]), + Color::BLACK, + Some(Color::rgba(0, 0, 255, 0.8)), ); txt.add_line(format!( " {}/{} trips done", diff --git a/editor/src/plugins/steep.rs b/editor/src/plugins/steep.rs index 63cce7a0d4..c90d1e2983 100644 --- a/editor/src/plugins/steep.rs +++ b/editor/src/plugins/steep.rs @@ -63,7 +63,7 @@ impl Plugin for SteepnessVisualizer { ID::Lane(l) => { let normalized = (self.get_delta(ctx.map, ctx.map.get_l(l)) - self.min_difference) / (self.max_difference - self.min_difference); - Some([normalized as f32, 0.0, 0.0, 1.0]) + Some(Color::rgb_f(normalized as f32, 0.0, 0.0)) } _ => None, } diff --git a/editor/src/plugins/stop_sign_editor.rs b/editor/src/plugins/stop_sign_editor.rs index 1e34dcfac6..b191e65d6c 100644 --- a/editor/src/plugins/stop_sign_editor.rs +++ b/editor/src/plugins/stop_sign_editor.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use control::stop_signs::TurnPriority; use ezgui::Color; use map_model::IntersectionID; @@ -105,12 +104,14 @@ impl Plugin for StopSignEditor { match (self, obj) { (StopSignEditor::Active(i), ID::Turn(t)) => { if t.parent != *i { - return Some(ctx.cs.get(Colors::TurnIrrelevant)); + return Some(ctx.cs.get("irrelevant turn", Color::grey(0.3))); } match ctx.control_map.stop_signs[i].get_priority(t) { - TurnPriority::Priority => Some(ctx.cs.get(Colors::PriorityTurn)), - TurnPriority::Yield => Some(ctx.cs.get(Colors::YieldTurn)), - TurnPriority::Stop => Some(ctx.cs.get(Colors::StopTurn)), + TurnPriority::Priority => { + Some(ctx.cs.get("priority stop sign turn", Color::GREEN)) + } + TurnPriority::Yield => Some(ctx.cs.get("yield stop sign turn", Color::YELLOW)), + TurnPriority::Stop => Some(ctx.cs.get("stop turn", Color::RED)), } } _ => None, diff --git a/editor/src/plugins/traffic_signal_editor.rs b/editor/src/plugins/traffic_signal_editor.rs index b982929d42..25b08c742e 100644 --- a/editor/src/plugins/traffic_signal_editor.rs +++ b/editor/src/plugins/traffic_signal_editor.rs @@ -2,7 +2,6 @@ // TODO how to edit cycle time? -use colors::Colors; use ezgui::Color; use map_model::IntersectionID; use objects::{Ctx, ID}; @@ -117,23 +116,20 @@ impl Plugin for TrafficSignalEditor { match (self, obj) { (TrafficSignalEditor::Active { i, current_cycle }, ID::Turn(t)) => { if t.parent != *i { - return Some(ctx.cs.get(Colors::TurnIrrelevant)); + return Some(ctx.cs.get("irrelevant turn", Color::grey(0.3))); } let cycle = &ctx.control_map.traffic_signals[&i].cycles[*current_cycle]; if cycle.contains(t) { - Some(ctx.cs.get(Colors::SignalEditorTurnInCurrentCycle)) + Some(ctx.cs.get("turn in current cycle", Color::GREEN)) } else if !cycle.conflicts_with(t, ctx.map) { - Some( - ctx.cs - .get(Colors::SignalEditorTurnCompatibleWithCurrentCycle), - ) + Some(ctx.cs.get( + "turn could be in current cycle", + Color::rgba(0, 255, 0, 0.2), + )) } else { - Some( - ctx.cs - .get(Colors::SignalEditorTurnConflictsWithCurrentCycle), - ) + Some(ctx.cs.get("turn conflicts with current cycle", Color::RED)) } // TODO maybe something to indicate unused in any cycle so far } diff --git a/editor/src/plugins/turn_cycler.rs b/editor/src/plugins/turn_cycler.rs index 709792ced1..aaa42e3eef 100644 --- a/editor/src/plugins/turn_cycler.rs +++ b/editor/src/plugins/turn_cycler.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use ezgui::{Color, GfxCtx}; use map_model::{IntersectionID, LaneID}; use objects::{Ctx, ID}; @@ -65,6 +64,8 @@ impl Plugin for TurnCyclerState { } fn draw(&self, g: &mut GfxCtx, ctx: Ctx) { + let color = ctx.cs.get("current selected turn", Color::RED); + match self { TurnCyclerState::Inactive => {} TurnCyclerState::Active(l, current_turn_index) => { @@ -74,12 +75,10 @@ impl Plugin for TurnCyclerState { Some(idx) => { let turn = relevant_turns[idx % relevant_turns.len()]; let draw_turn = ctx.draw_map.get_t(turn.id); - draw_turn.draw_full(g, ctx.cs.get(Colors::Turn)); + draw_turn.draw_full(g, color); } None => for turn in &relevant_turns { - ctx.draw_map - .get_t(turn.id) - .draw_full(g, ctx.cs.get(Colors::Turn)); + ctx.draw_map.get_t(turn.id).draw_full(g, color); }, } } @@ -90,9 +89,7 @@ impl Plugin for TurnCyclerState { let (cycle, _) = signal.current_cycle_and_remaining_time(ctx.sim.time.as_time()); for t in &cycle.turns { - ctx.draw_map - .get_t(*t) - .draw_full(g, ctx.cs.get(Colors::Turn)); + ctx.draw_map.get_t(*t).draw_full(g, color); } } } @@ -106,7 +103,10 @@ impl Plugin for TurnCyclerState { let relevant_turns = ctx.map.get_turns_from_lane(*l); if relevant_turns[idx % relevant_turns.len()].conflicts_with(ctx.map.get_t(t)) { - Some(ctx.cs.get(Colors::ConflictingTurn)) + Some(ctx.cs.get( + "turn conflicts with current turn", + Color::rgba(255, 0, 0, 0.5), + )) } else { None } diff --git a/editor/src/render/area.rs b/editor/src/render/area.rs index ecd93573f8..99aaed17e4 100644 --- a/editor/src/render/area.rs +++ b/editor/src/render/area.rs @@ -1,5 +1,5 @@ -use colors::Colors; -use ezgui::GfxCtx; +use colors::ColorScheme; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, Polygon, Pt2D}; use map_model::{Area, AreaID, AreaType, Map}; use objects::{Ctx, ID}; @@ -9,18 +9,19 @@ use render::{RenderOptions, Renderable}; pub struct DrawArea { pub id: AreaID, fill_polygon: Polygon, - color: Colors, + // TODO precomputing this means live color picker changes won't work. :( + color: Color, } impl DrawArea { - pub fn new(area: &Area) -> DrawArea { + pub fn new(area: &Area, cs: &mut ColorScheme) -> DrawArea { DrawArea { id: area.id, fill_polygon: area.get_polygon(), color: match area.area_type { - AreaType::Park => Colors::ParkArea, - AreaType::Swamp => Colors::SwampArea, - AreaType::Water => Colors::WaterArea, + AreaType::Park => cs.get("park area", Color::GREEN), + AreaType::Swamp => cs.get("swamp area", Color::rgb_f(0.0, 1.0, 0.6)), + AreaType::Water => cs.get("water area", Color::BLUE), }, } } @@ -31,11 +32,8 @@ impl Renderable for DrawArea { ID::Area(self.id) } - fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { - g.draw_polygon( - opts.color.unwrap_or(ctx.cs.get(self.color)), - &self.fill_polygon, - ); + fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, _ctx: Ctx) { + g.draw_polygon(opts.color.unwrap_or(self.color), &self.fill_polygon); } fn get_bounds(&self) -> Bounds { diff --git a/editor/src/render/building.rs b/editor/src/render/building.rs index d57849c44f..9580b4629a 100644 --- a/editor/src/render/building.rs +++ b/editor/src/render/building.rs @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, Line, PolyLine, Polygon, Pt2D}; use map_model::{Building, BuildingID, Map}; use objects::{Ctx, ID}; @@ -35,14 +34,19 @@ impl Renderable for DrawBuilding { fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { // Buildings look better without boundaries, actually - //g.draw_polygon(ctx.cs.get(Colors::BuildingBoundary), &self.boundary_polygon); + //g.draw_polygon(ctx.cs.get("building boundary", Color::rgb(0, 100, 0)), &self.boundary_polygon); g.draw_polygon( - opts.color.unwrap_or(ctx.cs.get(Colors::Building)), + opts.color + .unwrap_or(ctx.cs.get("building", Color::rgba_f(0.7, 0.7, 0.7, 0.8))), &self.fill_polygon, ); // TODO tune width - g.draw_rounded_line(ctx.cs.get(Colors::BuildingPath), 1.0, &self.front_path); + g.draw_rounded_line( + ctx.cs.get("building path", Color::grey(0.6)), + 1.0, + &self.front_path, + ); } fn get_bounds(&self) -> Bounds { diff --git a/editor/src/render/bus_stop.rs b/editor/src/render/bus_stop.rs index aa246a4e13..1f7b51dd65 100644 --- a/editor/src/render/bus_stop.rs +++ b/editor/src/render/bus_stop.rs @@ -1,6 +1,5 @@ -use colors::Colors; use dimensioned::si; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, PolyLine, Polygon, Pt2D}; use map_model::{BusStop, BusStopID, Map, LANE_THICKNESS}; use objects::{Ctx, ID}; @@ -42,7 +41,10 @@ impl Renderable for DrawBusStop { fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { g.draw_polygon( - opts.color.unwrap_or(ctx.cs.get(Colors::BusStopMarking)), + opts.color.unwrap_or( + ctx.cs + .get("bus stop marking", Color::rgba(220, 160, 220, 0.8)), + ), &self.polygon, ); } diff --git a/editor/src/render/car.rs b/editor/src/render/car.rs index 6714ad363b..b9dffe3315 100644 --- a/editor/src/render/car.rs +++ b/editor/src/render/car.rs @@ -1,5 +1,4 @@ -use colors::Colors; -use ezgui::{shift_color, GfxCtx}; +use ezgui::{Color, GfxCtx}; use geom::{Angle, Bounds, Line, PolyLine, Polygon, Pt2D}; use map_model::Map; use objects::{Ctx, ID}; @@ -91,24 +90,34 @@ impl Renderable for DrawCar { let color = opts.color.unwrap_or_else(|| { // TODO if it's a bus, color it differently -- but how? :\ match ctx.sim.get_car_state(self.id) { - CarState::Debug => shift_color(ctx.cs.get(Colors::DebugCar), self.id.0), - CarState::Moving => shift_color(ctx.cs.get(Colors::MovingCar), self.id.0), - CarState::Stuck => shift_color(ctx.cs.get(Colors::StuckCar), self.id.0), - CarState::Parked => shift_color(ctx.cs.get(Colors::ParkedCar), self.id.0), + CarState::Debug => ctx + .cs + .get("debug car", Color::rgba(0, 0, 255, 0.8)) + .shift(self.id.0), + CarState::Moving => ctx.cs.get("moving car", Color::CYAN).shift(self.id.0), + CarState::Stuck => ctx.cs.get("stuck car", Color::RED).shift(self.id.0), + CarState::Parked => ctx + .cs + .get("parked car", Color::rgb(180, 233, 76)) + .shift(self.id.0), } }); g.draw_polygon(color, &self.body_polygon); for p in &self.window_polygons { - g.draw_polygon([0.0, 0.0, 0.0, 1.0], p); + g.draw_polygon(ctx.cs.get("car window", Color::BLACK), p); } // TODO tune color, sizes if let Some(ref a) = self.turn_arrow { - g.draw_arrow([0.0, 1.0, 1.0, 1.0], 0.25, 1.0, a); + g.draw_arrow(ctx.cs.get("car turn arrow", Color::CYAN), 0.25, 1.0, a); } if let Some(ref t) = self.stopping_buffer { - g.draw_polygon([1.0, 0.0, 0.0, 0.7], t); + g.draw_polygon( + ctx.cs + .get("car stopping buffer", Color::rgba(255, 0, 0, 0.7)), + t, + ); } } diff --git a/editor/src/render/extra_shape.rs b/editor/src/render/extra_shape.rs index 6048ce3d66..84faeb669d 100644 --- a/editor/src/render/extra_shape.rs +++ b/editor/src/render/extra_shape.rs @@ -1,5 +1,4 @@ -use colors::Colors; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, Circle, Polygon, Pt2D}; use kml::{ExtraShape, ExtraShapeGeom, ExtraShapeID}; use map_model::Map; @@ -50,7 +49,7 @@ impl Renderable for DrawExtraShape { } fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { - let color = opts.color.unwrap_or(ctx.cs.get(Colors::ExtraShape)); + let color = opts.color.unwrap_or(ctx.cs.get("extra shape", Color::CYAN)); match self.shape { Shape::Polygon(ref p) => g.draw_polygon(color, &p), Shape::Circle(ref c) => g.draw_circle(color, c), diff --git a/editor/src/render/intersection.rs b/editor/src/render/intersection.rs index 6027d0064f..4a84ec77a5 100644 --- a/editor/src/render/intersection.rs +++ b/editor/src/render/intersection.rs @@ -1,8 +1,7 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use dimensioned::si; -use ezgui::GfxCtx; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, Circle, Line, Polygon, Pt2D}; use map_model::{Intersection, IntersectionID, LaneType, Map, LANE_THICKNESS}; use objects::{Ctx, ID}; @@ -52,7 +51,7 @@ impl DrawIntersection { fn draw_stop_sign(&self, g: &mut GfxCtx, ctx: Ctx) { // TODO rotate it g.draw_polygon( - ctx.cs.get(Colors::StopSignBackground), + ctx.cs.get("stop sign background", Color::RED), &Polygon::regular_polygon(self.center, 8, 1.5), ); // TODO draw "STOP" @@ -62,7 +61,7 @@ impl DrawIntersection { let radius = 0.5; g.draw_rectangle( - ctx.cs.get(Colors::TrafficSignalBox), + ctx.cs.get("traffic signal box", Color::BLACK), [ self.center.x() - (2.0 * radius), self.center.y() - (4.0 * radius), @@ -72,17 +71,17 @@ impl DrawIntersection { ); g.draw_circle( - ctx.cs.get(Colors::TrafficSignalYellow), + ctx.cs.get("traffic signal yellow", Color::YELLOW), &Circle::new(self.center, radius), ); g.draw_circle( - ctx.cs.get(Colors::TrafficSignalGreen), + ctx.cs.get("traffic signal green", Color::GREEN), &Circle::new(self.center.offset(0.0, radius * 2.0), radius), ); g.draw_circle( - ctx.cs.get(Colors::TrafficSignalRed), + ctx.cs.get("traffic signal red", Color::RED), &Circle::new(self.center.offset(0.0, radius * -2.0), radius), ); } @@ -103,9 +102,10 @@ impl Renderable for DrawIntersection { false }; if changed { - ctx.cs.get(Colors::ChangedIntersection) + ctx.cs + .get("changed intersection", Color::rgb_f(0.8, 0.6, 0.6)) } else { - ctx.cs.get(Colors::UnchangedIntersection) + ctx.cs.get("unchanged intersection", Color::grey(0.6)) } }); g.draw_polygon(color, &self.polygon); @@ -113,7 +113,7 @@ impl Renderable for DrawIntersection { for crosswalk in &self.crosswalks { for line in crosswalk { g.draw_line( - ctx.cs.get(Colors::Crosswalk), + ctx.cs.get("crosswalk", Color::WHITE), // TODO move this somewhere 0.25, line, diff --git a/editor/src/render/lane.rs b/editor/src/render/lane.rs index 15993de82f..2033e6c6cf 100644 --- a/editor/src/render/lane.rs +++ b/editor/src/render/lane.rs @@ -1,9 +1,9 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; +use colors::ColorScheme; use control::ControlMap; use dimensioned::si; -use ezgui::{GfxCtx, Text}; +use ezgui::{Color, GfxCtx, Text}; use geom::{Bounds, Circle, Line, Polygon, Pt2D}; use map_model; use map_model::{LaneID, LANE_THICKNESS}; @@ -15,7 +15,8 @@ const MIN_ZOOM_FOR_LANE_MARKERS: f64 = 5.0; #[derive(Debug)] struct Marking { lines: Vec, - color: Colors, + // TODO precomputing ruins live color picker changes + color: Color, thickness: f64, round: bool, } @@ -33,7 +34,12 @@ pub struct DrawLane { } impl DrawLane { - pub fn new(lane: &map_model::Lane, map: &map_model::Map, control_map: &ControlMap) -> DrawLane { + pub fn new( + lane: &map_model::Lane, + map: &map_model::Map, + control_map: &ControlMap, + cs: &mut ColorScheme, + ) -> DrawLane { let road = map.get_r(lane.parent); let start = new_perp_line(lane.first_line(), LANE_THICKNESS); let end = new_perp_line(lane.last_line().reverse(), LANE_THICKNESS); @@ -43,27 +49,27 @@ impl DrawLane { if road.is_canonical_lane(lane.id) { markings.push(Marking { lines: road.center_pts.lines(), - color: Colors::RoadOrientation, + color: cs.get("road center line", Color::YELLOW), thickness: BIG_ARROW_THICKNESS, round: true, }); } match lane.lane_type { map_model::LaneType::Sidewalk => { - markings.push(calculate_sidewalk_lines(lane)); + markings.push(calculate_sidewalk_lines(lane, cs)); } map_model::LaneType::Parking => { - markings.push(calculate_parking_lines(lane)); + markings.push(calculate_parking_lines(lane, cs)); } map_model::LaneType::Driving => { - for m in calculate_driving_lines(lane, road) { + for m in calculate_driving_lines(lane, road, cs) { markings.push(m); } } map_model::LaneType::Biking => {} }; if lane.is_driving() && !map.get_i(lane.dst_i).has_traffic_signal { - if let Some(m) = calculate_stop_sign_line(lane, control_map) { + if let Some(m) = calculate_stop_sign_line(lane, control_map, cs) { markings.push(m); } } @@ -79,11 +85,13 @@ impl DrawLane { } fn draw_debug(&self, g: &mut GfxCtx, ctx: Ctx) { - let circle_color = ctx.cs.get(Colors::BrightDebug); + let circle_color = ctx + .cs + .get("debug line endpoint", Color::rgb_f(0.8, 0.1, 0.1)); for l in ctx.map.get_l(self.id).lane_center_pts.lines() { g.draw_line( - ctx.cs.get(Colors::Debug), + ctx.cs.get("debug line", Color::RED), PARCEL_BOUNDARY_THICKNESS / 2.0, &l, ); @@ -117,13 +125,13 @@ impl Renderable for DrawLane { let color = opts.color.unwrap_or_else(|| { let l = ctx.map.get_l(self.id); let mut default = match l.lane_type { - map_model::LaneType::Driving => ctx.cs.get(Colors::Road), - map_model::LaneType::Parking => ctx.cs.get(Colors::Parking), - map_model::LaneType::Sidewalk => ctx.cs.get(Colors::Sidewalk), - map_model::LaneType::Biking => ctx.cs.get(Colors::Biking), + map_model::LaneType::Driving => ctx.cs.get("driving lane", Color::BLACK), + map_model::LaneType::Parking => ctx.cs.get("parking lane", Color::grey(0.2)), + map_model::LaneType::Sidewalk => ctx.cs.get("sidewalk", Color::grey(0.8)), + map_model::LaneType::Biking => ctx.cs.get("bike lane", Color::rgb(15, 125, 75)), }; if l.probably_broken { - default = ctx.cs.get(Colors::Broken); + default = ctx.cs.get("broken lane", Color::rgb_f(1.0, 0.0, 0.565)); } default }); @@ -133,9 +141,9 @@ impl Renderable for DrawLane { for m in &self.markings { for line in &m.lines { if m.round { - g.draw_rounded_line(ctx.cs.get(m.color), m.thickness, line); + g.draw_rounded_line(m.color, m.thickness, line); } else { - g.draw_line(ctx.cs.get(m.color), m.thickness, line); + g.draw_line(m.color, m.thickness, line); } } } @@ -191,7 +199,7 @@ fn new_perp_line(l: Line, length: f64) -> Line { Line::new(pt1, pt2) } -fn calculate_sidewalk_lines(lane: &map_model::Lane) -> Marking { +fn calculate_sidewalk_lines(lane: &map_model::Lane, cs: &mut ColorScheme) -> Marking { let tile_every = LANE_THICKNESS * si::M; let length = lane.length(); @@ -209,13 +217,13 @@ fn calculate_sidewalk_lines(lane: &map_model::Lane) -> Marking { Marking { lines, - color: Colors::SidewalkMarking, + color: cs.get("sidewalk lines", Color::grey(0.7)), thickness: 0.25, round: false, } } -fn calculate_parking_lines(lane: &map_model::Lane) -> Marking { +fn calculate_parking_lines(lane: &map_model::Lane, cs: &mut ColorScheme) -> Marking { // meters, but the dims get annoying below to remove // TODO make Pt2D natively understand meters, projecting away by an angle let leg_length = 1.0; @@ -244,13 +252,17 @@ fn calculate_parking_lines(lane: &map_model::Lane) -> Marking { Marking { lines, - color: Colors::ParkingMarking, + color: cs.get("parking line", Color::WHITE), thickness: 0.25, round: false, } } -fn calculate_driving_lines(lane: &map_model::Lane, parent: &map_model::Road) -> Option { +fn calculate_driving_lines( + lane: &map_model::Lane, + parent: &map_model::Road, + cs: &mut ColorScheme, +) -> Option { // The rightmost lanes don't have dashed white lines. if parent.dir_and_offset(lane.id).1 == 0 { return None; @@ -281,13 +293,17 @@ fn calculate_driving_lines(lane: &map_model::Lane, parent: &map_model::Road) -> Some(Marking { lines, - color: Colors::DrivingLaneMarking, + color: cs.get("dashed lane line", Color::WHITE), thickness: 0.25, round: false, }) } -fn calculate_stop_sign_line(lane: &map_model::Lane, control_map: &ControlMap) -> Option { +fn calculate_stop_sign_line( + lane: &map_model::Lane, + control_map: &ControlMap, + cs: &mut ColorScheme, +) -> Option { if control_map.stop_signs[&lane.dst_i].is_priority_lane(lane.id) { return None; } @@ -299,7 +315,7 @@ fn calculate_stop_sign_line(lane: &map_model::Lane, control_map: &ControlMap) -> let pt2 = pt1.project_away(1.0, angle); Some(Marking { lines: vec![perp_line(Line::new(pt1, pt2), LANE_THICKNESS)], - color: Colors::StopSignMarking, + color: cs.get("stop line for lane", Color::RED), thickness: 0.45, round: true, }) diff --git a/editor/src/render/map.rs b/editor/src/render/map.rs index b16a42b2dc..886ab9045c 100644 --- a/editor/src/render/map.rs +++ b/editor/src/render/map.rs @@ -2,6 +2,7 @@ use aabb_quadtree::geom::{Point, Rect}; use aabb_quadtree::QuadTree; +use colors::ColorScheme; use control::ControlMap; use geom::{Bounds, LonLat, Pt2D}; use kml::{ExtraShape, ExtraShapeID}; @@ -42,10 +43,16 @@ pub struct DrawMap { } impl DrawMap { - pub fn new(map: &Map, control_map: &ControlMap, raw_extra_shapes: Vec) -> DrawMap { + // TODO really hacky to take ColorScheme here for lanes and areas. :( + pub fn new( + map: &Map, + control_map: &ControlMap, + raw_extra_shapes: Vec, + cs: &mut ColorScheme, + ) -> DrawMap { let mut lanes: Vec = Vec::new(); for l in map.all_lanes() { - lanes.push(DrawLane::new(l, map, control_map)); + lanes.push(DrawLane::new(l, map, control_map, cs)); } let mut turn_to_lane_offset: HashMap = HashMap::new(); @@ -81,7 +88,11 @@ impl DrawMap { for s in map.all_bus_stops().values() { bus_stops.insert(s.id, DrawBusStop::new(s, map)); } - let areas: Vec = map.all_areas().iter().map(|a| DrawArea::new(a)).collect(); + let areas: Vec = map + .all_areas() + .iter() + .map(|a| DrawArea::new(a, cs)) + .collect(); // min_y here due to the wacky y inversion let bounds = map.get_gps_bounds(); @@ -156,9 +167,15 @@ impl DrawMap { } } - pub fn edit_lane_type(&mut self, id: LaneID, map: &Map, control_map: &ControlMap) { + pub fn edit_lane_type( + &mut self, + id: LaneID, + map: &Map, + control_map: &ControlMap, + cs: &mut ColorScheme, + ) { // No need to edit the quadtree; the bbox shouldn't depend on lane type. - self.lanes[id.0] = DrawLane::new(map.get_l(id), map, control_map); + self.lanes[id.0] = DrawLane::new(map.get_l(id), map, control_map, cs); } pub fn edit_remove_turn(&mut self, id: TurnID) { diff --git a/editor/src/render/parcel.rs b/editor/src/render/parcel.rs index b31cc765cf..975cc8f703 100644 --- a/editor/src/render/parcel.rs +++ b/editor/src/render/parcel.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use ezgui::{Color, GfxCtx}; use geom::{Bounds, PolyLine, Polygon, Pt2D}; use map_model::{Map, Parcel, ParcelID}; @@ -9,20 +8,21 @@ use render::{RenderOptions, Renderable, PARCEL_BOUNDARY_THICKNESS}; const COLORS: [Color; 14] = [ // TODO these are awful choices - [1.0, 1.0, 0.0, 1.0], - [1.0, 0.0, 1.0, 1.0], - [0.0, 1.0, 1.0, 1.0], - [0.5, 0.2, 0.7, 1.0], - [0.5, 0.5, 0.0, 0.5], - [0.5, 0.0, 0.5, 0.5], - [0.0, 0.5, 0.5, 0.5], - [0.0, 0.0, 0.5, 0.5], - [0.3, 0.2, 0.5, 0.5], - [0.4, 0.2, 0.5, 0.5], - [0.5, 0.2, 0.5, 0.5], - [0.6, 0.2, 0.5, 0.5], - [0.7, 0.2, 0.5, 0.5], - [0.8, 0.2, 0.5, 0.5], + // TODO can we express these with the nicer functions? probably need constexpr + Color([1.0, 1.0, 0.0, 1.0]), + Color([1.0, 0.0, 1.0, 1.0]), + Color([0.0, 1.0, 1.0, 1.0]), + Color([0.5, 0.2, 0.7, 1.0]), + Color([0.5, 0.5, 0.0, 0.5]), + Color([0.5, 0.0, 0.5, 0.5]), + Color([0.0, 0.5, 0.5, 0.5]), + Color([0.0, 0.0, 0.5, 0.5]), + Color([0.3, 0.2, 0.5, 0.5]), + Color([0.4, 0.2, 0.5, 0.5]), + Color([0.5, 0.2, 0.5, 0.5]), + Color([0.6, 0.2, 0.5, 0.5]), + Color([0.7, 0.2, 0.5, 0.5]), + Color([0.8, 0.2, 0.5, 0.5]), ]; #[derive(Debug)] @@ -56,7 +56,10 @@ impl Renderable for DrawParcel { }); g.draw_polygon(color, &self.fill_polygon); - g.draw_polygon(ctx.cs.get(Colors::ParcelBoundary), &self.boundary_polygon); + g.draw_polygon( + ctx.cs.get("parcel boundary", Color::grey(0.3)), + &self.boundary_polygon, + ); } fn get_bounds(&self) -> Bounds { diff --git a/editor/src/render/pedestrian.rs b/editor/src/render/pedestrian.rs index ff66f4ff02..34ebc2c9a3 100644 --- a/editor/src/render/pedestrian.rs +++ b/editor/src/render/pedestrian.rs @@ -1,5 +1,4 @@ -use colors::Colors; -use ezgui::{shift_color, GfxCtx}; +use ezgui::{Color, GfxCtx}; use geom::{Bounds, Circle, Line, Pt2D}; use map_model::Map; use objects::{Ctx, ID}; @@ -39,14 +38,21 @@ impl Renderable for DrawPedestrian { } fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { - let color = opts - .color - .unwrap_or(shift_color(ctx.cs.get(Colors::Pedestrian), self.id.0)); + let color = opts.color.unwrap_or_else(|| { + ctx.cs + .get("pedestrian", Color::rgb_f(0.2, 0.7, 0.7)) + .shift(self.id.0) + }); g.draw_circle(color, &self.circle); // TODO tune color, sizes if let Some(ref a) = self.turn_arrow { - g.draw_rounded_arrow([0.0, 1.0, 1.0, 1.0], 0.25, 0.3, a); + g.draw_rounded_arrow( + ctx.cs.get("pedestrian turn arrow", Color::CYAN), + 0.25, + 0.3, + a, + ); } } diff --git a/editor/src/render/turn.rs b/editor/src/render/turn.rs index 74e533afb9..bbcf1d288f 100644 --- a/editor/src/render/turn.rs +++ b/editor/src/render/turn.rs @@ -1,6 +1,5 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use colors::Colors; use dimensioned::si; use ezgui::{Color, GfxCtx}; use geom::{Bounds, Circle, Line, Pt2D}; @@ -66,10 +65,14 @@ impl Renderable for DrawTurn { } fn draw(&self, g: &mut GfxCtx, opts: RenderOptions, ctx: Ctx) { - g.draw_circle(ctx.cs.get(Colors::TurnIconCircle), &self.icon_circle); + g.draw_circle( + ctx.cs.get("turn icon circle", Color::grey(0.3)), + &self.icon_circle, + ); g.draw_arrow( - opts.color.unwrap_or(ctx.cs.get(Colors::TurnIconInactive)), + opts.color + .unwrap_or_else(|| ctx.cs.get("inactive turn icon", Color::grey(0.7))), TURN_ICON_ARROW_THICKNESS, TURN_ICON_ARROW_TIP_LENGTH, &self.icon_arrow, diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 4c1df991c3..75826fbbb1 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -3,7 +3,7 @@ // TODO this should just be a way to handle interactions between plugins use abstutil; -use colors::{ColorScheme, Colors}; +use colors::ColorScheme; use control::ControlMap; //use cpuprofiler; use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Text, UserInput, BOTTOM_LEFT, GUI}; @@ -22,6 +22,7 @@ use plugins::Plugin; use render::{DrawMap, RenderOptions}; use sim; use sim::{Sim, SimFlags}; +use std::cell::RefCell; use std::process; const MIN_ZOOM_FOR_MOUSEOVER: f64 = 4.0; @@ -38,7 +39,8 @@ pub struct UI { active_plugin: Option, canvas: Canvas, - cs: ColorScheme, + // TODO mutable ColorScheme to slurp up defaults is NOT ideal. + cs: RefCell, // Remember this to support loading a new PerMapUI kml: Option, @@ -90,7 +92,7 @@ impl GUI for UI { }; // TODO maybe make state line up with the map, so loading from a new map doesn't break abstutil::write_json("editor_state", &state).expect("Saving editor_state failed"); - abstutil::write_json("color_scheme", &self.cs).expect("Saving color_scheme failed"); + self.cs.borrow().save(); info!("Saved editor_state and color_scheme"); //cpuprofiler::PROFILER.lock().unwrap().stop().unwrap(); process::exit(0); @@ -119,7 +121,7 @@ impl GUI for UI { } fn draw(&self, g: &mut GfxCtx, osd: Text) { - g.clear(self.cs.get(Colors::Background)); + g.clear(self.cs.borrow_mut().get("map background", Color::WHITE)); let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen( self.canvas.get_screen_bbox(), @@ -139,7 +141,7 @@ impl GUI for UI { g, opts, Ctx { - cs: &self.cs, + cs: &mut self.cs.borrow_mut(), map: &self.primary.map, control_map: &self.primary.control_map, draw_map: &self.primary.draw_map, @@ -158,7 +160,7 @@ impl GUI for UI { g, opts, Ctx { - cs: &self.cs, + cs: &mut self.cs.borrow_mut(), map: &self.primary.map, control_map: &self.primary.control_map, draw_map: &self.primary.draw_map, @@ -172,7 +174,7 @@ impl GUI for UI { p.draw( g, Ctx { - cs: &self.cs, + cs: &mut self.cs.borrow_mut(), map: &self.primary.map, control_map: &self.primary.control_map, draw_map: &self.primary.draw_map, @@ -225,7 +227,11 @@ impl PluginsPerMap { } impl PerMapUI { - pub fn new(flags: SimFlags, kml: &Option) -> (PerMapUI, PluginsPerMap) { + pub fn new( + flags: SimFlags, + kml: &Option, + cs: &mut ColorScheme, + ) -> (PerMapUI, PluginsPerMap) { flame::start("setup"); let (map, control_map, sim) = sim::load(flags.clone(), Some(sim::Tick::from_seconds(30))); let extra_shapes = if let Some(path) = kml { @@ -235,7 +241,7 @@ impl PerMapUI { }; flame::start("draw_map"); - let draw_map = DrawMap::new(&map, &control_map, extra_shapes); + let draw_map = DrawMap::new(&map, &control_map, extra_shapes, cs); flame::end("draw_map"); flame::end("setup"); @@ -299,7 +305,8 @@ impl UI { // Do this first, so anything logged by sim::load isn't lost. let logs = plugins::logs::DisplayLogs::new(); - let (primary, primary_plugins) = PerMapUI::new(flags, &kml); + let mut cs = ColorScheme::load().unwrap(); + let (primary, primary_plugins) = PerMapUI::new(flags, &kml, &mut cs); let mut ui = UI { primary, primary_plugins, @@ -324,7 +331,7 @@ impl UI { active_plugin: None, canvas: Canvas::new(), - cs: ColorScheme::load("color_scheme").unwrap(), + cs: RefCell::new(cs), kml, }; @@ -377,11 +384,11 @@ impl UI { fn color_obj(&self, id: ID) -> Option { if Some(id) == self.primary.current_selection { - return Some(self.cs.get(Colors::Selected)); + return Some(self.cs.borrow_mut().get("selected", Color::BLUE)); } let ctx = Ctx { - cs: &self.cs, + cs: &mut self.cs.borrow_mut(), map: &self.primary.map, control_map: &self.primary.control_map, draw_map: &self.primary.draw_map, @@ -414,7 +421,7 @@ impl UI { primary: &mut self.primary, secondary: &mut self.secondary, canvas: &mut self.canvas, - cs: &mut self.cs, + cs: &mut self.cs.borrow_mut(), input, osd, kml: &self.kml, diff --git a/ezgui/Cargo.toml b/ezgui/Cargo.toml index 954c8d7ab7..dd366fb338 100644 --- a/ezgui/Cargo.toml +++ b/ezgui/Cargo.toml @@ -13,3 +13,5 @@ piston = "*" piston2d-graphics = "*" piston2d-opengl_graphics = "*" pistoncore-glutin_window = "*" +serde = "1.0" +serde_derive = "1.0" diff --git a/ezgui/src/lib.rs b/ezgui/src/lib.rs index 206f6bc0e0..67da30f2bb 100644 --- a/ezgui/src/lib.rs +++ b/ezgui/src/lib.rs @@ -10,6 +10,8 @@ extern crate log; extern crate opengl_graphics; extern crate palette; extern crate piston; +#[macro_use] +extern crate serde_derive; mod canvas; mod input; @@ -32,6 +34,7 @@ pub use menu::Menu; use opengl_graphics::{GlGraphics, Texture}; use piston::input::Key; pub use runner::{run, EventLoopMode, GUI}; +use std::fmt; pub use text::Text; pub use text_box::TextBox; pub use wizard::{Wizard, WrappedWizard}; @@ -59,13 +62,13 @@ impl<'a> GfxCtx<'a> { } pub fn clear(&mut self, color: Color) { - graphics::clear(color, self.gfx); + graphics::clear(color.0, self.gfx); } // Use graphics::Line internally for now, but make it easy to switch to something else by // picking this API now. pub fn draw_line(&mut self, color: Color, thickness: f64, line: &geom::Line) { - graphics::Line::new(color, thickness).draw( + graphics::Line::new(color.0, thickness).draw( line_to_array(line), &self.ctx.draw_state, self.ctx.transform, @@ -74,7 +77,7 @@ impl<'a> GfxCtx<'a> { } pub fn draw_rounded_line(&mut self, color: Color, thickness: f64, line: &geom::Line) { - graphics::Line::new_round(color, thickness).draw( + graphics::Line::new_round(color.0, thickness).draw( line_to_array(line), &self.ctx.draw_state, self.ctx.transform, @@ -83,7 +86,7 @@ impl<'a> GfxCtx<'a> { } pub fn draw_arrow(&mut self, color: Color, thickness: f64, head_size: f64, line: &geom::Line) { - graphics::Line::new(color, thickness).draw_arrow( + graphics::Line::new(color.0, thickness).draw_arrow( line_to_array(line), head_size, &self.ctx.draw_state, @@ -99,7 +102,7 @@ impl<'a> GfxCtx<'a> { head_size: f64, line: &geom::Line, ) { - graphics::Line::new_round(color, thickness).draw_arrow( + graphics::Line::new_round(color.0, thickness).draw_arrow( line_to_array(line), head_size, &self.ctx.draw_state, @@ -110,7 +113,7 @@ impl<'a> GfxCtx<'a> { pub fn draw_polygon(&mut self, color: Color, poly: &geom::Polygon) { for tri in &poly.triangles { - graphics::Polygon::new(color).draw( + graphics::Polygon::new(color.0).draw( &vec![ [tri.pt1.x(), tri.pt1.y()], [tri.pt2.x(), tri.pt2.y()], @@ -124,7 +127,7 @@ impl<'a> GfxCtx<'a> { } pub fn draw_circle(&mut self, color: Color, circle: &geom::Circle) { - graphics::Ellipse::new(color).draw( + graphics::Ellipse::new(color.0).draw( [ circle.center.x() - circle.radius, circle.center.y() - circle.radius, @@ -139,7 +142,7 @@ impl<'a> GfxCtx<'a> { // TODO probably better to have a Polygon::make_rectangle helper or something pub fn draw_rectangle(&mut self, color: Color, rect: [f64; 4]) { - graphics::Rectangle::new(color).draw( + graphics::Rectangle::new(color.0).draw( rect, &self.ctx.draw_state, self.ctx.transform, @@ -206,26 +209,6 @@ impl ToggleableLayer { } } -// Deterministically shift a color's brightness based on an ID. -pub fn shift_color(c: Color, id: usize) -> Color { - use palette::Shade; - - // TODO this needs tuning. too easy to get too light/dark, but also too easy to have too few - // variants. should maybe just manually come up with a list of 100 colors, hardcode in, modulo. - let variants = 10; - let half_variants = variants / 2; - let modulo = id % variants; - let scale = 1.0 / (variants as f32); - - let color = palette::Srgb::new(c[0], c[1], c[2]).into_linear(); - let new_color = if modulo < half_variants { - color.lighten(scale * (modulo as f32)) - } else { - color.darken(scale * ((modulo - half_variants) as f32)) - }; - [new_color.red, new_color.green, new_color.blue, 1.0] -} - fn line_to_array(l: &geom::Line) -> [f64; 4] { [l.pt1().x(), l.pt1().y(), l.pt2().x(), l.pt2().y()] } @@ -236,4 +219,75 @@ pub enum InputResult { Done(String, T), } -pub type Color = [f32; 4]; +// Copy could be reconsidered, but eh +// TODO only pub so we can construct constants elsewhere. need const fn. +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct Color(pub [f32; 4]); + +impl fmt::Display for Color { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Color(r={}, g={}, b={}, a={})", + self.0[0], self.0[1], self.0[2], self.0[3] + ) + } +} + +impl Color { + pub const BLACK: Color = Color([0.0, 0.0, 0.0, 1.0]); + pub const WHITE: Color = Color([1.0, 1.0, 1.0, 1.0]); + pub const RED: Color = Color([1.0, 0.0, 0.0, 1.0]); + pub const GREEN: Color = Color([0.0, 1.0, 0.0, 1.0]); + pub const BLUE: Color = Color([0.0, 0.0, 1.0, 1.0]); + pub const CYAN: Color = Color([0.0, 1.0, 1.0, 1.0]); + pub const YELLOW: Color = Color([1.0, 1.0, 0.0, 1.0]); + pub const PURPLE: Color = Color([0.5, 0.0, 0.5, 1.0]); + + // TODO should assert stuff about the inputs + + pub fn rgb(r: usize, g: usize, b: usize) -> Color { + Color::rgba(r, g, b, 1.0) + } + + pub fn rgb_f(r: f32, g: f32, b: f32) -> Color { + Color([r, g, b, 1.0]) + } + + pub fn rgba(r: usize, g: usize, b: usize, a: f32) -> Color { + Color([ + (r as f32) / 255.0, + (g as f32) / 255.0, + (b as f32) / 255.0, + a, + ]) + } + + pub fn rgba_f(r: f32, g: f32, b: f32, a: f32) -> Color { + Color([r, g, b, a]) + } + + pub fn grey(f: f32) -> Color { + Color([f, f, f, 1.0]) + } + + // Deterministically shift a color's brightness based on an ID. + pub fn shift(&self, id: usize) -> Color { + use palette::Shade; + + // TODO this needs tuning. too easy to get too light/dark, but also too easy to have too few + // variants. should maybe just manually come up with a list of 100 colors, hardcode in, modulo. + let variants = 10; + let half_variants = variants / 2; + let modulo = id % variants; + let scale = 1.0 / (variants as f32); + + let color = palette::Srgb::new(self.0[0], self.0[1], self.0[2]).into_linear(); + let new_color = if modulo < half_variants { + color.lighten(scale * (modulo as f32)) + } else { + color.darken(scale * ((modulo - half_variants) as f32)) + }; + Color([new_color.red, new_color.green, new_color.blue, 1.0]) + } +} diff --git a/ezgui/src/text.rs b/ezgui/src/text.rs index d450897618..9dbe99c33e 100644 --- a/ezgui/src/text.rs +++ b/ezgui/src/text.rs @@ -3,10 +3,10 @@ use graphics::{Image, Rectangle, Transformed}; use {Color, GfxCtx}; -pub const TEXT_FG_COLOR: Color = [0.0, 0.0, 0.0, 1.0]; -pub const TEXT_QUERY_COLOR: Color = [0.0, 0.0, 1.0, 0.5]; -pub const TEXT_FOCUS_COLOR: Color = [1.0, 0.0, 0.0, 0.5]; -const TEXT_BG_COLOR: Color = [0.0, 1.0, 0.0, 0.5]; +pub const TEXT_FG_COLOR: Color = Color([0.0, 0.0, 0.0, 1.0]); +pub const TEXT_QUERY_COLOR: Color = Color([0.0, 0.0, 1.0, 0.5]); +pub const TEXT_FOCUS_COLOR: Color = Color([1.0, 0.0, 0.0, 0.5]); +const TEXT_BG_COLOR: Color = Color([0.0, 1.0, 0.0, 0.5]); const FONT_SIZE: u32 = 24; // TODO this is a hack, need a glyphs.height() method as well! @@ -99,7 +99,7 @@ impl Text { pub fn draw_text_bubble(g: &mut GfxCtx, (x1, y1): (f64, f64), txt: Text) { let (total_width, total_height) = txt.dims(g); - Rectangle::new(txt.bg_color).draw( + Rectangle::new(txt.bg_color.0).draw( [x1, y1, total_width, total_height], &g.orig_ctx.draw_state, g.orig_ctx.transform, @@ -114,7 +114,7 @@ pub fn draw_text_bubble(g: &mut GfxCtx, (x1, y1): (f64, f64), txt: Text) { if let Some(color) = span.highlight_color { // TODO do we ever want to use total_width? let width = g.glyphs.width(FONT_SIZE, &span.text).unwrap(); - Rectangle::new(color).draw( + Rectangle::new(color.0).draw( [x, y - LINE_HEIGHT, width, LINE_HEIGHT], &g.orig_ctx.draw_state, g.orig_ctx.transform, @@ -122,7 +122,7 @@ pub fn draw_text_bubble(g: &mut GfxCtx, (x1, y1): (f64, f64), txt: Text) { ); } - let fg_text = Image::new_color(span.fg_color); + let fg_text = Image::new_color(span.fg_color.0); for ch in span.text.chars() { if let Ok(draw_ch) = g.glyphs.character(FONT_SIZE, ch) {