diff --git a/src/main.rs b/src/main.rs index dfc98a0..5566446 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use vello_common::kurbo::Affine; use vello_hybrid::{RenderSize, RenderTargetConfig, Renderer, Scene}; use wgpu::{ Adapter, Device, Features, Instance, Limits, Queue, Surface, @@ -5,6 +6,7 @@ use wgpu::{ }; use winit::{ application::ApplicationHandler, + dpi::PhysicalSize, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowAttributes, WindowId}, @@ -208,6 +210,41 @@ impl Default for ApplicationState { } } +fn draw( + surface: &mut RenderSurface, + scene: &mut Scene, + font: &ui::UiFont, + scale_factor: f64, +) { + let (win_w, win_h) = + (surface.config.width as f64, surface.config.height as f64); + + let transform = Affine::scale(scale_factor); + + scene.reset(); + + scene.push_layer(None, None, None, None, None); + scene.set_paint_transform(transform); + ui::draw_arrangement_view(scene, win_w, win_h, font); + scene.pop_layer(); +} + +fn resize_surface( + context: &RenderContext, + surface: &mut RenderSurface, + scene: &mut Scene, + font: &ui::UiFont, + size: PhysicalSize, + scale_factor: f64, +) { + context.resize_surface(surface, size.width.max(1), size.height.max(1)); + *scene = Scene::new( + u16::try_from(size.width.max(1)).unwrap_or(u16::MAX), + u16::try_from(size.height.max(1)).unwrap_or(u16::MAX), + ); + draw(surface, scene, font, scale_factor); +} + impl ApplicationHandler for ApplicationState { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { if matches!(self.render_state, RenderState::Active { .. }) { @@ -251,16 +288,12 @@ impl ApplicationHandler for ApplicationState { window, }; - let RenderState::Active { surface, .. } = &mut self.render_state else { + let RenderState::Active { surface, window } = &mut self.render_state + else { return; }; - ui::draw_arrangement_view( - &mut self.scene, - f64::from(surface.config.width), - f64::from(surface.config.height), - &self.font, - ); + draw(surface, &mut self.scene, &self.font, window.scale_factor()); } fn window_event( @@ -281,23 +314,33 @@ impl ApplicationHandler for ApplicationState { match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::SurfaceResized(size) => { - self.context.resize_surface( + resize_surface( + &self.context, surface, - size.width.max(1), - size.height.max(1), - ); - self.scene = Scene::new( - u16::try_from(size.width.max(1)).unwrap_or(u16::MAX), - u16::try_from(size.height.max(1)).unwrap_or(u16::MAX), + &mut self.scene, + &self.font, + size, + window.scale_factor(), ); - self.scene.reset(); - ui::draw_arrangement_view( + } + WindowEvent::ScaleFactorChanged { + scale_factor: _scale_factor, + surface_size_writer, + } => { + let new_surface_size = surface_size_writer + .surface_size() + .unwrap_or(PhysicalSize::new( + surface.config.width.max(1), + surface.config.height.max(1), + )); + resize_surface( + &self.context, + surface, &mut self.scene, - f64::from(surface.config.width), - f64::from(surface.config.height), &self.font, + new_surface_size, + window.scale_factor(), ); - window.request_redraw(); } WindowEvent::RedrawRequested => { let Some(renderer) = self.renderers[surface.dev_id].as_mut() @@ -344,8 +387,6 @@ impl ApplicationHandler for ApplicationState { device_handle.queue.submit([encoder.finish()]); surface_texture.present(); } - - // window.request_redraw(); } _ => {} } diff --git a/src/ui/arrangement.rs b/src/ui/arrangement.rs new file mode 100644 index 0000000..352ea6d --- /dev/null +++ b/src/ui/arrangement.rs @@ -0,0 +1,444 @@ +use vello_common::kurbo::BezPath; +use vello_hybrid::Scene; + +use crate::ui::UiFont; +use crate::ui::layout::Layout; +use crate::ui::primitives::{ + draw_triangle, fill_rect, fill_viewport, rgb, rgba, +}; +use crate::ui::text::draw_text; + +pub const BASE_WIDTH: f64 = 820.0; +pub const BASE_HEIGHT: f64 = 742.0; +const TOP_BAR_HEIGHT: f64 = 42.0; + +const LOCATOR_LEFT: f64 = 26.0; +const LOCATOR_VERSE_IN: f64 = 258.0; +const LOCATOR_VERSE_OUT: f64 = 287.0; + +pub fn draw(scene: &mut Scene, layout: &Layout, font: &UiFont) { + let view_w = layout.content_width(); + let view_h = layout.content_height(); + + fill_viewport(scene, layout, rgb(0.16, 0.17, 0.19)); + fill_rect( + scene, + layout, + 0.0, + 0.0, + view_w, + view_h, + rgb(0.16, 0.17, 0.19), + ); + + draw_grid(scene, layout, view_w, view_h); + draw_ruler(scene, layout, font, view_w); + draw_midi_lane(scene, layout, view_w); + draw_audio_blocks(scene, layout, font); + draw_locators(scene, layout, view_h); + draw_top_bar(scene, layout, font); +} + +fn draw_grid(scene: &mut Scene, layout: &Layout, view_w: f64, view_h: f64) { + let minor = rgb(0.11, 0.12, 0.13); + let major = rgb(0.17, 0.18, 0.2); + + let mut i = 0usize; + loop { + let x = 27.0 + i as f64 * 18.1; + if x > view_w { + break; + } + fill_rect( + scene, + layout, + x, + 0.0, + x + 3.0, + view_h, + if i % 4 == 0 { major } else { minor }, + ); + i += 1; + } + + for y in [264.0, 350.0, 460.0, 630.0, 720.0] { + fill_rect( + scene, + layout, + 0.0, + y, + view_w, + y + 5.0, + rgb(0.13, 0.14, 0.15), + ); + } +} + +fn draw_ruler(scene: &mut Scene, layout: &Layout, font: &UiFont, view_w: f64) { + let header = rgb(0.20, 0.21, 0.23); + let accent = rgba(0.81, 0.70, 0.52, 0.95); + + fill_rect(scene, layout, 27.0, 46.0, view_w, 78.0, header); + fill_rect( + scene, + layout, + 27.0, + 46.0, + view_w, + 48.5, + rgb(0.78, 0.67, 0.49), + ); + fill_rect(scene, layout, 27.0, 79.0, view_w, 122.0, header); + + draw_triangle(scene, layout, 29.0, 60.0, 28.0, 15.0, accent); + draw_triangle(scene, layout, 30.0, 100.0, 24.0, 17.0, accent); + draw_triangle( + scene, + layout, + LOCATOR_VERSE_IN + 1.0, + 100.0, + 27.0, + 33.0, + accent, + ); + draw_triangle(scene, layout, LOCATOR_VERSE_OUT, 100.0, 29.0, 33.0, accent); + + let tick_y0 = 29.0; + for (idx, beat) in [1, 5, 9, 13, 17, 21].iter().enumerate() { + let x = 49.0 + idx as f64 * 134.0; + fill_rect(scene, layout, x, tick_y0, x + 3.0, 49.0, accent); + fill_rect(scene, layout, x + 3.0, 44.0, x + 35.0, 47.0, accent); + draw_text( + scene, + layout, + font, + &beat.to_string(), + x + 15.0, + 39.0, + 18.0, + rgb(0.85, 0.75, 0.57), + ); + } + + draw_text( + scene, + layout, + font, + "Intro", + LOCATOR_LEFT + 37.0, + 112.0, + 42.0, + rgb(0.85, 0.75, 0.57), + ); + draw_text( + scene, + layout, + font, + "Verse A", + LOCATOR_VERSE_OUT + 38.0, + 112.0, + 42.0, + rgb(0.85, 0.75, 0.57), + ); +} + +fn draw_midi_lane(scene: &mut Scene, layout: &Layout, view_w: f64) { + fill_rect( + scene, + layout, + 27.0, + 124.0, + view_w, + 233.0, + rgb(0.35, 0.86, 0.81), + ); + fill_rect( + scene, + layout, + 27.0, + 172.0, + view_w, + 175.0, + rgb(0.12, 0.49, 0.46), + ); + fill_rect( + scene, + layout, + 27.0, + 231.0, + view_w, + 234.0, + rgb(0.12, 0.49, 0.46), + ); + + for i in 0..30 { + let x = 30.0 + f64::from(i) * 26.5; + fill_rect(scene, layout, x, 173.0, x + 2.0, 231.0, rgb(0.1, 0.53, 0.5)); + } + + for row in 0..3 { + let y = 200.0 + f64::from(row) * 11.0; + for i in 0..16 { + let x0 = 32.0 + + f64::from(i) * 65.0 + + if i % 2 == 0 { 0.0 } else { 18.0 }; + fill_rect( + scene, + layout, + x0, + y, + x0 + 42.0, + y + 2.5, + rgb(0.15, 0.19, 0.2), + ); + fill_rect( + scene, + layout, + x0 + 46.0, + y, + x0 + 57.0, + y + 2.5, + rgb(0.15, 0.19, 0.2), + ); + } + } +} + +fn draw_audio_blocks(scene: &mut Scene, layout: &Layout, font: &UiFont) { + let clip_color = rgb(0.98, 0.68, 0.17); + let clip_dark = rgb(0.42, 0.30, 0.22); + + let clips = [ + (310.0, 360.0, 440.0, 640.0), + (440.0, 360.0, 570.0, 640.0), + (584.0, 360.0, 690.0, 640.0), + (690.0, 360.0, 790.0, 640.0), + (790.0, 360.0, 820.0, 640.0), + ]; + + for (idx, &(x0, y0, x1, y1)) in clips.iter().enumerate() { + fill_rect(scene, layout, x0, y0, x1, y1, clip_color); + fill_rect( + scene, + layout, + x0 + 2.0, + y0 + 40.0, + x1 - 2.0, + y0 + 43.0, + rgb(0.49, 0.34, 0.22), + ); + + for row in 0..6 { + let y = y0 + 58.0 + row as f64 * 26.0; + draw_smooth_wave_band( + scene, + layout, + x0 + 2.0, + x1 - 2.0, + y, + 18.0, + row, + idx, + ); + if row % 2 == 1 { + fill_rect( + scene, + layout, + x0 + 2.0, + y, + x1 - 2.0, + y + 18.0, + clip_dark, + ); + draw_smooth_wave_band( + scene, + layout, + x0 + 2.0, + x1 - 2.0, + y, + 18.0, + row + 7, + idx, + ); + } + } + + draw_text( + scene, + layout, + font, + "Lead Ta", + x0 + 10.0, + y0 + 30.0, + 18.0, + rgb(0.12, 0.11, 0.10), + ); + + fill_rect(scene, layout, x0, y0, x0 + 2.5, y1, rgb(0.62, 0.45, 0.26)); + fill_rect(scene, layout, x1 - 2.5, y0, x1, y1, rgb(0.62, 0.45, 0.26)); + } +} + +fn draw_locators(scene: &mut Scene, layout: &Layout, view_h: f64) { + let locator = rgba(0.81, 0.70, 0.52, 0.95); + fill_rect( + scene, + layout, + LOCATOR_LEFT, + 0.0, + LOCATOR_LEFT + 3.0, + view_h, + locator, + ); + fill_rect( + scene, + layout, + LOCATOR_VERSE_IN, + 82.0, + LOCATOR_VERSE_IN + 3.0, + view_h, + locator, + ); + fill_rect( + scene, + layout, + LOCATOR_VERSE_OUT, + 82.0, + LOCATOR_VERSE_OUT + 3.0, + view_h, + locator, + ); +} + +fn draw_top_bar(scene: &mut Scene, layout: &Layout, font: &UiFont) { + let bar_bg = rgb(0.14, 0.15, 0.16); + let section_bg = rgb(0.19, 0.20, 0.22); + let section_border = rgb(0.26, 0.27, 0.29); + let text_color = rgb(0.84, 0.85, 0.87); + let accent = rgb(0.74, 0.76, 0.80); + + let viewport = layout.viewport_rect(); + let top_bar_bottom = layout.point(0.0, TOP_BAR_HEIGHT).y; + let divider = layout.length(1.5); + scene.set_paint(bar_bg); + scene.fill_rect(&vello_common::kurbo::Rect::new( + viewport.x0, + viewport.y0, + viewport.x1, + top_bar_bottom, + )); + scene.set_paint(rgb(0.08, 0.09, 0.10)); + scene.fill_rect(&vello_common::kurbo::Rect::new( + viewport.x0, + top_bar_bottom - divider, + viewport.x1, + top_bar_bottom, + )); + + let sections = [ + (18.0, 8.0, 90.0, 34.0, "124.00"), // bpm + (98.0, 8.0, 146.0, 34.0, "4/4"), // time signature + (154.0, 8.0, 252.0, 34.0, "Met 1/4"), // metronome options + (260.0, 8.0, 354.0, 34.0, "C Min"), // key/scale + (362.0, 8.0, 446.0, 34.0, "1.1.1"), // beats/bar indicator + ]; + + for (x0, y0, x1, y1, label) in sections { + fill_rect(scene, layout, x0, y0, x1, y1, section_bg); + fill_rect(scene, layout, x0, y0, x1, y0 + 1.5, section_border); + fill_rect(scene, layout, x0, y1 - 1.5, x1, y1, section_border); + draw_text( + scene, + layout, + font, + label, + x0 + 10.0, + y0 + 17.5, + 14.0, + text_color, + ); + } + + let button_y0 = 8.0; + let button_y1 = 34.0; + + // play + fill_rect( + scene, layout, 474.0, button_y0, 508.0, button_y1, section_bg, + ); + draw_triangle(scene, layout, 486.0, 14.0, 12.0, 14.0, accent); + + // pause + fill_rect( + scene, layout, 514.0, button_y0, 548.0, button_y1, section_bg, + ); + fill_rect(scene, layout, 526.0, 14.0, 531.0, 28.0, accent); + fill_rect(scene, layout, 535.0, 14.0, 540.0, 28.0, accent); + + // record + fill_rect( + scene, layout, 554.0, button_y0, 588.0, button_y1, section_bg, + ); + fill_rect( + scene, + layout, + 566.0, + 14.0, + 576.0, + 24.0, + rgb(0.88, 0.19, 0.23), + ); +} + +fn draw_smooth_wave_band( + scene: &mut Scene, + layout: &Layout, + x0: f64, + x1: f64, + y: f64, + h: f64, + row: usize, + clip: usize, +) { + let mid = y + h * 0.5; + let sample_step = 3.5; + let mut upper = Vec::new(); + let mut lower = Vec::new(); + + let mut x = x0; + while x <= x1 { + let t = (x - x0) / (x1 - x0 + 0.001); + let a = (t * std::f64::consts::TAU * 3.0 + + row as f64 * 0.42 + + clip as f64 * 0.81) + .sin(); + let b = (t * std::f64::consts::TAU * 8.0 + + row as f64 * 0.9 + + clip as f64 * 0.37) + .cos(); + let c = (t * std::f64::consts::TAU * 17.0 + clip as f64 * 0.55).sin(); + let envelope = (0.52 * a + 0.33 * b + 0.15 * c).abs(); + let amp = (1.1 + envelope * (h * 0.46)).min(h * 0.5 - 0.2); + + upper.push((x, mid - amp)); + lower.push((x, mid + amp)); + x += sample_step; + } + + let mut path = BezPath::new(); + if let Some((ux, uy)) = upper.first().copied() { + let p0 = layout.point(ux, uy); + path.move_to((p0.x, p0.y)); + for (px, py) in upper.iter().skip(1).copied() { + let p = layout.point(px, py); + path.line_to((p.x, p.y)); + } + for (px, py) in lower.iter().rev().copied() { + let p = layout.point(px, py); + path.line_to((p.x, p.y)); + } + path.close_path(); + scene.set_paint(rgb(0.10, 0.10, 0.10)); + scene.fill_path(&path); + } +} diff --git a/src/ui/layout.rs b/src/ui/layout.rs new file mode 100644 index 0000000..766a0a8 --- /dev/null +++ b/src/ui/layout.rs @@ -0,0 +1,76 @@ +use vello_common::kurbo::{Point, Rect}; + +pub struct Layout { + viewport_width: f64, + viewport_height: f64, + scale: f64, + offset_x: f64, + offset_y: f64, + content_width: f64, + content_height: f64, +} + +impl Layout { + pub fn new( + viewport_width: f64, + viewport_height: f64, + content_width: f64, + content_height: f64, + ) -> Self { + let safe_content_width = content_width.max(1.0); + let safe_content_height = content_height.max(1.0); + let safe_viewport_width = viewport_width.max(1.0); + let safe_viewport_height = viewport_height.max(1.0); + // Match DAW-like behavior: keep a 1:1 baseline size and only scale down + // when the window is too small, instead of scaling up on large windows. + let scale = (safe_viewport_width / safe_content_width) + .min(safe_viewport_height / safe_content_height) + .min(1.0); + Self { + viewport_width: safe_viewport_width, + viewport_height: safe_viewport_height, + scale, + // DAW UIs are usually anchored to the top-left, not centered. + offset_x: 0.0, + offset_y: 0.0, + content_width: safe_viewport_width / scale, + content_height: safe_viewport_height / scale, + } + } + + pub fn viewport_rect(&self) -> Rect { + Rect::new(0.0, 0.0, self.viewport_width, self.viewport_height) + } + + pub fn point(&self, x: f64, y: f64) -> Point { + Point::new( + self.offset_x + x * self.scale, + self.offset_y + y * self.scale, + ) + } + + pub fn rect(&self, x0: f64, y0: f64, x1: f64, y1: f64) -> Rect { + Rect::new( + self.offset_x + x0 * self.scale, + self.offset_y + y0 * self.scale, + self.offset_x + x1 * self.scale, + self.offset_y + y1 * self.scale, + ) + } + + pub fn font_size(&self, size: f32) -> f32 { + size * self.scale as f32 + } + + pub fn length(&self, value: f64) -> f64 { + value * self.scale + } + + pub fn content_width(&self) -> f64 { + self.content_width + } + + pub fn content_height(&self) -> f64 { + self.content_height + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1ebbb9a..032ec35 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,11 +1,11 @@ -use skrifa::raw::FileRef; -use skrifa::{FontRef, MetadataProvider}; -use vello_common::glyph::Glyph; -use vello_common::kurbo::{BezPath, Rect}; use vello_common::peniko::FontData; -use vello_common::peniko::color::{AlphaColor, Srgb}; use vello_hybrid::Scene; +mod arrangement; +mod layout; +mod primitives; +mod text; + #[derive(Clone)] pub struct UiFont { pub font_data: FontData, @@ -26,446 +26,11 @@ pub fn draw_arrangement_view( height: f64, font: &UiFont, ) { - let sx = width / 820.0; - let sy = height / 742.0; - - fill_rect( - scene, - scale_rect(0.0, 0.0, 820.0, 742.0, sx, sy), - rgb(0.16, 0.17, 0.19), - ); - - draw_grid(scene, sx, sy); - draw_ruler(scene, sx, sy, font); - draw_midi_lane(scene, sx, sy); - draw_audio_blocks(scene, sx, sy, font); - draw_locators(scene, sx, sy); -} - -const LOCATOR_LEFT: f64 = 26.0; -const LOCATOR_VERSE_IN: f64 = 258.0; -const LOCATOR_VERSE_OUT: f64 = 287.0; - -fn draw_grid(scene: &mut Scene, sx: f64, sy: f64) { - let minor = rgb(0.11, 0.12, 0.13); - let major = rgb(0.17, 0.18, 0.2); - - for i in 0..45 { - let x = 27.0 + f64::from(i) * 18.1; - let line = scale_rect(x, 0.0, x + 3.0, 742.0, sx, sy); - fill_rect(scene, line, if i % 4 == 0 { major } else { minor }); - } - - for y in [264.0, 350.0, 460.0, 630.0, 720.0] { - fill_rect( - scene, - scale_rect(0.0, y, 820.0, y + 5.0, sx, sy), - rgb(0.13, 0.14, 0.15), - ); - } -} - -fn draw_ruler(scene: &mut Scene, sx: f64, sy: f64, font: &UiFont) { - let header = rgb(0.20, 0.21, 0.23); - let accent = rgba(0.81, 0.70, 0.52, 0.95); - - fill_rect(scene, scale_rect(27.0, 46.0, 820.0, 78.0, sx, sy), header); - fill_rect( - scene, - scale_rect(27.0, 46.0, 820.0, 48.5, sx, sy), - rgb(0.78, 0.67, 0.49), - ); - fill_rect(scene, scale_rect(27.0, 79.0, 820.0, 122.0, sx, sy), header); - - draw_triangle(scene, 29.0, 60.0, 28.0, 15.0, sx, sy, accent); - draw_triangle(scene, 30.0, 100.0, 24.0, 17.0, sx, sy, accent); - draw_triangle( - scene, - LOCATOR_VERSE_IN + 1.0, - 100.0, - 27.0, - 33.0, - sx, - sy, - accent, - ); - draw_triangle(scene, LOCATOR_VERSE_OUT, 100.0, 29.0, 33.0, sx, sy, accent); - - let tick_y0 = 29.0; - for (idx, beat) in [1, 5, 9, 13, 17, 21].iter().enumerate() { - let x = 49.0 + idx as f64 * 134.0; - fill_rect(scene, scale_rect(x, tick_y0, x + 3.0, 49.0, sx, sy), accent); - fill_rect( - scene, - scale_rect(x + 3.0, 44.0, x + 35.0, 47.0, sx, sy), - accent, - ); - draw_text( - scene, - font, - &beat.to_string(), - x + 15.0, - 39.0, - 18.0, - rgb(0.85, 0.75, 0.57), - sx, - sy, - ); - } - - draw_text( - scene, - font, - "Intro", - LOCATOR_LEFT + 37.0, - 112.0, - 42.0, - rgb(0.85, 0.75, 0.57), - sx, - sy, - ); - draw_text( - scene, - font, - "Verse A", - LOCATOR_VERSE_OUT + 38.0, - 112.0, - 42.0, - rgb(0.85, 0.75, 0.57), - sx, - sy, - ); -} - -fn draw_midi_lane(scene: &mut Scene, sx: f64, sy: f64) { - fill_rect( - scene, - scale_rect(27.0, 124.0, 820.0, 233.0, sx, sy), - rgb(0.35, 0.86, 0.81), - ); - fill_rect( - scene, - scale_rect(27.0, 172.0, 820.0, 175.0, sx, sy), - rgb(0.12, 0.49, 0.46), - ); - fill_rect( - scene, - scale_rect(27.0, 231.0, 820.0, 234.0, sx, sy), - rgb(0.12, 0.49, 0.46), - ); - - for i in 0..30 { - let x = 30.0 + f64::from(i) * 26.5; - fill_rect( - scene, - scale_rect(x, 173.0, x + 2.0, 231.0, sx, sy), - rgb(0.1, 0.53, 0.5), - ); - } - - for row in 0..3 { - let y = 200.0 + f64::from(row) * 11.0; - for i in 0..16 { - let x0 = 32.0 - + f64::from(i) * 65.0 - + if i % 2 == 0 { 0.0 } else { 18.0 }; - fill_rect( - scene, - scale_rect(x0, y, x0 + 42.0, y + 2.5, sx, sy), - rgb(0.15, 0.19, 0.2), - ); - fill_rect( - scene, - scale_rect(x0 + 46.0, y, x0 + 57.0, y + 2.5, sx, sy), - rgb(0.15, 0.19, 0.2), - ); - } - } -} - -fn draw_audio_blocks(scene: &mut Scene, sx: f64, sy: f64, font: &UiFont) { - let clip_color = rgb(0.98, 0.68, 0.17); - let clip_dark = rgb(0.42, 0.30, 0.22); - - let clips = [ - (310.0, 360.0, 440.0, 640.0), - (440.0, 360.0, 570.0, 640.0), - (584.0, 360.0, 690.0, 640.0), - (690.0, 360.0, 790.0, 640.0), - (790.0, 360.0, 820.0, 640.0), - ]; - - for (idx, &(x0, y0, x1, y1)) in clips.iter().enumerate() { - fill_rect(scene, scale_rect(x0, y0, x1, y1, sx, sy), clip_color); - fill_rect( - scene, - scale_rect(x0 + 2.0, y0 + 40.0, x1 - 2.0, y0 + 43.0, sx, sy), - rgb(0.49, 0.34, 0.22), - ); - - for row in 0..6 { - let y = y0 + 58.0 + row as f64 * 26.0; - draw_smooth_wave_band( - scene, - x0 + 2.0, - x1 - 2.0, - y, - 18.0, - row, - idx, - sx, - sy, - ); - if row % 2 == 1 { - fill_rect( - scene, - scale_rect(x0 + 2.0, y, x1 - 2.0, y + 18.0, sx, sy), - clip_dark, - ); - draw_smooth_wave_band( - scene, - x0 + 2.0, - x1 - 2.0, - y, - 18.0, - row + 7, - idx, - sx, - sy, - ); - } - } - - draw_text( - scene, - font, - "Lead Ta", - x0 + 10.0, - y0 + 30.0, - 18.0, - rgb(0.12, 0.11, 0.10), - sx, - sy, - ); - - fill_rect( - scene, - scale_rect(x0, y0, x0 + 2.5, y1, sx, sy), - rgb(0.62, 0.45, 0.26), - ); - fill_rect( - scene, - scale_rect(x1 - 2.5, y0, x1, y1, sx, sy), - rgb(0.62, 0.45, 0.26), - ); - } -} - -fn draw_locators(scene: &mut Scene, sx: f64, sy: f64) { - let locator = rgba(0.81, 0.70, 0.52, 0.95); - fill_rect( - scene, - scale_rect(LOCATOR_LEFT, 0.0, LOCATOR_LEFT + 3.0, 742.0, sx, sy), - locator, - ); - fill_rect( - scene, - scale_rect( - LOCATOR_VERSE_IN, - 82.0, - LOCATOR_VERSE_IN + 3.0, - 742.0, - sx, - sy, - ), - locator, + let layout = layout::Layout::new( + width, + height, + arrangement::BASE_WIDTH, + arrangement::BASE_HEIGHT, ); - fill_rect( - scene, - scale_rect( - LOCATOR_VERSE_OUT, - 82.0, - LOCATOR_VERSE_OUT + 3.0, - 742.0, - sx, - sy, - ), - locator, - ); -} - -fn draw_smooth_wave_band( - scene: &mut Scene, - x0: f64, - x1: f64, - y: f64, - h: f64, - row: usize, - clip: usize, - sx: f64, - sy: f64, -) { - let mid = y + h * 0.5; - let sample_step = 3.5; - let mut upper = Vec::new(); - let mut lower = Vec::new(); - - let mut x = x0; - while x <= x1 { - let t = (x - x0) / (x1 - x0 + 0.001); - let a = (t * std::f64::consts::TAU * 3.0 - + row as f64 * 0.42 - + clip as f64 * 0.81) - .sin(); - let b = (t * std::f64::consts::TAU * 8.0 - + row as f64 * 0.9 - + clip as f64 * 0.37) - .cos(); - let c = (t * std::f64::consts::TAU * 17.0 + clip as f64 * 0.55).sin(); - let envelope = (0.52 * a + 0.33 * b + 0.15 * c).abs(); - let amp = (1.1 + envelope * (h * 0.46)).min(h * 0.5 - 0.2); - - upper.push((x, mid - amp)); - lower.push((x, mid + amp)); - x += sample_step; - } - - let mut path = BezPath::new(); - if let Some((ux, uy)) = upper.first().copied() { - path.move_to((ux * sx, uy * sy)); - for (px, py) in upper.iter().skip(1).copied() { - path.line_to((px * sx, py * sy)); - } - for (px, py) in lower.iter().rev().copied() { - path.line_to((px * sx, py * sy)); - } - path.close_path(); - scene.set_paint(rgb(0.10, 0.10, 0.10)); - scene.fill_path(&path); - } -} - -fn draw_triangle( - scene: &mut Scene, - x: f64, - y: f64, - w: f64, - h: f64, - sx: f64, - sy: f64, - color: AlphaColor, -) { - let mut path = BezPath::new(); - path.move_to((x * sx, y * sy)); - path.line_to(((x + w) * sx, (y + h * 0.5) * sy)); - path.line_to((x * sx, (y + h) * sy)); - path.close_path(); - scene.set_paint(color); - scene.fill_path(&path); -} - -fn draw_text( - scene: &mut Scene, - font: &UiFont, - text: &str, - x: f64, - y: f64, - size: f32, - color: AlphaColor, - sx: f64, - sy: f64, -) { - let scaled_size = size * sy as f32; - let (glyphs, _) = layout_glyphs(text, scaled_size, &font.font_data); - scene.set_paint(color); - scene - .glyph_run(&font.font_data) - .font_size(scaled_size) - .hint(true) - .fill_glyphs(glyphs.into_iter().map(|mut glyph| { - glyph.x += (x * sx) as f32; - glyph.y += (y * sy) as f32; - glyph - })); -} - -fn layout_glyphs( - text: &str, - font_size: f32, - font: &FontData, -) -> (Vec, f32) { - let font_ref = { - let Ok(file_ref) = FileRef::new(font.data.as_ref()) else { - return (Vec::new(), 0.0); - }; - - match file_ref { - FileRef::Font(font) => font, - FileRef::Collection(collection) => match collection.get(font.index) - { - Ok(font) => font, - Err(_) => return (Vec::new(), 0.0), - }, - } - }; - - layout_glyphs_from_ref(text, font_size, &font_ref) -} - -fn layout_glyphs_from_ref( - text: &str, - font_size: f32, - font_ref: &FontRef<'_>, -) -> (Vec, f32) { - let size = skrifa::instance::Size::new(font_size); - let axes = font_ref.axes(); - let variations: [(&str, f32); 0] = []; - let loc = axes.location(&variations); - let charmap = font_ref.charmap(); - let metrics = font_ref.metrics(size, &loc); - let glyph_metrics = font_ref.glyph_metrics(size, &loc); - - let mut pen_x = 0.0f32; - let mut pen_y = 0.0f32; - - let glyphs = text - .chars() - .filter_map(|ch| { - if ch == '\n' { - pen_x = 0.0; - pen_y += metrics.ascent - metrics.descent + metrics.leading; - return None; - } - - let gid = charmap.map(ch).unwrap_or_default(); - let advance = - glyph_metrics.advance_width(gid).unwrap_or(font_size * 0.45); - let glyph = Glyph { - id: gid.to_u32(), - x: pen_x, - y: pen_y, - }; - pen_x += advance; - Some(glyph) - }) - .collect::>(); - - (glyphs, metrics.ascent) -} - -fn fill_rect(scene: &mut Scene, rect: Rect, color: AlphaColor) { - scene.set_paint(color); - scene.fill_rect(&rect); -} - -fn scale_rect(x0: f64, y0: f64, x1: f64, y1: f64, sx: f64, sy: f64) -> Rect { - Rect::new(x0 * sx, y0 * sy, x1 * sx, y1 * sy) -} - -fn rgb(r: f32, g: f32, b: f32) -> AlphaColor { - AlphaColor::::new([r, g, b, 1.0]) -} - -fn rgba(r: f32, g: f32, b: f32, a: f32) -> AlphaColor { - AlphaColor::::new([r, g, b, a]) + arrangement::draw(scene, &layout, font); } diff --git a/src/ui/primitives.rs b/src/ui/primitives.rs new file mode 100644 index 0000000..4630cd0 --- /dev/null +++ b/src/ui/primitives.rs @@ -0,0 +1,56 @@ +use vello_common::kurbo::BezPath; +use vello_common::peniko::color::{AlphaColor, Srgb}; +use vello_hybrid::Scene; + +use crate::ui::layout::Layout; + +pub fn fill_rect( + scene: &mut Scene, + layout: &Layout, + x0: f64, + y0: f64, + x1: f64, + y1: f64, + color: AlphaColor, +) { + scene.set_paint(color); + scene.fill_rect(&layout.rect(x0, y0, x1, y1)); +} + +pub fn fill_viewport( + scene: &mut Scene, + layout: &Layout, + color: AlphaColor, +) { + scene.set_paint(color); + scene.fill_rect(&layout.viewport_rect()); +} + +pub fn draw_triangle( + scene: &mut Scene, + layout: &Layout, + x: f64, + y: f64, + w: f64, + h: f64, + color: AlphaColor, +) { + let mut path = BezPath::new(); + let p0 = layout.point(x, y); + let p1 = layout.point(x + w, y + h * 0.5); + let p2 = layout.point(x, y + h); + path.move_to((p0.x, p0.y)); + path.line_to((p1.x, p1.y)); + path.line_to((p2.x, p2.y)); + path.close_path(); + scene.set_paint(color); + scene.fill_path(&path); +} + +pub fn rgb(r: f32, g: f32, b: f32) -> AlphaColor { + AlphaColor::::new([r, g, b, 1.0]) +} + +pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> AlphaColor { + AlphaColor::::new([r, g, b, a]) +} diff --git a/src/ui/text.rs b/src/ui/text.rs new file mode 100644 index 0000000..c1435db --- /dev/null +++ b/src/ui/text.rs @@ -0,0 +1,99 @@ +use skrifa::raw::FileRef; +use skrifa::{FontRef, MetadataProvider}; +use vello_common::glyph::Glyph; +use vello_common::peniko::FontData; +use vello_common::peniko::color::{AlphaColor, Srgb}; +use vello_hybrid::Scene; + +use crate::ui::UiFont; +use crate::ui::layout::Layout; + +pub fn draw_text( + scene: &mut Scene, + layout: &Layout, + font: &UiFont, + text: &str, + x: f64, + y: f64, + size: f32, + color: AlphaColor, +) { + let scaled_size = layout.font_size(size); + let (glyphs, _) = layout_glyphs(text, scaled_size, &font.font_data); + let origin = layout.point(x, y); + + scene.set_paint(color); + scene + .glyph_run(&font.font_data) + .font_size(scaled_size) + .hint(true) + .fill_glyphs(glyphs.into_iter().map(|mut glyph| { + glyph.x += origin.x as f32; + glyph.y += origin.y as f32; + glyph + })); +} + +fn layout_glyphs( + text: &str, + font_size: f32, + font: &FontData, +) -> (Vec, f32) { + let font_ref = { + let Ok(file_ref) = FileRef::new(font.data.as_ref()) else { + return (Vec::new(), 0.0); + }; + + match file_ref { + FileRef::Font(font) => font, + FileRef::Collection(collection) => match collection.get(font.index) + { + Ok(font) => font, + Err(_) => return (Vec::new(), 0.0), + }, + } + }; + + layout_glyphs_from_ref(text, font_size, &font_ref) +} + +fn layout_glyphs_from_ref( + text: &str, + font_size: f32, + font_ref: &FontRef<'_>, +) -> (Vec, f32) { + let size = skrifa::instance::Size::new(font_size); + let axes = font_ref.axes(); + let variations: [(&str, f32); 0] = []; + let loc = axes.location(&variations); + let charmap = font_ref.charmap(); + let metrics = font_ref.metrics(size, &loc); + let glyph_metrics = font_ref.glyph_metrics(size, &loc); + + let mut pen_x = 0.0f32; + let mut pen_y = 0.0f32; + + let glyphs = text + .chars() + .filter_map(|ch| { + if ch == '\n' { + pen_x = 0.0; + pen_y += metrics.ascent - metrics.descent + metrics.leading; + return None; + } + + let gid = charmap.map(ch).unwrap_or_default(); + let advance = + glyph_metrics.advance_width(gid).unwrap_or(font_size * 0.45); + let glyph = Glyph { + id: gid.to_u32(), + x: pen_x, + y: pen_y, + }; + pen_x += advance; + Some(glyph) + }) + .collect::>(); + + (glyphs, metrics.ascent) +}