1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use crate::{
style::TabStyleOverride,
tree::{PaneId, PaneRole},
};
/// Per-tab drop constraints evaluated against the destination pane.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TabDropPolicy {
/// If set, this tab may only be dropped into the given pane.
pub locked_to_pane: Option<PaneId>,
/// If set, this tab may only be dropped into panes with the given role.
pub locked_to_role: Option<PaneRole>,
/// If set, this tab may only be dropped into panes in this allow-list.
pub allowed_panes: Option<Vec<PaneId>>,
/// If set, this tab may only be dropped into panes with roles in this allow-list.
pub allowed_roles: Option<Vec<PaneRole>>,
/// Panes in this block-list reject this tab even if otherwise allowed.
pub blocked_panes: Vec<PaneId>,
/// Roles in this block-list reject this tab even if otherwise allowed.
pub blocked_roles: Vec<PaneRole>,
}
impl TabDropPolicy {
/// Return whether this tab may be dropped into the given pane target.
///
/// Pane identity and semantic role are evaluated together. This lets
/// applications express either "only this pane instance" or "any pane with
/// this role" style rules.
pub fn allows_target(&self, pane_id: PaneId, role: Option<PaneRole>) -> bool {
if let Some(locked_to_pane) = self.locked_to_pane {
return locked_to_pane == pane_id;
}
if let Some(locked_to_role) = self.locked_to_role {
return role == Some(locked_to_role);
}
if let Some(allowed_panes) = &self.allowed_panes {
if !allowed_panes.contains(&pane_id) {
return false;
}
}
if let Some(allowed_roles) = &self.allowed_roles {
if !role.is_some_and(|role| allowed_roles.contains(&role)) {
return false;
}
}
if self.blocked_panes.contains(&pane_id) {
return false;
}
!role.is_some_and(|role| self.blocked_roles.contains(&role))
}
}
impl Default for TabDropPolicy {
fn default() -> Self {
Self {
locked_to_pane: None,
locked_to_role: None,
allowed_panes: None,
allowed_roles: None,
blocked_panes: Vec::new(),
blocked_roles: Vec::new(),
}
}
}
/// Leading tab icon content rendered before the tab title.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TabIcon {
/// Short text marker such as ASCII tokens or compact glyphs.
Text(String),
/// Texture-backed image icon with an explicit display size.
Texture {
texture_id: egui::TextureId,
size: egui::Vec2,
},
}
/// A single tab that lives inside a pane.
///
/// `T` is the caller-supplied identifier — must be `Clone + 'static`.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tab<T: Clone + 'static> {
pub title: String,
pub id: T,
/// Optional leading icon rendered before the title in tab headers.
pub icon: Option<TabIcon>,
/// Whether the tab may be dragged to another pane.
pub draggable: bool,
/// Per-tab destination constraints evaluated during drag-and-drop.
pub drop_policy: TabDropPolicy,
/// Whether the tab may be closed from the header UI.
pub closable: bool,
/// Optional visual overrides for this tab.
pub style_override: Option<TabStyleOverride>,
}
impl<T: Clone + 'static> Tab<T> {
/// Create a new tab with a title and caller-owned identifier.
pub fn new(title: impl Into<String>, id: T) -> Self {
Self {
title: title.into(),
id,
icon: None,
draggable: true,
drop_policy: TabDropPolicy::default(),
closable: false,
style_override: None,
}
}
/// Set a short text marker as the leading tab visual.
pub fn with_leading_visual(mut self, leading_visual: impl Into<String>) -> Self {
self.icon = Some(TabIcon::Text(leading_visual.into()));
self
}
/// Set the leading icon payload directly.
pub fn with_icon(mut self, icon: TabIcon) -> Self {
self.icon = Some(icon);
self
}
/// Set a texture-backed leading icon.
pub fn with_icon_texture(mut self, texture_id: egui::TextureId, size: egui::Vec2) -> Self {
self.icon = Some(TabIcon::Texture { texture_id, size });
self
}
/// Control whether this tab may be dragged at all.
pub fn with_draggable(mut self, draggable: bool) -> Self {
self.draggable = draggable;
self
}
/// Replace the full tab drop-policy payload.
pub fn with_drop_policy(mut self, drop_policy: TabDropPolicy) -> Self {
self.drop_policy = drop_policy;
self
}
/// Lock the tab to a specific pane instance.
pub fn with_locked_pane(mut self, pane_id: PaneId) -> Self {
self.drop_policy.locked_to_pane = Some(pane_id);
self
}
/// Lock the tab to panes with a specific semantic role.
pub fn with_locked_role(mut self, role: PaneRole) -> Self {
self.drop_policy.locked_to_role = Some(role);
self
}
/// Restrict the tab to a set of pane identifiers.
pub fn with_allowed_drop_panes(
mut self,
allowed_panes: impl IntoIterator<Item = PaneId>,
) -> Self {
self.drop_policy.allowed_panes = Some(allowed_panes.into_iter().collect());
self
}
/// Reject drops into a set of pane identifiers.
pub fn with_blocked_drop_panes(
mut self,
blocked_panes: impl IntoIterator<Item = PaneId>,
) -> Self {
self.drop_policy.blocked_panes = blocked_panes.into_iter().collect();
self
}
/// Restrict the tab to a set of pane roles.
pub fn with_allowed_drop_roles(
mut self,
allowed_roles: impl IntoIterator<Item = PaneRole>,
) -> Self {
self.drop_policy.allowed_roles = Some(allowed_roles.into_iter().collect());
self
}
/// Reject drops into panes with a set of semantic roles.
pub fn with_blocked_drop_roles(
mut self,
blocked_roles: impl IntoIterator<Item = PaneRole>,
) -> Self {
self.drop_policy.blocked_roles = blocked_roles.into_iter().collect();
self
}
/// Control whether the tab may be closed from the built-in header UI.
pub fn with_closable(mut self, closable: bool) -> Self {
self.closable = closable;
self
}
/// Apply a per-tab style override used by the built-in header renderer.
pub fn with_style_override(mut self, style_override: TabStyleOverride) -> Self {
self.style_override = Some(style_override);
self
}
}