diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f8280b0..c266292 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ windows-latest, macOS-latest, ubuntu-latest ] + os: [ windows-latest, macOS-latest ] steps: - uses: actions/checkout@v2 @@ -19,9 +19,22 @@ jobs: - name: Run tests run: cargo test --verbose + build-ubuntu: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install xkbcommon + run: sudo apt-get install libxkbcommon-dev + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + deploy: - needs: build + needs: [ build, build-ubuntu ] runs-on: macOS-latest steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7332221..8651b2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ windows-latest, macOS-latest, ubuntu-latest ] + os: [ windows-latest, macOS-latest ] steps: - uses: actions/checkout@v2 @@ -22,3 +22,16 @@ jobs: run: cargo build --verbose - name: Run tests run: cargo test --verbose + + build-ubuntu: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install xkbcommon + run: sudo apt-get install libxkbcommon-dev + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index d6d96e9..e9c55af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license-file = "LICENSE" name = "fpsdk" readme = "README.md" repository = "https://github.com/tonikasoft/fpsdk" -version = "1.0.3" +version = "1.1.0" [package.metadata.docs.rs] targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] @@ -15,12 +15,20 @@ targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] bitflags = "1.2" hresult = "0.0.1" log = "0.4" +raw-window-handle = "0.3" + +[target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.20" [build-dependencies] cc = { version = "1.0", features = ["parallel"] } [dev-dependencies] bincode = "1.2" +chrono = "0.4" +cocoa = "0.20" +iced_wgpu = { git = "https://github.com/hecrj/iced.git" } +iced_winit = { git = "https://github.com/hecrj/iced.git" } log = "0.4" serde = { version = "1.0", features = ["derive"] } simple-logging = "2.0" @@ -28,5 +36,9 @@ simplelog = "0.7" [[example]] name = "simple" -path = "examples/simple.rs" +path = "examples/simple/simple.rs" crate-type = ["cdylib"] + +[[example]] +name = "gui" +path = "examples/simple/main.rs" diff --git a/build.rs b/build.rs index 8a7ef55..9f01955 100644 --- a/build.rs +++ b/build.rs @@ -4,7 +4,7 @@ fn main() { .file("src/cxx/fp_plugclass.cpp") .file("src/cxx/wrapper.cpp") .cpp(true) - .flag("-std=c++11") + // .flag("-std=c++11") .compile("fpsdk"); println!("cargo:rerun-if-changed=src/cxx/fp_plugclass.h"); diff --git a/examples/simple/gui.rs b/examples/simple/gui.rs new file mode 100644 index 0000000..7580776 --- /dev/null +++ b/examples/simple/gui.rs @@ -0,0 +1,319 @@ +mod controls; + +use std::cell::RefCell; +use std::os::raw::c_void; + +use cocoa::appkit::{NSBackingStoreType, NSView, NSWindow, NSWindowStyleMask}; +use cocoa::base::{id, nil}; +use cocoa::foundation::{NSPoint, NSRect, NSSize}; + +use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; +use iced_winit::{futures, program, winit, Debug, Size}; + +use winit::event::{Event, ModifiersState, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::platform::desktop::EventLoopExtDesktop; +use winit::platform::macos::{ActivationPolicy, WindowBuilderExtMacOS, WindowExtMacOS}; + +use controls::Controls; + +pub struct Editor { + event_loop: EventLoop<()>, + event_handler: RefCell, +} + +impl Editor { + pub fn new() -> Self { + let event_loop = EventLoop::new(); + let event_handler = RefCell::new(EventHandler::new(&event_loop)); + + Self { + event_loop, + event_handler, + } + } + + #[cfg(target_os = "macos")] + pub fn raw_view(&self) -> *mut c_void { + self.event_handler.borrow().window.ns_view() + } + + #[cfg(windows)] + pub fn raw_view(&self) -> *mut c_void { + // TODO + std::ptr::null() as *mut c_void + } + + #[cfg(target_os = "linux")] + pub fn raw_view(&self) -> *mut c_void { + std::ptr::null() as *mut c_void + } + + pub fn open(&mut self) { + let handler = self.event_handler.get_mut(); + handler.is_opened = true; + } + + pub fn close(&mut self) { + let handler = self.event_handler.get_mut(); + handler.is_opened = false; + } + + pub fn event_loop_step(&mut self) { + let handler = self.event_handler.get_mut(); + + if !handler.is_opened { + return; + } + + self.event_loop + .run_return(|event, _, control_flow| handler.handle(event, control_flow)); + } +} + +struct EventHandler { + window: winit::window::Window, + viewport: Viewport, + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + format: wgpu::TextureFormat, + swap_chain: wgpu::SwapChain, + debug: Debug, + renderer: Renderer, + state: program::State, + modifiers: ModifiersState, + is_resized: bool, + is_opened: bool, +} + +impl EventHandler { + fn new(event_loop: &EventLoop<()>) -> Self { + let window = winit::window::WindowBuilder::new() + // .with_activation_policy(ActivationPolicy::Prohibited) + .with_visible(true) + .build(&event_loop) + .unwrap(); + let viewport = Self::init_viewport(&window); + + let surface = wgpu::Surface::create(&window); + let (mut device, queue) = Self::init_device_and_queue(&surface); + let format = wgpu::TextureFormat::Bgra8UnormSrgb; + let swap_chain = Self::init_swap_chain(&window, &device, &surface, &format); + let mut debug = Debug::new(); + let mut renderer = Renderer::new(Backend::new(&mut device, Settings::default())); + let state: program::State = program::State::new( + Controls::new(), + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + + EventHandler { + window, + viewport, + surface, + device, + queue, + format, + swap_chain, + debug, + renderer, + state, + modifiers: Default::default(), + is_resized: false, + is_opened: true, + } + } + + fn init_viewport(window: &winit::window::Window) -> Viewport { + let physical_size = window.inner_size(); + Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ) + } + + fn init_device_and_queue(surface: &wgpu::Surface) -> (wgpu::Device, wgpu::Queue) { + futures::executor::block_on(async { + let adapter = wgpu::Adapter::request( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + compatible_surface: Some(&surface), + }, + wgpu::BackendBit::PRIMARY, + ) + .await + .expect("Request adapter"); + + adapter + .request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: wgpu::Limits::default(), + }) + .await + }) + } + + fn init_swap_chain( + window: &winit::window::Window, + device: &wgpu::Device, + surface: &wgpu::Surface, + format: &wgpu::TextureFormat, + ) -> wgpu::SwapChain { + let size = window.inner_size(); + + device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: format.clone(), + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ) + } + + fn handle(&mut self, event: Event<()>, control_flow: &mut ControlFlow) { + match event { + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = new_modifiers; + } + WindowEvent::Resized(new_size) => { + self.viewport = Viewport::with_physical_size( + Size::new(new_size.width, new_size.height), + self.window.scale_factor(), + ); + + self.is_resized = true; + } + WindowEvent::CloseRequested => { + self.is_opened = false; + *control_flow = ControlFlow::Exit; + } + + _ => {} + } + + // Map window event to iced event + if let Some(event) = iced_winit::conversion::window_event( + &event, + self.window.scale_factor(), + self.modifiers, + ) { + self.state.queue_event(event); + } + } + Event::MainEventsCleared => { + // We update iced + let _ = self.state.update( + None, + self.viewport.logical_size(), + &mut self.renderer, + &mut self.debug, + ); + + // and request a redraw + self.window.request_redraw(); + } + Event::RedrawRequested(_) => { + if self.is_resized { + let size = self.window.inner_size(); + + self.swap_chain = self.device.create_swap_chain( + &self.surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: self.format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + } + + let frame = self.swap_chain.get_next_texture().expect("Next frame"); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 1.0, + g: 0.5, + b: 0.0, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + // And then iced on top + let mouse_interaction = self.renderer.backend_mut().draw( + &mut self.device, + &mut encoder, + &frame.view, + &self.viewport, + self.state.primitive(), + &self.debug.overlay(), + ); + + // Then we submit the work + self.queue.submit(&[encoder.finish()]); + + // And update the mouse cursor + self.window + .set_cursor_icon(iced_winit::conversion::mouse_interaction(mouse_interaction)); + } + // we use Poll instead of Wait, because we can't pause the thread on Plugin::idle + // and Plugin::idle does its own optimizations + _ => *control_flow = ControlFlow::Poll, + } + } +} + +// pub fn main() { +// let frame = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(500.0, 400.0)); +// let parent_window = unsafe { +// NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( +// frame, +// NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSTitledWindowMask, +// NSBackingStoreType::NSBackingStoreBuffered, +// 0, +// ) +// }; +// // this fixes mouse hover +// unsafe { +// parent_window.setAcceptsMouseMovedEvents_(1); +// }; +// +// unsafe { +// let child = window.ns_view() as id; +// NSView::setFrameSize(child, frame.size); +// NSView::setFrameOrigin(child, frame.origin); +// parent_window.contentView().addSubview_(child); +// }; +// +// // Initialize wgpu +// +// // Initialize GUI controls +// +// // Initialize iced +// +// unsafe { parent_window.orderFront_(parent_window) }; +// +// let mut is_close = false; +// +// while self.is_opened {} +// } diff --git a/examples/simple/gui/controls.rs b/examples/simple/gui/controls.rs new file mode 100644 index 0000000..415a2eb --- /dev/null +++ b/examples/simple/gui/controls.rs @@ -0,0 +1,63 @@ +use iced_wgpu::Renderer; +use iced_winit::{ + slider, Align, Color, Column, Command, Element, Length, Program, Row, Slider, Text, +}; + +pub struct Controls { + amp: f32, + slider: slider::State, +} + +#[derive(Debug)] +pub enum Message { + AmpChanged(f32), +} + +impl Controls { + pub fn new() -> Controls { + Controls { + amp: 0.0, + slider: Default::default(), + } + } +} + +impl Program for Controls { + type Renderer = Renderer; + type Message = Message; + + fn update(&mut self, message: Message) -> Command { + let Message::AmpChanged(amp) = message; + self.amp = amp; + + Command::none() + } + + fn view(&mut self) -> Element { + let slider = Row::new() + .width(Length::Units(500)) + .spacing(20) + .push(Slider::new( + &mut self.slider, + 0.0..=1.0, + self.amp, + move |r| Message::AmpChanged(r), + )); + + Row::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Fill) + .align_items(Align::Center) + .padding(10) + .spacing(10) + .push(Text::new("Amp").color(Color::WHITE)) + .push(slider) + .push(Text::new(format!("{:.2}", self.amp)).color(Color::WHITE)), + ) + .into() + } +} diff --git a/examples/simple/main.rs b/examples/simple/main.rs new file mode 100644 index 0000000..35880e0 --- /dev/null +++ b/examples/simple/main.rs @@ -0,0 +1,11 @@ +mod gui; +use gui::Editor; + +fn main() { + let mut editor = Editor::new(); + editor.open(); + + loop { + editor.event_loop_step(); + } +} diff --git a/examples/simple.rs b/examples/simple/simple.rs similarity index 82% rename from examples/simple.rs rename to examples/simple/simple.rs index 665a04c..66234cf 100644 --- a/examples/simple.rs +++ b/examples/simple/simple.rs @@ -1,3 +1,5 @@ +mod gui; + use std::collections::HashMap; #[cfg(unix)] use std::fs::OpenOptions; @@ -6,6 +8,12 @@ use std::sync::{Arc, Mutex, Once}; use std::time::{SystemTime, UNIX_EPOCH}; use bincode; +#[cfg(target_os = "macos")] +use cocoa::appkit::{NSBackingStoreType, NSColor, NSView, NSWindow, NSWindowStyleMask}; +#[cfg(target_os = "macos")] +use cocoa::base::{id, nil}; +#[cfg(target_os = "macos")] +use cocoa::foundation::NSSize; use log::{error, info, trace, LevelFilter}; use serde::{Deserialize, Serialize}; #[cfg(windows)] @@ -18,22 +26,27 @@ use fpsdk::plugin::message; use fpsdk::plugin::{self, Info, InfoBuilder, Plugin, StateReader, StateWriter}; use fpsdk::voice::{self, ReceiveVoiceHandler, SendVoiceHandler, Voice}; use fpsdk::{ - create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, Notes, NotesFlags, - ProcessParamFlags, TimeFormat, ValuePtr, + create_plugin, AsRawPtr, EditorHandle, FromRawPtr, MessageBoxFlags, MidiMessage, Note, Notes, + NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, }; +use gui::Editor; + static ONCE: Once = Once::new(); const LOG_PATH: &str = "simple.log"; -#[derive(Debug)] struct Simple { host: Host, tag: plugin::Tag, param_names: Vec, state: State, voice_handler: SimpleVoiceHandler, + editor: Editor, } +unsafe impl Send for Simple {} +unsafe impl Sync for Simple {} + #[derive(Debug, Default, Deserialize, Serialize)] struct State { _time: u64, @@ -60,6 +73,7 @@ impl Plugin for Simple { "Parameter 3".into(), ], state: Default::default(), + editor: Editor::new(), } } @@ -68,6 +82,7 @@ impl Plugin for Simple { InfoBuilder::new_full_gen("Simple", "Simple", self.param_names.len() as u32) // InfoBuilder::new_effect("Simple", "Simple", self.param_names.len() as u32) + .mac_needs_nsview() .want_new_tick() .with_out_ctrls(1) .with_out_voices(1) @@ -117,9 +132,29 @@ impl Plugin for Simple { ); if let host::Message::SetEnabled(enabled) = message { - self.on_set_enabled(enabled, message); + self.on_set_enabled(enabled, message.clone()); + // self.host.on_message(self.tag, message::VstiIdle); + self.host + .on_message(self.tag, message::WantIdle::EnabledAlways); } + if let host::Message::ShowEditor(handle) = message { + match handle { + Some(parent) => { + let parent_view = parent.raw_handle() as id; + // self.editor.open(); + // unsafe { + // parent_view.addSubview_(self.editor.raw_view() as id); + // } + } + None => self.editor.close(), + } + } + // if let host::Message::ShowEditor(None) = message { + // if self.view.is_some() { + // self.view.unwrap().close(); + // } + // } // self.host.midi_out(self.tag, MidiMessage { // status: 0x90, // data1: 60, @@ -149,7 +184,10 @@ impl Plugin for Simple { } fn idle(&mut self) { - trace!("{} idle", self.tag); + trace!("am i blocking?"); + self.editor.event_loop_step(); + trace!("no, i'm not"); + panic!("it's not blocking, if it's panic"); } // looks like it doesn't work in SDK @@ -305,6 +343,33 @@ impl Simple { fn say_hello_hint(&mut self) { self.host.on_hint(self.tag, "^c Hello".to_string()); } + + #[cfg(target_os = "linux")] + fn show_editor(&mut self, parent: EditorHandle) {} + + #[cfg(windows)] + fn show_editor(&mut self, parent: EditorHandle) {} + + #[cfg(target_os = "macos")] + fn show_editor(&mut self, parent: EditorHandle) { + let view = parent.raw_handle() as id; + unsafe { + NSView::setFrameSize(view, NSSize::new(200.0, 100.0)); + let frame = NSView::frame(view); + let window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( + frame, + NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSTitledWindowMask, + NSBackingStoreType::NSBackingStoreBuffered, + 0, + ); + let color = NSColor::colorWithRed_green_blue_alpha_(nil, 1.0, 0.5, 0.0, 1.0); + window.orderFront_(window); + window.contentView().setWantsLayer(1); + window.contentView().setBackgroundColor_(color); + + view.addSubview_(window.contentView()); + } + } } #[derive(Debug)] @@ -420,6 +485,11 @@ impl SendVoiceHandler for SimpleOutVoiceHandler { fn init_log() { ONCE.call_once(|| { _init_log(); + + std::panic::set_hook(Box::new(|info| { + error!("{}", info); + })); + info!("init log"); }); } diff --git a/src/cxx/wrapper.cpp b/src/cxx/wrapper.cpp index 79af9bd..c60c032 100644 --- a/src/cxx/wrapper.cpp +++ b/src/cxx/wrapper.cpp @@ -45,16 +45,15 @@ int istream_write(void *istream, const unsigned char *data, unsigned int size, void *create_plug_instance_c(void *host, intptr_t tag, void *adapter) { Info *info = plugin_info((PluginAdapter *)adapter); - int reserved[30] = {0}; - PFruityPlugInfo c_info = new TFruityPlugInfo{(int)info->sdk_version, - info->long_name, - info->short_name, - (int)info->flags, - (int)info->num_params, - (int)info->def_poly, - (int)info->num_out_ctrls, - (int)info->num_out_voices, - {*reserved}}; + PFruityPlugInfo c_info = (TFruityPlugInfo *)malloc(sizeof(TFruityPlugInfo)); + c_info->SDKVersion = (int)info->sdk_version; + c_info->LongName = info->long_name; + c_info->ShortName = info->short_name; + c_info->Flags = (int)info->flags; + c_info->NumParams = (int)info->num_params; + c_info->DefPoly = (int)info->def_poly; + c_info->NumOutCtrls = (int)info->num_out_ctrls; + c_info->NumOutVoices = (int)info->num_out_voices; free_rbox_raw(info); @@ -76,7 +75,7 @@ PluginWrapper::PluginWrapper(TFruityPlugHost *host_ptr, TPluginTag tag, PluginWrapper::~PluginWrapper() { free(Info->LongName); free(Info->ShortName); - delete Info; + free(Info); free_rbox_raw(adapter); } @@ -91,9 +90,9 @@ void _stdcall PluginWrapper::SaveRestoreState(IStream *stream, BOOL save) { intptr_t _stdcall PluginWrapper::Dispatcher(intptr_t id, intptr_t index, intptr_t value) { - // if (id == FPD_SetEnabled) { - // host->Dispatcher(HostTag, FHD_WantMIDIInput, 0, value); - // } + if (id == FPD_ShowEditor) { + EditorHandle = (HWND)value; + } FlMessage message = {id, index, value}; diff --git a/src/host.rs b/src/host.rs index 35ac007..2f4891e 100644 --- a/src/host.rs +++ b/src/host.rs @@ -13,8 +13,8 @@ use log::trace; use crate::plugin::{self, message}; use crate::voice::{self, SendVoiceHandler, Voice}; use crate::{ - intptr_t, AsRawPtr, FlMessage, FromRawPtr, MidiMessage, ProcessModeFlags, TTimeSigInfo, - TimeSignature, Transport, ValuePtr, WAVETABLE_SIZE, + intptr_t, AsRawPtr, EditorHandle, FlMessage, FromRawPtr, MidiMessage, ProcessModeFlags, + TTimeSigInfo, TimeSignature, Transport, ValuePtr, WAVETABLE_SIZE, }; /// Plugin host. @@ -221,7 +221,7 @@ impl Host { /// Get one of the buffers. /// - /// - `kind` the kind of the buffer you want to get + /// - `kind` the kind of the buffer you want to get /// (see [`host::Buffer`](../host/enum.Buffer.html)). /// - `length` is the buffer length (use the length of the output buffer passed to the render /// function). @@ -557,10 +557,11 @@ extern "C" { } /// Message from the host to the plugin. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Message<'a> { - /// Contains the handle of the parent window if the editor has to be shown. - ShowEditor(Option<*mut c_void>), + /// Contains the handle of the parent window if the editor has to be shown and `None` if the + /// editor has to be hidden. + ShowEditor(Option), /// Change the processing mode flags. This can be ignored. /// /// The value is [ProcessModeFlags](../struct.ProcessModeFlags.html). @@ -764,10 +765,10 @@ impl From for Message<'_> { impl Message<'_> { fn from_show_editor(message: FlMessage) -> Self { - if message.value == 1 { - Message::ShowEditor(None) + if message.value != 0 { + Message::ShowEditor(Some(EditorHandle::from_raw_ptr(message.value))) } else { - Message::ShowEditor(Some(message.value as *mut c_void)) + Message::ShowEditor(None) } } diff --git a/src/lib.rs b/src/lib.rs index 542e5e6..dee5912 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,12 @@ use std::mem; use std::os::raw::{c_char, c_int, c_void}; use bitflags::bitflags; +#[cfg(target_os = "macos")] +use cocoa::appkit::NSView; +#[cfg(target_os = "macos")] +use cocoa::base::id; use log::{debug, error}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; /// Current FL SDK version. pub const CURRENT_SDK_VERSION: u32 = 1; @@ -244,6 +249,71 @@ impl FromRawPtr for ValuePtr { } } +/// Handle for view/window to which you can attach your view/window. Host sends it to you in +/// [`host::Message::ShowEditor`](host/enum.Message.html#variant.ShowMessage) message. +/// +/// It's NSView on macOS and HWND on Windows. +/// +/// **Note: you need to call [`InfoBuilder::mac_needs_nsview`]( +/// plugin/struct.InfoBuilder.html#method.mac_needs_nsview) if you want to attach your view on +/// macOS.** +#[derive(Debug, Clone)] +pub struct EditorHandle { + raw_handle: *mut c_void, +} + +impl EditorHandle { + /// Constructor. + pub fn new(raw_handle: *mut c_void) -> Self { + Self { raw_handle } + } + + /// Get raw pointer to the view/window handler (NSView on macOS or HWND on Windows). + pub fn raw_handle(&self) -> *mut c_void { + self.raw_handle + } + /// Attach your editor the handle. + pub fn attach_editor(&self, view: &mut V) { + // We make separate private implementations here, because docs on docs.rs is compiled under + // linux, so if we just use different attach_editor per platform, it'll be hidden on + // docs.rs. + #[cfg(target_os = "macos")] + unsafe { + self.attach_editor_mac(view) + }; + + #[cfg(target_os = "windows")] + unsafe { + self.attach_editor_win(view) + }; + } + + #[cfg(target_os = "macos")] + unsafe fn attach_editor_mac(&self, view: &mut V) { + if let RawWindowHandle::MacOS(handle) = view.raw_window_handle() { + if handle.ns_view.is_null() { + return; + } + + let ns_view = handle.ns_view as id; // NSView + let parent_view = self.raw_handle as id; // NSView + parent_view.setFrameSize(ns_view.frame().size); + parent_view.addSubview_(ns_view); + } + } + + #[cfg(target_os = "windows")] + unsafe fn attach_editor_win(&self, view: &mut V) {} +} + +impl FromRawPtr for EditorHandle { + fn from_raw_ptr(value: intptr_t) -> Self { + Self { + raw_handle: value as *mut c_void, + } + } +} + /// Time signature. #[derive(Debug, Clone)] pub struct TimeSignature { @@ -396,7 +466,7 @@ impl FromRawPtr for TNameColor { } /// MIDI message. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MidiMessage { /// Status byte. pub status: u8, @@ -649,7 +719,7 @@ bitflags! { /// if `Cut`, `Copy`, `Paste`, `Insert`, `Delete`, `NextWindow`, `Enter`, `Escape`, `Yes`, `No`, /// `Fx` don't answer, standard keystrokes will be simulated #[allow(missing_docs)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Transport { /// Generic jog (can be used to select stuff). Jog(Jog), @@ -808,15 +878,15 @@ impl From for Transport { /// `0` for release, `1` for switch (if release is not supported), `2` for hold (if release should /// be expected). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Button(pub u8); /// `false` for release, `true` for hold. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Hold(pub bool); /// Value is an integer increment. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Jog(pub i64); bitflags! { diff --git a/src/plugin.rs b/src/plugin.rs index 331fd32..1b59e74 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -5,7 +5,6 @@ pub mod message; use std::ffi::CString; use std::io::{self, Read, Write}; use std::os::raw::{c_char, c_int, c_void}; -use std::panic::RefUnwindSafe; use hresult::HRESULT; use log::{debug, error}; @@ -52,7 +51,7 @@ macro_rules! create_plugin { } /// This trait must be implemented for your plugin. -pub trait Plugin: std::fmt::Debug + RefUnwindSafe + Send + Sync + 'static { +pub trait Plugin { /// Initializer. fn new(host: Host, tag: Tag) -> Self where @@ -353,6 +352,13 @@ impl InfoBuilder { self } + /// (macOS only) Allows you to make your plugin's view a subview of FL's plugin window. Should + /// be added if you're building a plugin with GUI and it's supposed to be used on macOS. + pub fn mac_needs_nsview(mut self) -> Self { + self.flags |= 1 << 27; + self + } + /// Finish builder and init [`Info`](struct.Info.html) pub fn build(self) -> Info { let log_err = |e| { @@ -438,7 +444,6 @@ fn check_hresult(result: HRESULT, read: usize, error_msg: &str) -> io::Result); /// [`Plugin::info`](trait.Plugin.html#tymethod.info) FFI. diff --git a/src/voice.rs b/src/voice.rs index ec40647..e81e188 100644 --- a/src/voice.rs +++ b/src/voice.rs @@ -1,5 +1,5 @@ -//! Voices used by generators to track events like their instantiation, release, freeing and -//! processing some events. +//! Voices used by the generator plugins to track their instantiation, release, freeing and events +//! processing. use std::os::raw::c_void; use crate::plugin::PluginAdapter;