diff --git a/neothesia-core/src/render/text/mod.rs b/neothesia-core/src/render/text/mod.rs index e284a301..54769609 100644 --- a/neothesia-core/src/render/text/mod.rs +++ b/neothesia-core/src/render/text/mod.rs @@ -102,6 +102,77 @@ impl TextRenderer { }); } + pub fn measure(buffer: &glyphon::cosmic_text::Buffer) -> (f32, f32) { + buffer + .layout_runs() + .fold((0.0, 0.0), |(width, height), run| { + (run.line_w.max(width), height + run.line_height) + }) + } + + pub fn gen_buffer(&mut self, size: f32, text: &str) -> glyphon::Buffer { + self.gen_buffer_with_attr( + size, + text, + glyphon::Attrs::new().family(glyphon::Family::Name("Roboto")), + ) + } + + pub fn gen_buffer_bold(&mut self, size: f32, text: &str) -> glyphon::Buffer { + self.gen_buffer_with_attr( + size, + text, + glyphon::Attrs::new() + .family(glyphon::Family::Name("Roboto")) + .weight(glyphon::Weight::BOLD), + ) + } + + pub fn gen_buffer_with_attr( + &mut self, + size: f32, + text: &str, + attrs: glyphon::Attrs, + ) -> glyphon::Buffer { + let mut buffer = + glyphon::Buffer::new(&mut self.font_system, glyphon::Metrics::new(size, size)); + buffer.set_size(&mut self.font_system, Some(f32::MAX), Some(f32::MAX)); + buffer.set_text(&mut self.font_system, text, attrs, glyphon::Shaping::Basic); + buffer.shape_until_scroll(&mut self.font_system, false); + buffer + } + + pub fn queue_buffer(&mut self, x: f32, y: f32, buffer: glyphon::Buffer) { + self.queue(TextArea { + buffer, + left: x, + top: y, + scale: 1.0, + bounds: glyphon::TextBounds::default(), + default_color: glyphon::Color::rgb(255, 255, 255), + }); + } + + pub fn queue_buffer_centered( + &mut self, + x: f32, + y: f32, + w: f32, + h: f32, + buffer: glyphon::Buffer, + ) { + let (text_w, text_h) = Self::measure(&buffer); + + self.queue(TextArea { + buffer, + left: x + w / 2.0 - text_w / 2.0, + top: y + h / 2.0 - text_h / 2.0, + scale: 1.0, + bounds: glyphon::TextBounds::default(), + default_color: glyphon::Color::rgb(255, 255, 255), + }); + } + pub fn queue_icon(&mut self, x: f32, y: f32, size: f32, icon: &str) { let mut buffer = glyphon::Buffer::new(&mut self.font_system, glyphon::Metrics::new(size, size)); diff --git a/neothesia/src/scene/playing_scene/mod.rs b/neothesia/src/scene/playing_scene/mod.rs index cb86108a..9f41949e 100644 --- a/neothesia/src/scene/playing_scene/mod.rs +++ b/neothesia/src/scene/playing_scene/mod.rs @@ -154,7 +154,7 @@ impl Scene for PlayingScene { self.keyboard .update(&mut self.quad_pipeline, LAYER_FG, &mut ctx.text_renderer); - TopBar::update(self, &ctx.window_state, &mut ctx.text_renderer); + TopBar::update(self, ctx); self.quad_pipeline.prepare(&ctx.gpu.device, &ctx.gpu.queue); diff --git a/neothesia/src/scene/playing_scene/top_bar.rs b/neothesia/src/scene/playing_scene/top_bar.rs index 70c1c6d8..42667a01 100644 --- a/neothesia/src/scene/playing_scene/top_bar.rs +++ b/neothesia/src/scene/playing_scene/top_bar.rs @@ -1,7 +1,7 @@ use std::time::Instant; use neothesia_core::{ - render::{QuadInstance, QuadPipeline, TextRenderer}, + render::{QuadInstance, QuadPipeline}, utils::Rect, }; use wgpu_jumpstart::Color; @@ -10,7 +10,7 @@ use winit::{ event::{ElementState, MouseButton, WindowEvent}, }; -use crate::{context::Context, utils::window::WindowState, NeothesiaEvent}; +use crate::{context::Context, NeothesiaEvent}; use super::{ animation::{Animated, Animation, Easing}, @@ -37,6 +37,8 @@ enum Msg { SettingsToggle, ProggresBar(ProgressBarMsg), LooperEvent(LooperMsg), + SpeedUpdateUp, + SpeedUpdateDown, } pub struct TopBar { @@ -113,6 +115,13 @@ impl TopBar { Msg::SettingsToggle => { scene.top_bar.settings_active = !scene.top_bar.settings_active; } + Msg::SpeedUpdateUp => { + ctx.config.speed_multiplier += 0.1; + } + Msg::SpeedUpdateDown => { + ctx.config.speed_multiplier -= 0.1; + ctx.config.speed_multiplier = ctx.config.speed_multiplier.max(0.0); + } Msg::LooperEvent(msg) => { Looper::on_msg(scene, ctx, msg); } @@ -211,7 +220,7 @@ impl TopBar { } #[profiling::function] - pub fn update(scene: &mut PlayingScene, window_state: &WindowState, text: &mut TextRenderer) { + pub fn update(scene: &mut PlayingScene, ctx: &mut Context) { let PlayingScene { top_bar, quad_pipeline, @@ -220,6 +229,8 @@ impl TopBar { .. } = scene; + let window_state = &ctx.window_state; + top_bar.bbox.size.width = window_state.logical_size.width; let h = top_bar.bbox.size.height; @@ -260,12 +271,12 @@ impl TopBar { Looper::update, update_settings_card, ] { - f(scene, text, &now); + f(scene, ctx, &now); } } } -fn update_settings_card(scene: &mut PlayingScene, _text: &mut TextRenderer, _now: &Instant) { +fn update_settings_card(scene: &mut PlayingScene, _ctx: &mut Context, _now: &Instant) { let PlayingScene { top_bar, quad_pipeline, diff --git a/neothesia/src/scene/playing_scene/top_bar/header.rs b/neothesia/src/scene/playing_scene/top_bar/header.rs index f535e730..d98a6980 100644 --- a/neothesia/src/scene/playing_scene/top_bar/header.rs +++ b/neothesia/src/scene/playing_scene/top_bar/header.rs @@ -1,11 +1,13 @@ use std::time::Instant; -use neothesia_core::render::TextRenderer; +use speed_pill::SpeedPill; -use crate::scene::playing_scene::PlayingScene; +use crate::{context::Context, scene::playing_scene::PlayingScene}; use super::{looper::LooperMsg, Button, Msg}; +mod speed_pill; + fn gear_icon() -> &'static str { "\u{F3E5}" } @@ -36,6 +38,8 @@ pub struct Header { play_button: Button, loop_button: Button, settings_button: Button, + + speed_pill: SpeedPill, } impl Header { @@ -76,6 +80,8 @@ impl Header { play_button, loop_button, settings_button, + + speed_pill: SpeedPill::new(elements), } } @@ -104,7 +110,7 @@ impl Header { }); } - pub fn update(scene: &mut PlayingScene, text: &mut TextRenderer, _now: &Instant) { + pub fn update(scene: &mut PlayingScene, ctx: &mut Context, now: &Instant) { Self::update_button_icons(scene); let PlayingScene { @@ -123,7 +129,12 @@ impl Header { ) }); - top_bar.header.layout.center.once(|_row| {}); + let (center_box,) = top_bar.header.layout.center.once(|row| { + ( + row.push(90.0), + // + ) + }); let (_play, _loop, _settings) = top_bar.header.layout.end.once(|row| { ( @@ -136,6 +147,15 @@ impl Header { top_bar.header.layout.resolve(0.0, w); + top_bar.header.speed_pill.update( + &mut top_bar.elements, + ctx, + quad_pipeline, + y, + &top_bar.header.layout.center.items()[center_box], + now, + ); + let start_row = top_bar.header.layout.start.items(); let _center_row = top_bar.header.layout.center.items(); let end_row = top_bar.header.layout.end.items(); @@ -167,7 +187,7 @@ impl Header { &top_bar.header.loop_button, &top_bar.header.settings_button, ] { - btn.draw(quad_pipeline, text); + btn.draw(quad_pipeline, &mut ctx.text_renderer); } } } diff --git a/neothesia/src/scene/playing_scene/top_bar/header/speed_pill.rs b/neothesia/src/scene/playing_scene/top_bar/header/speed_pill.rs new file mode 100644 index 00000000..94ff04ad --- /dev/null +++ b/neothesia/src/scene/playing_scene/top_bar/header/speed_pill.rs @@ -0,0 +1,114 @@ +use std::time::Instant; + +use neothesia_core::render::{QuadInstance, QuadPipeline}; +use wgpu_jumpstart::Color; + +use crate::context::Context; +use crate::scene::playing_scene::animation::{Animated, Easing}; +use crate::scene::playing_scene::LAYER_FG; + +use super::Msg; + +fn minus_icon() -> &'static str { + "\u{F2EA}" +} + +fn plus_icon() -> &'static str { + "\u{F4FE}" +} + +pub struct SpeedPill { + plus: nuon::ElementId, + minus: nuon::ElementId, + + plus_animation: Animated, + minus_animation: Animated, +} + +impl SpeedPill { + pub fn new(elements: &mut nuon::ElementsMap) -> Self { + Self { + plus: elements.insert( + nuon::ElementBuilder::new() + .name("PlusButton") + .on_click(Msg::SpeedUpdateUp), + ), + minus: elements.insert( + nuon::ElementBuilder::new() + .name("MinusButton") + .on_click(Msg::SpeedUpdateDown), + ), + + plus_animation: Animated::new(false) + .duration(50.0) + .easing(Easing::EaseInOut) + .delay(0.0), + minus_animation: Animated::new(false) + .duration(50.0) + .easing(Easing::EaseInOut) + .delay(0.0), + } + } + + pub fn update( + &mut self, + elements: &mut nuon::ElementsMap, + ctx: &mut Context, + quad_pipeline: &mut QuadPipeline, + y: f32, + item: &nuon::RowItem, + now: &Instant, + ) { + let text = &mut ctx.text_renderer; + + let y = y + 5.0; + let w = item.width; + let half_w = w / 2.0; + + let h = 19.0; + + elements.update( + self.minus, + nuon::Rect::new((item.x, y).into(), (half_w, h).into()), + ); + elements.update( + self.plus, + nuon::Rect::new((item.x + half_w, y).into(), (half_w, h).into()), + ); + + for (element, animation, border_radius) in [ + ( + self.minus, + &mut self.minus_animation, + [10.0, 0.0, 10.0, 0.0], + ), + (self.plus, &mut self.plus_animation, [0.0, 10.0, 0.0, 10.0]), + ] { + if let Some(element) = elements.get(element) { + animation.transition(element.hovered(), *now); + + let m = animation.animate(0.0, 20.0, *now) / 255.0; + let c = 67.0 / 255.0; + let color = Color::new(c + m, c + m, c + m, 1.0); + + quad_pipeline.push( + LAYER_FG, + QuadInstance { + position: element.rect().origin.into(), + size: element.rect().size.into(), + color: color.into_linear_rgba(), + border_radius, + }, + ); + } + } + + let pad = 2.0; + text.queue_icon(pad + item.x, y, h, minus_icon()); + text.queue_icon(item.x + item.width - h - pad, y, h, plus_icon()); + + let label = format!("{}%", (ctx.config.speed_multiplier * 100.0).round()); + let buffer = text.gen_buffer_bold(13.0, &label); + text.queue_buffer_centered(item.x, y, w, h, buffer); + } +} diff --git a/neothesia/src/scene/playing_scene/top_bar/looper.rs b/neothesia/src/scene/playing_scene/top_bar/looper.rs index 2f2e054a..4969c355 100644 --- a/neothesia/src/scene/playing_scene/top_bar/looper.rs +++ b/neothesia/src/scene/playing_scene/top_bar/looper.rs @@ -1,6 +1,5 @@ use std::time::{Duration, Instant}; -use neothesia_core::render::TextRenderer; use wgpu_jumpstart::Color; use crate::{context::Context, scene::playing_scene::PlayingScene}; @@ -132,7 +131,7 @@ impl Looper { } } - pub fn update(scene: &mut PlayingScene, _text: &mut TextRenderer, now: &Instant) { + pub fn update(scene: &mut PlayingScene, _ctx: &mut Context, now: &Instant) { let PlayingScene { top_bar, quad_pipeline, diff --git a/neothesia/src/scene/playing_scene/top_bar/progress_bar.rs b/neothesia/src/scene/playing_scene/top_bar/progress_bar.rs index a11ff259..fdc90dc8 100644 --- a/neothesia/src/scene/playing_scene/top_bar/progress_bar.rs +++ b/neothesia/src/scene/playing_scene/top_bar/progress_bar.rs @@ -1,7 +1,5 @@ use std::time::Instant; -use neothesia_core::render::TextRenderer; - use crate::{ context::Context, scene::playing_scene::{rewind_controller::RewindController, PlayingScene}, @@ -60,7 +58,7 @@ impl ProgressBar { } } - pub fn update(scene: &mut PlayingScene, _text: &mut TextRenderer, _now: &Instant) { + pub fn update(scene: &mut PlayingScene, _ctx: &mut Context, _now: &Instant) { let PlayingScene { top_bar, quad_pipeline, diff --git a/nuon/src/lib.rs b/nuon/src/lib.rs index 62ea7181..886288b8 100644 --- a/nuon/src/lib.rs +++ b/nuon/src/lib.rs @@ -312,13 +312,13 @@ mod row_layout { &self.items } - pub fn resolve_left(&mut self, x: f32) { + pub fn resolve_left(&mut self, origin_x: f32) { if !self.dirty { return; } self.dirty = false; - let mut x = x; + let mut x = origin_x; for item in self.items.iter_mut() { item.x = x; @@ -328,13 +328,18 @@ mod row_layout { self.width = self .items .last() - .map(|i| (i.x - x) + i.width) + .map(|i| (i.x + i.width) - origin_x) .unwrap_or(0.0); } pub fn resolve_center(&mut self, x: f32, width: f32) { + if !self.dirty { + return; + } self.resolve_left(x); + let center_x = width / 2.0 - self.width() / 2.0; + for item in self.items.iter_mut() { item.x += center_x; }