Skip to content
Open
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [
] }

[workspace]
members = ["examples/cursors", "examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"]
members = ["examples/cursors", "examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu", "examples/plugin_clack_femtovg"]

[lints.clippy]
missing-safety-doc = "allow"
Expand Down
4 changes: 3 additions & 1 deletion examples/plugin_clack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ crate-type = ["cdylib"]
[dependencies]
clack-plugin = "0.1.1"
clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] }
baseview = { path = "../..", features = ["opengl"] }
baseview = { path = "../..", features = ["tracing"] }
softbuffer = "0.4.8"

tracing-subscriber = { workspace = true }
tracing = "0.1.44"
11 changes: 6 additions & 5 deletions examples/plugin_clack/src/gui.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::window_handler::OpenWindowExample;
use crate::ExamplePluginMainThread;
use baseview::dpi::*;
use baseview::gl::GlConfig;
use baseview::host::{Host, HostCallbacks, HostMainThreadCaller};
use baseview::{HandlerError, Window, WindowSettings, WindowSize};
use clack_extensions::gui::{
Expand All @@ -10,6 +9,7 @@ use clack_extensions::gui::{
};
use clack_plugin::plugin::PluginError;
use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle};
use tracing::Level;

pub struct ExamplePluginGui {
pub handle: Window,
Expand All @@ -29,10 +29,11 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> {
}

fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> {
let options = WindowSettings::new()
.wait_for_parent()
.with_size(PhysicalSize::new(400, 200))
.with_gl_config(GlConfig::default());
let _ =
tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).with_ansi(false).try_init();

let options =
WindowSettings::new().wait_for_parent().with_size(PhysicalSize::new(400, 200));

let mut host = Host::new().with_main_thread(unsafe {
MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() }
Expand Down
17 changes: 17 additions & 0 deletions examples/plugin_clack_femtovg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "plugin_clack_femtovg"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
clack-plugin = "0.1.1"
clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] }
baseview = { path = "../..", features = ["opengl", "tracing"] }
femtovg = "0.26"
raw-window-handle = { version = "0.6.2", features = ["std"] }

tracing-subscriber = { workspace = true }
tracing = "0.1.44"
31 changes: 31 additions & 0 deletions examples/plugin_clack_femtovg/src/audio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::ExamplePluginMainThread;
use clack_plugin::prelude::*;

pub struct ExamplePluginAudioProcessor;

impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread<'a>> for ExamplePluginAudioProcessor {
fn activate(
_host: HostAudioProcessorHandle<'a>, _main_thread: &mut ExamplePluginMainThread,
_shared: &'a (), _audio_config: PluginAudioConfiguration,
) -> Result<Self, PluginError> {
Ok(Self)
}

fn process(
&mut self, _process: Process, mut audio: Audio, _events: Events,
) -> Result<ProcessStatus, PluginError> {
for mut port in audio.port_pairs() {
let channels = port.channels()?.into_f32().expect("Expected f32 channels");

for channel_pair in channels {
match channel_pair {
ChannelPair::OutputOnly(o) => o.fill(0.0),
ChannelPair::InputOutput(i, o) => o.copy_from_slice(i),
_ => {}
}
}
}

Ok(ProcessStatus::Continue)
}
}
195 changes: 195 additions & 0 deletions examples/plugin_clack_femtovg/src/gui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use crate::window_handler::FemtovgExample;
use crate::ExamplePluginMainThread;
use baseview::dpi::*;
use baseview::gl::GlConfig;
use baseview::host::{Host, HostCallbacks, HostMainThreadCaller};
use baseview::{HandlerError, Window, WindowSettings, WindowSize};
use clack_extensions::gui::{
AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui,
PluginGuiImpl, Window as ClapWindow,
};
use clack_plugin::plugin::PluginError;
use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle};
use tracing::Level;

pub struct ExamplePluginGui {
pub handle: Window,
}

impl PluginGuiImpl for ExamplePluginMainThread<'_> {
fn is_api_supported(&mut self, configuration: GuiConfiguration) -> bool {
!configuration.is_floating
&& Some(configuration.api_type) == GuiApiType::default_for_current_platform()
}

fn get_preferred_api(&mut self) -> Option<GuiConfiguration<'_>> {
Some(GuiConfiguration {
api_type: GuiApiType::default_for_current_platform()?,
is_floating: false,
})
}

fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> {
let _ =
tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).with_ansi(false).try_init();

let options = WindowSettings::new()
.wait_for_parent()
.with_size(PhysicalSize::new(400, 200))
.with_gl_config(GlConfig::default());

let mut host = Host::new().with_main_thread(unsafe {
MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() }
});

if let Some(gui) = self.host_gui {
host = host.with_callbacks(unsafe {
HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() }
});
}

let window = Window::create_with_host(options, FemtovgExample::new, host)?;

self.gui = Some(ExamplePluginGui { handle: window });
Ok(())
}

fn destroy(&mut self) {
let Some(gui) = self.gui.take() else { return };

gui.handle.close()
}

fn set_scale(&mut self, scale: f64) -> Result<(), PluginError> {
let Some(gui) = &self.gui else {
return Err(PluginError::Message("set_scale called without a GUI active"));
};
gui.handle.suggest_fallback_scale_factor(scale)?;

Ok(())
}

fn get_size(&mut self) -> Option<GuiSize> {
let Some(gui) = &self.gui else {
eprintln!("get_size called without a GUI active");
return None;
};

let size = gui.handle.size().to_native_size();

Some(GuiSize { width: size.width, height: size.height })
}

fn can_resize(&mut self) -> bool {
let Some(gui) = &self.gui else { return false };

gui.handle.is_resizable()
}

fn get_resize_hints(&mut self) -> Option<GuiResizeHints> {
let can_resize = self.can_resize();

Some(GuiResizeHints {
strategy: AspectRatioStrategy::Disregard, // Not supported

can_resize_vertically: can_resize,
can_resize_horizontally: can_resize,
})
}

fn adjust_size(&mut self, mut size: GuiSize) -> Option<GuiSize> {
let Some(gui) = &self.gui else { return None };
let scale_factor = gui.handle.size().scale_factor;

if let Some(max_size) = gui.handle.max_size() {
let max_size = NativeSize::from_size(max_size, scale_factor);
size.width = size.width.min(max_size.width);
size.height = size.height.min(max_size.height);
}

if let Some(min_size) = gui.handle.min_size() {
let min_size = NativeSize::from_size(min_size, scale_factor);
size.width = size.width.max(min_size.width);
size.height = size.height.max(min_size.height);
}

Some(size)
}

fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> {
let Some(gui) = &self.gui else {
return Err(PluginError::Message("set_size called without a GUI active"));
};

gui.handle.resize(NativeSize { width: size.width, height: size.height })?;

Ok(())
}

fn set_parent(&mut self, window: ClapWindow) -> Result<(), PluginError> {
let Some(gui) = &self.gui else {
return Err(PluginError::Message("set_parent called without a GUI active"));
};

// SAFETY: The CLAP spec ensures the parent window handle is valid for at least this call
let parent = unsafe { window.borrow_handle_unchecked()? };

gui.handle.set_parent(&parent)?;
gui.handle.show()?;

Ok(())
}

fn set_transient(&mut self, _window: ClapWindow) -> Result<(), PluginError> {
unimplemented!() // Not supported yet
}

fn suggest_title(&mut self, _title: &str) {
// Not supported yet
}

fn show(&mut self) -> Result<(), PluginError> {
let Some(gui) = &self.gui else {
return Err(PluginError::Message("show called without a GUI active"));
};
gui.handle.show()?;

Ok(())
}

fn hide(&mut self) -> Result<(), PluginError> {
let Some(gui) = &self.gui else {
return Err(PluginError::Message("hide called without a GUI active"));
};
gui.handle.show()?;

Ok(())
}
}

struct MainThreadHandler {
host: HostSharedHandle<'static>,
}

impl HostMainThreadCaller for MainThreadHandler {
fn call_main_thread(&mut self) {
self.host.request_callback();
}
}

struct HostGuiCallbacks {
ext: HostGui,
host: HostMainThreadHandle<'static>,
}

impl HostCallbacks for HostGuiCallbacks {
fn request_resize(&mut self, new_size: WindowSize) -> Result<(), HandlerError> {
let new_size = new_size.to_native_size();
self.ext.request_resize(&self.host, new_size.width, new_size.height)?;
Ok(())
}

fn destroyed(&mut self) {
self.ext.closed(&self.host, true);
}
}
77 changes: 77 additions & 0 deletions examples/plugin_clack_femtovg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::audio::ExamplePluginAudioProcessor;
use crate::gui::ExamplePluginGui;
use clack_extensions::gui::{HostGui, PluginGui};
use clack_extensions::state::{PluginState, PluginStateImpl};
use clack_plugin::prelude::*;
use clack_plugin::stream::{InputStream, OutputStream};

mod audio;
mod gui;
mod window_handler;

/// The type that represents our plugin in Clack.
///
/// This is what implements the [`Plugin`] trait, where all the other subtypes are attached.
pub struct ExamplePlugin;

impl Plugin for ExamplePlugin {
type AudioProcessor<'a> = ExamplePluginAudioProcessor;
type Shared<'a> = ();
type MainThread<'a> = ExamplePluginMainThread<'a>;

fn declare_extensions(builder: &mut PluginExtensions<Self>, _shared: Option<&()>) {
builder.register::<PluginGui>().register::<PluginState>();
}
}

impl DefaultPluginFactory for ExamplePlugin {
fn get_descriptor() -> PluginDescriptor {
use clack_plugin::plugin::features::*;

PluginDescriptor::new(
"org.rust-audio.clack.gain-baseview-femtovg",
"Clack Gain Baseview Femtovg Example",
)
.with_features([AUDIO_EFFECT, STEREO])
}

fn new_shared(_host: HostSharedHandle<'_>) -> Result<Self::Shared<'_>, PluginError> {
Ok(())
}

fn new_main_thread<'a>(
host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>,
) -> Result<Self::MainThread<'a>, PluginError> {
Ok(Self::MainThread { gui: None, host_gui: host.get_extension(), host })
}
}

/// The data that belongs to the main thread of our plugin.
pub struct ExamplePluginMainThread<'a> {
/// The host handle
host: HostMainThreadHandle<'a>,
// The host GUI extension handle
host_gui: Option<HostGui>,
/// The plugin's GUI state and context
gui: Option<ExamplePluginGui>,
}

impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> {
fn on_main_thread(&mut self) {
if let Some(gui) = self.gui.as_mut() {
gui.handle.host_main_thread_callback();
}
}
}

impl PluginStateImpl for ExamplePluginMainThread<'_> {
fn save(&mut self, _output: &mut OutputStream) -> Result<(), PluginError> {
Ok(())
}

fn load(&mut self, _input: &mut InputStream) -> Result<(), PluginError> {
Ok(())
}
}

clack_export_entry!(SinglePluginEntry<ExamplePlugin>);
Loading