use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;
use geometry_core::Rect;
use layout_core::AvailableSpace;
use platform_core::{Event, WindowCommand};
use renderer_core::{BorderRadius, Color, DrawCommand};
use ui_core::{ComponentList, EventResult, NodeId, Surface, compute_layout, mark_dirty};
use ui_tree::{Component, NodeVec, RenderNode};
use crate::app_context::AppCtx;
pub type DrawList = Vec<DrawCommand>;
pub type WindowCommands = Vec<WindowCommand>;
pub use platform_core::Event as PluginEvent;
pub use renderer_core::Color as PluginColor;
pub fn composite(rect: Rect, image_salt: u64, mut commands: DrawList) -> RenderNode {
if image_salt != 0 {
const ID_BITS: u32 = 40;
for cmd in &mut commands {
if let DrawCommand::Image { data, .. } = cmd {
let salted = (image_salt << ID_BITS) | (data.id & ((1u64 << ID_BITS) - 1));
std::sync::Arc::make_mut(data).id = salted;
}
}
}
RenderNode::Clip {
rect,
radius: BorderRadius::zero(),
children: NodeVec::collect([RenderNode::Transform {
matrix: [1.0, 0.0, 0.0, 1.0, rect.x, rect.y],
children: NodeVec::collect(commands.into_iter().map(RenderNode::Primitive)),
}]),
}
}
pub trait EmbeddedApp: 'static {
fn build(&mut self);
fn layout_root(&self) -> NodeId;
fn view(&self) -> RenderNode;
fn on_event(&mut self, event: &Event) -> EventResult;
fn relayout_viewports(&mut self) {}
fn activate(&mut self) {}
fn on_frame(&mut self, _ctx: &mut AppCtx) {}
fn clear_color(&self) -> Option<Color> {
None
}
fn title(&self) -> String;
fn icon(&self) -> Option<Vec<u8>> {
None
}
fn id(&self) -> String;
}
struct EmbeddedComponent(Rc<RefCell<Box<dyn EmbeddedApp>>>);
impl Component for EmbeddedComponent {
fn view(&self) -> RenderNode {
self.0.borrow().view()
}
fn on_event(&mut self, event: &Event) -> EventResult {
self.0.borrow_mut().on_event(event)
}
fn debug_name(&self) -> &'static str {
"PluginRoot"
}
}
pub struct PluginInstance {
embedded: Rc<RefCell<Box<dyn EmbeddedApp>>>,
tree: ComponentList,
root: NodeId,
size: (f32, f32),
surface: Rc<Surface>,
}
impl PluginInstance {
pub fn new(embedded: Box<dyn EmbeddedApp>) -> Self {
let surface = Surface::new();
let embedded = Rc::new(RefCell::new(embedded));
let (root, tree) = {
let _g = surface.enter();
embedded.borrow_mut().build();
let root = embedded.borrow().layout_root();
let tree = ComponentList::new(EmbeddedComponent(Rc::clone(&embedded)));
(root, tree)
};
Self {
surface,
embedded,
tree,
root,
size: (0.0, 0.0),
}
}
pub fn relayout(&mut self, width: f32, height: f32) {
let _g = self.surface.enter();
self.size = (width, height);
let _ = mark_dirty(self.root);
let _ = compute_layout(
self.root,
AvailableSpace::Definite(width),
AvailableSpace::Definite(height),
);
let embedded = &self.embedded;
reactive_core::batch(|| embedded.borrow_mut().relayout_viewports());
self.tree.bump_force_ticks();
}
pub fn relayout_dirty(&self) {
let _g = self.surface.enter();
ui_core::relayout_if_dirty();
}
pub fn paint(&self) -> DrawList {
let _g = self.surface.enter();
self.tree.commands().clone()
}
pub fn generation(&self) -> u64 {
let _g = self.surface.enter();
self.tree.generation()
}
pub fn motion_active(&self) -> bool {
let _g = self.surface.enter();
motion_core::has_active()
}
pub fn on_event(&mut self, event: &Event) -> bool {
let _g = self.surface.enter();
self.tree.on_event(event) == EventResult::Handled
}
pub fn dispatch_overlays(&self, event: &Event) -> bool {
let _g = self.surface.enter();
reactive_core::batch(|| ui_core::dispatch_overlays(event) == EventResult::Handled)
}
pub fn motion_tick(&self, now: Instant) {
let _g = self.surface.enter();
reactive_core::begin_batch();
motion_core::tick(now);
reactive_core::end_batch();
}
pub fn drain_window_commands(&self) -> WindowCommands {
let _g = self.surface.enter();
platform_core::take_window_commands()
}
pub fn set_system_dark(&self, dark: bool) {
let _g = self.surface.enter();
reactive_core::begin_batch();
theme_core::set_system_dark(dark);
reactive_core::end_batch();
}
pub fn on_frame(&mut self, ctx: &mut AppCtx) {
let _g = self.surface.enter();
let embedded = &self.embedded;
reactive_core::batch(|| embedded.borrow_mut().on_frame(ctx));
}
pub fn activate(&mut self) {
let _g = self.surface.enter();
let embedded = &self.embedded;
reactive_core::batch(|| embedded.borrow_mut().activate());
self.tree.bump_force_ticks();
}
pub fn clear_color(&self) -> Option<Color> {
let _g = self.surface.enter();
self.embedded.borrow().clear_color()
}
pub fn title(&self) -> String {
let _g = self.surface.enter();
self.embedded.borrow().title()
}
pub fn icon(&self) -> Option<Vec<u8>> {
let _g = self.surface.enter();
self.embedded.borrow().icon()
}
pub fn id(&self) -> String {
let _g = self.surface.enter();
self.embedded.borrow().id()
}
}
#[doc(hidden)]
pub fn __plugin_create(embedded: Box<dyn EmbeddedApp>) -> *mut PluginInstance {
Box::into_raw(Box::new(PluginInstance::new(embedded)))
}
#[doc(hidden)]
pub unsafe fn __plugin_destroy(inst: *mut PluginInstance) {
drop(unsafe { Box::from_raw(inst) });
}
macro_rules! plugin_shim {
($(#[$m:meta])* $vis_fn:ident ($($arg:ident : $ty:ty),*) $(-> $ret:ty)? => $method:ident) => {
$(#[$m])*
#[doc(hidden)]
pub unsafe fn $vis_fn(inst: *mut PluginInstance $(, $arg: $ty)*) $(-> $ret)? {
unsafe { (*inst).$method($($arg),*) }
}
};
}
plugin_shim!(__plugin_relayout(width: f32, height: f32) => relayout);
plugin_shim!(__plugin_relayout_dirty() => relayout_dirty);
plugin_shim!(__plugin_paint() -> DrawList => paint);
plugin_shim!(__plugin_generation() -> u64 => generation);
plugin_shim!(__plugin_on_event(event: &Event) -> bool => on_event);
plugin_shim!(__plugin_dispatch_overlays(event: &Event) -> bool => dispatch_overlays);
plugin_shim!(__plugin_motion_tick(now: Instant) => motion_tick);
plugin_shim!(__plugin_motion_active() -> bool => motion_active);
plugin_shim!(__plugin_drain_window_commands() -> WindowCommands => drain_window_commands);
plugin_shim!(__plugin_set_system_dark(dark: bool) => set_system_dark);
plugin_shim!(__plugin_activate() => activate);
plugin_shim!(__plugin_clear_color() -> Option<Color> => clear_color);
plugin_shim!(__plugin_title() -> String => title);
plugin_shim!(__plugin_icon() -> Option<Vec<u8>> => icon);
plugin_shim!(__plugin_id() -> String => id);
#[doc(hidden)]
pub unsafe fn __plugin_on_frame(inst: *mut PluginInstance, ctx: &mut AppCtx) {
unsafe { (*inst).on_frame(ctx) }
}
#[macro_export]
macro_rules! plugin {
($factory:expr) => {
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_create(
args: &[::std::string::String],
) -> *mut $crate::plugin::PluginInstance {
$crate::plugin::__plugin_create(($factory)(args))
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_destroy(inst: *mut $crate::plugin::PluginInstance) {
unsafe { $crate::plugin::__plugin_destroy(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_relayout(
inst: *mut $crate::plugin::PluginInstance,
width: f32,
height: f32,
) {
unsafe { $crate::plugin::__plugin_relayout(inst, width, height) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_relayout_dirty(
inst: *mut $crate::plugin::PluginInstance,
) {
unsafe { $crate::plugin::__plugin_relayout_dirty(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_paint(
inst: *mut $crate::plugin::PluginInstance,
) -> $crate::plugin::DrawList {
unsafe { $crate::plugin::__plugin_paint(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_generation(
inst: *mut $crate::plugin::PluginInstance,
) -> u64 {
unsafe { $crate::plugin::__plugin_generation(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_on_event(
inst: *mut $crate::plugin::PluginInstance,
event: &$crate::plugin::PluginEvent,
) -> bool {
unsafe { $crate::plugin::__plugin_on_event(inst, event) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_dispatch_overlays(
inst: *mut $crate::plugin::PluginInstance,
event: &$crate::plugin::PluginEvent,
) -> bool {
unsafe { $crate::plugin::__plugin_dispatch_overlays(inst, event) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_motion_tick(
inst: *mut $crate::plugin::PluginInstance,
now: ::std::time::Instant,
) {
unsafe { $crate::plugin::__plugin_motion_tick(inst, now) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_motion_active(
inst: *mut $crate::plugin::PluginInstance,
) -> bool {
unsafe { $crate::plugin::__plugin_motion_active(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_drain_window_commands(
inst: *mut $crate::plugin::PluginInstance,
) -> $crate::plugin::WindowCommands {
unsafe { $crate::plugin::__plugin_drain_window_commands(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_set_system_dark(
inst: *mut $crate::plugin::PluginInstance,
dark: bool,
) {
unsafe { $crate::plugin::__plugin_set_system_dark(inst, dark) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_activate(
inst: *mut $crate::plugin::PluginInstance,
) {
unsafe { $crate::plugin::__plugin_activate(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_clear_color(
inst: *mut $crate::plugin::PluginInstance,
) -> ::std::option::Option<$crate::plugin::PluginColor> {
unsafe { $crate::plugin::__plugin_clear_color(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_title(
inst: *mut $crate::plugin::PluginInstance,
) -> ::std::string::String {
unsafe { $crate::plugin::__plugin_title(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_icon(
inst: *mut $crate::plugin::PluginInstance,
) -> ::std::option::Option<::std::vec::Vec<u8>> {
unsafe { $crate::plugin::__plugin_icon(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_id(
inst: *mut $crate::plugin::PluginInstance,
) -> ::std::string::String {
unsafe { $crate::plugin::__plugin_id(inst) }
}
#[unsafe(no_mangle)]
pub unsafe extern "Rust" fn _rsx_plugin_on_frame(
inst: *mut $crate::plugin::PluginInstance,
ctx: &mut $crate::AppCtx,
) {
unsafe { $crate::plugin::__plugin_on_frame(inst, ctx) }
}
};
}
#[cfg(feature = "plugin-host")]
pub use host::{LoadedPlugin, load_plugin};
#[cfg(feature = "plugin-host")]
mod host {
use super::*;
use std::path::Path;
struct Symbols {
destroy: unsafe extern "Rust" fn(*mut PluginInstance),
relayout: unsafe extern "Rust" fn(*mut PluginInstance, f32, f32),
relayout_dirty: unsafe extern "Rust" fn(*mut PluginInstance),
paint: unsafe extern "Rust" fn(*mut PluginInstance) -> DrawList,
generation: unsafe extern "Rust" fn(*mut PluginInstance) -> u64,
on_event: unsafe extern "Rust" fn(*mut PluginInstance, &Event) -> bool,
dispatch_overlays: unsafe extern "Rust" fn(*mut PluginInstance, &Event) -> bool,
motion_tick: unsafe extern "Rust" fn(*mut PluginInstance, Instant),
motion_active: unsafe extern "Rust" fn(*mut PluginInstance) -> bool,
drain_window_commands: unsafe extern "Rust" fn(*mut PluginInstance) -> WindowCommands,
set_system_dark: unsafe extern "Rust" fn(*mut PluginInstance, bool),
activate: unsafe extern "Rust" fn(*mut PluginInstance),
clear_color: unsafe extern "Rust" fn(*mut PluginInstance) -> Option<Color>,
title: unsafe extern "Rust" fn(*mut PluginInstance) -> String,
icon: unsafe extern "Rust" fn(*mut PluginInstance) -> Option<Vec<u8>>,
id: unsafe extern "Rust" fn(*mut PluginInstance) -> String,
on_frame: unsafe extern "Rust" fn(*mut PluginInstance, &mut AppCtx),
}
pub struct LoadedPlugin {
inst: *mut PluginInstance,
symbols: Symbols,
_lib: libloading::Library,
}
pub fn load_plugin(
path: &Path,
args: &[String],
) -> Result<LoadedPlugin, Box<dyn std::error::Error>> {
#[cfg(unix)]
let lib = unsafe {
libloading::os::unix::Library::open(
Some(path.as_os_str()),
libc::RTLD_NOW | libc::RTLD_LOCAL,
)
.map(libloading::Library::from)?
};
#[cfg(not(unix))]
let lib = unsafe { libloading::Library::new(path)? };
let symbols = unsafe {
Symbols {
destroy: *lib.get(b"_rsx_plugin_destroy\0")?,
relayout: *lib.get(b"_rsx_plugin_relayout\0")?,
relayout_dirty: *lib.get(b"_rsx_plugin_relayout_dirty\0")?,
paint: *lib.get(b"_rsx_plugin_paint\0")?,
generation: *lib.get(b"_rsx_plugin_generation\0")?,
on_event: *lib.get(b"_rsx_plugin_on_event\0")?,
dispatch_overlays: *lib.get(b"_rsx_plugin_dispatch_overlays\0")?,
motion_tick: *lib.get(b"_rsx_plugin_motion_tick\0")?,
motion_active: *lib.get(b"_rsx_plugin_motion_active\0")?,
drain_window_commands: *lib.get(b"_rsx_plugin_drain_window_commands\0")?,
set_system_dark: *lib.get(b"_rsx_plugin_set_system_dark\0")?,
activate: *lib.get(b"_rsx_plugin_activate\0")?,
clear_color: *lib.get(b"_rsx_plugin_clear_color\0")?,
title: *lib.get(b"_rsx_plugin_title\0")?,
icon: *lib.get(b"_rsx_plugin_icon\0")?,
id: *lib.get(b"_rsx_plugin_id\0")?,
on_frame: *lib.get(b"_rsx_plugin_on_frame\0")?,
}
};
let create: libloading::Symbol<unsafe extern "Rust" fn(&[String]) -> *mut PluginInstance> =
unsafe { lib.get(b"_rsx_plugin_create\0")? };
let inst = unsafe { create(args) };
if inst.is_null() {
return Err("plugin _rsx_plugin_create returned null".into());
}
Ok(LoadedPlugin {
inst,
symbols,
_lib: lib,
})
}
impl LoadedPlugin {
pub fn relayout(&self, width: f32, height: f32) {
unsafe { (self.symbols.relayout)(self.inst, width, height) }
}
pub fn relayout_dirty(&self) {
unsafe { (self.symbols.relayout_dirty)(self.inst) }
}
pub fn paint(&self) -> DrawList {
unsafe { (self.symbols.paint)(self.inst) }
}
pub fn generation(&self) -> u64 {
unsafe { (self.symbols.generation)(self.inst) }
}
pub fn on_event(&self, event: &Event) -> bool {
unsafe { (self.symbols.on_event)(self.inst, event) }
}
pub fn dispatch_overlays(&self, event: &Event) -> bool {
unsafe { (self.symbols.dispatch_overlays)(self.inst, event) }
}
pub fn motion_tick(&self, now: Instant) {
unsafe { (self.symbols.motion_tick)(self.inst, now) }
}
pub fn motion_active(&self) -> bool {
unsafe { (self.symbols.motion_active)(self.inst) }
}
pub fn drain_window_commands(&self) -> WindowCommands {
unsafe { (self.symbols.drain_window_commands)(self.inst) }
}
pub fn set_system_dark(&self, dark: bool) {
unsafe { (self.symbols.set_system_dark)(self.inst, dark) }
}
pub fn activate(&self) {
unsafe { (self.symbols.activate)(self.inst) }
}
pub fn clear_color(&self) -> Option<Color> {
unsafe { (self.symbols.clear_color)(self.inst) }
}
pub fn title(&self) -> String {
unsafe { (self.symbols.title)(self.inst) }
}
pub fn icon(&self) -> Option<Vec<u8>> {
unsafe { (self.symbols.icon)(self.inst) }
}
pub fn id(&self) -> String {
unsafe { (self.symbols.id)(self.inst) }
}
pub fn on_frame(&self, ctx: &mut AppCtx) {
unsafe { (self.symbols.on_frame)(self.inst, ctx) }
}
}
impl Drop for LoadedPlugin {
fn drop(&mut self) {
unsafe { (self.symbols.destroy)(self.inst) }
}
}
}