From e32d114f9b8535253174a88a75a05f8e06d32945 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Mon, 25 May 2020 23:41:23 +0300 Subject: [PATCH 01/13] Add add_child_window function for macOS --- Cargo.toml | 1 + build.rs | 4 +- examples/simple.rs | 2 + examples/ui.rs | 80 +++++++++++++++++++++++++++++++++++++ src/cxx/add_child_window.h | 5 +++ src/cxx/add_child_window.mm | 5 +++ src/cxx/wrapper.cpp | 21 +++++----- 7 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 examples/ui.rs create mode 100644 src/cxx/add_child_window.h create mode 100644 src/cxx/add_child_window.mm diff --git a/Cargo.toml b/Cargo.toml index d6d96e9..e1312ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ log = "0.4" serde = { version = "1.0", features = ["derive"] } simple-logging = "2.0" simplelog = "0.7" +minifb = "0.16" [[example]] name = "simple" diff --git a/build.rs b/build.rs index 8a7ef55..ffaf480 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,10 @@ fn main() { .include("src/cxx") .file("src/cxx/fp_plugclass.cpp") .file("src/cxx/wrapper.cpp") + .file("src/cxx/add_child_window.mm") .cpp(true) - .flag("-std=c++11") + // .flag("-std=c++11") + // .flag("-fobjc-arc").flag("-framework").flag("Foundation") .compile("fpsdk"); println!("cargo:rerun-if-changed=src/cxx/fp_plugclass.h"); diff --git a/examples/simple.rs b/examples/simple.rs index 665a04c..67fbfbb 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,3 +1,5 @@ +pub mod ui; + use std::collections::HashMap; #[cfg(unix)] use std::fs::OpenOptions; diff --git a/examples/ui.rs b/examples/ui.rs new file mode 100644 index 0000000..00ccdc7 --- /dev/null +++ b/examples/ui.rs @@ -0,0 +1,80 @@ +use std::os::raw::c_void; + +use minifb::{Key, Scale, ScaleMode, Window, WindowOptions}; + +const WIDTH: usize = 100; +const HEIGHT: usize = 100; +const FRACTAL_DEPTH: u32 = 64; +const GENERATION_INFINITY: f64 = 16.; + +pub fn init_view(parent_ptr: *mut c_void) { + let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; + + let mut window = Window::new( + "Fractal - ESC to exit", + WIDTH, + HEIGHT, + WindowOptions { + resize: true, + scale: Scale::X2, + scale_mode: ScaleMode::AspectRatioStretch, + ..WindowOptions::default() + }, + ) + .expect("Unable to Open Window"); + + // Limit to max ~60 fps update rate + window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); + + let range = 2.0; + let x_min = 0. - range; + let y_min = 0. - range; + + let x_max = 0. + range; + let y_max = 0. + range; + + let mut angle: f64 = 0.0; + + window.set_background_color(0, 0, 20); + + while window.is_open() && !window.is_key_down(Key::Escape) { + for i in 0..buffer.len() { + let mut real = map((i % WIDTH) as f64, 0., WIDTH as f64, x_min, x_max); + let mut imag = map((i / HEIGHT) as f64, 0., HEIGHT as f64, y_min, y_max); + + let mut n = 0; + + while n < FRACTAL_DEPTH { + let re = real.powf(2.) - imag.powf(2.); + let im = 2. * real * imag; + + real = re + angle.cos(); + imag = im + angle.sin(); + + if (real + imag).abs() > GENERATION_INFINITY { + break; // Leave when achieve infinity + } + n += 1; + } + + buffer[i] = fill(n); + } + + angle += 0.1; + + // We unwrap here as we want this code to exit if it fails + window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); + } +} + +fn map(val: f64, start1: f64, stop1: f64, start2: f64, stop2: f64) -> f64 { + start2 + (stop2 - start2) * ((val - start1) / (stop1 - start1)) +} + +fn fill(n: u32) -> u32 { + if FRACTAL_DEPTH == n { + return 0x00; + } else { + return n * 32 % 255; + } +} diff --git a/src/cxx/add_child_window.h b/src/cxx/add_child_window.h new file mode 100644 index 0000000..36e394b --- /dev/null +++ b/src/cxx/add_child_window.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern "C" void add_child_window(void *parent, void *child); diff --git a/src/cxx/add_child_window.mm b/src/cxx/add_child_window.mm new file mode 100644 index 0000000..c0b88c8 --- /dev/null +++ b/src/cxx/add_child_window.mm @@ -0,0 +1,5 @@ +#include "add_child_window.h" + +void add_child_window(void *parent, void *child) { + [(NSWindow *)parent addChildWindow:(NSWindow *)child ordered:NSWindowAbove]; +} diff --git a/src/cxx/wrapper.cpp b/src/cxx/wrapper.cpp index 79af9bd..d99dd18 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); } From 7e0af0ffa9c3584a7d36ac1714c985ba0e99bbb6 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Tue, 26 May 2020 00:27:35 +0300 Subject: [PATCH 02/13] Implement add_child_window - implement Clone for host::Message --- build.rs | 2 ++ examples/simple.rs | 15 +++++++-- examples/ui.rs | 63 +++++++++++++++++++------------------ src/cxx/add_child_window.h | 2 -- src/cxx/add_child_window.mm | 4 +++ src/host.rs | 2 +- src/lib.rs | 18 ++++++++--- 7 files changed, 65 insertions(+), 41 deletions(-) diff --git a/build.rs b/build.rs index ffaf480..182da64 100644 --- a/build.rs +++ b/build.rs @@ -12,4 +12,6 @@ fn main() { println!("cargo:rerun-if-changed=src/cxx/fp_plugclass.h"); println!("cargo:rerun-if-changed=src/cxx/wrapper.h"); println!("cargo:rerun-if-changed=src/cxx/wrapper.cpp"); + println!("cargo:rerun-if-changed=src/cxx/add_child_window.h"); + println!("cargo:rerun-if-changed=src/cxx/add_child_window.mm"); } diff --git a/examples/simple.rs b/examples/simple.rs index 67fbfbb..1b971e8 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -20,10 +20,12 @@ 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, + add_child_window_s, create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, + Notes, NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, }; +use ui::init_window; + static ONCE: Once = Once::new(); const LOG_PATH: &str = "simple.log"; @@ -119,7 +121,14 @@ impl Plugin for Simple { ); if let host::Message::SetEnabled(enabled) = message { - self.on_set_enabled(enabled, message); + self.on_set_enabled(enabled, message.clone()); + } + + if let host::Message::ShowEditor(Some(parent)) = message { + let window = init_window(); + let handle = window.get_window_handle(); + info!("got window handle {:?}", handle); + add_child_window_s(parent, handle); } // self.host.midi_out(self.tag, MidiMessage { diff --git a/examples/ui.rs b/examples/ui.rs index 00ccdc7..e1c57e1 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -7,7 +7,7 @@ const HEIGHT: usize = 100; const FRACTAL_DEPTH: u32 = 64; const GENERATION_INFINITY: f64 = 16.; -pub fn init_view(parent_ptr: *mut c_void) { +pub fn init_window() -> Window { let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; let mut window = Window::new( @@ -36,37 +36,40 @@ pub fn init_view(parent_ptr: *mut c_void) { let mut angle: f64 = 0.0; window.set_background_color(0, 0, 20); - - while window.is_open() && !window.is_key_down(Key::Escape) { - for i in 0..buffer.len() { - let mut real = map((i % WIDTH) as f64, 0., WIDTH as f64, x_min, x_max); - let mut imag = map((i / HEIGHT) as f64, 0., HEIGHT as f64, y_min, y_max); - - let mut n = 0; - - while n < FRACTAL_DEPTH { - let re = real.powf(2.) - imag.powf(2.); - let im = 2. * real * imag; - - real = re + angle.cos(); - imag = im + angle.sin(); - - if (real + imag).abs() > GENERATION_INFINITY { - break; // Leave when achieve infinity - } - n += 1; - } - - buffer[i] = fill(n); - } - - angle += 0.1; - - // We unwrap here as we want this code to exit if it fails - window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); - } + window } +// pub fn draw(window: &mut Window) { + // while window.is_open() && !window.is_key_down(Key::Escape) { + // for i in 0..buffer.len() { + // let mut real = map((i % WIDTH) as f64, 0., WIDTH as f64, x_min, x_max); + // let mut imag = map((i / HEIGHT) as f64, 0., HEIGHT as f64, y_min, y_max); +// + // let mut n = 0; +// + // while n < FRACTAL_DEPTH { + // let re = real.powf(2.) - imag.powf(2.); + // let im = 2. * real * imag; +// + // real = re + angle.cos(); + // imag = im + angle.sin(); +// + // if (real + imag).abs() > GENERATION_INFINITY { + // break; // Leave when achieve infinity + // } + // n += 1; + // } +// + // buffer[i] = fill(n); + // } +// + // angle += 0.1; +// + // // We unwrap here as we want this code to exit if it fails + // window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); + // } +// } + fn map(val: f64, start1: f64, stop1: f64, start2: f64, stop2: f64) -> f64 { start2 + (stop2 - start2) * ((val - start1) / (stop1 - start1)) } diff --git a/src/cxx/add_child_window.h b/src/cxx/add_child_window.h index 36e394b..bf60988 100644 --- a/src/cxx/add_child_window.h +++ b/src/cxx/add_child_window.h @@ -1,5 +1,3 @@ #pragma once -#include - extern "C" void add_child_window(void *parent, void *child); diff --git a/src/cxx/add_child_window.mm b/src/cxx/add_child_window.mm index c0b88c8..ac7d0b9 100644 --- a/src/cxx/add_child_window.mm +++ b/src/cxx/add_child_window.mm @@ -1,5 +1,9 @@ +#include + #include "add_child_window.h" +#include "wrapper.h" void add_child_window(void *parent, void *child) { + fplog("im in add_child_window"); [(NSWindow *)parent addChildWindow:(NSWindow *)child ordered:NSWindowAbove]; } diff --git a/src/host.rs b/src/host.rs index 35ac007..51b713c 100644 --- a/src/host.rs +++ b/src/host.rs @@ -557,7 +557,7 @@ 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>), diff --git a/src/lib.rs b/src/lib.rs index 542e5e6..e69f33a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,14 @@ macro_rules! implement_tag { }; } +pub fn add_child_window_s(parent: *mut c_void, child: *mut c_void) { + unsafe { add_child_window(parent, child) }; +} + +extern "C" { + fn add_child_window(parent: *mut c_void, child: *mut c_void); +} + #[derive(Debug, Clone)] #[repr(C)] struct FlMessage { @@ -396,7 +404,7 @@ impl FromRawPtr for TNameColor { } /// MIDI message. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MidiMessage { /// Status byte. pub status: u8, @@ -649,7 +657,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 +816,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! { From 7e8ff4f66f8c365db59257923d2e397664870f90 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 01:36:55 +0300 Subject: [PATCH 03/13] Add EditorHandle - add InfoBuilder::mac_needs_nsview --- Cargo.toml | 2 + build.rs | 4 -- examples/simple.rs | 25 +++++++++--- src/cxx/add_child_window.h | 3 -- src/cxx/add_child_window.mm | 9 ----- src/cxx/wrapper.cpp | 6 +-- src/host.rs | 17 ++++---- src/lib.rs | 78 +++++++++++++++++++++++++++++++++---- src/plugin.rs | 7 ++++ 9 files changed, 110 insertions(+), 41 deletions(-) delete mode 100644 src/cxx/add_child_window.h delete mode 100644 src/cxx/add_child_window.mm diff --git a/Cargo.toml b/Cargo.toml index e1312ca..f1e9f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,10 @@ targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] [dependencies] bitflags = "1.2" +cocoa = "0.20" hresult = "0.0.1" log = "0.4" +raw-window-handle = "0.3" [build-dependencies] cc = { version = "1.0", features = ["parallel"] } diff --git a/build.rs b/build.rs index 182da64..9f01955 100644 --- a/build.rs +++ b/build.rs @@ -3,15 +3,11 @@ fn main() { .include("src/cxx") .file("src/cxx/fp_plugclass.cpp") .file("src/cxx/wrapper.cpp") - .file("src/cxx/add_child_window.mm") .cpp(true) // .flag("-std=c++11") - // .flag("-fobjc-arc").flag("-framework").flag("Foundation") .compile("fpsdk"); println!("cargo:rerun-if-changed=src/cxx/fp_plugclass.h"); println!("cargo:rerun-if-changed=src/cxx/wrapper.h"); println!("cargo:rerun-if-changed=src/cxx/wrapper.cpp"); - println!("cargo:rerun-if-changed=src/cxx/add_child_window.h"); - println!("cargo:rerun-if-changed=src/cxx/add_child_window.mm"); } diff --git a/examples/simple.rs b/examples/simple.rs index 1b971e8..4446093 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -21,7 +21,7 @@ use fpsdk::plugin::{self, Info, InfoBuilder, Plugin, StateReader, StateWriter}; use fpsdk::voice::{self, ReceiveVoiceHandler, SendVoiceHandler, Voice}; use fpsdk::{ add_child_window_s, create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, - Notes, NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, + Notes, NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, VstView, }; use ui::init_window; @@ -36,6 +36,7 @@ struct Simple { param_names: Vec, state: State, voice_handler: SimpleVoiceHandler, + view: Option, } #[derive(Debug, Default, Deserialize, Serialize)] @@ -64,6 +65,7 @@ impl Plugin for Simple { "Parameter 3".into(), ], state: Default::default(), + view: None, } } @@ -72,6 +74,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) @@ -125,12 +128,22 @@ impl Plugin for Simple { } if let host::Message::ShowEditor(Some(parent)) = message { - let window = init_window(); - let handle = window.get_window_handle(); - info!("got window handle {:?}", handle); - add_child_window_s(parent, handle); + if !parent.is_null() { + if self.view.is_none() { + self.view = Some(VstView::new(parent)); + } + self.view.as_mut().unwrap().open(); + } + // let window = init_window(); + // let handle = window.get_window_handle(); + // info!("got window handle {:?}", handle); + // add_child_window_s(parent, handle); } - + // 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, diff --git a/src/cxx/add_child_window.h b/src/cxx/add_child_window.h deleted file mode 100644 index bf60988..0000000 --- a/src/cxx/add_child_window.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -extern "C" void add_child_window(void *parent, void *child); diff --git a/src/cxx/add_child_window.mm b/src/cxx/add_child_window.mm deleted file mode 100644 index ac7d0b9..0000000 --- a/src/cxx/add_child_window.mm +++ /dev/null @@ -1,9 +0,0 @@ -#include - -#include "add_child_window.h" -#include "wrapper.h" - -void add_child_window(void *parent, void *child) { - fplog("im in add_child_window"); - [(NSWindow *)parent addChildWindow:(NSWindow *)child ordered:NSWindowAbove]; -} diff --git a/src/cxx/wrapper.cpp b/src/cxx/wrapper.cpp index d99dd18..20daec9 100644 --- a/src/cxx/wrapper.cpp +++ b/src/cxx/wrapper.cpp @@ -90,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 = value; + } FlMessage message = {id, index, value}; diff --git a/src/host.rs b/src/host.rs index 51b713c..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). @@ -559,8 +559,9 @@ extern "C" { /// Message from the host to the plugin. #[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 e69f33a..089d3e5 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; @@ -94,14 +99,6 @@ macro_rules! implement_tag { }; } -pub fn add_child_window_s(parent: *mut c_void, child: *mut c_void) { - unsafe { add_child_window(parent, child) }; -} - -extern "C" { - fn add_child_window(parent: *mut c_void, child: *mut c_void); -} - #[derive(Debug, Clone)] #[repr(C)] struct FlMessage { @@ -252,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(&mut 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(&mut 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(&mut 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 { diff --git a/src/plugin.rs b/src/plugin.rs index 331fd32..2d50ad1 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -353,6 +353,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| { From 11db55d8f24aa84b04670a054c8ffc40e52e2a24 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 01:44:54 +0300 Subject: [PATCH 04/13] Specify per os dependencies --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1e9f12..e36c640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,13 @@ targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] [dependencies] bitflags = "1.2" -cocoa = "0.20" 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"] } From 1dee37cf2ce4a02f8f1ba55844c0a96d940d67dd Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 01:54:40 +0300 Subject: [PATCH 05/13] Add rustup update to CI --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7332221..16ac440 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Update Rust + run: rustup update - name: Build run: cargo build --verbose - name: Run tests From 5f66223f2efc52c7266b9065e9db34ffafdea81f Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 02:05:24 +0300 Subject: [PATCH 06/13] Install dependencies on Ubuntu CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16ac440..e7e051f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,8 +18,8 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Update Rust - run: rustup update + - name: Install xkbcommon + run: sudo apt-get install libxkbcommon-dev - name: Build run: cargo build --verbose - name: Run tests From ceab3481e9ac54b1cb5e0f3c19f6417fe8f287c8 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 02:10:36 +0300 Subject: [PATCH 07/13] Separate Ubuntu job in CI --- .github/workflows/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7e051f..8651b2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,18 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ windows-latest, macOS-latest, ubuntu-latest ] + os: [ windows-latest, macOS-latest ] + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + + build-ubuntu: + + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From dbe4d5da72b14935f0950b3631470f90e7abe78f Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 02:15:39 +0300 Subject: [PATCH 08/13] Update CD --- .github/workflows/cd.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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: From a1af20b674ad96a612e389cda5c58453a0219bf9 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Thu, 28 May 2020 02:16:22 +0300 Subject: [PATCH 09/13] Update version in Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e36c640..50981f9 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" ] From 541692319b760c6c95a37fa4bc62a9758f61f88f Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Sat, 30 May 2020 02:29:40 +0300 Subject: [PATCH 10/13] Simulate winit-like view attachment in the example --- Cargo.toml | 5 +- examples/simple/gui.rs | 133 ++++++++++++++++++++++++++++++++ examples/{ => simple}/simple.rs | 45 +++++++---- examples/ui.rs | 83 -------------------- src/lib.rs | 6 +- src/voice.rs | 4 +- 6 files changed, 171 insertions(+), 105 deletions(-) create mode 100644 examples/simple/gui.rs rename examples/{ => simple}/simple.rs (90%) delete mode 100644 examples/ui.rs diff --git a/Cargo.toml b/Cargo.toml index 50981f9..2e49fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,14 @@ cc = { version = "1.0", features = ["parallel"] } [dev-dependencies] bincode = "1.2" +chrono = "0.4" +iced = { git = "https://github.com/hecrj/iced.git", features = ["canvas", "tokio", "debug"] } log = "0.4" serde = { version = "1.0", features = ["derive"] } simple-logging = "2.0" simplelog = "0.7" -minifb = "0.16" [[example]] name = "simple" -path = "examples/simple.rs" +path = "examples/simple/simple.rs" crate-type = ["cdylib"] diff --git a/examples/simple/gui.rs b/examples/simple/gui.rs new file mode 100644 index 0000000..9c583c7 --- /dev/null +++ b/examples/simple/gui.rs @@ -0,0 +1,133 @@ +use iced::{ + canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, + executor, time, Application, Color, Command, Container, Element, Length, + Point, Rectangle, Settings, Subscription, Vector, +}; + +pub fn main() { + Clock::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +struct Clock { + now: chrono::DateTime, + clock: Cache, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + ( + Clock { + now: chrono::Local::now(), + clock: Default::default(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time; + + if now != self.now { + self.now = now; + self.clock.clear(); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)) + .map(|_| Message::Tick(chrono::Local::now())) + } + + fn view(&mut self) -> Element { + let canvas = Canvas::new(self) + .width(Length::Units(400)) + .height(Length::Units(400)); + + Container::new(canvas) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() + } +} + +impl canvas::Program for Clock { + fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { + use chrono::Timelike; + + let clock = self.clock.draw(bounds.size(), |frame| { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + + let background = Path::circle(center, radius); + frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); + + let short_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); + + let long_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); + + let thin_stroke = Stroke { + width: radius / 100.0, + color: Color::WHITE, + line_cap: LineCap::Round, + ..Stroke::default() + }; + + let wide_stroke = Stroke { + width: thin_stroke.width * 3.0, + ..thin_stroke + }; + + frame.translate(Vector::new(center.x, center.y)); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.hour(), 12)); + frame.stroke(&short_hand, wide_stroke); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.minute(), 60)); + frame.stroke(&long_hand, wide_stroke); + }); + + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.second(), 60)); + frame.stroke(&long_hand, thin_stroke); + }) + }); + + vec![clock] + } +} + +fn hand_rotation(n: u32, total: u32) -> f32 { + let turns = n as f32 / total as f32; + + 2.0 * std::f32::consts::PI * turns +} diff --git a/examples/simple.rs b/examples/simple/simple.rs similarity index 90% rename from examples/simple.rs rename to examples/simple/simple.rs index 4446093..bda5c84 100644 --- a/examples/simple.rs +++ b/examples/simple/simple.rs @@ -1,4 +1,4 @@ -pub mod ui; +pub mod gui; use std::collections::HashMap; #[cfg(unix)] @@ -8,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::{NSPoint, NSRect, NSSize}; use log::{error, info, trace, LevelFilter}; use serde::{Deserialize, Serialize}; #[cfg(windows)] @@ -20,11 +26,11 @@ use fpsdk::plugin::message; use fpsdk::plugin::{self, Info, InfoBuilder, Plugin, StateReader, StateWriter}; use fpsdk::voice::{self, ReceiveVoiceHandler, SendVoiceHandler, Voice}; use fpsdk::{ - add_child_window_s, create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, - Notes, NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, VstView, + create_plugin, AsRawPtr, FromRawPtr, MessageBoxFlags, MidiMessage, Note, Notes, NotesFlags, + ProcessParamFlags, TimeFormat, ValuePtr, }; -use ui::init_window; +// use gui; static ONCE: Once = Once::new(); const LOG_PATH: &str = "simple.log"; @@ -36,9 +42,11 @@ struct Simple { param_names: Vec, state: State, voice_handler: SimpleVoiceHandler, - view: Option, } +unsafe impl Send for Simple {} +unsafe impl Sync for Simple {} + #[derive(Debug, Default, Deserialize, Serialize)] struct State { _time: u64, @@ -65,7 +73,6 @@ impl Plugin for Simple { "Parameter 3".into(), ], state: Default::default(), - view: None, } } @@ -128,16 +135,24 @@ impl Plugin for Simple { } if let host::Message::ShowEditor(Some(parent)) = message { - if !parent.is_null() { - if self.view.is_none() { - self.view = Some(VstView::new(parent)); - } - self.view.as_mut().unwrap().open(); + 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()); } - // let window = init_window(); - // let handle = window.get_window_handle(); - // info!("got window handle {:?}", handle); - // add_child_window_s(parent, handle); } // if let host::Message::ShowEditor(None) = message { // if self.view.is_some() { diff --git a/examples/ui.rs b/examples/ui.rs deleted file mode 100644 index e1c57e1..0000000 --- a/examples/ui.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::os::raw::c_void; - -use minifb::{Key, Scale, ScaleMode, Window, WindowOptions}; - -const WIDTH: usize = 100; -const HEIGHT: usize = 100; -const FRACTAL_DEPTH: u32 = 64; -const GENERATION_INFINITY: f64 = 16.; - -pub fn init_window() -> Window { - let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; - - let mut window = Window::new( - "Fractal - ESC to exit", - WIDTH, - HEIGHT, - WindowOptions { - resize: true, - scale: Scale::X2, - scale_mode: ScaleMode::AspectRatioStretch, - ..WindowOptions::default() - }, - ) - .expect("Unable to Open Window"); - - // Limit to max ~60 fps update rate - window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); - - let range = 2.0; - let x_min = 0. - range; - let y_min = 0. - range; - - let x_max = 0. + range; - let y_max = 0. + range; - - let mut angle: f64 = 0.0; - - window.set_background_color(0, 0, 20); - window -} - -// pub fn draw(window: &mut Window) { - // while window.is_open() && !window.is_key_down(Key::Escape) { - // for i in 0..buffer.len() { - // let mut real = map((i % WIDTH) as f64, 0., WIDTH as f64, x_min, x_max); - // let mut imag = map((i / HEIGHT) as f64, 0., HEIGHT as f64, y_min, y_max); -// - // let mut n = 0; -// - // while n < FRACTAL_DEPTH { - // let re = real.powf(2.) - imag.powf(2.); - // let im = 2. * real * imag; -// - // real = re + angle.cos(); - // imag = im + angle.sin(); -// - // if (real + imag).abs() > GENERATION_INFINITY { - // break; // Leave when achieve infinity - // } - // n += 1; - // } -// - // buffer[i] = fill(n); - // } -// - // angle += 0.1; -// - // // We unwrap here as we want this code to exit if it fails - // window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); - // } -// } - -fn map(val: f64, start1: f64, stop1: f64, start2: f64, stop2: f64) -> f64 { - start2 + (stop2 - start2) * ((val - start1) / (stop1 - start1)) -} - -fn fill(n: u32) -> u32 { - if FRACTAL_DEPTH == n { - return 0x00; - } else { - return n * 32 % 255; - } -} diff --git a/src/lib.rs b/src/lib.rs index 089d3e5..dee5912 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -273,7 +273,7 @@ impl EditorHandle { self.raw_handle } /// Attach your editor the handle. - pub fn attach_editor(&mut self, view: &mut V) { + 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. @@ -289,7 +289,7 @@ impl EditorHandle { } #[cfg(target_os = "macos")] - unsafe fn attach_editor_mac(&mut self, view: &mut V) { + 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; @@ -303,7 +303,7 @@ impl EditorHandle { } #[cfg(target_os = "windows")] - unsafe fn attach_editor_win(&mut self, view: &mut V) {} + unsafe fn attach_editor_win(&self, view: &mut V) {} } impl FromRawPtr for EditorHandle { 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; From 5e38745d3801b99bcfa4450acc490d3c403f99f6 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Sat, 30 May 2020 02:39:47 +0300 Subject: [PATCH 11/13] Make it cross-platform compatible --- examples/simple/simple.rs | 52 +++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/examples/simple/simple.rs b/examples/simple/simple.rs index bda5c84..22c9c2b 100644 --- a/examples/simple/simple.rs +++ b/examples/simple/simple.rs @@ -13,7 +13,7 @@ use cocoa::appkit::{NSBackingStoreType, NSColor, NSView, NSWindow, NSWindowStyle #[cfg(target_os = "macos")] use cocoa::base::{id, nil}; #[cfg(target_os = "macos")] -use cocoa::foundation::{NSPoint, NSRect, NSSize}; +use cocoa::foundation::NSSize; use log::{error, info, trace, LevelFilter}; use serde::{Deserialize, Serialize}; #[cfg(windows)] @@ -26,8 +26,8 @@ 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; @@ -135,24 +135,7 @@ impl Plugin for Simple { } if let host::Message::ShowEditor(Some(parent)) = message { - 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()); - } + self.show_editor(parent); } // if let host::Message::ShowEditor(None) = message { // if self.view.is_some() { @@ -344,6 +327,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)] From 0d6f1946eff9c08cec7a2f7c9ee2ee4f49a37355 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Sat, 30 May 2020 02:43:32 +0300 Subject: [PATCH 12/13] Fix editor handle conversion on windows --- src/cxx/wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cxx/wrapper.cpp b/src/cxx/wrapper.cpp index 20daec9..c60c032 100644 --- a/src/cxx/wrapper.cpp +++ b/src/cxx/wrapper.cpp @@ -91,7 +91,7 @@ intptr_t _stdcall PluginWrapper::Dispatcher(intptr_t id, intptr_t index, intptr_t value) { if (id == FPD_ShowEditor) { - EditorHandle = value; + EditorHandle = (HWND)value; } FlMessage message = {id, index, value}; From fb81662bf0e4e61e1fc3e08e149836cae1f4d0df Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Fri, 5 Jun 2020 01:21:27 +0300 Subject: [PATCH 13/13] Add editor GUI on macOS --- Cargo.toml | 8 +- examples/simple/gui.rs | 388 +++++++++++++++++++++++--------- examples/simple/gui/controls.rs | 63 ++++++ examples/simple/main.rs | 11 + examples/simple/simple.rs | 33 ++- src/plugin.rs | 4 +- 6 files changed, 396 insertions(+), 111 deletions(-) create mode 100644 examples/simple/gui/controls.rs create mode 100644 examples/simple/main.rs diff --git a/Cargo.toml b/Cargo.toml index 2e49fd0..e9c55af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,9 @@ cc = { version = "1.0", features = ["parallel"] } [dev-dependencies] bincode = "1.2" chrono = "0.4" -iced = { git = "https://github.com/hecrj/iced.git", features = ["canvas", "tokio", "debug"] } +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" @@ -36,3 +38,7 @@ simplelog = "0.7" name = "simple" path = "examples/simple/simple.rs" crate-type = ["cdylib"] + +[[example]] +name = "gui" +path = "examples/simple/main.rs" diff --git a/examples/simple/gui.rs b/examples/simple/gui.rs index 9c583c7..7580776 100644 --- a/examples/simple/gui.rs +++ b/examples/simple/gui.rs @@ -1,133 +1,319 @@ -use iced::{ - canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, time, Application, Color, Command, Container, Element, Length, - Point, Rectangle, Settings, Subscription, Vector, -}; - -pub fn main() { - Clock::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} +mod controls; -struct Clock { - now: chrono::DateTime, - clock: Cache, -} +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; -#[derive(Debug, Clone, Copy)] -enum Message { - Tick(chrono::DateTime), +pub struct Editor { + event_loop: EventLoop<()>, + event_handler: RefCell, } -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - type Flags = (); +impl Editor { + pub fn new() -> Self { + let event_loop = EventLoop::new(); + let event_handler = RefCell::new(EventHandler::new(&event_loop)); - fn new(_flags: ()) -> (Self, Command) { - ( - Clock { - now: chrono::Local::now(), - clock: Default::default(), - }, - Command::none(), - ) + Self { + event_loop, + event_handler, + } } - fn title(&self) -> String { - String::from("Clock - Iced") + #[cfg(target_os = "macos")] + pub fn raw_view(&self) -> *mut c_void { + self.event_handler.borrow().window.ns_view() } - fn update(&mut self, message: Message) -> Command { - match message { - Message::Tick(local_time) => { - let now = local_time; + #[cfg(windows)] + pub fn raw_view(&self) -> *mut c_void { + // TODO + std::ptr::null() as *mut c_void + } - if now != self.now { - self.now = now; - self.clock.clear(); - } - } - } + #[cfg(target_os = "linux")] + pub fn raw_view(&self) -> *mut c_void { + std::ptr::null() as *mut c_void + } - Command::none() + pub fn open(&mut self) { + let handler = self.event_handler.get_mut(); + handler.is_opened = true; } - fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)) - .map(|_| Message::Tick(chrono::Local::now())) + pub fn close(&mut self) { + let handler = self.event_handler.get_mut(); + handler.is_opened = false; } - fn view(&mut self) -> Element { - let canvas = Canvas::new(self) - .width(Length::Units(400)) - .height(Length::Units(400)); - - Container::new(canvas) - .width(Length::Fill) - .height(Length::Fill) - .padding(20) - .center_x() - .center_y() - .into() + 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)); } } -impl canvas::Program for Clock { - fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { - use chrono::Timelike; +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(), + ); - let clock = self.clock.draw(bounds.size(), |frame| { - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; + self.is_resized = true; + } + WindowEvent::CloseRequested => { + self.is_opened = false; + *control_flow = ControlFlow::Exit; + } - let background = Path::circle(center, radius); - frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); + _ => {} + } - let short_hand = - Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); + // 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, + ); - let long_hand = - Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); + // and request a redraw + self.window.request_redraw(); + } + Event::RedrawRequested(_) => { + if self.is_resized { + let size = self.window.inner_size(); - let thin_stroke = Stroke { - width: radius / 100.0, - color: Color::WHITE, - line_cap: LineCap::Round, - ..Stroke::default() - }; + 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 wide_stroke = Stroke { - width: thin_stroke.width * 3.0, - ..thin_stroke - }; + let frame = self.swap_chain.get_next_texture().expect("Next frame"); - frame.translate(Vector::new(center.x, center.y)); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.hour(), 12)); - frame.stroke(&short_hand, wide_stroke); - }); + 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, + }); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.minute(), 60)); - frame.stroke(&long_hand, wide_stroke); - }); + // 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(), + ); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.second(), 60)); - frame.stroke(&long_hand, thin_stroke); - }) - }); + // Then we submit the work + self.queue.submit(&[encoder.finish()]); - vec![clock] + // 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, + } } } -fn hand_rotation(n: u32, total: u32) -> f32 { - let turns = n as f32 / total as f32; - - 2.0 * std::f32::consts::PI * turns -} +// 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/simple.rs b/examples/simple/simple.rs index 22c9c2b..66234cf 100644 --- a/examples/simple/simple.rs +++ b/examples/simple/simple.rs @@ -1,4 +1,4 @@ -pub mod gui; +mod gui; use std::collections::HashMap; #[cfg(unix)] @@ -30,18 +30,18 @@ use fpsdk::{ NotesFlags, ProcessParamFlags, TimeFormat, ValuePtr, }; -// use gui; +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 {} @@ -73,6 +73,7 @@ impl Plugin for Simple { "Parameter 3".into(), ], state: Default::default(), + editor: Editor::new(), } } @@ -132,10 +133,22 @@ impl Plugin for Simple { if let host::Message::SetEnabled(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(Some(parent)) = message { - self.show_editor(parent); + 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() { @@ -171,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 @@ -469,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/plugin.rs b/src/plugin.rs index 2d50ad1..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 @@ -445,7 +444,6 @@ fn check_hresult(result: HRESULT, read: usize, error_msg: &str) -> io::Result); /// [`Plugin::info`](trait.Plugin.html#tymethod.info) FFI.