diff --git a/ezgui/src/drawing.rs b/ezgui/src/drawing.rs index 4632c63402..14d08ca3ae 100644 --- a/ezgui/src/drawing.rs +++ b/ezgui/src/drawing.rs @@ -1,4 +1,5 @@ use crate::assets::Assets; +use crate::svg; use crate::widgets::ContextMenu; use crate::{ text, Canvas, Color, EventCtx, HorizontalAlignment, Key, ScreenDims, ScreenPt, ScreenRectangle, @@ -411,6 +412,24 @@ impl GeomBatch { } ScreenDims::new(bounds.max_x - bounds.min_x, bounds.max_y - bounds.min_y) } + + // Slightly weird use case, but hotswap colors. + pub fn rewrite_color(&mut self, from: Color, to: Color) { + for (c, _) in self.list.iter_mut() { + if *c == from { + *c = to; + } + } + } + + // TODO Weird API... + pub fn add_svg(&mut self, filename: &str, dx: f64, dy: f64) { + let mut batch = GeomBatch::new(); + svg::add_svg(&mut batch, filename); + for (color, poly) in batch.consume() { + self.push(color, poly.translate(dx, dy)); + } + } } // Something that's been sent to the GPU already. diff --git a/ezgui/src/svg.rs b/ezgui/src/svg.rs index e1fef16a9b..30154efad0 100644 --- a/ezgui/src/svg.rs +++ b/ezgui/src/svg.rs @@ -6,15 +6,21 @@ use lyon::math::Point; use lyon::path::PathEvent; use lyon::tessellation; use lyon::tessellation::geometry_builder::{simple_builder, VertexBuffers}; -use lyon::tessellation::FillVertex; +use lyon::tessellation::{FillVertex, StrokeVertex}; const TOLERANCE: f32 = 0.01; -// No offset. Returns the button bounds. +// Code here adapted from +// https://github.com/nical/lyon/blob/b5c87c9a22dccfab24daa1947419a70915d60914/examples/wgpu_svg/src/main.rs. + +// No offset. I'm not exactly sure how the simplification in usvg works, but this doesn't support +// transforms or strokes or text, just fills. Luckily, all of the files exported from Figma so far +// work just fine. pub fn add_svg(batch: &mut GeomBatch, filename: &str) -> Bounds { let mut fill_tess = tessellation::FillTessellator::new(); let mut stroke_tess = tessellation::StrokeTessellator::new(); - let mut mesh_per_color: VecMap> = VecMap::new(); + let mut fill_mesh_per_color: VecMap> = VecMap::new(); + let mut stroke_mesh_per_color: VecMap> = VecMap::new(); let svg_tree = usvg::Tree::from_file(&filename, &usvg::Options::default()).unwrap(); for node in svg_tree.root().descendants() { @@ -23,31 +29,40 @@ pub fn add_svg(batch: &mut GeomBatch, filename: &str) -> Bounds { if let Some(ref fill) = p.fill { let color = convert_color(&fill.paint, fill.opacity.value()); - let geom = mesh_per_color.mut_or_insert(color, VertexBuffers::new); + let geom = fill_mesh_per_color.mut_or_insert(color, VertexBuffers::new); fill_tess .tessellate_path( convert_path(p), &tessellation::FillOptions::tolerance(TOLERANCE), &mut simple_builder(geom), ) - .unwrap(); + .expect(&format!("Couldn't tesellate something from {}", filename)); } if let Some(ref stroke) = p.stroke { - panic!("aww we have a stroke {:?}", stroke); let (color, stroke_opts) = convert_stroke(stroke); - let geom: &mut VertexBuffers = - mesh_per_color.mut_or_insert(color, VertexBuffers::new); - /*stroke_tess.tessellate_path( - convert_path(p), - &stroke_opts, - &mut simple_builder(geom)).unwrap();*/ + let geom = stroke_mesh_per_color.mut_or_insert(color, VertexBuffers::new); + stroke_tess + .tessellate_path(convert_path(p), &stroke_opts, &mut simple_builder(geom)) + .unwrap(); } } } let mut bounds = Bounds::new(); - for (color, mesh) in mesh_per_color.consume() { + for (color, mesh) in fill_mesh_per_color.consume() { + let poly = Polygon::precomputed( + mesh.vertices + .into_iter() + .map(|v| Pt2D::new(v.position.x as f64, v.position.y as f64)) + .collect(), + mesh.indices.into_iter().map(|idx| idx as usize).collect(), + None, + ); + bounds.union(poly.get_bounds()); + batch.push(color, poly); + } + for (color, mesh) in stroke_mesh_per_color.consume() { let poly = Polygon::precomputed( mesh.vertices .into_iter() diff --git a/ezgui/src/widgets/button.rs b/ezgui/src/widgets/button.rs index 8d1266a950..0a77f438a0 100644 --- a/ezgui/src/widgets/button.rs +++ b/ezgui/src/widgets/button.rs @@ -168,19 +168,24 @@ impl Button { Button::new(normal, hovered, key, "", bg) } - pub fn rectangle_svg(filename: &str, key: Option, ctx: &EventCtx) -> Button { + pub fn rectangle_svg( + filename: &str, + tooltip: &str, + key: Option, + ctx: &EventCtx, + ) -> Button { let mut normal = GeomBatch::new(); let bounds = svg::add_svg(&mut normal, filename); - // TODO Rewrite colors let mut hovered = GeomBatch::new(); svg::add_svg(&mut hovered, filename); + hovered.rewrite_color(Color::WHITE, Color::ORANGE); Button::new( DrawBoth::new(ctx, normal, Vec::new()), DrawBoth::new(ctx, hovered, Vec::new()), key, - "", + tooltip, bounds.get_rectangle(), ) } diff --git a/ezgui/src/widgets/no_op.rs b/ezgui/src/widgets/no_op.rs index e774fde929..3a9fdc6a7d 100644 --- a/ezgui/src/widgets/no_op.rs +++ b/ezgui/src/widgets/no_op.rs @@ -1,4 +1,5 @@ use crate::layout::Widget; +use crate::svg; use crate::{DrawBoth, EventCtx, GeomBatch, GfxCtx, ScreenDims, ScreenPt, Text}; // Just draw something. A widget just so layouting works. @@ -18,6 +19,15 @@ impl JustDraw { } } + pub fn svg(filename: &str, ctx: &EventCtx) -> JustDraw { + let mut batch = GeomBatch::new(); + svg::add_svg(&mut batch, filename); + JustDraw { + draw: DrawBoth::new(ctx, batch, vec![]), + top_left: ScreenPt::new(0.0, 0.0), + } + } + pub fn text(text: Text, ctx: &EventCtx) -> JustDraw { JustDraw { draw: DrawBoth::new(ctx, GeomBatch::new(), vec![(text, ScreenPt::new(0.0, 0.0))]), diff --git a/game/assets/pregame/back.png b/game/assets/pregame/back.png deleted file mode 100644 index e7e15a43b1..0000000000 Binary files a/game/assets/pregame/back.png and /dev/null differ diff --git a/game/assets/pregame/back.svg b/game/assets/pregame/back.svg new file mode 100644 index 0000000000..ff0be5c205 --- /dev/null +++ b/game/assets/pregame/back.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/game/assets/pregame/challenges.svg b/game/assets/pregame/challenges.svg new file mode 100644 index 0000000000..6809dee9e2 --- /dev/null +++ b/game/assets/pregame/challenges.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/game/assets/pregame/logo.svg b/game/assets/pregame/logo.svg new file mode 100644 index 0000000000..477c8e651c --- /dev/null +++ b/game/assets/pregame/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/game/assets/pregame/quit.png b/game/assets/pregame/quit.png deleted file mode 100644 index 2c7fb56733..0000000000 Binary files a/game/assets/pregame/quit.png and /dev/null differ diff --git a/game/assets/pregame/quit.svg b/game/assets/pregame/quit.svg new file mode 100644 index 0000000000..7c9a08bedd --- /dev/null +++ b/game/assets/pregame/quit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/game/assets/pregame/tutorial.png b/game/assets/pregame/tutorial.png deleted file mode 100644 index 27d1b78c91..0000000000 Binary files a/game/assets/pregame/tutorial.png and /dev/null differ diff --git a/game/assets/pregame/tutorial.svg b/game/assets/pregame/tutorial.svg new file mode 100644 index 0000000000..93a9242aa0 --- /dev/null +++ b/game/assets/pregame/tutorial.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/game/assets/speed/pause.png b/game/assets/speed/pause.png deleted file mode 100644 index e51daaf951..0000000000 Binary files a/game/assets/speed/pause.png and /dev/null differ diff --git a/game/assets/speed/pause.svg b/game/assets/speed/pause.svg new file mode 100644 index 0000000000..5e61de0f58 --- /dev/null +++ b/game/assets/speed/pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/game/assets/speed/resume.png b/game/assets/speed/resume.png deleted file mode 100644 index 096d1004e9..0000000000 Binary files a/game/assets/speed/resume.png and /dev/null differ diff --git a/game/assets/speed/resume.svg b/game/assets/speed/resume.svg new file mode 100644 index 0000000000..d5002d6343 --- /dev/null +++ b/game/assets/speed/resume.svg @@ -0,0 +1,3 @@ + + + diff --git a/game/assets/speed/slow_down.png b/game/assets/speed/slow_down.png deleted file mode 100644 index a0da2e4aa3..0000000000 Binary files a/game/assets/speed/slow_down.png and /dev/null differ diff --git a/game/assets/speed/slow_down.svg b/game/assets/speed/slow_down.svg new file mode 100644 index 0000000000..1a99ede140 --- /dev/null +++ b/game/assets/speed/slow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/game/assets/speed/speed_up.png b/game/assets/speed/speed_up.png deleted file mode 100644 index f4f64162b6..0000000000 Binary files a/game/assets/speed/speed_up.png and /dev/null differ diff --git a/game/assets/speed/speed_up.svg b/game/assets/speed/speed_up.svg new file mode 100644 index 0000000000..0f1645abba --- /dev/null +++ b/game/assets/speed/speed_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/game/assets/speed/sunrise.png b/game/assets/speed/sunrise.png deleted file mode 100644 index aafee10aa5..0000000000 Binary files a/game/assets/speed/sunrise.png and /dev/null differ diff --git a/game/assets/speed/sunrise.svg b/game/assets/speed/sunrise.svg new file mode 100644 index 0000000000..d5fdcf460e --- /dev/null +++ b/game/assets/speed/sunrise.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/game/assets/speed/sunset.png b/game/assets/speed/sunset.png deleted file mode 100644 index 4603d0ab4a..0000000000 Binary files a/game/assets/speed/sunset.png and /dev/null differ diff --git a/game/assets/speed/sunset.svg b/game/assets/speed/sunset.svg new file mode 100644 index 0000000000..db803c6efe --- /dev/null +++ b/game/assets/speed/sunset.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/game/src/challenges.rs b/game/src/challenges.rs index f928336324..439eb667d8 100644 --- a/game/src/challenges.rs +++ b/game/src/challenges.rs @@ -111,9 +111,9 @@ pub fn challenges_picker(ctx: &EventCtx) -> Box { col.push(ManagedWidget::Row( LayoutStyle::Neutral, vec![ - ManagedWidget::img_button_no_bg( + ManagedWidget::svg_button( ctx, - "assets/pregame/back.png", + "assets/pregame/back.svg", "back", hotkey(Key::Escape), Box::new(|_, _| Some(Transition::Pop)), diff --git a/game/src/managed.rs b/game/src/managed.rs index 23a9f5caa1..71be56ef08 100644 --- a/game/src/managed.rs +++ b/game/src/managed.rs @@ -54,23 +54,13 @@ impl ManagedWidget { } pub fn svg_button( - ctx: &EventCtx, - filename: &str, - hotkey: Option, - onclick: Callback, - ) -> ManagedWidget { - let btn = Button::rectangle_svg(filename, hotkey, ctx); - ManagedWidget::Btn(btn, onclick) - } - - pub fn img_button_no_bg( ctx: &EventCtx, filename: &str, tooltip: &str, hotkey: Option, onclick: Callback, ) -> ManagedWidget { - let btn = Button::rectangle_img_no_bg(filename, tooltip, hotkey, ctx); + let btn = Button::rectangle_svg(filename, tooltip, hotkey, ctx); ManagedWidget::Btn(btn, onclick) } diff --git a/game/src/pregame.rs b/game/src/pregame.rs index 4d8774e517..432d89fd4a 100644 --- a/game/src/pregame.rs +++ b/game/src/pregame.rs @@ -75,9 +75,9 @@ pub fn main_menu(ctx: &EventCtx, ui: &UI) -> Box { col.push(ManagedWidget::Row( LayoutStyle::Neutral, vec![ - ManagedWidget::img_button_no_bg( + ManagedWidget::svg_button( ctx, - "assets/pregame/quit.png", + "assets/pregame/quit.svg", "quit", hotkey(Key::Escape), Box::new(|_, _| { @@ -101,15 +101,17 @@ pub fn main_menu(ctx: &EventCtx, ui: &UI) -> Box { col.push(ManagedWidget::Row( LayoutStyle::Centered, vec![ - ManagedWidget::img_button( + ManagedWidget::svg_button( ctx, - "assets/pregame/tutorial.png", + "assets/pregame/tutorial.svg", + "Tutorial", hotkey(Key::T), Box::new(|ctx, _| Some(Transition::Push(Box::new(TutorialMode::new(ctx))))), ), ManagedWidget::svg_button( ctx, "assets/pregame/sandbox.svg", + "Sandbox mode", hotkey(Key::S), Box::new(|ctx, ui| { Some(Transition::Push(Box::new(SandboxMode::new( @@ -158,9 +160,9 @@ pub fn main_menu(ctx: &EventCtx, ui: &UI) -> Box { fn about(ctx: &EventCtx) -> Box { let mut row = Vec::new(); - row.push(ManagedWidget::img_button_no_bg( + row.push(ManagedWidget::svg_button( ctx, - "assets/pregame/back.png", + "assets/pregame/back.svg", "back", hotkey(Key::Escape), Box::new(|_, _| Some(Transition::Pop)), diff --git a/game/src/sandbox/speed.rs b/game/src/sandbox/speed.rs index bade12d369..ac222f9e60 100644 --- a/game/src/sandbox/speed.rs +++ b/game/src/sandbox/speed.rs @@ -62,14 +62,12 @@ impl SpeedControls { Text::from(Line("00:00").size(12).roboto()).no_bg(), ScreenPt::new(25.0, 97.0), )); - let (sunrise_color, sunrise_rect) = ctx.canvas.texture_rect("assets/speed/sunrise.png"); - batch.push(sunrise_color, sunrise_rect.translate(94.0, 94.0)); + batch.add_svg("assets/speed/sunrise.svg", 94.0, 94.0); txt.push(( Text::from(Line("12:00").size(12).roboto()).no_bg(), ScreenPt::new(153.0, 97.0), )); - let (sunset_color, sunset_rect) = ctx.canvas.texture_rect("assets/speed/sunset.png"); - batch.push(sunset_color, sunset_rect.translate(220.0, 94.0)); + batch.add_svg("assets/speed/sunset.svg", 220.0, 94.0); txt.push(( Text::from(Line("24:00").size(12).roboto()).no_bg(), ScreenPt::new(280.0, 97.0), @@ -94,15 +92,11 @@ impl SpeedControls { }; // Row 1 - let resume_btn = Button::rectangle_img_no_bg( - "assets/speed/resume.png", - "resume", - hotkey(Key::Space), - ctx, - ) - .at(ScreenPt::new(23.0, 14.0)); + let resume_btn = + Button::rectangle_svg("assets/speed/resume.svg", "resume", hotkey(Key::Space), ctx) + .at(ScreenPt::new(23.0, 14.0)); let pause_btn = - Button::rectangle_img_no_bg("assets/speed/pause.png", "pause", hotkey(Key::Space), ctx) + Button::rectangle_svg("assets/speed/pause.svg", "pause", hotkey(Key::Space), ctx) .at(ScreenPt::new(23.0, 14.0)); let jump_to_time_btn = Button::rectangle_img_no_bg( @@ -140,15 +134,15 @@ impl SpeedControls { speed_slider.set_percent(ctx, (speed_cap / 1.0).powf(-1.0 / std::f64::consts::E)); speed_slider.set_pos(ScreenPt::new(92.0, 134.0)); - let slow_down_btn = Button::rectangle_img_no_bg( - "assets/speed/slow_down.png", + let slow_down_btn = Button::rectangle_svg( + "assets/speed/slow_down.svg", "slow down", hotkey(Key::LeftBracket), ctx, ) .at(ScreenPt::new(245.0, 129.0)); - let speed_up_btn = Button::rectangle_img_no_bg( - "assets/speed/speed_up.png", + let speed_up_btn = Button::rectangle_svg( + "assets/speed/speed_up.svg", "speed up", hotkey(Key::RightBracket), ctx, diff --git a/game/src/ui.rs b/game/src/ui.rs index 0fb961259a..f803ea6471 100644 --- a/game/src/ui.rs +++ b/game/src/ui.rs @@ -26,20 +26,11 @@ impl UI { let primary = ctx.loading_screen("load map", |ctx, mut timer| { ctx.set_textures( vec![ - ("assets/pregame/back.png", TextureType::Stretch), ("assets/pregame/challenges.png", TextureType::Stretch), - ("assets/pregame/quit.png", TextureType::Stretch), - ("assets/pregame/tutorial.png", TextureType::Stretch), ("assets/pregame/logo.png", TextureType::Stretch), ("assets/speed/jump_to_time.png", TextureType::Stretch), ("assets/speed/large_step.png", TextureType::Stretch), - ("assets/speed/pause.png", TextureType::Stretch), - ("assets/speed/resume.png", TextureType::Stretch), - ("assets/speed/slow_down.png", TextureType::Stretch), ("assets/speed/small_step.png", TextureType::Stretch), - ("assets/speed/speed_up.png", TextureType::Stretch), - ("assets/speed/sunrise.png", TextureType::Stretch), - ("assets/speed/sunset.png", TextureType::Stretch), ("assets/ui/edit_bike.png", TextureType::Stretch), ("assets/ui/edit_bus.png", TextureType::Stretch), ("assets/ui/edit_construction.png", TextureType::Stretch),