1 // Written in the D programming language.
2 /++
3  + Authors: KanzakiKino
4  + Copyright: KanzakiKino 2018
5  + License: LGPL-3.0
6 ++/
7 module w4d.widget.tabhost;
8 import w4d.layout.lineup,
9        w4d.layout.split,
10        w4d.parser.colorset,
11        w4d.style.rect,
12        w4d.style.scalar,
13        w4d.style.widget,
14        w4d.widget.base,
15        w4d.widget.button,
16        w4d.widget.panel,
17        w4d.exception;
18 import g4d.ft.font;
19 import std.algorithm,
20        std.array,
21        std.conv;
22 
23 /// A widget of host for tabs.
24 class TabHostWidget : Widget
25 {
26     protected class TabHeaderWidget : ButtonWidget
27     {
28         const int id;
29 
30         this ( int i )
31         {
32             super();
33             id = i;
34 
35             parseColorSetsFromFile!"colorset/tabheader.yaml"( style );
36             style.box.size.width     = Scalar.None;
37             style.box.size.height    = Scalar.None;
38             style.box.margins.bottom = 0.mm;
39         }
40 
41         override void handleButtonPress ()
42         {
43             super.handleButtonPress();
44             activateTab( findTabWithId(id) );
45         }
46     }
47 
48     protected class TabHeaderPanelWidget : PanelWidget
49     {
50         override @property Widget[] children ()
51         {
52             return _tabs.map!( x => x.header ).array.to!(Widget[]);
53         }
54         this ()
55         {
56             super();
57             setLayout!( HorizontalMonospacedSplitLayout );
58             style.box.size.height = 10.mm;
59         }
60     }
61 
62     protected class Tab
63     {
64         protected TabHeaderWidget _header;
65         protected Widget          _contents;
66 
67         @property header   () { return _header; }
68         @property contents () { return _contents; }
69 
70         @property id () { return _header.id; }
71 
72         this ( int id, dstring title, Widget w )
73         {
74             _header = new TabHeaderWidget( id );
75             this.title = title;
76 
77             _contents = w;
78         }
79 
80         @property title ( dstring v )
81         {
82             _header.loadText( v, _fontface );
83         }
84     }
85 
86     protected FontFace _fontface;
87 
88     protected Tab[] _tabs;
89     protected long  _activatedIndex;
90 
91     protected TabHeaderPanelWidget _headers;
92 
93     ///
94     override @property Widget[] children ()
95     {
96         Widget[] result = [_headers];
97         if ( auto tab = activatedTab ) {
98             result ~= tab.contents;
99         }
100         return result;
101     }
102 
103     ///
104     this ()
105     {
106         super();
107         _tabs = [];
108 
109         setLayout!( VerticalSplitLayout );
110         _headers = new TabHeaderPanelWidget;
111     }
112 
113     /// Changes the font of tab headers.
114     void setFontFace ( FontFace face )
115     {
116         _fontface = face;
117     }
118 
119     protected long indexOf ( in Tab t )
120     {
121         return _tabs.countUntil!"a is b"(t);
122     }
123     protected long indexOf ( int id )
124     {
125         return _tabs.countUntil!"a.id == b"(id);
126     }
127 
128     /// A tab that is activated.
129     @property activatedTab ()
130     {
131         if ( _activatedIndex >= 0 ) {
132             return _tabs[_activatedIndex.to!uint];
133         }
134         return null;
135     }
136 
137     /// Finds a tab that matched the id.
138     Tab findTabWithId ( int id )
139     {
140         auto index = indexOf( id );
141         return index >= 0? _tabs[index.to!uint]: null;
142     }
143 
144     /// Adds the tab.
145     void addTab ( int id, dstring title, Widget contents )
146     {
147         enforce( indexOf(id) < 0, "Id is duplicated." );
148 
149         _tabs ~= new Tab( id, title, contents );
150         if ( _activatedIndex < 0 ) {
151             activateTab( _tabs[0] );
152         }
153     }
154     /// Removes the tab.
155     void removeTab ( in Tab t )
156     {
157         _tabs = _tabs.remove!( x => x is t );
158     }
159 
160     /// Activates the tab.
161     void activateTab ( Tab t )
162     {
163         auto index = indexOf(t);
164         enforce( index >= 0, "Tab doesn't belong to this host." );
165 
166         _activatedIndex = index;
167 
168         _tabs.each!( x => x.header.disableState( WidgetState.Selected ) );
169         t.header.enableState( WidgetState.Selected );
170 
171         _context.setFocused( null ); // drop the focus focibly
172         requestLayout();
173     }
174 
175     ///
176     override @property bool trackable () { return false; }
177     ///
178     override @property bool focusable () { return false; }
179 }