i-slint-core 0.2.4

Internal Slint Runtime Library.
Documentation
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

/*!
This module contains types that are public and re-exported in the slint-rs as well as the slint-interpreter crate as public API.
*/

use alloc::boxed::Box;
use alloc::rc::Rc;

use crate::component::ComponentVTable;
use crate::window::WindowRc;

/// This enum describes a low-level access to specific graphics APIs used
/// by the renderer.
#[derive(Clone)]
#[non_exhaustive]
pub enum GraphicsAPI<'a> {
    /// The rendering is done using OpenGL.
    NativeOpenGL {
        /// Use this function pointer to obtain access to the OpenGL implementation - similar to `eglGetProcAddress`.
        get_proc_address: &'a dyn Fn(&str) -> *const core::ffi::c_void,
    },
    /// The rendering is done on a HTML Canvas element using WebGL.
    WebGL {
        /// The DOM element id of the HTML Canvas element used for rendering.
        canvas_element_id: &'a str,
        /// The drawing context type used on the HTML Canvas element for rendering. This is the argument to the
        /// `getContext` function on the HTML Canvas element.
        context_type: &'a str,
    },
}

impl<'a> core::fmt::Debug for GraphicsAPI<'a> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            GraphicsAPI::NativeOpenGL { .. } => write!(f, "GraphicsAPI::NativeOpenGL"),
            GraphicsAPI::WebGL { context_type, .. } => {
                write!(f, "GraphicsAPI::WebGL(context_type = {})", context_type)
            }
        }
    }
}

/// This enum describes the different rendering states, that will be provided
/// to the parameter of the callback for `set_rendering_notifier` on the `slint::Window`.
#[derive(Debug, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum RenderingState {
    /// The window has been created and the graphics adapter/context initialized. When OpenGL
    /// is used for rendering, the context will be current.
    RenderingSetup,
    /// The scene of items is about to be rendered.  When OpenGL
    /// is used for rendering, the context will be current.
    BeforeRendering,
    /// The scene of items was rendered, but the back buffer was not sent for display presentation
    /// yet (for example GL swap buffers). When OpenGL is used for rendering, the context will be current.
    AfterRendering,
    /// The window will be destroyed and/or graphics resources need to be released due to other
    /// constraints.
    RenderingTeardown,
}

/// Internal trait that's used to map rendering state callbacks to either a Rust-API provided
/// impl FnMut or a struct that invokes a C callback and implements Drop to release the closure
/// on the C++ side.
pub trait RenderingNotifier {
    /// Called to notify that rendering has reached a certain state.
    fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI);
}

impl<F: FnMut(RenderingState, &GraphicsAPI)> RenderingNotifier for F {
    fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI) {
        self(state, graphics_api)
    }
}

/// This enum describes the different error scenarios that may occur when the applicaton
/// registers a rendering notifier on a [`crate::Window`](struct.Window.html).
#[derive(Debug, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum SetRenderingNotifierError {
    /// The rendering backend does not support rendering notifiers.
    Unsupported,
    /// There is already a rendering notifier set, multiple notifiers are not supported.
    AlreadySet,
}

/// This type represents a window towards the windowing system, that's used to render the
/// scene of a component. It provides API to control windowing system specific aspects such
/// as the position on the screen.
#[repr(transparent)]
pub struct Window(WindowRc);

#[doc(hidden)]
impl From<WindowRc> for Window {
    fn from(window: WindowRc) -> Self {
        Self(window)
    }
}

/// This enum describes whether a Window is allowed to be hidden when the user tries to close the window.
/// It is the return type of the callback provided to [Window::on_close_requested].
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub enum CloseRequestResponse {
    HideWindow,
    KeepWindowShown,
}

impl Default for CloseRequestResponse {
    fn default() -> Self {
        Self::HideWindow
    }
}

impl Window {
    /// Registers the window with the windowing system in order to make it visible on the screen.
    pub fn show(&self) {
        self.0.show();
    }

    /// De-registers the window from the windowing system, therefore hiding it.
    pub fn hide(&self) {
        self.0.hide();
    }

    /// This function allows registering a callback that's invoked during the different phases of
    /// rendering. This allows custom rendering on top or below of the scene.
    pub fn set_rendering_notifier(
        &self,
        callback: impl FnMut(RenderingState, &GraphicsAPI) + 'static,
    ) -> Result<(), SetRenderingNotifierError> {
        self.0.set_rendering_notifier(Box::new(callback))
    }

    /// This function allows registering a callback that's invoked when the user tries to close a window.
    /// The callback has to return a [CloseRequestResponse].
    pub fn on_close_requested(&self, callback: impl FnMut() -> CloseRequestResponse + 'static) {
        self.0.on_close_requested(callback);
    }

    /// This function issues a request to the windowing system to redraw the contents of the window.
    pub fn request_redraw(&self) {
        self.0.request_redraw();

        // When this function is called by the user, we want it to translate to a requestAnimationFrame()
        // on the web. If called through the rendering notifier (so from within the event loop processing),
        // unfortunately winit will only do that if set the control flow to Poll. This hack achieves that.
        // Similarly, the winit win32 event loop doesn't queue the redraw request and needs a Poll nudge.
        #[cfg(any(target_arch = "wasm32", target_os = "windows"))]
        crate::animations::CURRENT_ANIMATION_DRIVER
            .with(|driver| driver.set_has_active_animations());
    }
}

impl crate::window::WindowHandleAccess for Window {
    fn window_handle(&self) -> &Rc<crate::window::Window> {
        &self.0
    }
}
/// This trait is used to obtain references to global singletons exported in `.slint`
/// markup. Alternatively, you can use [`ComponentHandle::global`] to obtain access.
///
/// This trait is implemented by the compiler for each global singleton that's exported.
///
/// # Example
/// The following example of `.slint` markup defines a global singleton called `Palette`, exports
/// it and modifies it from Rust code:
/// ```rust
/// # i_slint_backend_testing::init();
/// slint::slint!{
/// export global Palette := {
///     property<color> foreground-color;
///     property<color> background-color;
/// }
///
/// export App := Window {
///    background: Palette.background-color;
///    Text {
///       text: "Hello";
///       color: Palette.foreground-color;
///    }
///    // ...
/// }
/// }
/// let app = App::new();
/// app.global::<Palette>().set_background_color(slint::Color::from_rgb_u8(0, 0, 0));
///
/// // alternate way to access the global singleton:
/// Palette::get(&app).set_foreground_color(slint::Color::from_rgb_u8(255, 255, 255));
/// ```
///
/// See also the [language reference for global singletons](docs/langref/index.html#global-singletons) for more information.
///
/// **Note:** Only globals that are exported or re-exported from the main .slint file will
/// be exposed in the API
pub trait Global<'a, Component> {
    /// Returns a reference that's tied to the life time of the provided component.
    fn get(component: &'a Component) -> Self;
}

/// This trait describes the common public API of a strongly referenced Slint component.
/// It allows creating strongly-referenced clones, a conversion into/ a weak pointer as well
/// as other convenience functions.
///
/// This trait is implemented by the [generated component](mod@crate#generated-components)
pub trait ComponentHandle {
    /// The type of the generated component.
    #[doc(hidden)]
    type Inner;
    /// Returns a new weak pointer.
    fn as_weak(&self) -> Weak<Self>
    where
        Self: Sized;

    /// Returns a clone of this handle that's a strong reference.
    #[must_use]
    fn clone_strong(&self) -> Self;

    /// Internal function used when upgrading a weak reference to a strong one.
    #[doc(hidden)]
    fn from_inner(_: vtable::VRc<ComponentVTable, Self::Inner>) -> Self;

    /// Marks the window of this component to be shown on the screen. This registers
    /// the window with the windowing system. In order to react to events from the windowing system,
    /// such as draw requests or mouse/touch input, it is still necessary to spin the event loop,
    /// using [`crate::run_event_loop`](fn.run_event_loop.html).
    fn show(&self);

    /// Marks the window of this component to be hidden on the screen. This de-registers
    /// the window from the windowing system and it will not receive any further events.
    fn hide(&self);

    /// Returns the Window associated with this component. The window API can be used
    /// to control different aspects of the integration into the windowing system,
    /// such as the position on the screen.
    fn window(&self) -> &Window;

    /// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`](fn.run_event_loop.html)
    /// and [`Self::hide`].
    fn run(&self);

    /// This function provides access to instances of global singletons exported in `.slint`.
    /// See [`Global`] for an example how to export and access globals from `.slint` markup.
    fn global<'a, T: Global<'a, Self>>(&'a self) -> T
    where
        Self: Sized;
}

mod weak_handle {

    use super::*;

    /// Struct that's used to hold weak references of a [Slint component](mod@crate#generated-components)
    ///
    /// In order to create a Weak, you should use [`ComponentHandle::as_weak`].
    ///
    /// Strong references should not be captured by the functions given to a lambda,
    /// as this would produce a reference loop and leak the component.
    /// Instead, the callback function should capture a weak component.
    ///
    /// The Weak component also implement `Send` and can be send to another thread.
    /// but the upgrade function will only return a valid component from the same thread
    /// as the one it has been created from.
    /// This is useful to use with [`invoke_from_event_loop()`] or [`Self::upgrade_in_event_loop()`].
    pub struct Weak<T: ComponentHandle> {
        inner: vtable::VWeak<ComponentVTable, T::Inner>,
        #[cfg(feature = "std")]
        thread: std::thread::ThreadId,
    }

    impl<T: ComponentHandle> Clone for Weak<T> {
        fn clone(&self) -> Self {
            Self {
                inner: self.inner.clone(),
                #[cfg(feature = "std")]
                thread: self.thread,
            }
        }
    }

    impl<T: ComponentHandle> Weak<T> {
        #[doc(hidden)]
        pub fn new(rc: &vtable::VRc<ComponentVTable, T::Inner>) -> Self {
            Self {
                inner: vtable::VRc::downgrade(rc),
                #[cfg(feature = "std")]
                thread: std::thread::current().id(),
            }
        }

        /// Returns a new strongly referenced component if some other instance still
        /// holds a strong reference. Otherwise, returns None.
        ///
        /// This also returns None if the current thread is not the thread that created
        /// the component
        pub fn upgrade(&self) -> Option<T>
        where
            T: ComponentHandle,
        {
            #[cfg(feature = "std")]
            if std::thread::current().id() != self.thread {
                return None;
            }
            self.inner.upgrade().map(T::from_inner)
        }

        /// Convenience function that returns a new strongly referenced component if
        /// some other instance still holds a strong reference and the current thread
        /// is the thread that created this component.
        /// Otherwise, this function panics.
        pub fn unwrap(&self) -> T {
            self.upgrade().unwrap()
        }

        /// Convenience function that combines [`invoke_from_event_loop()`] with [`Self::upgrade()`]
        ///
        /// The given functor will be added to an internal queue and will wake the event loop.
        /// On the next iteration of the event loop, the functor will be executed with a `T` as an argument.
        ///
        /// If the component was dropped because there are no more strong reference to the component,
        /// the functor will not be called.
        ///
        /// # Example
        /// ```rust
        /// # i_slint_backend_testing::init();
        /// slint::slint! { MyApp := Window { property <int> foo; /* ... */ } }
        /// let handle = MyApp::new();
        /// let handle_weak = handle.as_weak();
        /// let thread = std::thread::spawn(move || {
        ///     // ... Do some computation in the thread
        ///     let foo = 42;
        ///     # assert!(handle_weak.upgrade().is_none()); // note that upgrade fails in a thread
        ///     // now forward the data to the main thread using upgrade_in_event_loop
        ///     handle_weak.upgrade_in_event_loop(move |handle| handle.set_foo(foo));
        /// });
        /// # thread.join().unwrap(); return; // don't run the event loop in examples
        /// handle.run();
        /// ```
        #[cfg(feature = "std")]
        pub fn upgrade_in_event_loop(&self, func: impl FnOnce(T) + Send + 'static)
        where
            T: 'static,
        {
            let weak_handle = self.clone();
            super::invoke_from_event_loop(move || {
                if let Some(h) = weak_handle.upgrade() {
                    func(h);
                }
            })
        }
    }

    // Safety: we make sure in upgrade that the thread is the proper one,
    // and the VWeak only use atomic pointer so it is safe to clone and drop in another thread
    #[allow(unsafe_code)]
    #[cfg(feature = "std")]
    unsafe impl<T: ComponentHandle> Send for Weak<T> {}
}

pub use weak_handle::*;

/// Adds the specified function to an internal queue, notifies the event loop to wake up.
/// Once woken up, any queued up functors will be invoked.
///
/// This function is thread-safe and can be called from any thread, including the one
/// running the event loop. The provided functors will only be invoked from the thread
/// that started the event loop.
///
/// You can use this to set properties or use any other Slint APIs from other threads,
/// by collecting the code in a functor and queuing it up for invocation within the event loop.
///
/// See also [`Weak::upgrade_in_event_loop`]
///
/// # Example
/// ```rust
/// slint::slint! { MyApp := Window { property <int> foo; /* ... */ } }
/// # i_slint_backend_testing::init();
/// let handle = MyApp::new();
/// let handle_weak = handle.as_weak();
/// let thread = std::thread::spawn(move || {
///     // ... Do some computation in the thread
///     let foo = 42;
///      // now forward the data to the main thread using invoke_from_event_loop
///     let handle_copy = handle_weak.clone();
///     slint::invoke_from_event_loop(move || handle_copy.unwrap().set_foo(foo));
/// });
/// # thread.join().unwrap(); return; // don't run the event loop in examples
/// handle.run();
/// ```
pub fn invoke_from_event_loop(func: impl FnOnce() + Send + 'static) {
    if let Some(backend) = crate::backend::instance() {
        backend.post_event(alloc::boxed::Box::new(func))
    } else {
        panic!("slint::invoke_from_event_loop() must be called after the Slint backend is initialized.")
    }
}