From dc0626d304f1754fb69a9771e1cfa0c748de1edd Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 28 Jun 2026 02:09:31 +0200 Subject: [PATCH 01/10] wip --- Cargo.toml | 2 +- examples/plugin_clack/Cargo.toml | 10 + examples/plugin_clack/src/audio.rs | 31 ++++ examples/plugin_clack/src/gui.rs | 109 +++++++++++ examples/plugin_clack/src/lib.rs | 54 ++++++ examples/plugin_clack/src/window_handler.rs | 196 ++++++++++++++++++++ src/host.rs | 6 + src/lib.rs | 4 + src/window.rs | 34 +++- src/window_builder.rs | 37 ++++ 10 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 examples/plugin_clack/Cargo.toml create mode 100644 examples/plugin_clack/src/audio.rs create mode 100644 examples/plugin_clack/src/gui.rs create mode 100644 examples/plugin_clack/src/lib.rs create mode 100644 examples/plugin_clack/src/window_handler.rs create mode 100644 src/host.rs create mode 100644 src/window_builder.rs diff --git a/Cargo.toml b/Cargo.toml index 257eb834..aa603595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ ] } [workspace] -members = ["examples/open_parented", "examples/open_window", "examples/render_femtovg", "examples/render_wgpu"] +members = ["examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"] [lints.clippy] missing-safety-doc = "allow" diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml new file mode 100644 index 00000000..32c7e0a5 --- /dev/null +++ b/examples/plugin_clack/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "plugin_clack" +version = "0.1.0" +edition = "2024" + +[dependencies] +clack-plugin = "0.1.0" +clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin"] } +baseview = { path = "../.." } +softbuffer = "0.4.8" diff --git a/examples/plugin_clack/src/audio.rs b/examples/plugin_clack/src/audio.rs new file mode 100644 index 00000000..8d5b9bf5 --- /dev/null +++ b/examples/plugin_clack/src/audio.rs @@ -0,0 +1,31 @@ +use crate::ExamplePluginMainThread; +use clack_plugin::prelude::*; + +pub struct ExamplePluginAudioProcessor; + +impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread> for ExamplePluginAudioProcessor { + fn activate( + _host: HostAudioProcessorHandle<'a>, _main_thread: &mut ExamplePluginMainThread, + _shared: &'a (), _audio_config: PluginAudioConfiguration, + ) -> Result { + Ok(Self) + } + + fn process( + &mut self, _process: Process, mut audio: Audio, _events: Events, + ) -> Result { + 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) + } +} diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs new file mode 100644 index 00000000..e08faab1 --- /dev/null +++ b/examples/plugin_clack/src/gui.rs @@ -0,0 +1,109 @@ +use crate::ExamplePluginMainThread; +use crate::window_handler::OpenWindowExample; +use baseview::{WindowBuilder, WindowHandle}; +use clack_extensions::gui::{ + AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, + Window, +}; +use clack_plugin::plugin::PluginError; + +pub struct ExamplePluginGui { + handle: WindowHandle, +} + +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> { + Some(GuiConfiguration { + api_type: GuiApiType::default_for_current_platform()?, + is_floating: false, + }) + } + + fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { + let handle = WindowBuilder::new().build(OpenWindowExample::new); // TODO: handle errors + + self.gui = Some(ExamplePluginGui { handle }); + + 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("Invalid GUI call: GUI is not created")); + }; + + gui.handle.suggest_fallback_scale(Some(scale)); + Ok(()) + } + + fn get_size(&mut self) -> Option { + let Some(gui) = &self.gui else { + return None; + }; + + let uses_logical = + matches!(GuiApiType::default_for_current_platform(), Some(a) if a.uses_logical_size()); + + let size = gui.handle.size(); + + if uses_logical { + let size = size.logical.cast(); + Some(GuiSize { width: size.width, height: size.height }) + } else { + Some(GuiSize { width: size.physical.width, height: size.physical.height }) + } + } + + fn can_resize(&mut self) -> bool { + true // Non-resizeable windows not supported yet + } + + fn get_resize_hints(&mut self) -> Option { + Some(GuiResizeHints { + can_resize_horizontally: true, + can_resize_vertically: true, + strategy: AspectRatioStrategy::Disregard, + }) + } + + fn adjust_size(&mut self, size: GuiSize) -> Option { + Some(size) // Not supported yet + } + + fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { + todo!() + } + + fn set_parent(&mut self, window: Window) -> Result<(), PluginError> { + todo!() + } + + fn set_transient(&mut self, window: Window) -> Result<(), PluginError> { + todo!() // Not supported yet + } + + fn suggest_title(&mut self, title: &str) { + if let Some(gui) = &self.gui { + gui.handle.set_title(title); + } + } + + fn show(&mut self) -> Result<(), PluginError> { + todo!() + } + + fn hide(&mut self) -> Result<(), PluginError> { + todo!() + } +} diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs new file mode 100644 index 00000000..31893f06 --- /dev/null +++ b/examples/plugin_clack/src/lib.rs @@ -0,0 +1,54 @@ +use crate::audio::ExamplePluginAudioProcessor; +use crate::gui::ExamplePluginGui; +use clack_extensions::gui::PluginGui; +use clack_plugin::prelude::*; + +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; + + fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { + builder.register::(); + } +} + +impl DefaultPluginFactory for ExamplePlugin { + fn get_descriptor() -> PluginDescriptor { + use clack_plugin::plugin::features::*; + + PluginDescriptor::new("org.rust-audio.clack.gain-egui", "Clack Gain EGUI Example") + .with_features([AUDIO_EFFECT, STEREO]) + } + + fn new_shared(_host: HostSharedHandle<'_>) -> Result, PluginError> { + Ok(()) + } + + fn new_main_thread<'a>( + _host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>, + ) -> Result, PluginError> { + Ok(Self::MainThread { gui: None }) + } +} + +/// The data that belongs to the main thread of our plugin. +pub struct ExamplePluginMainThread { + /// The plugin's GUI state and context + gui: Option, +} + +impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread { + fn on_main_thread(&mut self) {} +} + +clack_export_entry!(SinglePluginEntry); diff --git a/examples/plugin_clack/src/window_handler.rs b/examples/plugin_clack/src/window_handler.rs new file mode 100644 index 00000000..d0f446bb --- /dev/null +++ b/examples/plugin_clack/src/window_handler.rs @@ -0,0 +1,196 @@ +use baseview::dpi::PhysicalPosition; +use baseview::{ + Event, EventStatus, HostHandler, MouseEvent, WindowContext, WindowHandler, WindowSize, + copy_to_clipboard, +}; +use clack_extensions::gui::{GuiApiType, GuiSize, HostGui}; +use clack_plugin::prelude::HostMainThreadHandle; +use std::cell::{Cell, RefCell}; +use std::ffi::c_void; +use std::num::NonZeroU32; +use std::ptr::NonNull; +use std::rc::Rc; + +pub struct OpenWindowExample { + window_context: WindowContext, + + surface: RefCell>, + mouse_pos: Cell>, + is_cursor_inside: Cell, + damaged: Cell, +} + +impl WindowHandler for OpenWindowExample { + fn resized(&self, new_size: WindowSize) { + println!("Resized: {new_size:?}"); + + if let (Some(width), Some(height)) = + (NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height)) + { + self.surface.borrow_mut().resize(width, height).unwrap(); + self.damaged.set(true); + } + } + + fn on_frame(&self) { + if !self.damaged.get() { + return; + } + + let mut surface = self.surface.borrow_mut(); + let mut pixels = surface.buffer_mut().unwrap(); + let size = self.window_context.size(); + let scale_factor = self.window_context.scale_factor(); + let (width, height) = (size.physical.width, size.physical.height); + + for index in 0..(width * height) { + let x = index % width; + let y = index / width; + + let red = ((x as f32 / width as f32) * 255.0) as u32; + let green = ((y as f32 / height as f32) * 255.0) as u32; + let blue = (((x * y) as f64 / scale_factor) as u32) % 255; + + pixels[index as usize] = blue | (green << 8) | (red << 16) | 0xFF000000; + } + + for x in 0..width { + // Green line on top + let y = 0; + let index = y * width + x; + pixels[index as usize] = if (x % 10) < 5 { 0xFF00FF00 } else { 0xFF000000 }; + + // Magenta line on bottom + let y = height - 1; + let index = y * width + x; + pixels[index as usize] = if (x % 10) < 5 { 0xFFFF00FF } else { 0xFF000000 }; + } + + for y in 0..height { + // Green line on right + let x = width - 1; + let index = y * width + x; + pixels[index as usize] = if (y % 10) < 5 { 0xFF00FF00 } else { 0xFF000000 }; + + // Magenta line on left + let x = 0; + let index = y * width + x; + pixels[index as usize] = if (y % 10) < 5 { 0xFFFF00FF } else { 0xFF000000 }; + } + + if self.is_cursor_inside.get() { + let rect_size = (25.0 * scale_factor) as i32; + let mouse_pos = self.mouse_pos.get().cast::(); + + let rect_x_start = (mouse_pos.x - rect_size).clamp(0, width as i32) as u32; + let rect_x_end = (mouse_pos.x + rect_size).clamp(0, width as i32) as u32; + let rect_y_start = (mouse_pos.y - rect_size).clamp(0, height as i32) as u32; + let rect_y_end = (mouse_pos.y + rect_size).clamp(0, height as i32) as u32; + + for x in rect_x_start..rect_x_end { + for y in rect_y_start..rect_y_end { + let index = y * width + x; + pixels[index as usize] = 0xFF00FF00; + } + } + } + + pixels.present().unwrap(); + self.damaged.set(false); + } + + fn on_event(&self, event: Event) -> EventStatus { + match event { + #[cfg(target_os = "macos")] + Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"), + Event::Mouse(MouseEvent::CursorMoved { position, .. }) => { + self.mouse_pos.set(position); + self.damaged.set(true); + } + Event::Mouse(MouseEvent::CursorEntered) => { + self.is_cursor_inside.set(true); + self.damaged.set(true); + } + Event::Mouse(MouseEvent::CursorLeft) => { + self.is_cursor_inside.set(false); + self.damaged.set(true); + } + _ => {} + } + + log_event(&event); + + EventStatus::Captured + } +} + +impl OpenWindowExample { + pub fn new(window: WindowContext) -> Self { + let ctx = softbuffer::Context::new(window.clone()).unwrap(); + let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); + let size = window.size().physical; + surface + .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) + .unwrap(); + + OpenWindowExample { + window_context: window, + surface: surface.into(), + mouse_pos: PhysicalPosition::new(0., 0.).into(), + is_cursor_inside: false.into(), + damaged: true.into(), + } + } +} + +fn log_event(event: &Event) { + match event { + Event::Mouse(e) => println!("Mouse event: {:?}", e), + Event::Keyboard(e) => println!("Keyboard event: {:?}", e), + Event::Window(e) => println!("Window event: {:?}", e), + _ => {} + } +} + +pub struct ExampleHostHandler { + host: NonNull, + host_gui: HostGui, + destroyed: Rc>, +} + +impl ExampleHostHandler { + unsafe fn host_handle(&self) -> Option> { + if self.destroyed.get() { + return None; + } + + unsafe { Some(HostMainThreadHandle::from_raw(self.host.cast())) } + } + + fn platform_uses_logical_pixels() -> bool { + let Some(api_type) = GuiApiType::default_for_current_platform() else { + return false; + }; + + api_type.uses_logical_size() + } +} + +impl HostHandler for ExampleHostHandler { + fn request_resize(&mut self, size: WindowSize) { + let Some(host) = (unsafe { self.host_handle() }) else { return }; + + let (width, height) = if Self::platform_uses_logical_pixels() { + let size = size.logical.cast(); + (size.width, size.height) + } else { + (size.physical.width, size.physical.height) + }; + + self.host_gui.request_resize(&host, width, height); // TODO: handle error + } + + fn destroyed(&mut self) { + todo!() + } +} diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 00000000..b9286c7a --- /dev/null +++ b/src/host.rs @@ -0,0 +1,6 @@ +use crate::WindowSize; + +pub trait HostHandler: 'static { + fn request_resize(&mut self, size: WindowSize); + fn destroyed(&mut self); +} diff --git a/src/lib.rs b/src/lib.rs index e611d188..b852b2c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,11 @@ mod clipboard; mod context; mod event; mod handler; +mod host; mod keyboard; mod mouse_cursor; mod window; +mod window_builder; mod window_open_options; pub(crate) mod platform; @@ -17,8 +19,10 @@ pub use context::{PlatformHandle, WindowContext}; pub use dpi; pub use event::*; pub use handler::WindowHandler; +pub use host::HostHandler; pub use mouse_cursor::MouseCursor; pub use window::*; +pub use window_builder::*; pub use window_open_options::*; pub(crate) mod wrappers; diff --git a/src/window.rs b/src/window.rs index 5334d35a..bb095184 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,7 +2,7 @@ use crate::context::WindowContext; use crate::handler::WindowHandler; use crate::platform; use crate::window_open_options::WindowOpenOptions; -use dpi::{LogicalSize, PhysicalSize, Pixel}; +use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; use raw_window_handle::HasWindowHandle; use std::marker::PhantomData; @@ -27,6 +27,38 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.window_handle.is_open() } + + pub fn suggest_fallback_scale(&self, fallback_scale: Option) { + todo!() + } + + pub fn resize(&self, size: impl Into) { + todo!() + } + + pub fn size(&self) -> WindowSize { + todo!() + } + + pub fn make_floating(&self) { + todo!() + } + + pub fn set_parent(&self, parent: impl HasWindowHandle + 'static) { + todo!() + } + + pub fn show(&self) { + todo!() + } + + pub fn hide(&self) { + todo!() + } + + pub fn set_title(&self, title: impl Into) { + todo!() + } } pub struct Window { diff --git a/src/window_builder.rs b/src/window_builder.rs new file mode 100644 index 00000000..79be5c5f --- /dev/null +++ b/src/window_builder.rs @@ -0,0 +1,37 @@ +use crate::{HostHandler, WindowContext, WindowHandle, WindowHandler}; +use dpi::{LogicalSize, Size}; + +#[non_exhaustive] +pub struct WindowBuilder { + pub title: Option, + pub size: Size, + pub host_handler: Option>, +} + +impl WindowBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn with_size(mut self, size: Size) -> Self { + self.size = size; + self + } + + pub fn hosted(mut self, handler: impl HostHandler) -> Self { + self.host_handler = Some(Box::new(handler)); + self + } + + pub fn build( + self, handler_builder: impl FnOnce(WindowContext) -> H + Send + 'static, + ) -> WindowHandle { + todo!() + } +} + +impl Default for WindowBuilder { + fn default() -> Self { + Self { title: None, size: LogicalSize::new(420.0, 240.0).into(), host_handler: None } + } +} From a2211f0264bca7dde59ff46320bfea2cce7f8dfd Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:54:41 +0200 Subject: [PATCH 02/10] wip --- examples/plugin_clack/Cargo.toml | 3 ++- examples/plugin_clack/src/gui.rs | 32 ++++++++++++++++++++++++++++++-- src/window.rs | 11 ++++++++--- src/window_builder.rs | 13 ++++++++++++- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 32c7e0a5..70655614 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] clack-plugin = "0.1.0" -clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin"] } +clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin", "raw-window-handle_06"] } baseview = { path = "../.." } softbuffer = "0.4.8" +raw-window-handle = "0.6.2" diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index e08faab1..4fa93c06 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,11 +1,13 @@ use crate::ExamplePluginMainThread; use crate::window_handler::OpenWindowExample; +use baseview::dpi::{LogicalSize, PhysicalSize, Size}; use baseview::{WindowBuilder, WindowHandle}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, Window, }; use clack_plugin::plugin::PluginError; +use raw_window_handle::HasRawWindowHandle; pub struct ExamplePluginGui { handle: WindowHandle, @@ -82,11 +84,26 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { - todo!() + let Some(gui) = &self.gui else { + return Err(PluginError::Message("Invalid GUI call: GUI is not created")); + }; + + let size = size_from_clap(size); + gui.handle.resize(size); + Ok(()) } fn set_parent(&mut self, window: Window) -> Result<(), PluginError> { - todo!() + let Some(gui) = &self.gui else { + return Err(PluginError::Message("Invalid GUI call: GUI is not created")); + }; + + let handle = window.raw_window_handle()?; + + let handle = unsafe { raw_window_handle::WindowHandle::borrow_raw(handle) }; + gui.handle.set_parent(&handle); + + Ok(()) } fn set_transient(&mut self, window: Window) -> Result<(), PluginError> { @@ -107,3 +124,14 @@ impl PluginGuiImpl for ExamplePluginMainThread { todo!() } } + +fn size_from_clap(size: GuiSize) -> Size { + let uses_logical = + matches!(GuiApiType::default_for_current_platform(), Some(a) if a.uses_logical_size()); + + if uses_logical { + Size::Logical(LogicalSize::new(size.width as _, size.height as _)) + } else { + Size::Physical(PhysicalSize::new(size.width, size.height)) + } +} diff --git a/src/window.rs b/src/window.rs index bb095184..aa875804 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,6 +4,7 @@ use crate::platform; use crate::window_open_options::WindowOpenOptions; use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; use raw_window_handle::HasWindowHandle; +use std::error::Error; use std::marker::PhantomData; pub struct WindowHandle { @@ -13,15 +14,19 @@ pub struct WindowHandle { } impl WindowHandle { - fn new(window_handle: platform::WindowHandle) -> Self { + pub(crate) fn new(window_handle: platform::WindowHandle) -> Self { Self { window_handle, phantom: PhantomData } } /// Close the window - pub fn close(&self) { + pub fn close(self) { self.window_handle.close(); } + pub fn run_until_closed(self) -> Result<(), Box> { + todo!() + } + /// Returns `true` if the window is still open, and returns `false` /// if the window was closed/dropped. pub fn is_open(&self) -> bool { @@ -44,7 +49,7 @@ impl WindowHandle { todo!() } - pub fn set_parent(&self, parent: impl HasWindowHandle + 'static) { + pub fn set_parent(&self, parent: &impl HasWindowHandle) { todo!() } diff --git a/src/window_builder.rs b/src/window_builder.rs index 79be5c5f..79ff6b5f 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -6,6 +6,7 @@ pub struct WindowBuilder { pub title: Option, pub size: Size, pub host_handler: Option>, + pub parented: bool, } impl WindowBuilder { @@ -18,6 +19,11 @@ impl WindowBuilder { self } + pub fn parented(mut self) -> Self { + self.parented = true; + self + } + pub fn hosted(mut self, handler: impl HostHandler) -> Self { self.host_handler = Some(Box::new(handler)); self @@ -32,6 +38,11 @@ impl WindowBuilder { impl Default for WindowBuilder { fn default() -> Self { - Self { title: None, size: LogicalSize::new(420.0, 240.0).into(), host_handler: None } + Self { + title: None, + size: LogicalSize::new(420.0, 240.0).into(), + host_handler: None, + parented: false, + } } } From 639c7bee2c023f6c683b269134c30d2cc16095b5 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:54:20 +0200 Subject: [PATCH 03/10] wip --- src/lib.rs | 3 ++ src/platform/macos/view.rs | 66 +++++++++++++++++++++++++++--------- src/platform/macos/window.rs | 2 +- src/size.rs | 52 ++++++++++++++++++++++++++++ src/window.rs | 58 ++----------------------------- 5 files changed, 108 insertions(+), 73 deletions(-) create mode 100644 src/size.rs diff --git a/src/lib.rs b/src/lib.rs index b852b2c4..12a0385f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ mod window; mod window_builder; mod window_open_options; +mod size; + pub(crate) mod platform; #[cfg(feature = "opengl")] @@ -21,6 +23,7 @@ pub use event::*; pub use handler::WindowHandler; pub use host::HostHandler; pub use mouse_cursor::MouseCursor; +pub use size::*; pub use window::*; pub use window_builder::*; pub use window_open_options::*; diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index f7a21b4e..5022f0a9 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -19,7 +19,7 @@ use objc2_app_kit::{ NSTrackingAreaOptions, NSView, NSWindow, }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; -use std::cell::{Cell, OnceCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::rc::Rc; pub enum ViewParentingType { @@ -30,7 +30,7 @@ pub enum ViewParentingType { pub(crate) struct BaseviewView { pub(crate) state: Rc, pub(crate) mtm: MainThreadMarker, - window_handler: OnceCell>, + window_handler: WindowHandlerContainer, frame_timer: Cell>, notification_center_observer: Cell>, @@ -60,7 +60,7 @@ impl BaseviewView { keyboard_state: KeyboardState::new(), frame_timer: None.into(), - window_handler: OnceCell::new(), + window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), parenting, @@ -92,10 +92,10 @@ impl BaseviewView { } let context = WindowContext::new(view); - let handler = Box::new(builder(crate::WindowContext::new(context))); + let handler = builder(crate::WindowContext::new(context)); // Initialize handler - let Ok(()) = view.window_handler.set(handler) else { unreachable!() }; + view.window_handler.set(handler); // Set up anything that might trigger events to the handler @@ -125,6 +125,9 @@ impl BaseviewView { pub fn close(this: ViewRef) { this.state.closed.set(true); this.view.removeFromSuperview(); + this.notification_center_observer.take(); + this.frame_timer.take(); + this.window_handler.destroy(); if let ViewParentingType::Windowed { owned_window: parent_window, running_app } = &this.parenting @@ -167,17 +170,11 @@ impl BaseviewView { /// Trigger the event immediately and return the event status. fn trigger_event(this: ViewRef, event: Event) -> EventStatus { - let Some(handler) = this.window_handler.get() else { - return EventStatus::Ignored; - }; - - handler.on_event(event) + this.window_handler.use_handler(|h| h.on_event(event)).unwrap_or(EventStatus::Ignored) } fn trigger_frame(this: ViewRef) { - let Some(handler) = this.window_handler.get() else { return }; - - handler.on_frame(); + this.window_handler.use_handler(|h| h.on_frame()); } } @@ -224,9 +221,9 @@ impl ViewImpl for BaseviewView { this.state.size.set(current_size); this.state.scale_factor.set(current_scale_factor); - if let Some(handler) = this.window_handler.get() { - handler.resized(WindowSize::from_logical(current_size, current_scale_factor)) - } + this.window_handler.use_handler(|h| { + h.resized(WindowSize::from_logical(current_size, current_scale_factor)) + }); } } @@ -615,3 +612,40 @@ fn on_event(this: ViewRef, event: MouseEvent) -> NSDragOperation { _ => NSDragOperation::None, } } + +pub struct WindowHandlerContainer { + inner: RefCell>>, + must_be_destroyed: Cell, +} + +impl WindowHandlerContainer { + pub fn new() -> WindowHandlerContainer { + Self { inner: RefCell::new(None), must_be_destroyed: false.into() } + } + + pub fn use_handler(&self, user: impl FnOnce(&dyn WindowHandler) -> T) -> Option { + let returned = { + let inner = self.inner.try_borrow().ok()?; + user(inner.as_ref()?.as_ref()) + }; + + if self.must_be_destroyed.get() { + if let Ok(mut inner) = self.inner.try_borrow_mut() { + *inner = None; + } + } + + Some(returned) + } + + pub fn set(&self, handler: impl WindowHandler) { + self.inner.replace(Some(Box::new(handler))); + } + + pub fn destroy(&self) { + match self.inner.try_borrow_mut() { + Ok(mut inner) => *inner = None, + Err(_) => self.must_be_destroyed.set(true), + } + } +} diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index e8805eaa..d78c734c 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -19,7 +19,7 @@ pub struct WindowHandle { } impl WindowHandle { - pub fn close(&self) { + pub fn destroy(&self) { let Some(view) = self.view.take().and_then(|w| w.load()) else { return; }; diff --git a/src/size.rs b/src/size.rs new file mode 100644 index 00000000..35b8b67c --- /dev/null +++ b/src/size.rs @@ -0,0 +1,52 @@ +use dpi::{LogicalSize, PhysicalSize, Pixel}; + +/// A window's size, which can be read in either logical or physical pixels. +/// +/// Methods that produce this type in baseview guarantee that either the physical or the logical +/// size is directly from the underlying platform API. +/// +/// This means that for either of the size types, there is at most only one conversion performed, +/// which minimizes errors that may occur due to rounding. +#[derive(Debug, Copy, Clone)] +pub struct WindowSize { + /// The window's size in physical pixels + pub physical: PhysicalSize, + /// The window's size in logical pixels + pub logical: LogicalSize, + /// The backing scale factor of the window. + /// + /// This is the value used to convert between the physical and logical sizes. + pub scale_factor: f64, +} + +impl WindowSize { + /// Constructs a [`WindowSize`] from a given [`PhysicalSize`] and `scale_factor`. + /// + /// The [`LogicalSize`] is converted from the given physical size, using the given scale factor. + #[inline] + pub fn from_physical(physical: PhysicalSize, scale_factor: f64) -> Self { + Self { physical, logical: physical.to_logical(scale_factor), scale_factor } + } + + /// Constructs a [`WindowSize`] from a given [`LogicalSize`] and `scale_factor`. + /// + /// The [`PhysicalSize`] is converted from the given physical size, using the given scale factor. + #[inline] + pub fn from_logical(logical: LogicalSize, scale_factor: f64) -> Self { + Self { physical: logical.to_physical(scale_factor), logical, scale_factor } + } +} + +impl From for PhysicalSize

{ + #[inline] + fn from(size: WindowSize) -> Self { + size.physical.cast() + } +} + +impl From for LogicalSize

{ + #[inline] + fn from(size: WindowSize) -> Self { + size.logical.cast() + } +} diff --git a/src/window.rs b/src/window.rs index aa875804..194f05e3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,7 +1,4 @@ -use crate::context::WindowContext; -use crate::handler::WindowHandler; -use crate::platform; -use crate::window_open_options::WindowOpenOptions; +use super::*; use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; use raw_window_handle::HasWindowHandle; use std::error::Error; @@ -20,7 +17,7 @@ impl WindowHandle { /// Close the window pub fn close(self) { - self.window_handle.close(); + self.window_handle.destroy(); } pub fn run_until_closed(self) -> Result<(), Box> { @@ -85,54 +82,3 @@ impl Window { platform::Window::open_blocking(options, build) } } - -/// A window's size, which can be read in either logical or physical pixels. -/// -/// Methods that produce this type in baseview guarantee that either the physical or the logical -/// size is directly from the underlying platform API. -/// -/// This means that for either of the size types, there is at most only one conversion performed, -/// which minimizes errors that may occur due to rounding. -#[derive(Debug, Copy, Clone)] -pub struct WindowSize { - /// The window's size in physical pixels - pub physical: PhysicalSize, - /// The window's size in logical pixels - pub logical: LogicalSize, - /// The backing scale factor of the window. - /// - /// This is the value used to convert between the physical and logical sizes. - pub scale_factor: f64, -} - -impl WindowSize { - /// Constructs a [`WindowSize`] from a given [`PhysicalSize`] and `scale_factor`. - /// - /// The [`LogicalSize`] is converted from the given physical size, using the given scale factor. - #[inline] - pub fn from_physical(physical: PhysicalSize, scale_factor: f64) -> Self { - Self { physical, logical: physical.to_logical(scale_factor), scale_factor } - } - - /// Constructs a [`WindowSize`] from a given [`LogicalSize`] and `scale_factor`. - /// - /// The [`PhysicalSize`] is converted from the given physical size, using the given scale factor. - #[inline] - pub fn from_logical(logical: LogicalSize, scale_factor: f64) -> Self { - Self { physical: logical.to_physical(scale_factor), logical, scale_factor } - } -} - -impl From for PhysicalSize

{ - #[inline] - fn from(size: WindowSize) -> Self { - size.physical.cast() - } -} - -impl From for LogicalSize

{ - #[inline] - fn from(size: WindowSize) -> Self { - size.logical.cast() - } -} From 56d1bae8445e8ec20b9c921aec22723852759abe Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:09:31 +0200 Subject: [PATCH 04/10] wip --- examples/open_window/src/main.rs | 12 ++- src/platform/macos/window.rs | 154 +++++++++++++++++-------------- src/window.rs | 28 ++---- src/window_builder.rs | 13 ++- 4 files changed, 112 insertions(+), 95 deletions(-) diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index ee68627b..8c6e6fd8 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -8,8 +8,8 @@ use rtrb::{Consumer, RingBuffer}; use baseview::copy_to_clipboard; use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::{ - Event, EventStatus, MouseEvent, Window, WindowContext, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, MouseEvent, Window, WindowBuilder, WindowContext, WindowHandler, + WindowOpenOptions, WindowSize, }; #[derive(Debug, Clone)] @@ -136,7 +136,7 @@ impl WindowHandler for OpenWindowExample { } fn main() { - let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); + let window_open_options = WindowBuilder::new().with_size(LogicalSize::new(512.0, 512.0)); let (mut tx, rx) = RingBuffer::new(128); @@ -148,7 +148,7 @@ fn main() { } }); - Window::open_blocking(window_open_options, |window| { + baseview::create_window(window_open_options, |window| { let ctx = softbuffer::Context::new(window.clone()).unwrap(); let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); let size = window.size().physical; @@ -164,7 +164,9 @@ fn main() { is_cursor_inside: false.into(), damaged: true.into(), } - }); + }) + .run_until_closed() + .unwrap(); } fn log_event(event: &Event) { diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index d78c734c..4819e74f 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,30 +1,47 @@ use dpi::LogicalSize; -use objc2::rc::{autoreleasepool, Weak}; +use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; use objc2_app_kit::{ NSApplication, NSApplicationActivationPolicy, NSPasteboard, NSPasteboardTypeString, }; use objc2_foundation::{NSSize, NSString}; use raw_window_handle::HasWindowHandle; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::rc::Rc; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::wrappers::appkit::{create_window, extract_raw_window_handle, View}; -use crate::{WindowContext, WindowHandler, WindowOpenOptions}; +use crate::{WindowBuilder, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions}; -pub struct WindowHandle { - view: RefCell>>>, +enum WindowView { + Uninitialized { initializer: Box Box> }, + Initialized { inner: Weak> }, +} + +impl WindowView { + fn load(&self) -> Option>> { + match &self { + WindowView::Uninitialized { .. } => None, + WindowView::Initialized { inner } => inner.load(), + } + } +} + +pub struct Window { + // May be none if the Window is waiting to be initialized + view: WindowView, state: Rc, } -impl WindowHandle { - pub fn destroy(&self) { - let Some(view) = self.view.take().and_then(|w| w.load()) else { - return; - }; +impl Window { + pub fn create_window( + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H, + ) -> Self { + todo!() + } - BaseviewView::close(view.inner_ref()); + pub fn destroy(self) { + drop(self); } pub fn is_open(&self) -> bool { @@ -32,72 +49,75 @@ impl WindowHandle { } } -pub struct Window; - -impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - autoreleasepool(|_| { - let Some(parent_view) = extract_raw_window_handle(parent.window_handle().unwrap()) - else { - panic!("Invalid window handle: ns_view is NULL"); - }; - - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") - }; - - let parenting = - ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; - - let backing_scale_factor = - parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); - let final_size = options.size.to_logical(backing_scale_factor); - - let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size, mtm); - - WindowHandle { view: Some(Weak::from_retained(&ns_view)).into(), state } - }) +impl Drop for Window { + fn drop(&mut self) { + if let Some(view) = self.view.load() { + BaseviewView::close(view.inner_ref()) + } } +} - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - autoreleasepool(|_| { - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") - }; +pub fn open_parented( + parent: &impl HasWindowHandle, options: WindowOpenOptions, + build: impl FnOnce(WindowContext) -> H + Send + 'static, +) -> Window { + autoreleasepool(|_| { + let Some(parent_view) = extract_raw_window_handle(parent.window_handle().unwrap()) else { + panic!("Invalid window handle: ns_view is NULL"); + }; - // Creates the global NSApplication instance, if it doesn't exist yet - let app = NSApplication::sharedApplication(mtm); + let Some(mtm) = MainThreadMarker::new() else { + panic!("macOS: open_blocking can only be called on the main thread!") + }; + + let parenting = + ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; + + let backing_scale_factor = + parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); + let final_size = options.size.to_logical(backing_scale_factor); - let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size, mtm); - let initial_size = options.size.to_logical(1.0); - let window = create_window(initial_size, mtm); - window.center(); + Window { view: Some(Weak::from_retained(&ns_view)), state } + }) +} - let final_size = options.size.to_logical(window.backingScaleFactor()); - if final_size != initial_size { - window.setContentSize(NSSize::new(final_size.width, final_size.height)); - } +pub fn open_blocking( + options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, +) { + autoreleasepool(|_| { + let Some(mtm) = MainThreadMarker::new() else { + panic!("macOS: open_blocking can only be called on the main thread!") + }; - let title = NSString::from_str(&options.title); - window.setTitle(&title); - window.makeKeyAndOrderFront(None); + // Creates the global NSApplication instance, if it doesn't exist yet + let app = NSApplication::sharedApplication(mtm); - let parenting = ViewParentingType::Windowed { - running_app: Weak::from_retained(&app), - owned_window: Weak::from_retained(&window), - }; + let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); - let _ = BaseviewView::new(options, build, parenting, final_size, mtm); + let initial_size = options.size.to_logical(1.0); + let window = create_window(initial_size, mtm); + window.center(); - app.run(); - }) - } + let final_size = options.size.to_logical(window.backingScaleFactor()); + if final_size != initial_size { + window.setContentSize(NSSize::new(final_size.width, final_size.height)); + } + + let title = NSString::from_str(&options.title); + window.setTitle(&title); + window.makeKeyAndOrderFront(None); + + let parenting = ViewParentingType::Windowed { + running_app: Weak::from_retained(&app), + owned_window: Weak::from_retained(&window), + }; + + let _ = BaseviewView::new(options, build, parenting, final_size, mtm); + + app.run(); + }) } pub(crate) struct WindowSharedState { diff --git a/src/window.rs b/src/window.rs index 194f05e3..0e44f28f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,17 +1,17 @@ use super::*; -use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; +use dpi::Size; use raw_window_handle::HasWindowHandle; use std::error::Error; use std::marker::PhantomData; pub struct WindowHandle { - window_handle: platform::WindowHandle, + window_handle: platform::Window, // so that WindowHandle is !Send on all platforms phantom: PhantomData<*mut ()>, } impl WindowHandle { - pub(crate) fn new(window_handle: platform::WindowHandle) -> Self { + pub(crate) fn new(window_handle: platform::Window) -> Self { Self { window_handle, phantom: PhantomData } } @@ -63,22 +63,8 @@ impl WindowHandle { } } -pub struct Window { - _private: (), -} - -impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - let window_handle = platform::Window::open_parented(parent, options, build); - WindowHandle::new(window_handle) - } - - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - platform::Window::open_blocking(options, build) - } +pub fn create_window( + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H, +) -> WindowHandle { + WindowHandle::new(platform::Window::create_window(builder, handler)) } diff --git a/src/window_builder.rs b/src/window_builder.rs index 79ff6b5f..1bfd8e93 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -1,11 +1,13 @@ use crate::{HostHandler, WindowContext, WindowHandle, WindowHandler}; use dpi::{LogicalSize, Size}; +use raw_window_handle::HasWindowHandle; #[non_exhaustive] pub struct WindowBuilder { pub title: Option, pub size: Size, pub host_handler: Option>, + pub parent: Option>, pub parented: bool, } @@ -14,8 +16,8 @@ impl WindowBuilder { Self::default() } - pub fn with_size(mut self, size: Size) -> Self { - self.size = size; + pub fn with_size(mut self, size: impl Into) -> Self { + self.size = size.into(); self } @@ -24,6 +26,12 @@ impl WindowBuilder { self } + pub fn with_parent(mut self, parent: Box) -> Self { + self.parent = Some(parent); + self.parented = true; + self + } + pub fn hosted(mut self, handler: impl HostHandler) -> Self { self.host_handler = Some(Box::new(handler)); self @@ -42,6 +50,7 @@ impl Default for WindowBuilder { title: None, size: LogicalSize::new(420.0, 240.0).into(), host_handler: None, + parent: None, parented: false, } } From 276a9fdfe4c95454fbcb87e955f2d92bd08cdf20 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:58:15 +0200 Subject: [PATCH 05/10] wip --- examples/open_window/src/main.rs | 3 +- src/platform/macos/view.rs | 7 +- src/platform/macos/window.rs | 188 ++++++++++++++++++++----------- src/window.rs | 4 +- src/window_builder.rs | 4 + 5 files changed, 130 insertions(+), 76 deletions(-) diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index 8c6e6fd8..c2f17608 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -8,8 +8,7 @@ use rtrb::{Consumer, RingBuffer}; use baseview::copy_to_clipboard; use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::{ - Event, EventStatus, MouseEvent, Window, WindowBuilder, WindowContext, WindowHandler, - WindowOpenOptions, WindowSize, + Event, EventStatus, MouseEvent, WindowBuilder, WindowContext, WindowHandler, WindowSize, }; #[derive(Debug, Clone)] diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 5022f0a9..32441b38 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -6,8 +6,8 @@ use crate::platform::macos::context::WindowContext; use crate::wrappers::appkit::*; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::{ - DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, ScrollDelta, WindowEvent, - WindowHandler, WindowOpenOptions, WindowSize, + DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, ScrollDelta, WindowBuilder, + WindowEvent, WindowHandler, WindowOpenOptions, WindowSize, }; use dpi::{LogicalPosition, LogicalSize, Size}; use objc2::__framework_prelude::Retained; @@ -45,8 +45,7 @@ pub(crate) struct BaseviewView { impl BaseviewView { pub fn new( - _options: WindowOpenOptions, - builder: impl FnOnce(crate::WindowContext) -> H + Send + 'static, + _options: WindowBuilder, builder: impl FnOnce(crate::WindowContext) -> H + 'static, parenting: ViewParentingType, final_size: LogicalSize, mtm: MainThreadMarker, ) -> (Retained>, Rc) { let view_rect = diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 4819e74f..ac0f4823 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,43 +1,129 @@ use dpi::LogicalSize; use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; -use objc2_app_kit::{ - NSApplication, NSApplicationActivationPolicy, NSPasteboard, NSPasteboardTypeString, -}; +use objc2_app_kit::{NSApplication, NSPasteboard, NSPasteboardTypeString, NSWindow}; use objc2_foundation::{NSSize, NSString}; use raw_window_handle::HasWindowHandle; use std::cell::Cell; +use std::error::Error; use std::rc::Rc; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::wrappers::appkit::{create_window, extract_raw_window_handle, View}; -use crate::{WindowBuilder, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions}; +use crate::{WindowBuilder, WindowContext, WindowHandler, WindowOpenOptions}; enum WindowView { - Uninitialized { initializer: Box Box> }, - Initialized { inner: Weak> }, + Uninitialized { + initializer: Box Box>, + builder: WindowBuilder, + }, + Initialized { + inner: Weak>, + window: Option>, + }, + InitializationFailed, } impl WindowView { fn load(&self) -> Option>> { match &self { - WindowView::Uninitialized { .. } => None, - WindowView::Initialized { inner } => inner.load(), + WindowView::Initialized { inner, .. } => inner.load(), + _ => None, } } } pub struct Window { - // May be none if the Window is waiting to be initialized view: WindowView, state: Rc, + mtm: MainThreadMarker, } impl Window { pub fn create_window( - builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H, + mut builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, ) -> Self { - todo!() + autoreleasepool(|_| { + let Some(mtm) = MainThreadMarker::new() else { + panic!("macOS: Windows can only be created on the main thread!") + }; + + // Creates the global NSApplication instance, if it doesn't exist yet + let _ = NSApplication::sharedApplication(mtm); + + if let Some(parent) = builder.parent.take() { + return Self::create_window_parented(builder, handler, parent, mtm); + } + + if !builder.parented { + return Self::create_window_standalone(builder, handler, mtm); + } + + // Delay window creation until parent is known + Self { + mtm, + state: Rc::new(WindowSharedState::new_uninitialized(&builder)), + view: WindowView::Uninitialized { + initializer: Box::new(|c| Box::new(handler(c))), + builder, + }, + } + }) + } + + pub fn create_window_parented( + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, + parent: Box, mtm: MainThreadMarker, + ) -> Self { + let parent_view = extract_raw_window_handle(parent.window_handle().unwrap()).unwrap(); + + let parenting = + ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; + + let backing_scale_factor = + parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); + let final_size = builder.size.to_logical(backing_scale_factor); + + let (ns_view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); + + Self { + mtm, + state, + view: WindowView::Initialized { window: None, inner: Weak::from_retained(&ns_view) }, + } + } + + pub fn create_window_standalone( + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, + mtm: MainThreadMarker, + ) -> Self { + let app = NSApplication::sharedApplication(mtm); + let window = create_window_with_options(&builder, mtm); + + let final_size = window.contentRectForFrameRect(window.frame()).size; + let final_size = LogicalSize::new(final_size.width, final_size.height); + + let parenting = ViewParentingType::Windowed { + running_app: Weak::from_retained(&app), + owned_window: Weak::from_retained(&window), + }; + + let (view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); + + Self { + mtm, + state, + view: WindowView::Initialized { + inner: Weak::from_retained(&view), + window: Some(window), + }, + } + } + + pub fn run_until_closed(self) -> Result<(), Box> { + NSApplication::sharedApplication(self.mtm).run(); + + Ok(()) } pub fn destroy(self) { @@ -57,67 +143,25 @@ impl Drop for Window { } } -pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, -) -> Window { - autoreleasepool(|_| { - let Some(parent_view) = extract_raw_window_handle(parent.window_handle().unwrap()) else { - panic!("Invalid window handle: ns_view is NULL"); - }; - - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") - }; - - let parenting = - ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; - - let backing_scale_factor = - parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); - let final_size = options.size.to_logical(backing_scale_factor); - - let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size, mtm); - - Window { view: Some(Weak::from_retained(&ns_view)), state } - }) -} - -pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, -) { - autoreleasepool(|_| { - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") - }; - - // Creates the global NSApplication instance, if it doesn't exist yet - let app = NSApplication::sharedApplication(mtm); +fn create_window_with_options( + options: &WindowBuilder, mtm: MainThreadMarker, +) -> Retained { + let initial_size = options.size.to_logical(1.0); + let window = create_window(initial_size, mtm); + window.center(); - let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); - - let initial_size = options.size.to_logical(1.0); - let window = create_window(initial_size, mtm); - window.center(); - - let final_size = options.size.to_logical(window.backingScaleFactor()); - if final_size != initial_size { - window.setContentSize(NSSize::new(final_size.width, final_size.height)); - } + let final_size = options.size.to_logical(window.backingScaleFactor()); + if final_size != initial_size { + window.setContentSize(NSSize::new(final_size.width, final_size.height)); + } - let title = NSString::from_str(&options.title); + if let Some(title) = &options.title { + let title = NSString::from_str(title); window.setTitle(&title); - window.makeKeyAndOrderFront(None); - - let parenting = ViewParentingType::Windowed { - running_app: Weak::from_retained(&app), - owned_window: Weak::from_retained(&window), - }; - - let _ = BaseviewView::new(options, build, parenting, final_size, mtm); + } - app.run(); - }) + window.makeKeyAndOrderFront(None); + window } pub(crate) struct WindowSharedState { @@ -130,6 +174,14 @@ impl WindowSharedState { pub fn new(size: LogicalSize, scale_factor: f64) -> Self { Self { closed: false.into(), size: size.into(), scale_factor: scale_factor.into() } } + + pub fn new_uninitialized(builder: &WindowBuilder) -> Self { + Self { + closed: false.into(), + size: builder.size.to_logical(1.0).into(), + scale_factor: 1.0.into(), + } + } } pub fn copy_to_clipboard(string: &str) { diff --git a/src/window.rs b/src/window.rs index 0e44f28f..af32ec34 100644 --- a/src/window.rs +++ b/src/window.rs @@ -21,7 +21,7 @@ impl WindowHandle { } pub fn run_until_closed(self) -> Result<(), Box> { - todo!() + self.window_handle.run_until_closed() } /// Returns `true` if the window is still open, and returns `false` @@ -64,7 +64,7 @@ impl WindowHandle { } pub fn create_window( - builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H, + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, ) -> WindowHandle { WindowHandle::new(platform::Window::create_window(builder, handler)) } diff --git a/src/window_builder.rs b/src/window_builder.rs index 1bfd8e93..3a8571d7 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -9,6 +9,8 @@ pub struct WindowBuilder { pub host_handler: Option>, pub parent: Option>, pub parented: bool, + #[cfg(feature = "opengl")] + pub gl_config: Option, } impl WindowBuilder { @@ -52,6 +54,8 @@ impl Default for WindowBuilder { host_handler: None, parent: None, parented: false, + #[cfg(feature = "opengl")] + gl_config: None, } } } From d6553bba6417b08c031b8acecb4d9bf4f741558c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:45:35 +0200 Subject: [PATCH 06/10] wip --- examples/open_parented/src/main.rs | 19 +++++++++--------- examples/plugin_clack/src/gui.rs | 12 +++++------ examples/render_femtovg/src/main.rs | 7 +++---- examples/render_wgpu/src/main.rs | 11 ++++------ src/window.rs | 8 ++++---- src/window_builder.rs | 31 +++++++++++++++++++---------- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index ccfc72cf..5a3c62f3 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -1,7 +1,6 @@ use baseview::dpi::LogicalSize; use baseview::{ - Event, EventStatus, Window, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, Window, WindowBuilder, WindowContext, WindowHandler, WindowSize, }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -10,7 +9,7 @@ struct ParentWindowHandler { surface: RefCell>, damaged: Cell, - _child_window: Option, + _child_window: Option, } impl ParentWindowHandler { @@ -22,12 +21,12 @@ impl ParentWindowHandler { .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) .unwrap(); - let window_open_options = WindowOpenOptions::new() + let window_open_options = WindowBuilder::new() .with_size(LogicalSize::new(256, 256)) - .with_title("baseview child"); + .with_title("baseview child") + .with_parent(window); - let child_window = - Window::open_parented(&window, window_open_options, ChildWindowHandler::new); + let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new); Self { surface: surface.into(), damaged: true.into(), _child_window: Some(child_window) } } @@ -120,7 +119,9 @@ impl WindowHandler for ChildWindowHandler { } fn main() { - let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); + let window_open_options = WindowBuilder::new().with_size(LogicalSize::new(512.0, 512.0)); - Window::open_blocking(window_open_options, ParentWindowHandler::new); + baseview::create_window(window_open_options, ParentWindowHandler::new) + .run_until_closed() + .unwrap(); } diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 4fa93c06..5b1e2537 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,16 +1,16 @@ use crate::ExamplePluginMainThread; use crate::window_handler::OpenWindowExample; use baseview::dpi::{LogicalSize, PhysicalSize, Size}; -use baseview::{WindowBuilder, WindowHandle}; +use baseview::{Window, WindowBuilder}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, - Window, + Window as ClapWindow, }; use clack_plugin::plugin::PluginError; use raw_window_handle::HasRawWindowHandle; pub struct ExamplePluginGui { - handle: WindowHandle, + handle: Window, } impl PluginGuiImpl for ExamplePluginMainThread { @@ -27,7 +27,7 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - let handle = WindowBuilder::new().build(OpenWindowExample::new); // TODO: handle errors + let handle = baseview::create_window(WindowBuilder::new(), OpenWindowExample::new); self.gui = Some(ExamplePluginGui { handle }); @@ -93,7 +93,7 @@ impl PluginGuiImpl for ExamplePluginMainThread { Ok(()) } - fn set_parent(&mut self, window: Window) -> Result<(), PluginError> { + fn set_parent(&mut self, window: ClapWindow) -> Result<(), PluginError> { let Some(gui) = &self.gui else { return Err(PluginError::Message("Invalid GUI call: GUI is not created")); }; @@ -106,7 +106,7 @@ impl PluginGuiImpl for ExamplePluginMainThread { Ok(()) } - fn set_transient(&mut self, window: Window) -> Result<(), PluginError> { + fn set_transient(&mut self, window: ClapWindow) -> Result<(), PluginError> { todo!() // Not supported yet } diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 69904f64..7556b4f8 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -1,8 +1,7 @@ use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::gl::{GlConfig, GlContext}; use baseview::{ - Event, EventStatus, MouseEvent, Window, WindowContext, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, MouseEvent, WindowBuilder, WindowContext, WindowHandler, WindowSize, }; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; @@ -112,12 +111,12 @@ impl WindowHandler for FemtovgExample { } fn main() { - let window_open_options = WindowOpenOptions::new() + let window_open_options = WindowBuilder::new() .with_title("Femtovg on Baseview") .with_size(LogicalSize::new(512, 512)) .with_gl_config(GlConfig { alpha_bits: 8, ..GlConfig::default() }); - Window::open_blocking(window_open_options, FemtovgExample::new); + baseview::create_window(window_open_options, FemtovgExample::new).run_until_closed().unwrap(); } fn log_event(event: &Event) { diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 043e1c47..2e4ed59e 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -1,7 +1,5 @@ use baseview::dpi::{LogicalSize, PhysicalSize}; -use baseview::{ - Event, EventStatus, Window, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, -}; +use baseview::{Event, EventStatus, WindowBuilder, WindowContext, WindowHandler, WindowSize}; use log::LevelFilter; use std::cell::RefCell; @@ -206,11 +204,10 @@ impl WindowHandler for WgpuExample { fn main() { env_logger::builder().filter_level(LevelFilter::Debug).init(); - let window_open_options = WindowOpenOptions::new() - .with_title("WGPU on Baseview") - .with_size(LogicalSize::new(512, 512)); + let window_open_options = + WindowBuilder::new().with_title("WGPU on Baseview").with_size(LogicalSize::new(512, 512)); - Window::open_blocking(window_open_options, |c| pollster::block_on(WgpuExample::new(c))); + baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c))); } fn log_event(event: &Event) { diff --git a/src/window.rs b/src/window.rs index af32ec34..58d23179 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,13 +4,13 @@ use raw_window_handle::HasWindowHandle; use std::error::Error; use std::marker::PhantomData; -pub struct WindowHandle { +pub struct Window { window_handle: platform::Window, // so that WindowHandle is !Send on all platforms phantom: PhantomData<*mut ()>, } -impl WindowHandle { +impl Window { pub(crate) fn new(window_handle: platform::Window) -> Self { Self { window_handle, phantom: PhantomData } } @@ -65,6 +65,6 @@ impl WindowHandle { pub fn create_window( builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, -) -> WindowHandle { - WindowHandle::new(platform::Window::create_window(builder, handler)) +) -> Window { + Window::new(platform::Window::create_window(builder, handler)) } diff --git a/src/window_builder.rs b/src/window_builder.rs index 3a8571d7..a70ffebc 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -1,4 +1,4 @@ -use crate::{HostHandler, WindowContext, WindowHandle, WindowHandler}; +use crate::HostHandler; use dpi::{LogicalSize, Size}; use raw_window_handle::HasWindowHandle; @@ -7,7 +7,7 @@ pub struct WindowBuilder { pub title: Option, pub size: Size, pub host_handler: Option>, - pub parent: Option>, + pub parent: Option>, pub parented: bool, #[cfg(feature = "opengl")] pub gl_config: Option, @@ -23,13 +23,30 @@ impl WindowBuilder { self } + pub fn with_title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + #[cfg(feature = "opengl")] + pub fn with_gl(mut self) -> Self { + self.gl_config = Some(crate::gl::GlConfig::default()); + self + } + + #[cfg(feature = "opengl")] + pub fn with_gl_config(mut self, config: crate::gl::GlConfig) -> Self { + self.gl_config = Some(config); + self + } + pub fn parented(mut self) -> Self { self.parented = true; self } - pub fn with_parent(mut self, parent: Box) -> Self { - self.parent = Some(parent); + pub fn with_parent(mut self, parent: impl HasWindowHandle + 'static) -> Self { + self.parent = Some(Box::new(parent)); self.parented = true; self } @@ -38,12 +55,6 @@ impl WindowBuilder { self.host_handler = Some(Box::new(handler)); self } - - pub fn build( - self, handler_builder: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - todo!() - } } impl Default for WindowBuilder { From 3f911a72ca840d65ca8a4d684253631329f9bb52 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 8 Jul 2026 19:37:29 +0200 Subject: [PATCH 07/10] X11 wip --- Cargo.toml | 2 +- src/lib.rs | 2 - src/platform/x11/mod.rs | 14 +- src/platform/x11/window.rs | 237 +++++++++++----------- src/platform/x11/window_thread.rs | 32 +++ src/platform/x11/window_thread_channel.rs | 35 ++++ src/window_builder.rs | 20 +- src/window_open_options.rs | 78 ------- 8 files changed, 201 insertions(+), 219 deletions(-) create mode 100644 src/platform/x11/window_thread.rs create mode 100644 src/platform/x11/window_thread_channel.rs delete mode 100644 src/window_open_options.rs diff --git a/Cargo.toml b/Cargo.toml index aa603595..73a34b83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ dpi = "0.1.2" x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false } xkbcommon-dl = { version = "0.4.2", features = ["x11"] } x11-dl = { version = "2.21.0" } -polling = "3.11.0" +calloop = "0.14.4" percent-encoding = "2.3.2" bytemuck = "1.25.0" diff --git a/src/lib.rs b/src/lib.rs index 12a0385f..69b18dd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ mod keyboard; mod mouse_cursor; mod window; mod window_builder; -mod window_open_options; mod size; @@ -26,6 +25,5 @@ pub use mouse_cursor::MouseCursor; pub use size::*; pub use window::*; pub use window_builder::*; -pub use window_open_options::*; pub(crate) mod wrappers; diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 220fb807..e0f7116e 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,6 +1,6 @@ mod xcb_connection; -use raw_window_handle::{DisplayHandle, XcbWindowHandle}; +use raw_window_handle::{DisplayHandle, HasWindowHandle, XcbWindowHandle}; use std::fmt::Formatter; use std::num::NonZero; use std::rc::Rc; @@ -15,6 +15,8 @@ mod drag_n_drop; mod event_loop; mod keyboard; mod visual_info; +mod window_thread; +mod window_thread_channel; mod window_shared; @@ -56,3 +58,13 @@ impl std::fmt::Debug for PlatformHandle { .finish() } } + +pub struct ParentWindowHandle { + window_id: u32, +} + +impl ParentWindowHandle { + pub fn extract(window: &impl HasWindowHandle) -> Self { + todo!() + } +} diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index 1b4c0501..364cbe0b 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -18,14 +18,7 @@ use super::X11Connection; use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; use crate::context::WindowContext; use crate::platform::x11::window_shared::WindowInner; -use crate::{WindowHandler, WindowOpenOptions, WindowScalePolicy, WindowSize}; - -pub struct WindowHandle { - window_id: Option>, - event_loop_handle: Cell>>, - close_requested: Arc, - is_open: Arc, -} +use crate::{WindowBuilder, WindowHandler, WindowSize}; impl WindowHandle { pub fn close(&self) { @@ -116,79 +109,88 @@ impl Window { } fn window_thread( - parent: Option, options: WindowOpenOptions, + parent: Option, options: WindowBuilder, build: impl FnOnce(WindowContext) -> H + Send + 'static, tx: mpsc::SyncSender, parent_handle: Option, ) -> Result<(), Box> { // Connect to the X server - // FIXME: baseview error type instead of unwrap() - let xcb_connection = X11Connection::new()?; - + let xcb_connection = Rc::new(X11Connection::new()?); // Setup xkbcommon let xkb_state = crate::wrappers::xkbcommon::XkbcommonState::new(&xcb_connection); - // Get screen information - let screen = xcb_connection.screen(); - let parent_id = parent.unwrap_or(screen.root); + let inner = create_window_inner(options, xcb_connection, parent)?; - let gc_id = xcb_connection.conn.generate_id()?; - xcb_connection.conn.create_gc( - gc_id, - parent_id, - &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), - )?; - - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), - WindowScalePolicy::ScaleFactor(scale) => scale, - }; - - let physical_size = options.size.to_physical(scaling); - - #[cfg(feature = "opengl")] - let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; + let handler = build(WindowContext::new(Rc::clone(&inner))); - #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; + let _ = tx.send(Ok(inner.window_id)); - let Some(window_id) = NonZero::new(xcb_connection.conn.generate_id()?) else { - unreachable!(); - }; + EventLoop::new(inner, handler, parent_handle, xkb_state).run()?; - xcb_connection.conn.create_window( - visual_info.visual_depth, - window_id.get(), - parent_id, - 0, // x coordinate of the new window - 0, // y coordinate of the new window - physical_size.width, // window width - physical_size.height, // window height - 0, // window border - WindowClass::INPUT_OUTPUT, - visual_info.visual_id, - &CreateWindowAux::new() - .event_mask( - EventMask::EXPOSURE - | EventMask::POINTER_MOTION - | EventMask::BUTTON_PRESS - | EventMask::BUTTON_RELEASE - | EventMask::KEY_PRESS - | EventMask::KEY_RELEASE - | EventMask::STRUCTURE_NOTIFY - | EventMask::ENTER_WINDOW - | EventMask::LEAVE_WINDOW - | EventMask::FOCUS_CHANGE, - ) - // As mentioned above, these two values are needed to be able to create a window - // with a depth of 32-bits when the parent window has a different depth - .colormap(visual_info.color_map) - .border_pixel(0), - )?; - xcb_connection.conn.map_window(window_id.get())?; + Ok(()) + } +} - // Change window title - let title = options.title; +pub fn create_window_inner( + options: WindowBuilder, xcb_connection: Rc, parent: Option, +) -> Result, Box> { + // Get screen information + let screen = xcb_connection.screen(); + let parent_id = parent.unwrap_or(screen.root); + + let gc_id = xcb_connection.conn.generate_id()?; + xcb_connection.conn.create_gc( + gc_id, + parent_id, + &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), + )?; + + let scaling = xcb_connection.get_scaling(); + let physical_size = options.size.to_physical(scaling); + + #[cfg(feature = "opengl")] + let visual_info = + WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; + + #[cfg(not(feature = "opengl"))] + let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; + + let Some(window_id) = NonZero::new(xcb_connection.conn.generate_id()?) else { + unreachable!(); + }; + + xcb_connection.conn.create_window( + visual_info.visual_depth, + window_id.get(), + parent_id, + 0, // x coordinate of the new window + 0, // y coordinate of the new window + physical_size.width, // window width + physical_size.height, // window height + 0, // window border + WindowClass::INPUT_OUTPUT, + visual_info.visual_id, + &CreateWindowAux::new() + .event_mask( + EventMask::EXPOSURE + | EventMask::POINTER_MOTION + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::KEY_PRESS + | EventMask::KEY_RELEASE + | EventMask::STRUCTURE_NOTIFY + | EventMask::ENTER_WINDOW + | EventMask::LEAVE_WINDOW + | EventMask::FOCUS_CHANGE, + ) + // As mentioned above, these two values are needed to be able to create a window + // with a depth of 32-bits when the parent window has a different depth + .colormap(visual_info.color_map) + .border_pixel(0), + )?; + xcb_connection.conn.map_window(window_id.get())?; + + // Change window title + if let Some(title) = options.title { xcb_connection.conn.change_property8( PropMode::REPLACE, window_id.get(), @@ -196,63 +198,50 @@ impl Window { AtomEnum::STRING, title.as_bytes(), )?; + } - xcb_connection.conn.change_property32( - PropMode::REPLACE, - window_id.get(), - xcb_connection.atoms.WM_PROTOCOLS, - AtomEnum::ATOM, - &[xcb_connection.atoms.WM_DELETE_WINDOW], - )?; - - // Enable drag and drop (TODO: Make this toggleable?) - xcb_connection.conn.change_property32( - PropMode::REPLACE, - window_id.get(), - xcb_connection.atoms.XdndAware, - AtomEnum::ATOM, - &[5u32], // Latest version; hasn't changed since 2002 - )?; - - xcb_connection.conn.flush()?; - let xcb_connection = Rc::new(xcb_connection); - + xcb_connection.conn.change_property32( + PropMode::REPLACE, + window_id.get(), + xcb_connection.atoms.WM_PROTOCOLS, + AtomEnum::ATOM, + &[xcb_connection.atoms.WM_DELETE_WINDOW], + )?; + + // Enable drag and drop (TODO: Make this toggleable?) + xcb_connection.conn.change_property32( + PropMode::REPLACE, + window_id.get(), + xcb_connection.atoms.XdndAware, + AtomEnum::ATOM, + &[5u32], // Latest version; hasn't changed since 2002 + )?; + + xcb_connection.conn.flush()?; + + #[cfg(feature = "opengl")] + let gl_context = visual_info.fb_config.map(|fb_config| { + use std::ffi::c_ulong; + + let window = window_id.get() as c_ulong; + + // Because of the visual negotation we had to take some extra steps to create this context + let context = + super::gl::GlContextInner::create(window, Rc::clone(&xcb_connection), fb_config) + .expect("Could not create OpenGL context"); + + Rc::new(context) + }); + + Ok(Rc::new(WindowInner::new( + xcb_connection, + window_id, + physical_size, + scaling, + visual_info.visual_id.try_into()?, #[cfg(feature = "opengl")] - let gl_context = visual_info.fb_config.map(|fb_config| { - use std::ffi::c_ulong; - - let window = window_id.get() as c_ulong; - - // Because of the visual negotation we had to take some extra steps to create this context - let context = - super::gl::GlContextInner::create(window, Rc::clone(&xcb_connection), fb_config) - .expect("Could not create OpenGL context"); - - Rc::new(context) - }); - - let inner = Rc::new(WindowInner::new( - xcb_connection, - window_id, - physical_size, - scaling, - visual_info.visual_id.try_into()?, - #[cfg(feature = "opengl")] - gl_context, - )); - - let handler = build(WindowContext::new(Rc::clone(&inner))); - - // Send an initial window resized event so the user is alerted of - // the correct dpi scaling. - handler.resized(WindowSize::from_physical(physical_size.cast(), scaling)); - - let _ = tx.send(Ok(window_id)); - - EventLoop::new(inner, handler, parent_handle, xkb_state).run()?; - - Ok(()) - } + gl_context, + ))) } pub fn copy_to_clipboard(_data: &str) { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs new file mode 100644 index 00000000..48f06907 --- /dev/null +++ b/src/platform/x11/window_thread.rs @@ -0,0 +1,32 @@ +use crate::platform::x11::window_thread_channel::{thread_channel, HandleChannel, ThreadChannel}; +use crate::WindowBuilder; +use calloop::EventLoop; +use std::error::Error; +use std::thread::{JoinHandle, Thread}; + +pub struct WindowThreadHandle { + join_handle: JoinHandle<()>, + channel: HandleChannel, +} + +impl WindowThreadHandle { + pub fn start(builder: WindowBuilder) -> Result> { + let (thread_channel, mut handle) = thread_channel(); + + let join_handle = std::thread::spawn(move || window_thread(builder, thread_channel)); + + let (wid, sig) = handle.wait_for_create(); + + Ok(todo!()) + } +} + +fn window_thread(builder: WindowBuilder, thread_channel: ThreadChannel) { + // Create Event Loop + // Create Window + // Initialize handler + + // Send Created event + // Add channel + X11 connection to event loop + // Start loop +} diff --git a/src/platform/x11/window_thread_channel.rs b/src/platform/x11/window_thread_channel.rs new file mode 100644 index 00000000..10e40552 --- /dev/null +++ b/src/platform/x11/window_thread_channel.rs @@ -0,0 +1,35 @@ +use calloop::LoopSignal; +use std::num::NonZeroU32; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; + +enum ThreadToHandleMessage { + WindowCreated { window_id: NonZeroU32, loop_signal: LoopSignal }, +} + +enum HandleToThreadMessage {} + +pub struct ThreadChannel { + pub send: mpsc::Sender, + pub recv: calloop::channel::Channel, +} + +pub struct HandleChannel { + pub recv: Receiver, + pub send: calloop::channel::Sender, +} + +impl HandleChannel { + pub fn wait_for_create(&mut self) -> (NonZeroU32, LoopSignal) { + loop { + let msg = self.recv.recv().unwrap(); + if let ThreadToHandleMessage::WindowCreated { window_id, loop_signal } = msg { + return (window_id, loop_signal); + } + } + } +} + +pub fn thread_channel() -> (ThreadChannel, HandleChannel) { + todo!() +} diff --git a/src/window_builder.rs b/src/window_builder.rs index a70ffebc..da531416 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -1,4 +1,5 @@ -use crate::HostHandler; +use super::*; +use crate::platform::ParentWindowHandle; use dpi::{LogicalSize, Size}; use raw_window_handle::HasWindowHandle; @@ -6,11 +7,10 @@ use raw_window_handle::HasWindowHandle; pub struct WindowBuilder { pub title: Option, pub size: Size, - pub host_handler: Option>, - pub parent: Option>, - pub parented: bool, + parented: bool, + parent: Option, #[cfg(feature = "opengl")] - pub gl_config: Option, + gl_config: Option, } impl WindowBuilder { @@ -45,16 +45,11 @@ impl WindowBuilder { self } - pub fn with_parent(mut self, parent: impl HasWindowHandle + 'static) -> Self { - self.parent = Some(Box::new(parent)); + pub fn with_parent(mut self, parent: &impl HasWindowHandle) -> Self { + self.parent = Some(ParentWindowHandle::extract(parent)); self.parented = true; self } - - pub fn hosted(mut self, handler: impl HostHandler) -> Self { - self.host_handler = Some(Box::new(handler)); - self - } } impl Default for WindowBuilder { @@ -62,7 +57,6 @@ impl Default for WindowBuilder { Self { title: None, size: LogicalSize::new(420.0, 240.0).into(), - host_handler: None, parent: None, parented: false, #[cfg(feature = "opengl")] diff --git a/src/window_open_options.rs b/src/window_open_options.rs deleted file mode 100644 index e276f9d8..00000000 --- a/src/window_open_options.rs +++ /dev/null @@ -1,78 +0,0 @@ -use dpi::{LogicalSize, Size}; - -#[cfg(feature = "opengl")] -use crate::gl::GlConfig; - -/// The dpi scaling policy of the window -#[derive(Default, Debug, Clone, Copy, PartialEq)] -pub enum WindowScalePolicy { - /// Use the system's dpi scale factor - #[default] - SystemScaleFactor, - /// Use the given dpi scale factor (e.g. `1.0` = 96 dpi) - ScaleFactor(f64), -} - -/// The options for opening a new window -#[derive(Debug, Clone, PartialEq)] -#[non_exhaustive] -pub struct WindowOpenOptions { - pub title: String, - - /// The size of the window, either in physical or logical coordinates - pub size: Size, - - /// The dpi scaling policy - pub scale: WindowScalePolicy, - - /// If provided, then an OpenGL context will be created for this window. You'll be able to - /// access this context through [crate::WindowContext::gl_context]. - /// - /// By default, this is set to `None`. - #[cfg(feature = "opengl")] - pub gl_config: Option, -} - -impl WindowOpenOptions { - #[inline] - pub fn new() -> Self { - Self::default() - } - - #[inline] - pub fn with_title(mut self, title: impl Into) -> Self { - self.title = title.into(); - self - } - - #[inline] - pub fn with_size(mut self, size: impl Into) -> Self { - self.size = size.into(); - self - } - - #[inline] - pub fn with_scale_policy(mut self, scale: WindowScalePolicy) -> Self { - self.scale = scale; - self - } - - #[cfg(feature = "opengl")] - #[inline] - pub fn with_gl_config(mut self, gl_config: impl Into>) -> Self { - self.gl_config = gl_config.into(); - self - } -} - -impl Default for WindowOpenOptions { - fn default() -> Self { - Self { - title: String::from("baseview window"), - size: LogicalSize { width: 500.0, height: 400.0 }.into(), - scale: WindowScalePolicy::default(), - #[cfg(feature = "opengl")] - gl_config: None, - } - } -} From e96dbff971ccf39aff19f4d692c49ba1bbee297e Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 9 Jul 2026 01:43:45 +0200 Subject: [PATCH 08/10] wip --- examples/open_parented/src/main.rs | 2 +- src/handler.rs | 19 +++- src/platform/x11/event_loop.rs | 126 +++++++++++++--------- src/platform/x11/mod.rs | 10 +- src/platform/x11/window.rs | 125 +++------------------ src/platform/x11/window_thread.rs | 49 ++++++--- src/platform/x11/window_thread_channel.rs | 6 ++ src/window.rs | 5 +- src/window_builder.rs | 4 +- src/wrappers.rs | 3 - src/wrappers/connection_poller.rs | 66 ------------ 11 files changed, 167 insertions(+), 248 deletions(-) delete mode 100644 src/wrappers/connection_poller.rs diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index 5a3c62f3..fb005a18 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -24,7 +24,7 @@ impl ParentWindowHandler { let window_open_options = WindowBuilder::new() .with_size(LogicalSize::new(256, 256)) .with_title("baseview child") - .with_parent(window); + .with_parent(&window); let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new); diff --git a/src/handler.rs b/src/handler.rs index a3ee7087..58588a64 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,24 @@ -use crate::{Event, EventStatus, WindowSize}; +use crate::{Event, EventStatus, WindowContext, WindowSize}; pub trait WindowHandler: 'static { fn on_frame(&self); fn resized(&self, new_size: WindowSize); fn on_event(&self, event: Event) -> EventStatus; } + +#[allow(unused)] +pub struct WindowHandlerBuilder { + inner: Box Box + Send + 'static>, +} + +impl WindowHandlerBuilder { + pub fn new( + f: impl FnOnce(WindowContext) -> H + Send + 'static, + ) -> WindowHandlerBuilder { + Self { inner: Box::new(|c| Box::new(f(c))) } + } + + pub fn build(self, ctx: WindowContext) -> Box { + (self.inner)(ctx) + } +} diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 63163f9f..df337665 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -2,49 +2,71 @@ use super::drag_n_drop::DragNDropState; use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; use super::*; -use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; +use calloop::generic::Generic; +use calloop::timer::{TimeoutAction, Timer}; +use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken}; use dpi::{PhysicalPosition, PhysicalSize}; use std::error::Error; use std::rc::Rc; use std::time::{Duration, Instant}; use x11rb::connection::Connection; +use x11rb::errors::ConnectionError; use x11rb::protocol::Event as XEvent; pub(crate) struct EventLoop { handler: Box, window: Rc, - parent_handle: Option, new_physical_size: Option>, - frame_interval: Duration, event_loop_running: bool, + frame_timer: RegistrationToken, + loop_handle: LoopHandle<'static, Self>, + loop_signal: LoopSignal, + drag_n_drop: DragNDropState, xkb_state: Option, } +const FRAME_INTERVAL: Duration = Duration::from_millis(15); + impl EventLoop { pub fn new( - window: Rc, handler: impl WindowHandler, parent_handle: Option, - xkb_state: Option, + window: Rc, handler: Box, + inner: &mut calloop::EventLoop<'static, Self>, ) -> Self { + let loop_handle = inner.handle(); + let frame_timer = loop_handle + .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| e.handle_frame(i)) + .unwrap(); + + loop_handle.disable(&frame_timer).unwrap(); + + loop_handle + .insert_source( + Generic::new_with_error(window.connection.conn.clone(), Interest::READ, Mode::Edge), + |_, _, e| e.handle_connection_event_ready(), + ) + .unwrap(); + Self { - window, - handler: Box::new(handler), - parent_handle, - frame_interval: Duration::from_millis(15), + loop_handle, + loop_signal: inner.get_signal(), + handler, + frame_timer, event_loop_running: false, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, - xkb_state, + xkb_state: XkbcommonState::new(&window.connection), + window, } } #[inline] - fn drain_xcb_events(&mut self) -> Result<(), Box> { + fn drain_xcb_events(&mut self) -> Result<(), ConnectionError> { // the X server has a tendency to send spurious/extraneous configure notify events when a // window is resized, and we need to batch those together and just send one resize event // when they've all been coalesced. @@ -66,54 +88,56 @@ impl EventLoop { } // Event loop - pub fn run(&mut self) -> Result<(), Box> { - let connection = Rc::clone(&self.window.connection); - let mut poller = ConnectionPoller::new(&connection.conn)?; - - let mut last_frame = Instant::now(); - self.event_loop_running = true; - - while self.event_loop_running { - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. The conditional here is needed because event handling and frame - // drawing is interleaved. The `poll()` function below will wait until the next frame - // can be drawn, or until the window receives an event. We thus need to manually check - // if it's already time to draw a new frame. - let next_frame = last_frame + self.frame_interval; - if Instant::now() >= next_frame { - self.handler.on_frame(); - last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); - } + pub fn run(&mut self, mut inner: calloop::EventLoop) -> Result<(), Box> { + inner.run(None, self, Self::handle_idle)?; - // Check for any events in the internal buffers - // before going to sleep: - self.drain_xcb_events()?; + Ok(()) + } - // FIXME: handle errors - if let PollStatus::ReadAvailable = poller.wait(next_frame).unwrap() { - self.drain_xcb_events()?; - } + fn handle_connection_event_ready(&mut self) -> Result { + self.drain_xcb_events()?; - // Check if the parents's handle was dropped (such as when the host - // requested the window to close) - if let Some(parent_handle) = &self.parent_handle { - if parent_handle.parent_did_drop() { - self.handle_must_close(); - self.window.close_requested.set(false); - } - } + Ok(PostAction::Continue) + } + + fn handle_frame(&mut self, previous_deadline: Instant) -> TimeoutAction { + self.handler.on_frame(); + + // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in + // the expected frame time, this will throttle down to prevent multiple frames from + // being queued up. + + let now = Instant::now(); + let next_deadline = if previous_deadline + FRAME_INTERVAL >= now { + now + FRAME_INTERVAL + } else { + previous_deadline + FRAME_INTERVAL + }; + + TimeoutAction::ToInstant(next_deadline) + } + + fn handle_idle(&mut self) { + // Check for any events in the internal buffers + // before going to sleep: + self.drain_xcb_events().unwrap(); - // Check if the user has requested the window to close - if self.window.close_requested.get() { + /* + // Check if the parents's handle was dropped (such as when the host + // requested the window to close) + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { self.handle_must_close(); self.window.close_requested.set(false); } - } + }*/ - poller.delete()?; - - Ok(()) + // Check if the user has requested the window to close + if self.window.close_requested.get() { + self.handle_must_close(); + self.loop_signal.stop(); + self.window.close_requested.set(false); + } } fn handle_xcb_event(&mut self, event: XEvent) { diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index e0f7116e..a55f7af7 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,6 +1,6 @@ mod xcb_connection; -use raw_window_handle::{DisplayHandle, HasWindowHandle, XcbWindowHandle}; +use raw_window_handle::{DisplayHandle, HasWindowHandle, RawWindowHandle, XcbWindowHandle}; use std::fmt::Formatter; use std::num::NonZero; use std::rc::Rc; @@ -65,6 +65,12 @@ pub struct ParentWindowHandle { impl ParentWindowHandle { pub fn extract(window: &impl HasWindowHandle) -> Self { - todo!() + let window_id = match window.window_handle().unwrap().as_raw() { + RawWindowHandle::Xlib(h) => h.window as u32, + RawWindowHandle::Xcb(h) => h.window.get(), + h => panic!("unsupported parent handle type {:?}", h), + }; + + Self { window_id } } } diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index 364cbe0b..15012899 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -1,12 +1,6 @@ -use raw_window_handle::{HasWindowHandle, RawWindowHandle}; -use std::cell::Cell; use std::error::Error; use std::num::NonZero; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc; -use std::sync::Arc; -use std::thread::{self, JoinHandle}; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ @@ -14,128 +8,43 @@ use x11rb::protocol::xproto::{ }; use x11rb::wrapper::ConnectionExt as _; +use super::visual_info::WindowVisualConfig; use super::X11Connection; -use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; -use crate::context::WindowContext; +use crate::handler::WindowHandlerBuilder; use crate::platform::x11::window_shared::WindowInner; -use crate::{WindowBuilder, WindowHandler, WindowSize}; - -impl WindowHandle { - pub fn close(&self) { - self.close_requested.store(true, Ordering::Relaxed); - if let Some(event_loop) = self.event_loop_handle.take() { - let _ = event_loop.join(); - } - } - - pub fn is_open(&self) -> bool { - self.is_open.load(Ordering::Relaxed) - } -} - -pub(crate) struct ParentHandle { - close_requested: Arc, - is_open: Arc, -} - -impl ParentHandle { - pub fn new() -> (Self, WindowHandle) { - let close_requested = Arc::new(AtomicBool::new(false)); - let is_open = Arc::new(AtomicBool::new(true)); - let handle = WindowHandle { - window_id: None, - event_loop_handle: None.into(), - close_requested: Arc::clone(&close_requested), - is_open: Arc::clone(&is_open), - }; - - (Self { close_requested, is_open }, handle) - } +use crate::platform::x11::window_thread::WindowThreadHandle; +use crate::{WindowBuilder, WindowHandler}; - pub fn parent_did_drop(&self) -> bool { - self.close_requested.load(Ordering::Relaxed) - } +pub struct Window { + thread: WindowThreadHandle, } -impl Drop for ParentHandle { - fn drop(&mut self) { - self.is_open.store(false, Ordering::Relaxed); - } -} - -pub struct Window; - -type WindowOpenResult = Result, ()>; - impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - // Convert parent into something that X understands - let parent_id = match parent.window_handle().unwrap().as_raw() { - RawWindowHandle::Xlib(h) => h.window as u32, - RawWindowHandle::Xcb(h) => h.window.get(), - h => panic!("unsupported parent handle type {:?}", h), - }; - - let (tx, rx) = mpsc::sync_channel::(1); - let (parent_handle, mut window_handle) = ParentHandle::new(); - let join_handle = thread::spawn(move || { - Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)) - .unwrap(); - }); - - let raw_window_handle = rx.recv().unwrap().unwrap(); - window_handle.window_id = Some(raw_window_handle); - window_handle.event_loop_handle = Some(join_handle).into(); - window_handle + pub fn create_window(builder: WindowBuilder, handler: WindowHandlerBuilder) -> Self { + Self { thread: WindowThreadHandle::start(builder, handler).unwrap() } } - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - let (tx, rx) = mpsc::sync_channel::(1); - - let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx, None).unwrap(); - }); - - let _ = rx.recv().unwrap().unwrap(); - - thread.join().unwrap_or_else(|err| { - eprintln!("Window thread panicked: {:#?}", err); - }); + pub fn is_open(&self) -> bool { + todo!() } - fn window_thread( - parent: Option, options: WindowBuilder, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - tx: mpsc::SyncSender, parent_handle: Option, - ) -> Result<(), Box> { - // Connect to the X server - let xcb_connection = Rc::new(X11Connection::new()?); - // Setup xkbcommon - let xkb_state = crate::wrappers::xkbcommon::XkbcommonState::new(&xcb_connection); - - let inner = create_window_inner(options, xcb_connection, parent)?; - - let handler = build(WindowContext::new(Rc::clone(&inner))); - - let _ = tx.send(Ok(inner.window_id)); + pub fn destroy(self) { + todo!() + } - EventLoop::new(inner, handler, parent_handle, xkb_state).run()?; + pub fn run_until_closed(self) -> Result<(), Box> { + self.thread.join(); Ok(()) } } pub fn create_window_inner( - options: WindowBuilder, xcb_connection: Rc, parent: Option, + options: WindowBuilder, xcb_connection: Rc, ) -> Result, Box> { // Get screen information let screen = xcb_connection.screen(); - let parent_id = parent.unwrap_or(screen.root); + let parent_id = options.parent.map(|p| p.window_id).unwrap_or(screen.root); let gc_id = xcb_connection.conn.generate_id()?; xcb_connection.conn.create_gc( diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 48f06907..9ebcf8eb 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -1,30 +1,55 @@ +use crate::handler::WindowHandlerBuilder; +use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::window_thread_channel::{thread_channel, HandleChannel, ThreadChannel}; -use crate::WindowBuilder; -use calloop::EventLoop; +use crate::platform::{create_window_inner, X11Connection}; +use crate::{WindowBuilder, WindowContext}; +use calloop::LoopSignal; use std::error::Error; -use std::thread::{JoinHandle, Thread}; +use std::num::NonZeroU32; +use std::rc::Rc; +use std::thread::JoinHandle; pub struct WindowThreadHandle { join_handle: JoinHandle<()>, channel: HandleChannel, + window_id: NonZeroU32, + loop_signal: LoopSignal, } impl WindowThreadHandle { - pub fn start(builder: WindowBuilder) -> Result> { - let (thread_channel, mut handle) = thread_channel(); + pub fn start( + builder: WindowBuilder, handler: WindowHandlerBuilder, + ) -> Result> { + let (thread_channel, mut handle_channel) = thread_channel(); - let join_handle = std::thread::spawn(move || window_thread(builder, thread_channel)); + let join_handle = + std::thread::spawn(move || window_thread(builder, handler, thread_channel)); - let (wid, sig) = handle.wait_for_create(); + let (wid, sig) = handle_channel.wait_for_create(); - Ok(todo!()) + Ok(Self { join_handle, channel: handle_channel, window_id: wid, loop_signal: sig }) + } + + pub fn join(self) { + self.join_handle.join().unwrap(); } } -fn window_thread(builder: WindowBuilder, thread_channel: ThreadChannel) { - // Create Event Loop - // Create Window - // Initialize handler +fn window_thread( + builder: WindowBuilder, handler: WindowHandlerBuilder, mut thread_channel: ThreadChannel, +) { + // Connect to the X server + let xcb_connection = Rc::new(X11Connection::new().unwrap()); + let inner = create_window_inner(builder, xcb_connection).unwrap(); + let handler = handler.build(WindowContext::new(Rc::clone(&inner))); + + let mut event_loop_inner = calloop::EventLoop::try_new().unwrap(); + + thread_channel.send_create(inner.window_id, event_loop_inner.get_signal()); + + let mut event_loop = EventLoop::new(inner, handler, &mut event_loop_inner); + + event_loop.run(event_loop_inner).unwrap(); // Send Created event // Add channel + X11 connection to event loop diff --git a/src/platform/x11/window_thread_channel.rs b/src/platform/x11/window_thread_channel.rs index 10e40552..2fc6e20d 100644 --- a/src/platform/x11/window_thread_channel.rs +++ b/src/platform/x11/window_thread_channel.rs @@ -14,6 +14,12 @@ pub struct ThreadChannel { pub recv: calloop::channel::Channel, } +impl ThreadChannel { + pub fn send_create(&mut self, window_id: NonZeroU32, loop_signal: LoopSignal) { + self.send.send(ThreadToHandleMessage::WindowCreated { window_id, loop_signal }).unwrap(); + } +} + pub struct HandleChannel { pub recv: Receiver, pub send: calloop::channel::Sender, diff --git a/src/window.rs b/src/window.rs index 58d23179..107062d8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,4 +1,5 @@ use super::*; +use crate::handler::WindowHandlerBuilder; use dpi::Size; use raw_window_handle::HasWindowHandle; use std::error::Error; @@ -64,7 +65,7 @@ impl Window { } pub fn create_window( - builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + 'static, + builder: WindowBuilder, handler: impl FnOnce(WindowContext) -> H + Send + 'static, ) -> Window { - Window::new(platform::Window::create_window(builder, handler)) + Window::new(platform::Window::create_window(builder, WindowHandlerBuilder::new(handler))) } diff --git a/src/window_builder.rs b/src/window_builder.rs index da531416..a593ff2c 100644 --- a/src/window_builder.rs +++ b/src/window_builder.rs @@ -8,9 +8,9 @@ pub struct WindowBuilder { pub title: Option, pub size: Size, parented: bool, - parent: Option, + pub(crate) parent: Option, #[cfg(feature = "opengl")] - gl_config: Option, + pub(crate) gl_config: Option, } impl WindowBuilder { diff --git a/src/wrappers.rs b/src/wrappers.rs index d5d30536..01483e1e 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -23,9 +23,6 @@ pub mod xkbcommon; #[cfg(all(target_os = "linux", feature = "opengl"))] pub mod glx; -#[cfg(target_os = "linux")] -pub mod connection_poller; - /// Wrappers and utilities around the Win32 API #[cfg(target_os = "windows")] pub mod win32; diff --git a/src/wrappers/connection_poller.rs b/src/wrappers/connection_poller.rs deleted file mode 100644 index 19a8b913..00000000 --- a/src/wrappers/connection_poller.rs +++ /dev/null @@ -1,66 +0,0 @@ -use polling::{Event, Events, Poller}; -use std::io; -use std::os::fd::{AsFd, BorrowedFd}; -use std::time::Instant; - -pub struct ConnectionPoller<'a> { - poller: Poller, - events: Events, - fd: BorrowedFd<'a>, -} - -const CONNECTION_KEY: usize = 42; - -impl<'a> ConnectionPoller<'a> { - pub fn new(source: &'a impl AsFd) -> io::Result { - let poller = Poller::new()?; - let fd = source.as_fd(); - unsafe { poller.add(&fd, Event::readable(CONNECTION_KEY))? }; - - Ok(Self { poller, fd, events: Events::new() }) - } - - pub fn wait(&mut self, deadline: Instant) -> io::Result { - self.events.clear(); - // NOTE: polling crate already handles retrying on EINTR - let new_events_count = self.poller.wait_deadline(&mut self.events, deadline)?; - - if new_events_count == 0 { - return Ok(PollStatus::Nothing); - } - - for event in self.events.iter() { - if event.key != CONNECTION_KEY { - continue; - } - - if let Some(true) = event.is_err() { - panic!("xcb connection poll error") - } - - if event.is_interrupt() { - return Ok(PollStatus::ConnectionClosed); - } - - return Ok(PollStatus::ReadAvailable); - } - - Ok(PollStatus::Nothing) - } - - pub fn delete(self) -> io::Result<()> { - self.poller.delete(self.fd) - } -} - -impl<'a> Drop for ConnectionPoller<'a> { - fn drop(&mut self) { - let _ = self.poller.delete(self.fd); - } -} - -pub enum PollStatus { - Nothing, - ReadAvailable, - ConnectionClosed, -} From 83e425a17ddec98b72915ed54788224b245ba15d Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 9 Jul 2026 01:59:21 +0200 Subject: [PATCH 09/10] wip --- examples/render_wgpu/src/main.rs | 4 +++- src/platform/x11/event_loop.rs | 7 ++----- src/platform/x11/window_thread_channel.rs | 5 ++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 2e4ed59e..6c7af347 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -207,7 +207,9 @@ fn main() { let window_open_options = WindowBuilder::new().with_title("WGPU on Baseview").with_size(LogicalSize::new(512, 512)); - baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c))); + baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c))) + .run_until_closed() + .unwrap(); } fn log_event(event: &Event) { diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index df337665..959b81b1 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -20,14 +20,12 @@ pub(crate) struct EventLoop { window: Rc, new_physical_size: Option>, - event_loop_running: bool, frame_timer: RegistrationToken, loop_handle: LoopHandle<'static, Self>, loop_signal: LoopSignal, drag_n_drop: DragNDropState, - xkb_state: Option, } @@ -43,7 +41,7 @@ impl EventLoop { .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| e.handle_frame(i)) .unwrap(); - loop_handle.disable(&frame_timer).unwrap(); + //loop_handle.disable(&frame_timer).unwrap(); loop_handle .insert_source( @@ -57,7 +55,6 @@ impl EventLoop { loop_signal: inner.get_signal(), handler, frame_timer, - event_loop_running: false, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), @@ -323,7 +320,7 @@ impl EventLoop { fn handle_must_close(&mut self) { self.handle_event(Event::Window(WindowEvent::WillClose)); - self.event_loop_running = false; + self.loop_signal.stop(); } } diff --git a/src/platform/x11/window_thread_channel.rs b/src/platform/x11/window_thread_channel.rs index 2fc6e20d..f0b3e0b7 100644 --- a/src/platform/x11/window_thread_channel.rs +++ b/src/platform/x11/window_thread_channel.rs @@ -37,5 +37,8 @@ impl HandleChannel { } pub fn thread_channel() -> (ThreadChannel, HandleChannel) { - todo!() + let (st, rt) = calloop::channel::channel(); + let (send, recv) = mpsc::channel::(); + + (ThreadChannel { send, recv: rt }, HandleChannel { recv, send: st }) } From 65c018259023a0c8d975764ce6dd83197ae4ee51 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 11 Jul 2026 05:03:49 +0200 Subject: [PATCH 10/10] wip --- src/platform/x11/window.rs | 6 +----- src/platform/x11/window_thread.rs | 26 ++++++++++++++++++++++---- src/window.rs | 25 ++++++++++++++++++------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index 15012899..5514e175 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -13,7 +13,7 @@ use super::X11Connection; use crate::handler::WindowHandlerBuilder; use crate::platform::x11::window_shared::WindowInner; use crate::platform::x11::window_thread::WindowThreadHandle; -use crate::{WindowBuilder, WindowHandler}; +use crate::WindowBuilder; pub struct Window { thread: WindowThreadHandle, @@ -28,10 +28,6 @@ impl Window { todo!() } - pub fn destroy(self) { - todo!() - } - pub fn run_until_closed(self) -> Result<(), Box> { self.thread.join(); diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 9ebcf8eb..c324ec0b 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -10,7 +10,7 @@ use std::rc::Rc; use std::thread::JoinHandle; pub struct WindowThreadHandle { - join_handle: JoinHandle<()>, + join_handle: Option>, channel: HandleChannel, window_id: NonZeroU32, loop_signal: LoopSignal, @@ -27,11 +27,29 @@ impl WindowThreadHandle { let (wid, sig) = handle_channel.wait_for_create(); - Ok(Self { join_handle, channel: handle_channel, window_id: wid, loop_signal: sig }) + Ok(Self { + join_handle: Some(join_handle), + channel: handle_channel, + window_id: wid, + loop_signal: sig, + }) } - pub fn join(self) { - self.join_handle.join().unwrap(); + pub fn join(mut self) { + if let Some(handle) = self.join_handle.take() { + handle.join().unwrap(); + } + } +} + +impl Drop for WindowThreadHandle { + fn drop(&mut self) { + self.loop_signal.stop(); + self.loop_signal.wakeup(); + + if let Some(handle) = self.join_handle.take() { + handle.join().unwrap(); + } } } diff --git a/src/window.rs b/src/window.rs index 107062d8..b3651155 100644 --- a/src/window.rs +++ b/src/window.rs @@ -6,62 +6,73 @@ use std::error::Error; use std::marker::PhantomData; pub struct Window { - window_handle: platform::Window, + inner: platform::Window, // so that WindowHandle is !Send on all platforms phantom: PhantomData<*mut ()>, } impl Window { - pub(crate) fn new(window_handle: platform::Window) -> Self { - Self { window_handle, phantom: PhantomData } + pub(crate) fn new(inner: platform::Window) -> Self { + Self { inner, phantom: PhantomData } } /// Close the window + #[inline] pub fn close(self) { - self.window_handle.destroy(); + drop(self); } + #[inline] pub fn run_until_closed(self) -> Result<(), Box> { - self.window_handle.run_until_closed() + self.inner.run_until_closed() } /// Returns `true` if the window is still open, and returns `false` /// if the window was closed/dropped. + #[inline] pub fn is_open(&self) -> bool { - self.window_handle.is_open() + self.inner.is_open() } + /* pub fn suggest_fallback_scale(&self, fallback_scale: Option) { todo!() } + #[inline] pub fn resize(&self, size: impl Into) { todo!() } + #[inline] pub fn size(&self) -> WindowSize { todo!() } + #[inline] pub fn make_floating(&self) { todo!() } + #[inline] pub fn set_parent(&self, parent: &impl HasWindowHandle) { todo!() } + #[inline] pub fn show(&self) { todo!() } + #[inline] pub fn hide(&self) { todo!() } + #[inline] pub fn set_title(&self, title: impl Into) { todo!() - } + }*/ } pub fn create_window(