diff --git a/Cargo.lock b/Cargo.lock index db5f6d1..16bf05d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,6 +324,8 @@ dependencies = [ name = "ebu-dsp" version = "0.1.0" dependencies = [ + "femtovg", + "image", "nih_plug", ] @@ -368,9 +370,9 @@ dependencies = [ [[package]] name = "femtovg" -version = "0.18.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0530af3119be5658d8c1f7e69248d46e2c59e500dc2ef373cf25b355158ef101" +checksum = "be5d925785ad33d7b0ae2b445d9f157c3ab42ff3c515fff0b46d53d4a86c43c5" dependencies = [ "bitflags 2.10.0", "bytemuck", @@ -384,6 +386,7 @@ dependencies = [ "rgb", "rustybuzz", "slotmap", + "ttf-parser", "unicode-bidi", "unicode-segmentation", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 27b8a80..cef4c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", parking_lot = "0.12.5" baseview = { git = "https://github.com/RustAudio/baseview", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", features = ["opengl"]} crossbeam = "0.8.4" -femtovg = "0.18.1" +femtovg = "0.19.3" serde = { version = "1.0.228", features = ["derive"] } image = { version = "0.25.9", default-features = false, features = ["png"] } diff --git a/ebu-dsp/Cargo.toml b/ebu-dsp/Cargo.toml index 3a71db8..ded46df 100644 --- a/ebu-dsp/Cargo.toml +++ b/ebu-dsp/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] +femtovg = "0.19.3" +image = { version = "0.25.9", default-features = false, features = ["png"] } nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false } diff --git a/ebu-dsp/src/gui/mod.rs b/ebu-dsp/src/gui/mod.rs new file mode 100644 index 0000000..e0a5a67 --- /dev/null +++ b/ebu-dsp/src/gui/mod.rs @@ -0,0 +1,187 @@ +use std::sync::{Arc, atomic::Ordering}; + +use femtovg::{Canvas, ImageFlags, ImageId, Paint, Path, renderer::OpenGl}; +use nih_plug::prelude::AtomicF32; + +use crate::{Rect, ScaledRect}; + +pub struct SpriteSheet { + scale_factor: Arc, + image: Result, + width: usize, + height: usize, + frame_width: usize, + frame_height: usize, + frames_x: usize, +} + +impl SpriteSheet { + pub fn empty() -> Self { + Self { + scale_factor: Arc::new(AtomicF32::new(1.0)), + image: Err("No image loaded".to_owned()), + width: 0, + height: 0, + frame_width: 0, + frame_height: 0, + frames_x: 0, + } + } + pub fn new( + canvas: &mut Canvas, + data: &[u8], + scale_factor: Arc, + frame_width: usize, + frame_height: usize, + frames: usize, + ) -> Self { + let image = canvas + .load_image_mem(data, ImageFlags::empty()) + .map_err(|e| format!("{e:?}")); + if let Ok(image) = image { + let (width, height) = canvas.image_size(image).unwrap_or_default(); + Self { + scale_factor, + image: Ok(image), + width, + height, + frame_width, + frame_height, + frames_x: width / frame_width, + } + } else { + Self { + scale_factor, + image, + width: 0, + height: 0, + frame_width, + frame_height, + frames_x: 0, + } + } + } + pub fn draw(&self, canvas: &mut Canvas, x: f32, y: f32, frame: usize) { + let factor = self.scale_factor.load(Ordering::Relaxed); + + let frame_x = frame % self.frames_x; + let frame_y = frame / self.frames_x; + + let screen_rect = Rect { + x: x * factor, + y: y * factor, + width: self.frame_width as f32 * factor, + height: self.frame_height as f32 * factor, + }; + let image_rect = Rect { + x: screen_rect.x - (frame_x * self.frame_width) as f32 * factor, + y: screen_rect.y - (frame_y * self.frame_height) as f32 * factor, + width: self.width as f32 * factor, + height: self.height as f32 * factor, + }; + + let mut screen_path = Path::new(); + screen_path.rect( + screen_rect.x, + screen_rect.y, + screen_rect.width, + screen_rect.height, + ); + if let Ok(image) = self.image { + canvas.fill_path( + &screen_path, + &Paint::image( + image, + image_rect.x, + image_rect.y, + image_rect.width, + image_rect.height, + 0.0, + 1.0, + ), + ); + } + } + pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect { + ScaledRect::new_from( + self.scale_factor.clone(), + (x, y, self.frame_width as f32, self.frame_height as f32), + ) + } +} + +pub struct Sprite { + scale_factor: Arc, + image: Result, + width: usize, + height: usize, +} +impl Sprite { + pub fn empty() -> Self { + Self { + scale_factor: Arc::new(AtomicF32::new(1.0)), + image: Err("No image loaded".to_owned()), + width: 0, + height: 0, + } + } + pub fn new(canvas: &mut Canvas, data: &[u8], scale_factor: Arc) -> Self { + let image = canvas + .load_image_mem(data, ImageFlags::empty()) + .map_err(|e| format!("{e:?}")); + if let Ok(image) = image { + let (width, height) = canvas.image_size(image).unwrap_or_default(); + Self { + scale_factor, + image: Ok(image), + width, + height, + } + } else { + Self { + scale_factor, + image, + width: 0, + height: 0, + } + } + } + pub fn draw(&self, canvas: &mut Canvas, x: f32, y: f32, alpha: f32) { + let factor = self.scale_factor.load(Ordering::Relaxed); + + let screen_rect = Rect { + x: x * factor, + y: y * factor, + width: self.width as f32 * factor, + height: self.height as f32 * factor, + }; + + let mut screen_path = Path::new(); + screen_path.rect( + screen_rect.x, + screen_rect.y, + screen_rect.width, + screen_rect.height, + ); + if let Ok(image) = self.image { + canvas.fill_path( + &screen_path, + &Paint::image( + image, + screen_rect.x, + screen_rect.y, + screen_rect.width, + screen_rect.height, + 0.0, + alpha, + ), + ); + } + } + pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect { + ScaledRect::new_from( + self.scale_factor.clone(), + (x, y, self.width as f32, self.height as f32), + ) + } +} diff --git a/ebu-dsp/src/lib.rs b/ebu-dsp/src/lib.rs index 1e70855..3429e30 100644 --- a/ebu-dsp/src/lib.rs +++ b/ebu-dsp/src/lib.rs @@ -4,13 +4,16 @@ mod comp; mod decibel; mod eq; mod freq_split; +mod gui; mod meter; mod ring_buffer; mod smoother; mod traits; use std::{ - fmt::Debug, ops::Add, time::Duration + fmt::Debug, + sync::{Arc, atomic::Ordering}, + time::Duration, }; pub use amplitude::Amplitude; @@ -19,42 +22,159 @@ pub use comp::{Compressor, CompressorState}; pub use decibel::Decibel; pub use eq::{Equalizer, EqualizerState}; pub use freq_split::{FreqSplitter, FreqSplitterState}; +pub use gui::{SpriteSheet, Sprite}; +use nih_plug::prelude::AtomicF32; pub use traits::{FloatFormatter, IntFormatter, Lerp, Processor}; -pub struct Rect { - pub x: T, - pub y: T, - pub width: T, - pub height: T, +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct Point { + x: f32, + y: f32, } -impl Debug for Rect { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Rect") - .field("x", &self.x) - .field("y", &self.y) - .field("width", &self.width) - .field("height", &self.height) - .finish() +impl Point { + pub const fn new(x: f32, y: f32) -> Self { + Self { x, y } } } -impl + Copy> Rect { - pub fn contains(&self, value: (T, T)) -> bool { - value.0 >= self.x - && value.1 >= self.y - && value.0 < self.x + self.width - && value.1 < self.y + self.height +impl From<(f32, f32)> for Point { + fn from(value: (f32, f32)) -> Self { + Point { + x: value.0, + y: value.1, + } } } -impl Default for Rect -where - T: Default, -{ - fn default() -> Self { + +#[derive(Clone, Debug, Default)] +pub struct ScaledPoint { + pub factor: Arc, + pub x: f32, + pub y: f32, +} +impl PartialEq for ScaledPoint { + fn eq(&self, other: &Self) -> bool { + self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire) + && self.x == other.x + && self.y == other.y + } +} +impl ScaledPoint { + pub fn new(factor: Arc) -> Self { Self { - x: Default::default(), - y: Default::default(), - width: Default::default(), - height: Default::default(), + factor, + ..Default::default() + } + } + pub fn new_from

(factor: Arc, point: P) -> Self + where + P: Into, + { + let point = point.into(); + Self { + factor, + x: point.x, + y: point.y, + } + } + pub fn set

(&mut self, point: P) + where + P: Into, + { + let point = point.into(); + self.x = point.x; + self.y = point.y; + } + pub fn set_scaled

(&mut self, point: P) + where + P: Into, + { + let factor = self.factor.load(Ordering::Acquire); + let point = point.into(); + self.x = point.x / factor; + self.y = point.y / factor; + } + pub fn as_point(&self) -> Point { + let factor = self.factor.load(Ordering::Acquire); + Point { + x: self.x * factor, + y: self.y * factor, + } + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct Rect { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} +impl Rect { + pub fn contains

(&self, value: P) -> bool + where + P: Into, + { + let value = value.into(); + value.x >= self.x + && value.y >= self.y + && value.x < self.x + self.width + && value.y < self.y + self.height + } +} +impl From<(f32, f32, f32, f32)> for Rect { + fn from(value: (f32, f32, f32, f32)) -> Self { + Rect { + x: value.0, + y: value.1, + width: value.2, + height: value.3, + } + } +} +#[derive(Clone, Debug, Default)] +pub struct ScaledRect { + pub factor: Arc, + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} +impl PartialEq for ScaledRect { + fn eq(&self, other: &Self) -> bool { + self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire) + && self.x == other.x + && self.y == other.y + && self.width == other.width + && self.height == other.height + } +} +impl ScaledRect { + pub fn new(factor: Arc) -> Self { + Self { + factor, + ..Default::default() + } + } + pub fn new_from(factor: Arc, rect: R) -> Self + where + R: Into, + { + let rect = rect.into(); + Self { + factor, + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + } + } + pub fn as_rect(&self) -> Rect { + let factor = self.factor.load(Ordering::Acquire); + Rect { + x: self.x * factor, + y: self.y * factor, + width: self.width * factor, + height: self.height * factor, } } } @@ -194,4 +314,3 @@ pub fn rms(sample_buffer: &RingBuffer, last_rms: f64) -> last_rms.powf(2.0) + (1.0 / N as f64) * (sample_buffer[0].powf(2.0) - sample_buffer[N - 1].powf(2.0)) } - diff --git a/src/editor.rs b/src/editor.rs index 82d66df..444701f 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -8,6 +8,8 @@ use crossbeam::atomic::AtomicCell; use nih_plug::params::persist::PersistentField; use serde::{Deserialize, Serialize}; +use crate::window::EditorWindow; + #[derive(Debug, Serialize, Deserialize)] pub struct EditorState { /// The window's size in logical pixels before applying `scale_factor`. @@ -20,7 +22,10 @@ pub struct EditorState { impl EditorState { pub fn from_size(size: (u32, u32)) -> Arc { Arc::new(Self { - size: AtomicCell::new(size), + size: AtomicCell::new(( + (size.0 as f32 * EditorWindow::VIRTUAL_SCALE) as u32, + (size.1 as f32 * EditorWindow::VIRTUAL_SCALE) as u32, + )), open: AtomicBool::new(false), }) } @@ -47,7 +52,7 @@ impl<'a> PersistentField<'a, EditorState> for Arc { pub struct EditorHandle { pub state: Arc, - pub window: WindowHandle + pub window: WindowHandle, } unsafe impl Send for EditorHandle {} impl Drop for EditorHandle { @@ -55,4 +60,4 @@ impl Drop for EditorHandle { self.state.open.store(false, Ordering::Release); self.window.close(); } -} \ No newline at end of file +} diff --git a/src/gui.rs b/src/gui.rs index 8a09185..eac355d 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,40 +1,38 @@ -use crate::{parameters::PluginParams, window::EditorWindow}; +use crate::parameters::PluginParams; use baseview::{ Event, EventStatus, MouseButton, MouseEvent, WindowEvent, WindowHandler, gl::GlContext, }; -use crossbeam::atomic::AtomicCell; -use ebu_dsp::Rect; -use femtovg::{Canvas, Color, FontId, ImageFlags, ImageId, Paint, Path, renderer::OpenGl}; +use ebu_dsp::{ScaledPoint, ScaledRect, Sprite, SpriteSheet}; +use femtovg::{Canvas, Color, FontId, Paint, Path, renderer::OpenGl}; use nih_plug::prelude::*; -use std::sync::Arc; +use std::sync::{Arc, atomic::Ordering}; const DROID_SANS_FONT: &'static [u8] = include_bytes!("../assets/DroidSans.ttf"); const FRESHENER_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/sheet.png"); const NOT_SO_FRESH_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg0.png"); const FRESH_DUMBLEDORE_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg1.png"); -const FRESHENER_FRAMES: u32 = 256; -const FRESHENER_FRAMES_X: u32 = 20; -const FRESHENER_FRAMES_Y: u32 = 13; -const FRESHENER_FRAME_WIDTH: f32 = 73.0; -const FRESHENER_FRAME_HEIGHT: f32 = 144.0; +const FRESHENER_FRAMES: usize = 256; +const FRESHENER_FRAME_WIDTH: usize = 73; +const FRESHENER_FRAME_HEIGHT: usize = 144; +const FRESHENER_SCREEN_X: f32 = 120.0; +const FRESHENER_SCREEN_Y: f32 = 20.0; pub struct PluginGui { font: Result, params: Arc, canvas: Option>, _gui_context: Arc, - scaling_factor: Arc>>, + scaling_factor: Arc, - freshener_screen_bounds: Rect, - - freshener_image: Result, - not_so_fresh_image: Result, - fresh_dumbledore_image: Result, + freshener_image: SpriteSheet, + freshener_image_bounds: ScaledRect, + not_so_fresh_image: Sprite, + fresh_dumbledore_image: Sprite, dirty: bool, - mouse_position: (f32, f32), - drag_start_mouse_pos: (f32, f32), + mouse_position: ScaledPoint, + drag_start_mouse_pos: ScaledPoint, drag_start_parameter_value: f32, dragging: bool, } @@ -42,13 +40,13 @@ pub struct PluginGui { fn create_canvas( context: &GlContext, params: &PluginParams, - scaling_factor: &AtomicCell>, + scaling_factor: &AtomicF32, ) -> Result, &'static str> { let renderer = unsafe { OpenGl::new_from_function(|s| context.get_proc_address(s)) } .map_err(|_| "Failed to create OpenGL renderer")?; let mut canvas = Canvas::new(renderer).map_err(|_| "Failed to create femtovg canvas")?; let (width, height) = params.editor_state.size(); - canvas.set_size(width, height, scaling_factor.load().unwrap_or(1.0)); + canvas.set_size(width, height, scaling_factor.load(Ordering::Relaxed)); Ok(canvas) } @@ -57,7 +55,7 @@ impl PluginGui { window: &mut baseview::Window<'_>, gui_context: Arc, params: Arc, - scaling_factor: Arc>>, + scaling_factor: Arc, ) -> Self { let mut this = Self { font: Err("Not loaded".to_owned()), @@ -66,14 +64,14 @@ impl PluginGui { _gui_context: gui_context, scaling_factor: scaling_factor.clone(), dirty: true, - mouse_position: (0.0, 0.0), - drag_start_mouse_pos: (0.0, 0.0), + mouse_position: ScaledPoint::new(scaling_factor.clone()), + drag_start_mouse_pos: ScaledPoint::new(scaling_factor.clone()), drag_start_parameter_value: 0.0, dragging: false, - freshener_image: Err("Not loaded".to_owned()), - fresh_dumbledore_image: Err("Not loaded".to_owned()), - not_so_fresh_image: Err("Not loaded".to_owned()), - freshener_screen_bounds: Rect::default(), + freshener_image: SpriteSheet::empty(), + freshener_image_bounds: ScaledRect::new(scaling_factor.clone()), + fresh_dumbledore_image: Sprite::empty(), + not_so_fresh_image: Sprite::empty(), }; if let Some(context) = window.gl_context() { @@ -84,15 +82,24 @@ impl PluginGui { this.font = canvas .add_font_mem(DROID_SANS_FONT) .map_err(|err| format!("{:?}", err)); - this.freshener_image = canvas - .load_image_mem(FRESHENER_IMAGE, ImageFlags::empty()) - .map_err(|err| format!("{:?}", err)); - this.fresh_dumbledore_image = canvas - .load_image_mem(FRESH_DUMBLEDORE_BG_IMAGE, ImageFlags::empty()) - .map_err(|err| format!("{:?}", err)); - this.not_so_fresh_image = canvas - .load_image_mem(NOT_SO_FRESH_BG_IMAGE, ImageFlags::empty()) - .map_err(|err| format!("{:?}", err)); + this.freshener_image = SpriteSheet::new( + &mut canvas, + FRESHENER_IMAGE, + scaling_factor.clone(), + FRESHENER_FRAME_WIDTH, + FRESHENER_FRAME_HEIGHT, + FRESHENER_FRAMES, + ); + this.freshener_image_bounds = this + .freshener_image + .screen_bounds(FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y); + this.fresh_dumbledore_image = Sprite::new( + &mut canvas, + FRESH_DUMBLEDORE_BG_IMAGE, + scaling_factor.clone(), + ); + this.not_so_fresh_image = + Sprite::new(&mut canvas, NOT_SO_FRESH_BG_IMAGE, scaling_factor.clone()); this.canvas = Some(canvas); } unsafe { @@ -115,15 +122,6 @@ impl PluginGui { */ this } - fn scaling_factor(&self) -> f32 { - if let Some(factor) = self.scaling_factor.load() { - factor - } else if let Some(canvas) = &self.canvas { - canvas.width() as f32 / EditorWindow::WINDOW_SIZE.0 as f32 - } else { - 1.0 - } - } } impl WindowHandler for PluginGui { @@ -131,7 +129,7 @@ impl WindowHandler for PluginGui { if self.canvas.is_none() { return; } - let scaling_factor = self.scaling_factor(); + let scaling_factor = self.scaling_factor.load(Ordering::Relaxed); let canvas = self.canvas.as_mut().unwrap(); if !self.dirty { @@ -139,12 +137,6 @@ impl WindowHandler for PluginGui { } let font_size = 12.0 * scaling_factor; - self.freshener_screen_bounds = Rect { - x: 120.0 * scaling_factor, - y: 20.0 * scaling_factor, - width: FRESHENER_FRAME_WIDTH * scaling_factor, - height: FRESHENER_FRAME_HEIGHT * scaling_factor, - }; let context = match window.gl_context() { None => { @@ -169,62 +161,11 @@ impl WindowHandler for PluginGui { let mut full_window_path = Path::new(); full_window_path.rect(0.0, 0.0, width, height); - let mut freshener_path = Path::new(); - freshener_path.rect( - self.freshener_screen_bounds.x, - self.freshener_screen_bounds.y, - self.freshener_screen_bounds.width, - self.freshener_screen_bounds.height, - ); - - let frame_index = (self.params.freshness.unmodulated_normalized_value() - * (FRESHENER_FRAMES - 1) as f32) - .floor(); - let frame_x = (frame_index % FRESHENER_FRAMES_X as f32).floor(); - let frame_y = (frame_index / FRESHENER_FRAMES_X as f32).floor(); - - let freshener_image_source_rect = Rect { - x: self.freshener_screen_bounds.x - frame_x * FRESHENER_FRAME_WIDTH, - y: self.freshener_screen_bounds.y - frame_y * FRESHENER_FRAME_HEIGHT, - width: FRESHENER_FRAME_WIDTH * FRESHENER_FRAMES_X as f32 * scaling_factor, - height: FRESHENER_FRAME_HEIGHT * FRESHENER_FRAMES_Y as f32 * scaling_factor, - }; - - if let Ok(not_so_fresh) = self.not_so_fresh_image { - canvas.fill_path( - &full_window_path, - &Paint::image(not_so_fresh, 0.0, 0.0, width, height, 0.0, 1.0), - ); - } - if let Ok(fresh_dumbledore) = self.fresh_dumbledore_image { - canvas.fill_path( - &full_window_path, - &Paint::image( - fresh_dumbledore, - 0.0, - 0.0, - width, - height, - 0.0, - self.params.freshness.unmodulated_normalized_value(), - ), - ); - } - - if let Ok(freshener) = self.freshener_image { - canvas.fill_path( - &freshener_path, - &Paint::image( - freshener, - freshener_image_source_rect.x, - freshener_image_source_rect.y, - freshener_image_source_rect.width, - freshener_image_source_rect.height, - 0.0, - 1.0, - ), - ); - } + let freshness = self.params.freshness.unmodulated_normalized_value(); + let frame_index = (freshness * (FRESHENER_FRAMES - 1) as f32).floor() as usize; + self.not_so_fresh_image.draw(canvas, 0.0, 0.0, 1.0); + self.fresh_dumbledore_image.draw(canvas, 0.0, 0.0, freshness); + self.freshener_image.draw(canvas, FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y, frame_index); #[cfg(debug_assertions)] { @@ -250,21 +191,8 @@ impl WindowHandler for PluginGui { print("Debug version"); print(&format!("scaling_factor {:?}", scaling_factor)); - print(&format!( - "screen_bounds {:#?}", - self.freshener_screen_bounds - )); - print(&format!("frame_bounds {:#?}", freshener_image_source_rect)); - - if let Err(e) = &self.freshener_image { - print(e); - } - if let Err(e) = &self.fresh_dumbledore_image { - print(e); - } - if let Err(e) = &self.not_so_fresh_image { - print(e); - } + print(&format!("mouse_pos {:?}", self.mouse_position)); + print(&format!("frame_index {:?}", frame_index)); } } @@ -282,23 +210,20 @@ impl WindowHandler for PluginGui { event: baseview::Event, ) -> baseview::EventStatus { let setter = ParamSetter::new(self._gui_context.as_ref()); - let scaling_factor = self.scaling_factor(); + let scaling_factor = self.scaling_factor.load(Ordering::Acquire); match event { Event::Window(WindowEvent::Resized(size)) => { let phys_size = size.physical_size(); if let Some(canvas) = self.canvas.as_mut() { - canvas.set_size( - phys_size.width, - phys_size.height, - self.scaling_factor.load().unwrap_or(1.0), - ); + canvas.set_size(phys_size.width, phys_size.height, scaling_factor); } self.dirty = true; } Event::Mouse(MouseEvent::CursorMoved { position, .. }) => { - self.mouse_position = (position.x as f32 / scaling_factor, position.y as f32 / scaling_factor); + self.mouse_position + .set((position.x as f32, position.y as f32)); if self.dragging { - let delta = self.mouse_position.1 - self.drag_start_mouse_pos.1; + let delta = self.mouse_position.y - self.drag_start_mouse_pos.y; let new_value = (self.drag_start_parameter_value - delta * 0.01).clamp(0.0, 1.0); setter.set_parameter_normalized(&self.params.freshness, new_value); @@ -306,11 +231,14 @@ impl WindowHandler for PluginGui { self.dirty = true; } Event::Mouse(MouseEvent::ButtonPressed { button, .. }) => { - self.dragging = self.freshener_screen_bounds.contains(self.mouse_position) + self.dragging = self + .freshener_image_bounds + .as_rect() + .contains(self.mouse_position.as_point()) && button == MouseButton::Left; if self.dragging { setter.begin_set_parameter(&self.params.freshness); - self.drag_start_mouse_pos = self.mouse_position; + self.drag_start_mouse_pos = self.mouse_position.clone(); self.drag_start_parameter_value = self.params.freshness.unmodulated_normalized_value(); } diff --git a/src/window.rs b/src/window.rs index d792501..6c646dc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,23 +1,21 @@ -use std::sync::{Arc, atomic::Ordering}; +use crate::{AirFreshener, editor::EditorHandle, gui::PluginGui, parameters::PluginParams}; use baseview::{Window, WindowOpenOptions, WindowScalePolicy, gl::GlConfig}; -use crossbeam::atomic::AtomicCell; +use nih_plug::prelude::AtomicF32; use nih_plug::{editor::Editor, plugin::Plugin}; -use crate::{AirFreshener, editor::EditorHandle, parameters::PluginParams, gui::PluginGui}; +use std::sync::{Arc, atomic::Ordering}; pub struct EditorWindow { params: Arc, - scaling_factor: Arc>>, + scaling_factor: Arc, } impl EditorWindow { + pub const VIRTUAL_SCALE: f32 = 1.0; pub const WINDOW_SIZE: (u32, u32) = (230, 320); pub fn new(params: Arc) -> Self { Self { params, - #[cfg(target_os = "macos")] - scaling_factor: Arc::new(AtomicCell::new(None)), - #[cfg(not(target_os = "macos"))] - scaling_factor: Arc::new(AtomicCell::new(Some(1.0))), + scaling_factor: Arc::new(AtomicF32::new(EditorWindow::VIRTUAL_SCALE)), } } } @@ -29,33 +27,43 @@ impl Editor for EditorWindow { context: Arc, ) -> Box { let (unscaled_width, unscaled_height) = self.params.editor_state.size(); - let scaling_factor = self.scaling_factor.load(); + let scaling_factor = self.scaling_factor.load(Ordering::Acquire); let move_scaling_factor = self.scaling_factor.clone(); let gui_context = context.clone(); let params = self.params.clone(); - let window = Window::open_parented(&parent, WindowOpenOptions { - title: AirFreshener::NAME.to_owned(), - size: baseview::Size { width: unscaled_width as f64, height: unscaled_height as f64 }, - scale: scaling_factor.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)).unwrap_or(WindowScalePolicy::SystemScaleFactor), - gl_config: Some(GlConfig { - version: (3, 2), - red_bits: 8, - green_bits: 8, - blue_bits: 8, - alpha_bits: 8, - samples: None, - srgb: true, - double_buffer: true, - vsync: false, - ..Default::default() - }) - }, move |window: &mut baseview::Window<'_>| -> PluginGui { - PluginGui::new(window, gui_context, params, move_scaling_factor) - }); + let window = Window::open_parented( + &parent, + WindowOpenOptions { + title: AirFreshener::NAME.to_owned(), + size: baseview::Size { + width: unscaled_width as f64, + height: unscaled_height as f64, + }, + #[cfg(target_os = "macos")] + scale: WindowScalePolicy::SystemScaleFactor, + #[cfg(not(target_os = "macos"))] + scale: WindowScalePolicy::ScaleFactor(scaling_factor as f64), + gl_config: Some(GlConfig { + version: (3, 2), + red_bits: 8, + green_bits: 8, + blue_bits: 8, + alpha_bits: 8, + samples: None, + srgb: true, + double_buffer: true, + vsync: false, + ..Default::default() + }), + }, + move |window: &mut baseview::Window<'_>| -> PluginGui { + PluginGui::new(window, gui_context, params, move_scaling_factor) + }, + ); self.params.editor_state.open.store(true, Ordering::Release); Box::new(EditorHandle { state: self.params.editor_state.clone(), - window + window, }) } @@ -67,7 +75,7 @@ impl Editor for EditorWindow { if self.params.editor_state.is_open() { return false; } - self.scaling_factor.store(Some(factor)); + self.scaling_factor.store(factor, Ordering::Release); true }