ltk/widget/tab_strip/
desktop.rs

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
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use crate::layout::row::row;
use crate::types::{ Corners, Length, WidgetId };
use crate::widget::flex::flex;
use crate::widget::pressable::pressable;
use crate::widget::scroll::scroll;
use crate::widget::viewport::viewport;
use crate::widget::{ container, text, Element };

use super::row::{ TabCells, TabRow };
use super::theme::{ EDGE_WIDTH, GAP, ICON_SIZE, LABEL_SIZE, TAB_CLOSE_ICON, TAB_ID, TAB_RADIUS };
use super::{ parts, IndexMsg, TabItem, TabStrip, TabStripColors };

/// `[leading] [tabs…][after tabs]  clear  [trailing]`, the tabs scrolling sideways when they do not fit.
pub( super ) fn build<Msg: Clone + 'static>( strip: TabStrip<Msg> ) -> Element<Msg>
{
	let TabStrip { tabs, selected, on_select, on_close, reorderable, leading, after, trailing, colors, motion, .. } = strip;
	let keys    = tabs.iter().map( |tab| tab.key ).collect();
	let current = tabs.get( selected ).map( |tab| tab.key );
	let handles = tabs.into_iter().enumerate()
		.map( |( idx, tab )| ( tab.key, handle( tab, idx, idx == selected, reorderable, &colors, on_select.as_ref(), on_close.as_ref() ) ) )
		.collect();
	let cells   = TabCells { tabs: handles, after, motion: motion.clone() };
	let tab_row = TabRow { child: Box::new( scroll( cells ).horizontal().into() ), keys, selected: current, motion };

	let mut bar = row().spacing( Length::widget( GAP ) );
	for button in leading
	{
		bar = bar.push( button );
	}
	bar = bar.push( flex( tab_row ) );
	for button in trailing
	{
		bar = bar.push( button );
	}
	container( bar )
		.background( colors.strip )
		.padding_h( Length::widget( 6.0 ) )
		.padding_top( Length::widget( 4.0 ) )
		.into()
}

/// Clipped to its slot, so it can grow from nothing and shrink away.
fn handle<Msg: Clone + 'static>(
	tab:       TabItem,
	idx:       usize,
	active:    bool,
	draggable: bool,
	colors:    &TabStripColors,
	on_select: Option<&IndexMsg<Msg>>,
	on_close:  Option<&IndexMsg<Msg>>,
) -> Element<Msg>
{
	let ( fill, edge, ink ) = colors.of( active );
	let mut body = row().spacing( Length::widget( 8.0 ) );
	if let Some( icon ) = parts::icon( &tab, Length::widget( ICON_SIZE ), ink )
	{
		body = body.push( icon );
	}
	body = body.push( flex( text( tab.label ).size( LABEL_SIZE ).weight( 400 ).color( ink ) ) );
	if let Some( close ) = on_close.filter( |_| tab.closable )
	{
		body = body.push( parts::close( close( idx ), ink, TAB_CLOSE_ICON ) );
	}
	let face = container( body )
		.background( fill )
		.border( edge, EDGE_WIDTH )
		.radius( Corners::top( TAB_RADIUS ) )
		.padding_left( Length::widget( 10.0 ) )
		.padding_right( Length::widget( 4.0 ) )
		.padding_v( Length::widget( 3.0 ) );
	let mut press = pressable( face ).id( WidgetId::keyed( TAB_ID, tab.key ) );
	if let Some( select ) = on_select
	{
		press = press.on_press( select( idx ) );
		if draggable { press = press.on_drag_start( select( idx ) ); }
	}
	viewport( press ).into()
}