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 }