Skip to main content

lingxia_platform/traits/
ui.rs

1use std::future::Future;
2
3use lingxia_surface::LayoutPresentationPlan;
4
5use crate::error::PlatformError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ToastIcon {
9    Success,
10    Error,
11    Loading,
12    None,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ToastPosition {
17    Top,
18    Center,
19    Bottom,
20}
21
22#[derive(Debug, Clone)]
23pub struct ToastOptions {
24    pub title: String,
25    pub icon: ToastIcon,
26    pub image: Option<String>,
27    pub duration: f64,
28    pub mask: bool,
29    pub position: ToastPosition,
30}
31
32#[derive(Debug, Clone)]
33pub struct ModalOptions {
34    pub title: String,
35    pub content: String,
36    pub show_cancel: bool,
37    pub cancel_text: String,
38    pub cancel_color: Option<String>,
39    pub confirm_text: String,
40    pub confirm_color: Option<String>,
41}
42
43#[repr(i32)]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SurfaceKind {
46    Window = 0,
47    Overlay = 1,
48}
49
50/// The arbitrated role that drives how the platform presents a surface:
51/// `Main` = a top-level window/primary, `Aside` = a docked split companion,
52/// `Float` = a positioned popup (it keeps its edge/center placement but never
53/// splits the main). Distinguishes a float-popup-at-edge from an aside-dock.
54#[repr(i32)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub enum SurfaceRole {
57    #[default]
58    Main = 0,
59    Aside = 1,
60    Float = 2,
61}
62
63impl SurfaceRole {
64    pub const fn as_str(self) -> &'static str {
65        match self {
66            Self::Main => "main",
67            Self::Aside => "aside",
68            Self::Float => "float",
69        }
70    }
71}
72
73impl From<lingxia_surface::Role> for SurfaceRole {
74    fn from(role: lingxia_surface::Role) -> Self {
75        match role {
76            lingxia_surface::Role::Main => Self::Main,
77            lingxia_surface::Role::Aside => Self::Aside,
78            lingxia_surface::Role::Float => Self::Float,
79        }
80    }
81}
82
83impl From<SurfaceRole> for lingxia_surface::Role {
84    fn from(role: SurfaceRole) -> Self {
85        match role {
86            SurfaceRole::Main => Self::Main,
87            SurfaceRole::Aside => Self::Aside,
88            SurfaceRole::Float => Self::Float,
89        }
90    }
91}
92
93#[repr(i32)]
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum SurfaceContent {
96    #[default]
97    Page = 0,
98    Url = 1,
99}
100
101#[repr(i32)]
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub enum SurfacePosition {
104    #[default]
105    Center = 0,
106    Bottom = 1,
107    Left = 2,
108    Right = 3,
109    Top = 4,
110}
111
112/// Window decoration for `SurfaceKind::Window`.
113///
114/// `Full` extends the page to the window edge while keeping the system
115/// minimize, maximize, resize, and drag affordances. The runtime owns a native
116/// drag strip across the top and publishes its height to the page, so a page
117/// that does nothing to opt in still cannot trap the user — which is what sank
118/// the earlier edge-to-edge attempt.
119#[repr(i32)]
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
121pub enum WindowChrome {
122    #[default]
123    System = 0,
124    Full = 1,
125}
126
127impl WindowChrome {
128    /// Logical height of the drag strip `Full` reserves across the top. One
129    /// number for every consumer: the runtime publishes it to the page as the
130    /// page-chrome `topInset`, and each platform reserves the same band —
131    /// macOS lays out a strip view, Windows hit-tests it as caption. Logical,
132    /// so a Windows hit test must scale it by the window's DPI.
133    pub const FULL_DRAG_STRIP_HEIGHT: f64 = 28.0;
134}
135
136#[derive(Debug, Clone)]
137pub struct SurfaceRequest {
138    pub id: String,
139    pub app_id: String,
140    pub path: String,
141    pub session_id: u64,
142    pub page_instance_id: String,
143    pub content: SurfaceContent,
144    pub kind: SurfaceKind,
145    pub width: f64,
146    pub height: f64,
147    pub width_ratio: f64,
148    pub height_ratio: f64,
149    pub position: SurfacePosition,
150    /// Arbitrated role; the platform uses it to decide dock vs popup vs window.
151    pub role: SurfaceRole,
152    /// Resolved interaction contract. Platforms render this verbatim.
153    pub interaction: lingxia_surface::SurfaceInteraction,
154    /// Window decoration. Ignored unless `kind` is `Window`.
155    pub chrome: WindowChrome,
156    /// `Url` content only: isolate the WebView's cookies/site storage from
157    /// shared persistent data and discard them when the surface closes (auth
158    /// handoffs). `Page` content ignores it.
159    pub ephemeral_web_data: bool,
160    /// `Url` content only: navigation is paired with a native callback
161    /// interception channel. Platforms use this to keep local-file access out
162    /// of callback surfaces without restricting ordinary browser surfaces.
163    pub url_callback: bool,
164}
165
166/// Callback adapter used by platform SDK handlers that cannot return a Rust
167/// future directly. `SurfacePresenter` exposes only `ManagedSurfaceFuture`.
168pub type ManagedSurfaceCompletion = Box<dyn FnOnce(Result<(), PlatformError>) + Send + 'static>;
169
170pub type ManagedSurfaceFuture = crate::traits::PlatformFuture;
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum ManagedSurfaceProvider {
174    Declared,
175    Native {
176        capability: String,
177        instance_key: Option<String>,
178    },
179}
180
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct ManagedSurfaceProviderRequest {
183    pub surface_id: String,
184    pub provider: ManagedSurfaceProvider,
185    pub role: Option<SurfaceRole>,
186    pub edge: Option<String>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct ManagedSurfaceProviderDestroyRequest {
191    pub surface_id: String,
192    pub provider: ManagedSurfaceProvider,
193    pub role: Option<SurfaceRole>,
194}
195
196pub trait SurfacePresenter: Send + Sync + 'static {
197    /// The shared core resolves a `LayoutPresentationPlan` for one window/graph
198    /// and the platform skin binds it. The per-surface methods below present a
199    /// single surface at a time.
200    fn present_layout(
201        &self,
202        _window_id: &str,
203        _plan: &LayoutPresentationPlan,
204    ) -> Result<(), PlatformError> {
205        Err(PlatformError::NotSupported(
206            "present_layout is not supported on this platform".to_string(),
207        ))
208    }
209
210    fn present_surface(&self, _request: SurfaceRequest) -> Result<(), PlatformError> {
211        Err(PlatformError::NotSupported(
212            "surface is not supported on this platform".to_string(),
213        ))
214    }
215
216    fn close_surface(&self, _app_id: &str, _id: &str, _reason: &str) -> Result<(), PlatformError> {
217        Err(PlatformError::NotSupported(
218            "surface close is not supported on this platform".to_string(),
219        ))
220    }
221
222    fn show_surface(&self, _app_id: &str, _id: &str) -> Result<(), PlatformError> {
223        Err(PlatformError::NotSupported(
224            "surface show is not supported on this platform".to_string(),
225        ))
226    }
227
228    fn hide_surface(&self, _app_id: &str, _id: &str) -> Result<(), PlatformError> {
229        Err(PlatformError::NotSupported(
230            "surface hide is not supported on this platform".to_string(),
231        ))
232    }
233
234    /// Ensure the platform provider exists for a core-owned Surface. Identity,
235    /// role, visibility, focus, and menu policy remain in the shared graph;
236    /// `present_layout` projects that state after this future succeeds.
237    fn ensure_managed_surface_provider(
238        &self,
239        _request: ManagedSurfaceProviderRequest,
240    ) -> ManagedSurfaceFuture {
241        Box::pin(async {
242            Err(PlatformError::NotSupported(
243                "managed surface providers are not supported on this platform".to_string(),
244            ))
245        })
246    }
247
248    /// Destroy provider state after the core removes a non-root Surface.
249    fn destroy_managed_surface_provider(
250        &self,
251        _request: ManagedSurfaceProviderDestroyRequest,
252    ) -> ManagedSurfaceFuture {
253        Box::pin(async {
254            Err(PlatformError::NotSupported(
255                "managed surface providers are not supported on this platform".to_string(),
256            ))
257        })
258    }
259}
260
261pub trait UIUpdate: Send + Sync + 'static {
262    fn update_navbar_ui(&self, appid: String) -> Result<(), PlatformError>;
263    fn update_tabbar_ui(&self, appid: String) -> Result<(), PlatformError>;
264
265    fn update_tabbar_ui_async(
266        &self,
267        appid: String,
268    ) -> impl Future<Output = Result<(), PlatformError>> + Send {
269        async move { self.update_tabbar_ui(appid) }
270    }
271
272    fn update_orientation_ui(&self, _appid: String) -> Result<(), PlatformError> {
273        Err(PlatformError::NotSupported(
274            "update_orientation_ui not implemented for this platform".to_string(),
275        ))
276    }
277
278    /// Effective host/Runner scheme used when an lxapp preference is `auto`.
279    fn host_appearance_dark(&self) -> bool {
280        false
281    }
282
283    /// Apply an lxapp-scoped scheme to native Page Chrome and every matching
284    /// WebView. Shared shell and unrelated browser/lxapp WebViews are excluded.
285    fn apply_lxapp_appearance(&self, _appid: &str, _dark: bool) -> Result<(), PlatformError> {
286        Ok(())
287    }
288
289    /// Clear platform state retained for a closed lxapp session.
290    fn clear_lxapp_appearance(&self, _appid: &str) {}
291
292    /// The home lxapp's entry page finished its first render (fired at most
293    /// once per process). Hosts dismiss the startup splash overlay on it.
294    fn notify_home_first_ready(&self) {}
295
296    /// Measure the visible capsule after native Page Chrome has laid out.
297    /// The JSON payload is an internal transport; app code only sees the
298    /// revisioned View snapshot assembled by `lingxia-lxapp`.
299    fn measure_page_chrome_capsule(
300        &self,
301        _appid: String,
302    ) -> impl Future<Output = Result<Option<String>, PlatformError>> + Send {
303        async { Ok(None) }
304    }
305
306    /// Acknowledge one Page Chrome revision after native layout and visuals
307    /// have been applied. Platforms with an asynchronous UI thread override
308    /// this; the default preserves the existing synchronous update path.
309    fn apply_page_chrome_revision(
310        &self,
311        appid: String,
312        _revision: u64,
313    ) -> impl Future<Output = Result<(), PlatformError>> + Send {
314        async move {
315            self.update_navbar_ui(appid.clone())?;
316            self.update_tabbar_ui_async(appid).await
317        }
318    }
319}
320
321pub trait UserFeedback: Send + Sync + 'static {
322    fn show_toast(&self, options: ToastOptions) -> Result<(), PlatformError>;
323    fn hide_toast(&self) -> Result<(), PlatformError>;
324
325    fn show_modal(
326        &self,
327        options: ModalOptions,
328    ) -> impl Future<Output = Result<String, PlatformError>> + Send;
329
330    fn show_action_sheet(
331        &self,
332        options: Vec<String>,
333        cancel_text: String,
334        item_color: String,
335    ) -> impl Future<Output = Result<String, PlatformError>> + Send;
336}