Skip to content

Commit fe546b2

Browse files
committed
Add clack example
1 parent a084fc1 commit fe546b2

6 files changed

Lines changed: 344 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [
9191
] }
9292

9393
[workspace]
94-
members = ["examples/open_parented", "examples/open_window", "examples/render_femtovg", "examples/render_wgpu"]
94+
members = ["examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"]
9595

9696
[lints.clippy]
9797
missing-safety-doc = "allow"

examples/plugin_clack/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "plugin_clack"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
clack-plugin = "0.1.0"
11+
clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin", "raw-window-handle_06"] }
12+
baseview = { path = "../..", features = ["opengl"] }
13+
softbuffer = "0.4.8"
14+
raw-window-handle = "0.6.2"

examples/plugin_clack/src/audio.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use crate::ExamplePluginMainThread;
2+
use clack_plugin::prelude::*;
3+
4+
pub struct ExamplePluginAudioProcessor;
5+
6+
impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread> for ExamplePluginAudioProcessor {
7+
fn activate(
8+
_host: HostAudioProcessorHandle<'a>, _main_thread: &mut ExamplePluginMainThread,
9+
_shared: &'a (), _audio_config: PluginAudioConfiguration,
10+
) -> Result<Self, PluginError> {
11+
Ok(Self)
12+
}
13+
14+
fn process(
15+
&mut self, _process: Process, mut audio: Audio, _events: Events,
16+
) -> Result<ProcessStatus, PluginError> {
17+
for mut port in audio.port_pairs() {
18+
let channels = port.channels()?.into_f32().expect("Expected f32 channels");
19+
20+
for channel_pair in channels {
21+
match channel_pair {
22+
ChannelPair::OutputOnly(o) => o.fill(0.0),
23+
ChannelPair::InputOutput(i, o) => o.copy_from_slice(i),
24+
_ => {}
25+
}
26+
}
27+
}
28+
29+
Ok(ProcessStatus::Continue)
30+
}
31+
}

examples/plugin_clack/src/gui.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::ExamplePluginMainThread;
2+
use crate::window_handler::OpenWindowExample;
3+
use baseview::dpi::PhysicalSize;
4+
use baseview::gl::GlConfig;
5+
use baseview::{Window, WindowHandle, WindowOpenOptions};
6+
use clack_extensions::gui::{
7+
GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, Window as ClapWindow,
8+
};
9+
use clack_plugin::plugin::PluginError;
10+
11+
#[allow(deprecated)]
12+
use raw_window_handle::HasRawWindowHandle;
13+
14+
pub struct ExamplePluginGui {
15+
handle: WindowHandle,
16+
}
17+
18+
impl PluginGuiImpl for ExamplePluginMainThread {
19+
fn is_api_supported(&mut self, configuration: GuiConfiguration) -> bool {
20+
!configuration.is_floating
21+
&& Some(configuration.api_type) == GuiApiType::default_for_current_platform()
22+
}
23+
24+
fn get_preferred_api(&mut self) -> Option<GuiConfiguration<'_>> {
25+
Some(GuiConfiguration {
26+
api_type: GuiApiType::default_for_current_platform()?,
27+
is_floating: false,
28+
})
29+
}
30+
31+
fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> {
32+
// Delay creation until set_parent
33+
Ok(())
34+
}
35+
36+
fn destroy(&mut self) {
37+
let Some(gui) = self.gui.take() else { return };
38+
39+
gui.handle.close()
40+
}
41+
42+
fn set_scale(&mut self, _scale: f64) -> Result<(), PluginError> {
43+
// Unsupported
44+
Ok(())
45+
}
46+
47+
fn get_size(&mut self) -> Option<GuiSize> {
48+
// Unsupported
49+
Some(GuiSize { width: 400, height: 200 })
50+
}
51+
52+
fn can_resize(&mut self) -> bool {
53+
false // Non-resizeable windows not supported yet
54+
}
55+
56+
fn get_resize_hints(&mut self) -> Option<GuiResizeHints> {
57+
None // Not supported yet
58+
}
59+
60+
fn adjust_size(&mut self, _size: GuiSize) -> Option<GuiSize> {
61+
None // Not supported yet
62+
}
63+
64+
fn set_size(&mut self, _size: GuiSize) -> Result<(), PluginError> {
65+
Ok(()) // Not supported yet
66+
}
67+
68+
#[allow(deprecated)]
69+
fn set_parent(&mut self, window: ClapWindow) -> Result<(), PluginError> {
70+
let parent = window.raw_window_handle()?;
71+
let parent = unsafe { raw_window_handle::WindowHandle::borrow_raw(parent) };
72+
73+
let options = WindowOpenOptions::new()
74+
.with_size(PhysicalSize::new(400, 200))
75+
.with_gl_config(GlConfig::default());
76+
77+
let window = Window::open_parented(&parent, options, OpenWindowExample::new);
78+
79+
self.gui = Some(ExamplePluginGui { handle: window });
80+
81+
Ok(())
82+
}
83+
84+
fn set_transient(&mut self, _window: ClapWindow) -> Result<(), PluginError> {
85+
unimplemented!() // Not supported yet
86+
}
87+
88+
fn suggest_title(&mut self, _title: &str) {
89+
// Not supported yet
90+
}
91+
92+
fn show(&mut self) -> Result<(), PluginError> {
93+
Ok(()) // Not supported yet
94+
}
95+
96+
fn hide(&mut self) -> Result<(), PluginError> {
97+
Ok(()) // Not supported yet
98+
}
99+
}

examples/plugin_clack/src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::audio::ExamplePluginAudioProcessor;
2+
use crate::gui::ExamplePluginGui;
3+
use clack_extensions::gui::PluginGui;
4+
use clack_plugin::prelude::*;
5+
6+
mod audio;
7+
mod gui;
8+
mod window_handler;
9+
10+
/// The type that represents our plugin in Clack.
11+
///
12+
/// This is what implements the [`Plugin`] trait, where all the other subtypes are attached.
13+
pub struct ExamplePlugin;
14+
15+
impl Plugin for ExamplePlugin {
16+
type AudioProcessor<'a> = ExamplePluginAudioProcessor;
17+
type Shared<'a> = ();
18+
type MainThread<'a> = ExamplePluginMainThread;
19+
20+
fn declare_extensions(builder: &mut PluginExtensions<Self>, _shared: Option<&()>) {
21+
builder.register::<PluginGui>();
22+
}
23+
}
24+
25+
impl DefaultPluginFactory for ExamplePlugin {
26+
fn get_descriptor() -> PluginDescriptor {
27+
use clack_plugin::plugin::features::*;
28+
29+
PluginDescriptor::new("org.rust-audio.clack.gain-egui", "Clack Gain EGUI Example")
30+
.with_features([AUDIO_EFFECT, STEREO])
31+
}
32+
33+
fn new_shared(_host: HostSharedHandle<'_>) -> Result<Self::Shared<'_>, PluginError> {
34+
Ok(())
35+
}
36+
37+
fn new_main_thread<'a>(
38+
_host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>,
39+
) -> Result<Self::MainThread<'a>, PluginError> {
40+
Ok(Self::MainThread { gui: None })
41+
}
42+
}
43+
44+
/// The data that belongs to the main thread of our plugin.
45+
pub struct ExamplePluginMainThread {
46+
/// The plugin's GUI state and context
47+
gui: Option<ExamplePluginGui>,
48+
}
49+
50+
impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread {
51+
fn on_main_thread(&mut self) {}
52+
}
53+
54+
clack_export_entry!(SinglePluginEntry<ExamplePlugin>);
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use baseview::dpi::PhysicalPosition;
2+
use baseview::{Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowSize};
3+
use std::cell::{Cell, RefCell};
4+
use std::num::NonZeroU32;
5+
6+
pub struct OpenWindowExample {
7+
window_context: WindowContext,
8+
9+
surface: RefCell<softbuffer::Surface<WindowContext, WindowContext>>,
10+
mouse_pos: Cell<PhysicalPosition<f64>>,
11+
is_cursor_inside: Cell<bool>,
12+
damaged: Cell<bool>,
13+
}
14+
15+
impl WindowHandler for OpenWindowExample {
16+
fn resized(&self, new_size: WindowSize) {
17+
println!("Resized: {new_size:?}");
18+
19+
if let (Some(width), Some(height)) =
20+
(NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height))
21+
{
22+
self.surface.borrow_mut().resize(width, height).unwrap();
23+
self.damaged.set(true);
24+
}
25+
}
26+
27+
fn on_frame(&self) {
28+
if !self.damaged.get() {
29+
return;
30+
}
31+
32+
let mut surface = self.surface.borrow_mut();
33+
let mut pixels = surface.buffer_mut().unwrap();
34+
let size = self.window_context.size();
35+
let scale_factor = self.window_context.scale_factor();
36+
let (width, height) = (size.physical.width, size.physical.height);
37+
38+
for index in 0..(width * height) {
39+
let x = index % width;
40+
let y = index / width;
41+
42+
let red = ((x as f32 / width as f32) * 255.0) as u32;
43+
let green = ((y as f32 / height as f32) * 255.0) as u32;
44+
let blue = (((x * y) as f64 / scale_factor) as u32) % 255;
45+
46+
pixels[index as usize] = blue | (green << 8) | (red << 16) | 0xFF000000;
47+
}
48+
49+
for x in 0..width {
50+
// Green line on top
51+
let y = 0;
52+
let index = y * width + x;
53+
pixels[index as usize] = if (x % 10) < 5 { 0xFF00FF00 } else { 0xFF000000 };
54+
55+
// Magenta line on bottom
56+
let y = height - 1;
57+
let index = y * width + x;
58+
pixels[index as usize] = if (x % 10) < 5 { 0xFFFF00FF } else { 0xFF000000 };
59+
}
60+
61+
for y in 0..height {
62+
// Green line on right
63+
let x = width - 1;
64+
let index = y * width + x;
65+
pixels[index as usize] = if (y % 10) < 5 { 0xFF00FF00 } else { 0xFF000000 };
66+
67+
// Magenta line on left
68+
let x = 0;
69+
let index = y * width + x;
70+
pixels[index as usize] = if (y % 10) < 5 { 0xFFFF00FF } else { 0xFF000000 };
71+
}
72+
73+
if self.is_cursor_inside.get() {
74+
let rect_size = (25.0 * scale_factor) as i32;
75+
let mouse_pos = self.mouse_pos.get().cast::<i32>();
76+
77+
let rect_x_start = (mouse_pos.x - rect_size).clamp(0, width as i32) as u32;
78+
let rect_x_end = (mouse_pos.x + rect_size).clamp(0, width as i32) as u32;
79+
let rect_y_start = (mouse_pos.y - rect_size).clamp(0, height as i32) as u32;
80+
let rect_y_end = (mouse_pos.y + rect_size).clamp(0, height as i32) as u32;
81+
82+
for x in rect_x_start..rect_x_end {
83+
for y in rect_y_start..rect_y_end {
84+
let index = y * width + x;
85+
pixels[index as usize] = 0xFF00FF00;
86+
}
87+
}
88+
}
89+
90+
pixels.present().unwrap();
91+
self.damaged.set(false);
92+
}
93+
94+
fn on_event(&self, event: Event) -> EventStatus {
95+
match event {
96+
#[cfg(target_os = "macos")]
97+
Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"),
98+
Event::Mouse(MouseEvent::CursorMoved { position, .. }) => {
99+
self.mouse_pos.set(position);
100+
self.damaged.set(true);
101+
}
102+
Event::Mouse(MouseEvent::CursorEntered) => {
103+
self.is_cursor_inside.set(true);
104+
self.damaged.set(true);
105+
}
106+
Event::Mouse(MouseEvent::CursorLeft) => {
107+
self.is_cursor_inside.set(false);
108+
self.damaged.set(true);
109+
}
110+
_ => {}
111+
}
112+
113+
log_event(&event);
114+
115+
EventStatus::Captured
116+
}
117+
}
118+
119+
impl OpenWindowExample {
120+
pub fn new(window: WindowContext) -> Self {
121+
let ctx = softbuffer::Context::new(window.clone()).unwrap();
122+
let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap();
123+
let size = window.size().physical;
124+
surface
125+
.resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap())
126+
.unwrap();
127+
128+
OpenWindowExample {
129+
window_context: window,
130+
surface: surface.into(),
131+
mouse_pos: PhysicalPosition::new(0., 0.).into(),
132+
is_cursor_inside: false.into(),
133+
damaged: true.into(),
134+
}
135+
}
136+
}
137+
138+
fn log_event(event: &Event) {
139+
match event {
140+
Event::Mouse(e) => println!("Mouse event: {:?}", e),
141+
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
142+
Event::Window(e) => println!("Window event: {:?}", e),
143+
_ => {}
144+
}
145+
}

0 commit comments

Comments
 (0)