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