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, params: Arc, canvas: Option>, _gui_context: Arc, scaling_factor: Arc>>, freshener_screen_bounds: Rect, freshener_image: Result, not_so_fresh_image: Result, fresh_dumbledore_image: Result, 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>, ) -> 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)); Ok(canvas) } impl PluginGui { pub fn new( window: &mut baseview::Window<'_>, gui_context: Arc, params: Arc, scaling_factor: Arc>>, ) -> 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 } }