322 lines
12 KiB
Rust
322 lines
12 KiB
Rust
use crate::{parameters::PluginParams, window::EditorWindow};
|
|
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 nih_plug::prelude::*;
|
|
use std::sync::Arc;
|
|
|
|
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;
|
|
|
|
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>>>,
|
|
|
|
freshener_screen_bounds: Rect<f32>,
|
|
|
|
freshener_image: Result<ImageId, String>,
|
|
not_so_fresh_image: Result<ImageId, String>,
|
|
fresh_dumbledore_image: Result<ImageId, String>,
|
|
|
|
dirty: bool,
|
|
mouse_position: (f32, f32),
|
|
drag_start_mouse_pos: (f32, f32),
|
|
drag_start_parameter_value: f32,
|
|
dragging: bool,
|
|
}
|
|
|
|
fn create_canvas(
|
|
context: &GlContext,
|
|
params: &PluginParams,
|
|
scaling_factor: &AtomicCell<Option<f32>>,
|
|
) -> 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));
|
|
Ok(canvas)
|
|
}
|
|
|
|
impl PluginGui {
|
|
pub fn new(
|
|
window: &mut baseview::Window<'_>,
|
|
gui_context: Arc<dyn GuiContext>,
|
|
params: Arc<PluginParams>,
|
|
scaling_factor: Arc<AtomicCell<Option<f32>>>,
|
|
) -> Self {
|
|
let mut this = Self {
|
|
font: Err("Not loaded".to_owned()),
|
|
params: params.clone(),
|
|
canvas: None,
|
|
_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),
|
|
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(),
|
|
};
|
|
|
|
if let Some(context) = window.gl_context() {
|
|
unsafe {
|
|
context.make_current();
|
|
}
|
|
if let Ok(mut canvas) = create_canvas(context, ¶ms, &scaling_factor) {
|
|
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.canvas = Some(canvas);
|
|
}
|
|
unsafe {
|
|
context.make_not_current();
|
|
}
|
|
};
|
|
|
|
/*
|
|
if let Some(canvas) = canvas {
|
|
this.freshener_image = canvas
|
|
.load_image_mem(FRESHENER_IMAGE, ImageFlags::empty())
|
|
.map_err(|_| "Failed to load le fresh")?;
|
|
let not_so_fresh_image = canvas
|
|
.load_image_mem(NOT_SO_FRESH_BG_IMAGE, ImageFlags::empty())
|
|
.map_err(|_| "Failed to load not so fresh")?;
|
|
let fresh_dumbledore_image = canvas
|
|
.load_image_mem(FRESH_DUMBLEDORE_BG_IMAGE, ImageFlags::empty())
|
|
.map_err(|_| "Failed to load fresh dumbledore")?;
|
|
}
|
|
*/
|
|
this
|
|
}
|
|
}
|
|
|
|
impl WindowHandler for PluginGui {
|
|
fn on_frame(&mut self, window: &mut baseview::Window) {
|
|
if self.canvas.is_none() {
|
|
return;
|
|
}
|
|
let canvas = self.canvas.as_mut().unwrap();
|
|
let scaling_factor = self.scaling_factor.load().unwrap_or(1.0);
|
|
|
|
if !self.dirty {
|
|
//return;
|
|
}
|
|
|
|
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 => {
|
|
nih_error!("No OpenGL context");
|
|
return;
|
|
}
|
|
Some(ctx) => ctx,
|
|
};
|
|
unsafe {
|
|
context.make_current();
|
|
}
|
|
let (width, height) = (canvas.width() as f32, canvas.height() as f32);
|
|
canvas.reset();
|
|
canvas.clear_rect(
|
|
0,
|
|
0,
|
|
width as u32,
|
|
height as u32,
|
|
Color::rgbaf(0.5, 0.5, 0.6, 1.0),
|
|
);
|
|
|
|
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,
|
|
height: FRESHENER_FRAME_HEIGHT * FRESHENER_FRAMES_Y as f32,
|
|
};
|
|
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
use femtovg::Baseline;
|
|
if let Ok(font) = self.font {
|
|
let mut y = 5.0;
|
|
let mut print = |str: &str| {
|
|
for s in str.lines() {
|
|
canvas
|
|
.fill_text(
|
|
5.0,
|
|
y,
|
|
s,
|
|
&Paint::color(Color::white())
|
|
.with_font(&[font])
|
|
.with_font_size(font_size)
|
|
.with_text_baseline(Baseline::Top),
|
|
)
|
|
.ok();
|
|
y += font_size;
|
|
}
|
|
};
|
|
let scaling_factor = self.scaling_factor.load();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
canvas.flush();
|
|
context.swap_buffers();
|
|
unsafe {
|
|
context.make_not_current();
|
|
}
|
|
self.dirty = false;
|
|
}
|
|
|
|
fn on_event(
|
|
&mut self,
|
|
_window: &mut baseview::Window,
|
|
event: baseview::Event,
|
|
) -> baseview::EventStatus {
|
|
let setter = ParamSetter::new(self._gui_context.as_ref());
|
|
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),
|
|
);
|
|
}
|
|
self.dirty = true;
|
|
}
|
|
Event::Mouse(MouseEvent::CursorMoved { position, .. }) => {
|
|
self.mouse_position = (position.x as f32, position.y as f32);
|
|
if self.dragging {
|
|
let delta = self.mouse_position.1 - self.drag_start_mouse_pos.1;
|
|
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);
|
|
}
|
|
self.dirty = true;
|
|
}
|
|
Event::Mouse(MouseEvent::ButtonPressed { button, .. }) => {
|
|
self.dragging = self.freshener_screen_bounds.contains(self.mouse_position)
|
|
&& button == MouseButton::Left;
|
|
if self.dragging {
|
|
setter.begin_set_parameter(&self.params.freshness);
|
|
self.drag_start_mouse_pos = self.mouse_position;
|
|
self.drag_start_parameter_value =
|
|
self.params.freshness.unmodulated_normalized_value();
|
|
}
|
|
self.dirty = true;
|
|
}
|
|
Event::Mouse(MouseEvent::ButtonReleased { .. }) => {
|
|
if self.dragging {
|
|
setter.end_set_parameter(&self.params.freshness);
|
|
}
|
|
self.dragging = false;
|
|
self.dirty = true;
|
|
}
|
|
_ => return EventStatus::Ignored,
|
|
}
|
|
EventStatus::Captured
|
|
}
|
|
}
|