diff --git a/neothesia-core/src/render/quad/mod.rs b/neothesia-core/src/render/quad/mod.rs index dfbab7e3..1b127db0 100644 --- a/neothesia-core/src/render/quad/mod.rs +++ b/neothesia-core/src/render/quad/mod.rs @@ -79,6 +79,10 @@ impl<'a> QuadPipeline { &mut self.instances.data } + pub fn push(&mut self, quad: QuadInstance) { + self.instances.data.push(quad) + } + pub fn prepare(&self, queue: &wgpu::Queue) { self.instances.update(queue); } diff --git a/neothesia-core/src/utils/bbox.rs b/neothesia-core/src/utils/bbox.rs new file mode 100644 index 00000000..39c1fc1a --- /dev/null +++ b/neothesia-core/src/utils/bbox.rs @@ -0,0 +1,36 @@ +use super::{Point, Size}; + +#[derive(Default, Clone, Copy)] +pub struct Bbox { + pub pos: Point<f32>, + pub size: Size<f32>, +} + +impl Bbox { + pub fn new(pos: Point<f32>, size: Size<f32>) -> Self { + Self { pos, size } + } + + pub fn contains(&self, px: f32, py: f32) -> bool { + let Point { x, y } = self.pos; + let Size { w, h } = self.size; + + (x..(x + w)).contains(&px) && (y..(y + h)).contains(&py) + } + + pub fn x(&self) -> f32 { + self.pos.x + } + + pub fn y(&self) -> f32 { + self.pos.y + } + + pub fn w(&self) -> f32 { + self.size.w + } + + pub fn h(&self) -> f32 { + self.size.h + } +} diff --git a/neothesia-core/src/utils/mod.rs b/neothesia-core/src/utils/mod.rs index 111f79ef..c9e483f7 100644 --- a/neothesia-core/src/utils/mod.rs +++ b/neothesia-core/src/utils/mod.rs @@ -1,11 +1,20 @@ +pub mod bbox; pub mod resources; +pub use bbox::Bbox; + #[derive(Debug, Default, Clone, Copy)] pub struct Point<T> { pub x: T, pub y: T, } +impl<T> Point<T> { + pub fn new(x: T, y: T) -> Self { + Self { x, y } + } +} + impl<T> From<(T, T)> for Point<T> { fn from((x, y): (T, T)) -> Self { Self { x, y } @@ -54,6 +63,12 @@ pub struct Size<T> { pub h: T, } +impl<T> Size<T> { + pub fn new(w: T, h: T) -> Self { + Self { w, h } + } +} + impl<T> From<(T, T)> for Size<T> { fn from((w, h): (T, T)) -> Self { Self { w, h } diff --git a/neothesia/src/scene/playing_scene/top_bar.rs b/neothesia/src/scene/playing_scene/top_bar.rs index a70b670e..d7785205 100644 --- a/neothesia/src/scene/playing_scene/top_bar.rs +++ b/neothesia/src/scene/playing_scene/top_bar.rs @@ -1,6 +1,9 @@ use std::time::Duration; -use neothesia_core::render::{QuadInstance, TextRenderer}; +use neothesia_core::{ + render::{QuadInstance, QuadPipeline, TextRenderer}, + utils::{Bbox, Point, Size}, +}; use wgpu_jumpstart::Color; use winit::{ dpi::PhysicalPosition, @@ -24,29 +27,6 @@ enum Element { None, } -#[derive(Default, Clone, Copy)] -struct ElementInfo { - x: f32, - y: f32, - w: f32, - h: f32, -} - -impl ElementInfo { - fn contains(&self, x: f32, y: f32) -> bool { - (self.x..(self.x + self.w)).contains(&x) && (self.y..(self.y + self.h)).contains(&y) - } - - fn pos(&self) -> [f32; 2] { - [self.x, self.y] - } - - fn size(&self) -> [f32; 2] { - [self.w, self.h] - } -} - -#[derive(Default)] pub struct TopBar { height: f32, loop_tick_height: f32, @@ -58,15 +38,82 @@ pub struct TopBar { pub loop_start: Duration, pub loop_end: Duration, - back_button: ElementInfo, - play_button: ElementInfo, - loop_button: ElementInfo, - loop_start_tick: ElementInfo, - loop_end_tick: ElementInfo, + back_button: Button, + play_button: Button, + loop_button: Button, + loop_start_tick: Bbox, + loop_end_tick: Bbox, pub loop_active: bool, } +#[derive(Default)] +struct Button { + bbox: Bbox, + element: Element, + is_hovered: bool, + icon: &'static str, +} + +impl Button { + fn new(element: Element) -> Self { + Self { + element, + bbox: Bbox::new(Point::new(0.0, 0.0), Size::new(30.0, 30.0)), + is_hovered: false, + icon: "", + } + } + + fn set_x(&mut self, x: f32) -> &mut Self { + self.bbox.pos.x = x; + self + } + + fn set_y(&mut self, y: f32) -> &mut Self { + self.bbox.pos.y = y; + self + } + + fn set_hovered(&mut self, hovered: bool) -> &mut Self { + self.is_hovered = hovered; + self + } + + fn set_icon(&mut self, icon: &'static str) -> &mut Self { + self.icon = icon; + self + } + + fn bbox_with_type(&self) -> (&Bbox, Element) { + (&self.bbox, self.element) + } + + fn draw(&mut self, quad_pipeline: &mut QuadPipeline, text: &mut TextRenderer) { + let color = if self.is_hovered { + BUTTON_HOVER + } else { + BAR_BG + } + .into_linear_rgba(); + + quad_pipeline.push(QuadInstance { + position: self.bbox.pos.into(), + size: self.bbox.size.into(), + color, + border_radius: [5.0; 4], + }); + + let icon_size = 20.0; + text.queue_icon( + self.bbox.x() + (self.bbox.w() - icon_size) / 2.0, + self.bbox.y() + (self.bbox.h() - icon_size) / 2.0, + icon_size, + self.icon, + ); + } +} + macro_rules! color_u8 { ($r: expr, $g: expr, $b: expr, $a: expr) => { Color::new($r as f32 / 255.0, $g as f32 / 255.0, $b as f32 / 255.0, 1.0) @@ -102,7 +149,17 @@ impl TopBar { Self { height: 45.0 + 30.0, loop_tick_height: 45.0 + 10.0, - ..Default::default() + loop_button: Button::new(Element::RepeatButton), + back_button: Button::new(Element::BackButton), + play_button: Button::new(Element::PlayButton), + drag: Element::None, + hovered: Element::None, + loop_start: Duration::ZERO, + loop_end: Duration::ZERO, + loop_active: false, + animation: 0.0, + loop_start_tick: Bbox::default(), + loop_end_tick: Bbox::default(), } } @@ -112,9 +169,9 @@ impl TopBar { fn hovered(&self, x: f32, y: f32) -> Element { [ - (&self.loop_button, Element::RepeatButton), - (&self.back_button, Element::BackButton), - (&self.play_button, Element::PlayButton), + self.loop_button.bbox_with_type(), + self.back_button.bbox_with_type(), + self.play_button.bbox_with_type(), (&self.loop_start_tick, Element::StartTick), (&self.loop_end_tick, Element::EndTick), ] @@ -245,7 +302,7 @@ impl TopBar { let w = window_state.logical_size.width; let progress_x = w * player.percentage(); - let mut is_hovered = window_state.cursor_logical_position.y < h * 1.2; + let mut is_hovered = window_state.cursor_logical_position.y < h * 1.7; if let RewindController::Mouse { .. } = rewind_controller { is_hovered = true; @@ -261,7 +318,7 @@ impl TopBar { top_bar.animation = top_bar.animation.max(0.0); if !is_hovered { - fg_quad_pipeline.instances().push(QuadInstance { + fg_quad_pipeline.push(QuadInstance { position: [0.0, 0.0], size: [progress_x, 5.0], color: BLUE.into_linear_rgba(), @@ -281,7 +338,7 @@ impl TopBar { let y = -h + (bar_animation * h); - fg_quad_pipeline.instances().push(QuadInstance { + fg_quad_pipeline.push(QuadInstance { position: [0.0, y], size: [w, h], color: BAR_BG.into_linear_rgba(), @@ -289,7 +346,7 @@ impl TopBar { }); let progress_x = w * player.percentage(); - fg_quad_pipeline.instances().push(QuadInstance { + fg_quad_pipeline.push(QuadInstance { position: [0.0, y + 30.0], size: [progress_x, h - 30.0], color: BLUE.into_linear_rgba(), @@ -309,7 +366,7 @@ impl TopBar { DARK_MEASURE }; - fg_quad_pipeline.instances().push(QuadInstance { + fg_quad_pipeline.push(QuadInstance { position: [x, y + 30.0], size: [1.0, h - 30.0], color: color.into_linear_rgba(), @@ -317,124 +374,77 @@ impl TopBar { }); } - update_loop_button(scene, y, w, text); + update_buttons(scene, y, w, text); update_looper(scene, w, bar_animation); } } -fn update_loop_button(scene: &mut PlayingScene, y: f32, w: f32, text: &mut TextRenderer) { - let top_bar = &mut scene.top_bar; - - top_bar.loop_button = ElementInfo { - x: w - 30.0, - y, - w: 30.0, - h: 30.0, - }; - - let color = if let Element::RepeatButton = top_bar.hovered { - BUTTON_HOVER - } else { - BAR_BG - }; - - scene.fg_quad_pipeline.instances().push(QuadInstance { - position: top_bar.loop_button.pos(), - size: top_bar.loop_button.size(), - color: color.into_linear_rgba(), - border_radius: [5.0; 4], - }); - - let icon_size = 20.0; - text.queue_icon( - top_bar.loop_button.x + (top_bar.loop_button.w - icon_size) / 2.0, - top_bar.loop_button.y + (top_bar.loop_button.h - icon_size) / 2.0, - icon_size, - repeat_icon(), - ); - - { - top_bar.play_button = ElementInfo { - x: top_bar.loop_button.x - 30.0, - ..top_bar.loop_button - }; - - let color = if let Element::PlayButton = top_bar.hovered { - BUTTON_HOVER - } else { - BAR_BG - }; - scene.fg_quad_pipeline.instances().push(QuadInstance { - position: top_bar.play_button.pos(), - size: top_bar.play_button.size(), - color: color.into_linear_rgba(), - border_radius: [5.0; 4], - }); - text.queue_icon( - top_bar.play_button.x + (top_bar.play_button.w - icon_size) / 2.0, - top_bar.play_button.y + (top_bar.play_button.h - icon_size) / 2.0, - icon_size, - if scene.player.is_paused() { - play_icon() - } else { - pause_icon() - }, - ); - } - - { - top_bar.back_button = ElementInfo { - x: 0.0, - ..top_bar.loop_button - }; - - let color = if let Element::BackButton = top_bar.hovered { - BUTTON_HOVER +fn update_buttons(scene: &mut PlayingScene, y: f32, w: f32, text: &mut TextRenderer) { + let PlayingScene { + top_bar, + fg_quad_pipeline, + .. + } = scene; + + top_bar + .back_button + .set_x(0.0) + .set_y(y) + .set_hovered(matches!(top_bar.hovered, Element::BackButton)) + .set_icon(left_arrow_icon()) + .draw(fg_quad_pipeline, text); + + top_bar + .loop_button + .set_x(w - 30.0) + .set_y(y) + .set_hovered(matches!(top_bar.hovered, Element::RepeatButton)) + .set_icon(repeat_icon()) + .draw(fg_quad_pipeline, text); + + top_bar + .play_button + .set_x(w - 30.0 * 2.0) + .set_y(y) + .set_hovered(matches!(top_bar.hovered, Element::PlayButton)) + .set_icon(if scene.player.is_paused() { + play_icon() } else { - BAR_BG - }; - scene.fg_quad_pipeline.instances().push(QuadInstance { - position: top_bar.back_button.pos(), - size: top_bar.back_button.size(), - color: color.into_linear_rgba(), - border_radius: [5.0; 4], - }); - - text.queue_icon( - top_bar.back_button.x + (top_bar.back_button.w - icon_size) / 2.0, - top_bar.back_button.y + (top_bar.back_button.h - icon_size) / 2.0, - icon_size, - left_arrow_icon(), - ); - } + pause_icon() + }) + .draw(fg_quad_pipeline, text); } fn update_looper(scene: &mut PlayingScene, w: f32, animation: f32) { - let top_bar = &mut scene.top_bar; - let quad_pipeline = &mut scene.fg_quad_pipeline; - - let h = top_bar.loop_tick_height; - - top_bar.loop_start_tick = ElementInfo { - x: scene.player.time_to_percentage(&top_bar.loop_start) * w, - y: 30.0, - w: 5.0, - h, - }; - top_bar.loop_end_tick = ElementInfo { - x: scene.player.time_to_percentage(&top_bar.loop_end) * w, - y: 30.0, - w: 5.0, - h, - }; + let PlayingScene { + top_bar, + fg_quad_pipeline, + ref player, + .. + } = scene; if !top_bar.loop_active { return; } - let offset = top_bar.loop_start_tick.y + h; - top_bar.loop_start_tick.y += -offset + (animation * offset); - top_bar.loop_end_tick.y = top_bar.loop_start_tick.y; + let h = top_bar.loop_tick_height; + let offset = 30.0 + h; + let y = 30.0 - offset + (animation * offset); + + top_bar.loop_start_tick = Bbox::new( + Point { + x: player.time_to_percentage(&top_bar.loop_start) * w, + y, + }, + Size::new(5.0, h), + ); + top_bar.loop_end_tick = Bbox::new( + Point { + x: player.time_to_percentage(&top_bar.loop_end) * w, + y, + }, + Size::new(5.0, h), + ); let (start_color, end_color) = match (top_bar.hovered, top_bar.drag) { (Element::StartTick, _) | (_, Element::StartTick) => (WHITE, LOOPER), @@ -444,25 +454,21 @@ fn update_looper(scene: &mut PlayingScene, w: f32, animation: f32) { let color = Color { a: 0.35, ..LOOPER }; - let length = top_bar.loop_end_tick.x - top_bar.loop_start_tick.x; + let length = top_bar.loop_end_tick.x() - top_bar.loop_start_tick.x(); - quad_pipeline.instances().push(QuadInstance { - position: top_bar.loop_start_tick.pos(), - size: [length, top_bar.loop_start_tick.h], - color: color.into_linear_rgba(), - ..Default::default() - }); + let mut bg_box = top_bar.loop_start_tick; + bg_box.size.w = length; - quad_pipeline.instances().push(QuadInstance { - position: top_bar.loop_start_tick.pos(), - size: top_bar.loop_start_tick.size(), - color: start_color.into_linear_rgba(), - ..Default::default() - }); - quad_pipeline.instances().push(QuadInstance { - position: top_bar.loop_end_tick.pos(), - size: top_bar.loop_end_tick.size(), - color: end_color.into_linear_rgba(), + draw_rect(fg_quad_pipeline, &bg_box, &color); + draw_rect(fg_quad_pipeline, &top_bar.loop_start_tick, &start_color); + draw_rect(fg_quad_pipeline, &top_bar.loop_end_tick, &end_color); +} + +fn draw_rect(quad_pipeline: &mut QuadPipeline, bbox: &Bbox, color: &Color) { + quad_pipeline.push(QuadInstance { + position: bbox.pos.into(), + size: bbox.size.into(), + color: color.into_linear_rgba(), ..Default::default() }); }