Improve screen scaling behavior

This commit is contained in:
Ebu
2025-12-03 12:37:38 +01:00
parent d8e9e12515
commit 3cf22b7189
8 changed files with 451 additions and 199 deletions

7
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

View File

@@ -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 }

187
ebu-dsp/src/gui/mod.rs Normal file
View File

@@ -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<AtomicF32>,
image: Result<ImageId, String>,
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<OpenGl>,
data: &[u8],
scale_factor: Arc<AtomicF32>,
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<OpenGl>, 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<AtomicF32>,
image: Result<ImageId, String>,
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<OpenGl>, data: &[u8], scale_factor: Arc<AtomicF32>) -> 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<OpenGl>, 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),
)
}
}

View File

@@ -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<T> {
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<T: Debug> Debug for Rect<T> {
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<T: PartialOrd + Add<Output = T> + Copy> Rect<T> {
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<T> Default for Rect<T>
where
T: Default,
{
fn default() -> Self {
#[derive(Clone, Debug, Default)]
pub struct ScaledPoint {
pub factor: Arc<AtomicF32>,
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<AtomicF32>) -> Self {
Self {
x: Default::default(),
y: Default::default(),
width: Default::default(),
height: Default::default(),
factor,
..Default::default()
}
}
pub fn new_from<P>(factor: Arc<AtomicF32>, point: P) -> Self
where
P: Into<Point>,
{
let point = point.into();
Self {
factor,
x: point.x,
y: point.y,
}
}
pub fn set<P>(&mut self, point: P)
where
P: Into<Point>,
{
let point = point.into();
self.x = point.x;
self.y = point.y;
}
pub fn set_scaled<P>(&mut self, point: P)
where
P: Into<Point>,
{
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<P>(&self, value: P) -> bool
where
P: Into<Point>,
{
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<AtomicF32>,
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<AtomicF32>) -> Self {
Self {
factor,
..Default::default()
}
}
pub fn new_from<R>(factor: Arc<AtomicF32>, rect: R) -> Self
where
R: Into<Rect>,
{
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<const N: usize>(sample_buffer: &RingBuffer<f64, N>, 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))
}

View File

@@ -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<Self> {
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<EditorState> {
pub struct EditorHandle {
pub state: Arc<EditorState>,
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();
}
}
}

View File

@@ -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<FontId, String>,
params: Arc<PluginParams>,
canvas: Option<Canvas<OpenGl>>,
_gui_context: Arc<dyn GuiContext>,
scaling_factor: Arc<AtomicCell<Option<f32>>>,
scaling_factor: Arc<AtomicF32>,
freshener_screen_bounds: Rect<f32>,
freshener_image: Result<ImageId, String>,
not_so_fresh_image: Result<ImageId, String>,
fresh_dumbledore_image: Result<ImageId, String>,
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<Option<f32>>,
scaling_factor: &AtomicF32,
) -> Result<Canvas<OpenGl>, &'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<dyn GuiContext>,
params: Arc<PluginParams>,
scaling_factor: Arc<AtomicCell<Option<f32>>>,
scaling_factor: Arc<AtomicF32>,
) -> 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();
}

View File

@@ -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<PluginParams>,
scaling_factor: Arc<AtomicCell<Option<f32>>>,
scaling_factor: Arc<AtomicF32>,
}
impl EditorWindow {
pub const VIRTUAL_SCALE: f32 = 1.0;
pub const WINDOW_SIZE: (u32, u32) = (230, 320);
pub fn new(params: Arc<PluginParams>) -> 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<dyn nih_plug::prelude::GuiContext>,
) -> Box<dyn std::any::Any + Send> {
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
}