Skip to main content

cranpose_native/
content.rs

1use std::{cell::RefCell, rc::Rc};
2
3use cranpose::{
4    native_view::{NativeView as Slot, NativeViewHost},
5    prelude::Modifier,
6};
7use cranpose_core::{CompositionLocal, CompositionLocalProvider, SideEffect, compositionLocalOf};
8
9use crate::NativeEvent;
10
11thread_local! {
12    static CONTEXT: CompositionLocal<Option<NativeContext>> = compositionLocalOf(|| None);
13}
14
15#[derive(Clone)]
16pub(crate) struct NativeContext {
17    pub(crate) views: NativeViewHost,
18    events: Rc<RefCell<Vec<NativeEvent>>>,
19}
20
21impl PartialEq for NativeContext {
22    fn eq(&self, other: &Self) -> bool {
23        Rc::ptr_eq(&self.events, &other.events)
24    }
25}
26
27impl NativeContext {
28    pub(crate) fn new() -> Self {
29        Self {
30            views: NativeViewHost::default(),
31            events: Rc::default(),
32        }
33    }
34
35    pub(crate) fn provide(&self, content: impl FnOnce()) {
36        CONTEXT.with(|local| {
37            CompositionLocalProvider([local.provides(Some(self.clone()))], || {
38                self.views.provide(content);
39            });
40        });
41    }
42
43    pub(crate) fn take_events(&self) -> Vec<NativeEvent> {
44        std::mem::take(&mut *self.events.borrow_mut())
45    }
46
47    fn current() -> Self {
48        CONTEXT
49            .with(CompositionLocal::current)
50            .expect("native content requires a NativeSession")
51    }
52}
53
54/// Application content and commands, created on the component's owning thread.
55pub struct NativeContent {
56    pub(crate) draw: Box<dyn FnMut()>,
57    pub(crate) event: Box<dyn FnMut(NativeEvent)>,
58}
59
60impl NativeContent {
61    /// Supplies a composition. The native runtime provides its host context.
62    pub fn new(content: impl FnMut() + 'static) -> Self {
63        Self {
64            draw: Box::new(content),
65            event: Box::new(|_| {}),
66        }
67    }
68
69    /// Handles application events sent through the platform view.
70    pub fn on_event(mut self, event: impl FnMut(NativeEvent) + 'static) -> Self {
71        self.event = Box::new(event);
72        self
73    }
74}
75
76/// Sends an application event to the native host after this composition commits.
77#[cranpose::prelude::composable]
78pub fn SendToHost(name: impl Into<String>, value: impl Into<String>) {
79    let context = NativeContext::current();
80    let event = NativeEvent {
81        name: name.into(),
82        value: value.into(),
83    };
84    SideEffect(move || context.events.borrow_mut().push(event));
85}
86
87/// Reserves a native child with an application-defined factory and configuration.
88///
89/// Factories own creation, updates and disposal. The host preserves the child
90/// until its slot is removed or its factory kind changes.
91#[cranpose::prelude::composable]
92pub fn NativeView(kind: &str, value: &str, modifier: Modifier, on_event: impl Fn(&str) + 'static) {
93    Slot(
94        NativeContext::current().views,
95        kind,
96        value,
97        modifier,
98        on_event,
99    );
100}