Skip to main content

teksilo_widgets/tab_widget/
handle.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! [`TabHandle`] — the runtime entity that lives in a tab list.
5//!
6//! Carries a stable [`TabId`], its presentation [`TabInfo`], a
7//! `kind` discriminator that selects the registered content
8//! factory, and an `Rc<dyn Any>` payload holding the heavy state
9//! (the document, the image, the page) the factory consumes.
10//!
11//! Heavy state lives **here**, in the handle, not in the content
12//! widget. Reorders / model rebuilds destroy and recreate widgets
13//! freely; the handle's payload is stable and the content factory
14//! produces a fresh view over it.
15
16use std::any::Any;
17use std::rc::Rc;
18
19use super::id::TabId;
20use super::info::TabInfo;
21
22/// Sentinel `kind` reserved for static tabs accumulated via
23/// [`TabWidget::static_tab`](crate::tab_widget::TabWidget::static_tab).
24/// Application-level `kind` strings must not collide with this
25/// value — the framework panics with a clear message at registration
26/// if [`dynamic_tab`](crate::tab_widget::TabWidget::dynamic_tab) is
27/// called with this name.
28pub const STATIC_KIND: &str = "__static__";
29
30/// One tab's identity, presentation, and state pointer.
31///
32/// `Clone` is cheap: `TabInfo` is shallow (the icon is an
33/// `Rc<dyn Fn() -> IconWidget>` factory) and `payload` is an
34/// `Rc<dyn Any>`.
35#[derive(Clone)]
36pub struct TabHandle {
37    pub id: TabId,
38    pub info: TabInfo,
39    pub kind: &'static str,
40    pub payload: Rc<dyn Any>,
41}
42
43impl std::fmt::Debug for TabHandle {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("TabHandle")
46            .field("id", &self.id)
47            .field("kind", &self.kind)
48            .field("info", &self.info)
49            .field("payload_type", &(*self.payload).type_id())
50            .finish()
51    }
52}
53
54impl TabHandle {
55    /// Construct a handle for the dynamic-tab path. The `kind`
56    /// must match a
57    /// [`dynamic_tab::<S>`](crate::tab_widget::TabWidget::dynamic_tab)
58    /// registration on the [`TabWidget`](crate::tab_widget::TabWidget)
59    /// where this handle lands; the framework downcasts
60    /// `payload` to `S` before calling the registered factory and
61    /// panics with a clear message on type mismatch.
62    pub fn dynamic<S: Any + 'static>(
63        id: TabId,
64        kind: &'static str,
65        info: TabInfo,
66        state: S,
67    ) -> Self {
68        assert!(
69            kind != STATIC_KIND,
70            "tab kind '{}' is reserved for static tabs; pick a different identifier",
71            STATIC_KIND
72        );
73        Self {
74            id,
75            info,
76            kind,
77            payload: Rc::new(state),
78        }
79    }
80
81    /// Construct a handle for the dynamic-tab path with a
82    /// pre-built `Rc<dyn Any>` payload — useful when several
83    /// handles share the same underlying state object.
84    pub fn dynamic_shared(
85        id: TabId,
86        kind: &'static str,
87        info: TabInfo,
88        payload: Rc<dyn Any>,
89    ) -> Self {
90        assert!(
91            kind != STATIC_KIND,
92            "tab kind '{}' is reserved for static tabs; pick a different identifier",
93            STATIC_KIND
94        );
95        Self {
96            id,
97            info,
98            kind,
99            payload,
100        }
101    }
102
103    /// Construct a static handle (used internally by
104    /// [`TabWidget::static_tab`](crate::tab_widget::TabWidget::static_tab)).
105    /// The `kind` is the [`STATIC_KIND`] sentinel; the payload is
106    /// the unit type. Apps should not call this directly — use
107    /// the `static_tab` builder method.
108    pub(crate) fn static_handle(id: TabId, info: TabInfo) -> Self {
109        Self {
110            id,
111            info,
112            kind: STATIC_KIND,
113            payload: Rc::new(()),
114        }
115    }
116}