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}