1use crate::adapt::{AdaptEvents, AdaptWidget};
9use crate::{AccessLabel, Row, Stack};
10use kas::layout::{FrameStorage, Visitor};
11use kas::messages::Select;
12use kas::prelude::*;
13use kas::theme::FrameStyle;
14use std::fmt::Debug;
15
16#[derive(Clone, Debug)]
17struct MsgSelectIndex(usize);
18
19impl_scope! {
20 #[autoimpl(HasStr using self.label)]
24 #[widget {
25 Data = ();
26 layout = button!(self.label);
27 navigable = true;
28 hover_highlight = true;
29 }]
30 pub struct Tab {
31 core: widget_core!(),
32 frame: FrameStorage,
33 #[widget]
34 label: AccessLabel,
35 }
36
37 impl Self {
38 #[inline]
40 pub fn new(label: impl Into<AccessString>) -> Self {
41 Tab {
42 core: Default::default(),
43 frame: FrameStorage::default(),
44 label: AccessLabel::new(label),
45 }
46 }
47 }
48
49 impl Layout for Self {
50 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
51 let label = Visitor::single(&mut self.label);
52 Visitor::frame(&mut self.frame, label, FrameStyle::Tab).size_rules(sizer, axis)
53 }
54
55 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
56 self.core.rect = rect;
57 let label = Visitor::single(&mut self.label);
58 Visitor::frame(&mut self.frame, label, FrameStyle::Tab).set_rect(cx, rect, hints)
59 }
60
61 fn find_id(&mut self, coord: Coord) -> Option<Id> {
62 self.rect().contains(coord).then_some(self.id())
63 }
64
65 fn draw(&mut self, draw: DrawCx) {
66 let label = Visitor::single(&mut self.label);
67 Visitor::frame(&mut self.frame, label, FrameStyle::Tab).draw(draw)
68 }
69 }
70
71 impl Events for Self {
72 fn handle_event(&mut self, cx: &mut EventCx, _: &(), event: Event) -> IsUsed {
73 event.on_activate(cx, self.id(), |cx| {
74 cx.push(Select);
75 Used
76 })
77 }
78
79 fn handle_messages(&mut self, cx: &mut EventCx, _: &()) {
80 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
81 cx.push(Select);
82 cx.depress_with_key(self.id(), code);
83 }
84 }
85 }
86
87 impl<T: Into<AccessString>> From<T> for Tab {
88 fn from(label: T) -> Self {
89 Tab::new(label)
90 }
91 }
92}
93
94pub type BoxTabStack<Data> = TabStack<Box<dyn Widget<Data = Data>>>;
98
99impl_scope! {
100 #[impl_default(Self::new())]
116 #[widget {
117 layout = list!(self.direction, [
118 self.stack,
119 self.tabs,
120 ]);
121 }]
122 pub struct TabStack<W: Widget> {
123 core: widget_core!(),
124 direction: Direction,
125 #[widget(&())]
126 tabs: AdaptEvents<Row<Vec<Tab>>>, #[widget]
128 stack: Stack<W>,
129 on_change: Option<Box<dyn Fn(&mut EventCx, &W::Data, usize, &str)>>,
130 }
131
132 impl Self {
133 pub fn new() -> Self {
137 Self {
138 core: Default::default(),
139 direction: Direction::Up,
140 stack: Stack::new(),
141 tabs: Row::new(vec![]).map_message(|index, Select| MsgSelectIndex(index)),
142 on_change: None,
143 }
144 }
145
146 pub fn set_direction(&mut self, direction: Direction) -> Action {
150 if direction == self.direction {
151 return Action::empty();
152 }
153
154 self.direction = direction;
155 Action::RESIZE
157 }
158
159 #[inline]
163 #[must_use]
164 pub fn with(mut self, f: impl Fn(&mut EventCx, &W::Data, usize, &str) + 'static) -> Self {
165 debug_assert!(self.on_change.is_none());
166 self.on_change = Some(Box::new(f));
167 self
168 }
169
170 #[inline]
174 #[must_use]
175 pub fn with_msg<M>(self, f: impl Fn(usize, &str) -> M + 'static) -> Self
176 where
177 M: std::fmt::Debug + 'static,
178 {
179 self.with(move |cx, _, index, title| cx.push(f(index, title)))
180 }
181 }
182
183 impl Layout for Self {
184 fn nav_next(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
185 let reverse = reverse ^ !self.direction.is_reversed();
186 kas::util::nav_next(reverse, from, self.num_children())
187 }
188 }
189
190 impl Events for Self {
191 type Data = W::Data;
192
193 fn handle_messages(&mut self, cx: &mut EventCx, data: &W::Data) {
194 if let Some(MsgSelectIndex(index)) = cx.try_pop() {
195 self.set_active(&mut cx.config_cx(), data, index);
196 if let Some(ref f) = self.on_change {
197 let title = self.tabs[index].get_str();
198 f(cx, data, index, title);
199 }
200 }
201 }
202 }
203}
204
205impl<W: Widget> TabStack<W> {
206 pub fn set_size_limit(&mut self, limit: usize) {
214 self.stack.set_size_limit(limit);
215 }
216
217 pub fn with_size_limit(mut self, limit: usize) -> Self {
225 self.stack.set_size_limit(limit);
226 self
227 }
228
229 #[inline]
231 pub fn active(&self) -> usize {
232 self.stack.active()
233 }
234
235 #[inline]
240 pub fn with_active(mut self, active: usize) -> Self {
241 self.stack = self.stack.with_active(active);
242 self
243 }
244
245 pub fn set_active(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize) {
247 self.stack.set_active(cx, data, index);
248 }
249
250 pub fn get_active(&self) -> Option<&W> {
252 self.stack.get_active()
253 }
254
255 pub fn is_empty(&self) -> bool {
257 self.stack.is_empty()
258 }
259
260 pub fn len(&self) -> usize {
262 self.stack.len()
263 }
264
265 pub fn clear(&mut self) {
269 self.stack.clear();
270 self.tabs.clear();
271 }
272
273 pub fn get(&self, index: usize) -> Option<&W> {
275 self.stack.get(index)
276 }
277
278 pub fn get_mut(&mut self, index: usize) -> Option<&mut W> {
280 self.stack.get_mut(index)
281 }
282
283 pub fn get_tab(&self, index: usize) -> Option<&Tab> {
285 self.tabs.get(index)
286 }
287
288 pub fn get_tab_mut(&mut self, index: usize) -> Option<&mut Tab> {
290 self.tabs.get_mut(index)
291 }
292
293 pub fn push(&mut self, cx: &mut ConfigCx, data: &W::Data, tab: Tab, widget: W) -> usize {
300 let ti = self.tabs.push(cx, &(), tab);
301 let si = self.stack.push(cx, data, widget);
302 debug_assert_eq!(ti, si);
303 si
304 }
305
306 pub fn pop(&mut self, cx: &mut EventState) -> Option<(Tab, W)> {
311 let tab = self.tabs.pop(cx);
312 let w = self.stack.pop(cx);
313 debug_assert_eq!(tab.is_some(), w.is_some());
314 tab.zip(w)
315 }
316
317 pub fn insert(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, tab: Tab, widget: W) {
323 self.tabs.insert(cx, &(), index, tab);
324 self.stack.insert(cx, data, index, widget);
325 }
326
327 pub fn remove(&mut self, cx: &mut EventState, index: usize) -> (Tab, W) {
334 let tab = self.tabs.remove(cx, index);
335 let stack = self.stack.remove(cx, index);
336 (tab, stack)
337 }
338
339 pub fn replace(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, w: W) -> W {
345 self.stack.replace(cx, data, index, w)
346 }
347
348 pub fn extend<T: IntoIterator<Item = (Tab, W)>>(
353 &mut self,
354 cx: &mut ConfigCx,
355 data: &W::Data,
356 iter: T,
357 ) {
358 let iter = iter.into_iter();
359 for (tab, w) in iter {
363 self.tabs.push(cx, &(), tab);
364 self.stack.push(cx, data, w);
365 }
366 }
367}
368
369impl<W: Widget, T, I> From<I> for TabStack<W>
370where
371 Tab: From<T>,
372 I: IntoIterator<Item = (T, W)>,
373{
374 #[inline]
375 fn from(iter: I) -> Self {
376 let iter = iter.into_iter();
377 let min_len = iter.size_hint().0;
378 let mut stack = Vec::with_capacity(min_len);
379 let mut tabs = Vec::with_capacity(min_len);
380 for (tab, w) in iter {
381 stack.push(w);
382 tabs.push(Tab::from(tab));
383 }
384 Self {
385 stack: Stack::from(stack),
386 tabs: Row::new(tabs).map_message(|index, Select| MsgSelectIndex(index)),
387 ..Default::default()
388 }
389 }
390}