baseview

repository·master·Indexed 18 days ago

https://github.com/rustaudio/baseview

A low-level, platform-independent windowing abstraction layer designed for creating audio plugin user interfaces. It abstracts platform-specific implementations such as winapi (Windows), cocoa (macOS), and xcb (Linux) to provide a lightweight API for window management, event handling, and input.

Tokens
6.2K
Snippets
20
Records
26
Agent score
63%

What's inside baseview

  1. Overview of baseview

    master
    baseview is a low-level windowing system designed specifically for creating audio plugin user interfaces (UIs). It provides a platform-independent API by abstracting away platform-specific windowing implementations such as winapi (Windows), cocoa (macOS), and xcb (Linux). The library is designed to be lightweight and stay out of the developer's way, allowing focus on UI implementation.
  2. Understand WindowSize and scaling

    master

    The WindowSize struct provides a unified way to handle window dimensions across different pixel densities. It contains:

    • physical: PhysicalSize<u32>
    • logical: LogicalSize<f64>
    • scale_factor: f64

    Scaling Fallbacks

    If the platform fails to provide an accurate scaling factor, you can suggest one using suggest_fallback_scale_factor(scale_factor: f64).

    • Win32: Used on early Windows 10 or earlier.
    • X11: Used if no Xft.dpi is set.
    • macOS: This is a no-op.
    let size = window.size();
    println!("Logical size: {:?}, Physical size: {:?}", size.logical, size.physical);
    
    // Suggest a fallback if needed
    window.suggest_fallback_scale_factor(2.0)?;
  3. Use PlatformHandle for cross-thread window access

    master

    PlatformHandle is a lightweight, cheaply cloneable, and thread-safe (Send + Sync) handle to a window and its display connection.

    Use PlatformHandle when you need to pass window information to other threads. It implements both HasWindowHandle and HasDisplayHandle from the raw-window-handle crate.

    Important Considerations:

    • Weak Handle: It is a weak handle. If the window it refers to is destroyed, methods like window_handle() will return HandleError::Unavailable.
    • Thread Safety: While it is Send and Sync, platform limitations may apply. On some platforms, calling window_handle() from a thread other than the main thread may return HandleError::Unavailable even if the window is still alive.
    • Creation: You can obtain a PlatformHandle from a WindowContext using the platform_handle() method.
    // Obtaining a PlatformHandle from WindowContext
    let handle = window_context.platform_handle();
    
    // The handle can be sent to another thread
    std::thread::spawn(move || {
        if let Ok(window_handle) = handle.window_handle() {
            // Use the window handle
        }
    });
  4. How the Window lifecycle and ownership work

    master

    In baseview, a Window manages its own lifecycle. Unlike other libraries where you might manage the event loop externally, the Window owns its associated WindowHandler and Host types.

    Key Lifecycle Rules:

    • Creation: Use Window::create or Window::create_with_host. This creates the window but does not show it. You must call .show() or .run_until_closed() to make it visible.
    • Destruction: Dropping the Window handle will always destroy the window and its associated handler/host. You can also call .close() to explicitly destroy it.
    • Unexpected Closure: A window can be destroyed before you drop the handle if:
      • The WindowHandler requests a close (e.g., via WindowContext::request_close).
      • A fatal error or panic occurs in the WindowContext.
      • The underlying platform or display server (like X11) closes the window.
    • Safety: Most Window methods return Result or become no-ops if the window has already been closed. Use Window::is_open() to check the current state.
  5. Core types and traits in baseview

    master

    The baseview crate provides a cross-platform abstraction for window management, event handling, and input. The primary entry points for users are:

    • WindowContext: Manages the platform-specific state and handles.
    • Window: Represents an individual window instance.
    • WindowHandler: A trait that must be implemented to process window events (keyboard, mouse, etc.).
    • PlatformHandle: Provides access to underlying platform resources.
    • Event: An enum representing various input and window events.
    • MouseCursor: Represents the state of the mouse pointer.
    • Clipboard: Provides access to system clipboard operations.
    • Settings: Configuration for window and platform behavior.
  6. Implement HostCallbacks to manage window lifecycle and resizing

    master

    To allow a baseview window to interact with its parent application (the host), you must implement the HostCallbacks trait. This is used to handle requests from the window to resize itself or to notify the host when the window is destroyed.

    Implement the following methods:

    • request_resize(new_size: WindowSize) -> Result<(), HandlerError>: Called when the window wants to change its size. Return Ok(()) to accept or an error to deny/cancel the resize.
    • destroyed(): Called when the window is destroyed due to external factors (e.g., lost display connection or handler crash). The host should use this signal to close its parent window.
    impl HostCallbacks for MyHost {
        fn request_resize(&mut self, new_size: WindowSize) -> Result<(), HandlerError> {
            // Handle resize logic here
            Ok(())
        }
    
        fn destroyed(&mut self) {
            // Clean up host resources
        }
    }
  7. Implement the WindowHandler trait

    master

    To handle window events, rendering, and resizing in baseview, you must implement the WindowHandler trait. This trait provides three primary callbacks:

    1. on_frame(): Called when the window needs to draw a new frame. If this returns an Err(HandlerError), the window is considered unable to render and will be closed.
    2. resized(new_size: WindowSize): Called when the window size changes. If this returns an error, baseview will attempt to revert the window and its parent to the previous size as a best effort.
    3. on_event(event: Event): Called when a window event occurs. It returns an EventStatus to indicate how the event should be handled.
    impl WindowHandler for MyHandler {
        fn on_frame(&self) -> core::result::Result<(), HandlerError> {
            // Your rendering logic here
            Ok(())
        }
    
        fn resized(&self, new_size: WindowSize) -> core::result::Result<(), HandlerError> {
            // Handle resize logic
            Ok(())
        }
    
        fn on_event(&self, event: Event) -> EventStatus {
            // Handle input events
            EventStatus::Consumed
        }
    }
  8. Handle window lifecycle with WindowEvent

    master

    The WindowEvent enum allows you to respond to changes in the window's state. Use these to manage focus or handle requests to close the application.

    Variants:

    • Focused: The window has gained focus.
    • Unfocused: The window has lost focus.
    • WillClose: The window is requesting to close.
    match window_event {
        WindowEvent::Focused => {
            // Enable UI interactions
        }
        WindowEvent::Unfocused => {
            // Pause certain animations or interactions
        }
        WindowEvent::WillClose => {
            // Perform cleanup before exiting
        }
    }
  9. Implement HostMainThreadCaller for X11 compatibility

    master

    On Linux (X11), windowing operations often need to be coordinated with the main thread. If you are building a host for baseview, implement the HostMainThreadCaller trait to allow the window thread to schedule callbacks on the main thread.

    Note: This is a no-op on Windows and macOS, as their windows already run on the main thread. On X11, call_main_thread() should be implemented to trigger the corresponding WindowHandle::host_main_thread_callback.

    impl HostMainThreadCaller for MyMainThreadCaller {
        fn call_main_thread(&mut self) {
            // Implementation to wake up/signal the main thread
        }
    }
  10. Return custom errors from `WindowHandler` using `HandlerError`

    master

    When implementing a WindowHandler, you may encounter errors that are not directly related to Baseview's internal windowing logic. To return these errors from your handler, wrap them in a HandlerError.

    HandlerError is a wrapper around a boxed dyn std::error::Error. It does not implement the std::error::Error trait itself, but it is designed to be easily converted into a Baseview Error using the ? operator or From implementation. This allows you to propagate your own application-specific errors up through the Baseview event loop.

    Key methods:

    • HandlerError::from_boxed(error: Box<dyn std::error::Error>): Creates a HandlerError from a boxed error.
    • .source(): Returns the underlying error.
    • .into_inner(): Consumes the HandlerError and returns the underlying Box<dyn std::error::Error>.
    // Example of using HandlerError in a WindowHandler implementation
    impl WindowHandler for MyHandler {
        fn handle_event(&mut self, event: Event) -> Result<(), HandlerError> {
            if let Some(data) = self.get_data() {
                // Convert a standard error into a HandlerError using the From implementation
                let custom_err = std::io::Error::new(std::io::ErrorKind::Other, "Something went wrong");
                return Err(custom_err.into());
            }
            Ok(())
        }
    }
  11. Configure a Host for Window creation

    master

    The Host struct is used to pass callbacks and platform-specific threading requirements to a baseview window. You can build a Host using a builder-like pattern before passing it to Window::create_with_host.

    • Host::new(): Creates an empty host with no callbacks.
    • .with_callbacks(callbacks): Attaches an implementation of HostCallbacks.
    • .with_main_thread(main_thread): Attaches an implementation of HostMainThreadCaller (required for X11/Linux).

    Safety Guarantee: All handlers provided to a Host are guaranteed to be destroyed alongside the window, preventing callbacks from firing after the Window is dropped.

    let host = Host::new()
        .with_callbacks(MyHostCallbacks::new())
        .with_main_thread(MyMainThreadCaller::new());
    
    // Use the host to create a window
    let window = Window::create_with_host(host, ...);
  12. Create a Window with a Host using Window::create_with_host

    master

    If you are building a plugin or a system where the window is hosted by another application, use Window::create_with_host. This allows you to pass an optional Host containing callbacks for the hosting system.

    let settings = WindowSettings::default();
    let host = Host::default(); // Or your custom host
    
    let window = Window::create_with_host(settings, |context| {
        Ok(MyHandler::new(context))
    }, host)?;
    
    window.show()?;