Skip to content

Commit 0de33ba

Browse files
authored
Add clack example (#293)
1 parent 26fdf4c commit 0de33ba

6 files changed

Lines changed: 342 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 = "2021"
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::window_handler::OpenWindowExample;
2+
use crate::ExamplePluginMainThread;
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: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
Event::Mouse(MouseEvent::CursorMoved { position, .. }) => {
97+
self.mouse_pos.set(position);
98+
self.damaged.set(true);
99+
}
100+
Event::Mouse(MouseEvent::CursorEntered) => {
101+
self.is_cursor_inside.set(true);
102+
self.damaged.set(true);
103+
}
104+
Event::Mouse(MouseEvent::CursorLeft) => {
105+
self.is_cursor_inside.set(false);
106+
self.damaged.set(true);
107+
}
108+
_ => {}
109+
}
110+
111+
log_event(&event);
112+
113+
EventStatus::Captured
114+
}
115+
}
116+
117+
impl OpenWindowExample {
118+
pub fn new(window: WindowContext) -> Self {
119+
let ctx = softbuffer::Context::new(window.clone()).unwrap();
120+
let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap();
121+
let size = window.size().physical;
122+
surface
123+
.resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap())
124+
.unwrap();
125+
126+
OpenWindowExample {
127+
window_context: window,
128+
surface: surface.into(),
129+
mouse_pos: PhysicalPosition::new(0., 0.).into(),
130+
is_cursor_inside: false.into(),
131+
damaged: true.into(),
132+
}
133+
}
134+
}
135+
136+
fn log_event(event: &Event) {
137+
match event {
138+
Event::Mouse(e) => println!("Mouse event: {:?}", e),
139+
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
140+
Event::Window(e) => println!("Window event: {:?}", e),
141+
_ => {}
142+
}
143+
}

0 commit comments

Comments
 (0)