diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-13 07:42:26 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-13 07:42:26 +0300 |
commit | 61ce76bde91aaa6a5267e88577e10115f9627e5f (patch) | |
tree | 5b1c7c98580c7109d9a6862e72a671a43b4554d5 /src | |
parent | fab098d5701a55fd4a0b3fb74ae332797e3be377 (diff) |
Toggle sidebar visibility; save width and mode
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 8 | ||||
-rw-r--r-- | src/gmdocument.c | 4 | ||||
-rw-r--r-- | src/gmdocument.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 15 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 115 | ||||
-rw-r--r-- | src/ui/sidebarwidget.h | 4 | ||||
-rw-r--r-- | src/ui/widget.c | 4 | ||||
-rw-r--r-- | src/ui/window.c | 8 |
8 files changed, 110 insertions, 49 deletions
@@ -8,6 +8,7 @@ | |||
8 | #include "ui/window.h" | 8 | #include "ui/window.h" |
9 | #include "ui/inputwidget.h" | 9 | #include "ui/inputwidget.h" |
10 | #include "ui/labelwidget.h" | 10 | #include "ui/labelwidget.h" |
11 | #include "ui/sidebarwidget.h" | ||
11 | #include "ui/documentwidget.h" | 12 | #include "ui/documentwidget.h" |
12 | #include "ui/util.h" | 13 | #include "ui/util.h" |
13 | #include "ui/text.h" | 14 | #include "ui/text.h" |
@@ -90,13 +91,18 @@ const iString *dateStr_(const iDate *date) { | |||
90 | 91 | ||
91 | static iString *serializePrefs_App_(const iApp *d) { | 92 | static iString *serializePrefs_App_(const iApp *d) { |
92 | iString *str = new_String(); | 93 | iString *str = new_String(); |
93 | iWindow *win = get_Window(); | 94 | const iSidebarWidget *sidebar = findWidget_App("sidebar"); |
94 | if (d->retainWindowSize) { | 95 | if (d->retainWindowSize) { |
95 | int w, h, x, y; | 96 | int w, h, x, y; |
96 | SDL_GetWindowSize(d->window->win, &w, &h); | 97 | SDL_GetWindowSize(d->window->win, &w, &h); |
97 | SDL_GetWindowPosition(d->window->win, &x, &y); | 98 | SDL_GetWindowPosition(d->window->win, &x, &y); |
98 | appendFormat_String(str, "restorewindow width:%d height:%d coord:%d %d\n", w, h, x, y); | 99 | appendFormat_String(str, "restorewindow width:%d height:%d coord:%d %d\n", w, h, x, y); |
100 | appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); | ||
99 | } | 101 | } |
102 | if (isVisible_Widget(constAs_Widget(sidebar))) { | ||
103 | appendCStr_String(str, "sidebar.toggle\n"); | ||
104 | } | ||
105 | appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar)); | ||
100 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); | 106 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); |
101 | return str; | 107 | return str; |
102 | } | 108 | } |
diff --git a/src/gmdocument.c b/src/gmdocument.c index 3fa4a32a..ed1d1cae 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -907,6 +907,10 @@ const iArray *headings_GmDocument(const iGmDocument *d) { | |||
907 | return &d->headings; | 907 | return &d->headings; |
908 | } | 908 | } |
909 | 909 | ||
910 | const iString *source_GmDocument(const iGmDocument *d) { | ||
911 | return &d->source; | ||
912 | } | ||
913 | |||
910 | iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { | 914 | iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { |
911 | const char * src = constBegin_String(&d->source); | 915 | const char * src = constBegin_String(&d->source); |
912 | const size_t startPos = (start ? start - src : 0); | 916 | const size_t startPos = (start ? start - src : 0); |
diff --git a/src/gmdocument.h b/src/gmdocument.h index fd5882bf..9f4bc1ca 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -86,6 +86,7 @@ void render_GmDocument (const iGmDocument *, iRangei visRan | |||
86 | iInt2 size_GmDocument (const iGmDocument *); | 86 | iInt2 size_GmDocument (const iGmDocument *); |
87 | iBool hasSiteBanner_GmDocument (const iGmDocument *); | 87 | iBool hasSiteBanner_GmDocument (const iGmDocument *); |
88 | const iArray * headings_GmDocument (const iGmDocument *); | 88 | const iArray * headings_GmDocument (const iGmDocument *); |
89 | const iString * source_GmDocument (const iGmDocument *); | ||
89 | 90 | ||
90 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); | 91 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
91 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 92 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 483dc983..11b92e63 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -887,17 +887,22 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
887 | : "Not trusted")); | 887 | : "Not trusted")); |
888 | return iTrue; | 888 | return iTrue; |
889 | } | 889 | } |
890 | else if (equal_Command(cmd, "copy")) { | 890 | else if (equal_Command(cmd, "copy") && document_App() == d) { |
891 | iString *copied; | ||
891 | if (d->selectMark.start) { | 892 | if (d->selectMark.start) { |
892 | iRangecc mark = d->selectMark; | 893 | iRangecc mark = d->selectMark; |
893 | if (mark.start > mark.end) { | 894 | if (mark.start > mark.end) { |
894 | iSwap(const char *, mark.start, mark.end); | 895 | iSwap(const char *, mark.start, mark.end); |
895 | } | 896 | } |
896 | iString *copied = newRange_String(mark); | 897 | copied = newRange_String(mark); |
897 | SDL_SetClipboardText(cstr_String(copied)); | 898 | } |
898 | delete_String(copied); | 899 | else { |
899 | return iTrue; | 900 | /* Full document. */ |
901 | copied = copy_String(source_GmDocument(d->doc)); | ||
900 | } | 902 | } |
903 | SDL_SetClipboardText(cstr_String(copied)); | ||
904 | delete_String(copied); | ||
905 | return iTrue; | ||
901 | } | 906 | } |
902 | else if (equalWidget_Command(cmd, w, "document.copylink")) { | 907 | else if (equalWidget_Command(cmd, w, "document.copylink")) { |
903 | if (d->hoverLink) { | 908 | if (d->hoverLink) { |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 8ca2e448..a5e0e4ee 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -46,6 +46,7 @@ struct Impl_SidebarWidget { | |||
46 | enum iSidebarMode mode; | 46 | enum iSidebarMode mode; |
47 | iScrollWidget *scroll; | 47 | iScrollWidget *scroll; |
48 | int scrollY; | 48 | int scrollY; |
49 | int width; | ||
49 | iLabelWidget *modeButtons[max_SidebarMode]; | 50 | iLabelWidget *modeButtons[max_SidebarMode]; |
50 | int itemHeight; | 51 | int itemHeight; |
51 | int maxButtonLabelWidth; | 52 | int maxButtonLabelWidth; |
@@ -131,6 +132,14 @@ void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { | |||
131 | d->itemHeight = heights[mode] * lineHeight_Text(default_FontId); | 132 | d->itemHeight = heights[mode] * lineHeight_Text(default_FontId); |
132 | } | 133 | } |
133 | 134 | ||
135 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { | ||
136 | return d->mode; | ||
137 | } | ||
138 | |||
139 | int width_SidebarWidget(const iSidebarWidget *d) { | ||
140 | return d->width; | ||
141 | } | ||
142 | |||
134 | static const char *normalModeLabels_[max_SidebarMode] = { | 143 | static const char *normalModeLabels_[max_SidebarMode] = { |
135 | "\U0001f5b9 Outline", | 144 | "\U0001f5b9 Outline", |
136 | "\U0001f588 Bookmarks", | 145 | "\U0001f588 Bookmarks", |
@@ -148,11 +157,15 @@ static const char *tightModeLabels_[max_SidebarMode] = { | |||
148 | void init_SidebarWidget(iSidebarWidget *d) { | 157 | void init_SidebarWidget(iSidebarWidget *d) { |
149 | iWidget *w = as_Widget(d); | 158 | iWidget *w = as_Widget(d); |
150 | init_Widget(w); | 159 | init_Widget(w); |
160 | setId_Widget(w, "sidebar"); | ||
151 | setBackgroundColor_Widget(w, none_ColorId); | 161 | setBackgroundColor_Widget(w, none_ColorId); |
152 | setFlags_Widget(w, hover_WidgetFlag | arrangeHorizontal_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | 162 | setFlags_Widget(w, |
163 | hidden_WidgetFlag | hover_WidgetFlag | arrangeHorizontal_WidgetFlag | | ||
164 | resizeWidthOfChildren_WidgetFlag | collapse_WidgetFlag, | ||
165 | iTrue); | ||
153 | d->scrollY = 0; | 166 | d->scrollY = 0; |
154 | d->mode = -1; | 167 | d->mode = -1; |
155 | w->rect.size.x = 75 * gap_UI; | 168 | d->width = 75 * gap_UI; |
156 | init_Array(&d->items, sizeof(iSidebarItem)); | 169 | init_Array(&d->items, sizeof(iSidebarItem)); |
157 | d->hoverItem = iInvalidPos; | 170 | d->hoverItem = iInvalidPos; |
158 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 171 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
@@ -165,7 +178,8 @@ void init_SidebarWidget(iSidebarWidget *d) { | |||
165 | new_LabelWidget(normalModeLabels_[i], 0, 0, format_CStr("sidebar.mode arg:%d", i))), | 178 | new_LabelWidget(normalModeLabels_[i], 0, 0, format_CStr("sidebar.mode arg:%d", i))), |
166 | frameless_WidgetFlag | expand_WidgetFlag); | 179 | frameless_WidgetFlag | expand_WidgetFlag); |
167 | d->maxButtonLabelWidth = | 180 | d->maxButtonLabelWidth = |
168 | iMaxi(d->maxButtonLabelWidth, 3 * gap_UI + measure_Text(default_FontId, normalModeLabels_[i]).x); | 181 | iMaxi(d->maxButtonLabelWidth, |
182 | 3 * gap_UI + measure_Text(default_FontId, normalModeLabels_[i]).x); | ||
169 | } | 183 | } |
170 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 184 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
171 | setThumb_ScrollWidget(d->scroll, 0, 0); | 185 | setThumb_ScrollWidget(d->scroll, 0, 0); |
@@ -245,6 +259,21 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
245 | } | 259 | } |
246 | } | 260 | } |
247 | 261 | ||
262 | void setWidth_SidebarWidget(iSidebarWidget *d, int width) { | ||
263 | iWidget *w = as_Widget(d); | ||
264 | width = iMax(30 * gap_UI, width); | ||
265 | d->width = width; | ||
266 | if (isVisible_Widget(w)) { | ||
267 | w->rect.size.x = width; | ||
268 | } | ||
269 | arrange_Widget(findWidget_App("doctabs")); | ||
270 | checkModeButtonLayout_SidebarWidget_(d); | ||
271 | if (!isRefreshPending_App()) { | ||
272 | updateSize_DocumentWidget(document_App()); | ||
273 | refresh_Widget(w); | ||
274 | } | ||
275 | } | ||
276 | |||
248 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { | 277 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { |
249 | iWidget *w = as_Widget(d); | 278 | iWidget *w = as_Widget(d); |
250 | /* Handle commands. */ | 279 | /* Handle commands. */ |
@@ -252,51 +281,57 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
252 | updateVisible_SidebarWidget_(d); | 281 | updateVisible_SidebarWidget_(d); |
253 | checkModeButtonLayout_SidebarWidget_(d); | 282 | checkModeButtonLayout_SidebarWidget_(d); |
254 | } | 283 | } |
255 | else if (isCommand_Widget(w, ev, "mouse.clicked")) { | 284 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { |
256 | const char *cmd = command_UserEvent(ev); | 285 | const char *cmd = command_UserEvent(ev); |
257 | if (argLabel_Command(cmd, "button") == SDL_BUTTON_LEFT) { | 286 | if (isCommand_Widget(w, ev, "mouse.clicked")) { |
258 | if (arg_Command(cmd)) { | 287 | if (argLabel_Command(cmd, "button") == SDL_BUTTON_LEFT) { |
259 | setFlags_Widget(d->resizer, pressed_WidgetFlag, iTrue); | 288 | if (arg_Command(cmd)) { |
260 | setBackgroundColor_Widget(d->resizer, gray75_ColorId); | 289 | setFlags_Widget(d->resizer, pressed_WidgetFlag, iTrue); |
261 | setMouseGrab_Widget(d->resizer); | 290 | setBackgroundColor_Widget(d->resizer, gray75_ColorId); |
262 | refresh_Widget(d->resizer); | 291 | setMouseGrab_Widget(d->resizer); |
263 | } | 292 | refresh_Widget(d->resizer); |
264 | else { | 293 | } |
265 | setFlags_Widget(d->resizer, pressed_WidgetFlag, iFalse); | 294 | else { |
266 | setBackgroundColor_Widget(d->resizer, none_ColorId); | 295 | setFlags_Widget(d->resizer, pressed_WidgetFlag, iFalse); |
267 | setMouseGrab_Widget(NULL); | 296 | setBackgroundColor_Widget(d->resizer, none_ColorId); |
268 | refresh_Widget(d->resizer); | 297 | setMouseGrab_Widget(NULL); |
298 | refresh_Widget(d->resizer); | ||
299 | } | ||
269 | } | 300 | } |
301 | return iTrue; | ||
270 | } | 302 | } |
271 | return iTrue; | 303 | else if (isCommand_Widget(w, ev, "mouse.moved")) { |
272 | } | 304 | if (isResizing_SidebarWidget_(d)) { |
273 | else if (isCommand_Widget(w, ev, "mouse.moved")) { | 305 | const iInt2 local = localCoord_Widget(w, coord_Command(cmd)); |
274 | const char *cmd = command_UserEvent(ev); | 306 | setWidth_SidebarWidget(d, local.x + d->resizer->rect.size.x / 2); |
275 | if (isResizing_SidebarWidget_(d)) { | ||
276 | const iInt2 local = localCoord_Widget(w, coord_Command(cmd)); | ||
277 | w->rect.size.x = iMax(30 * gap_UI, local.x + d->resizer->rect.size.x / 2); | ||
278 | arrange_Widget(findWidget_App("doctabs")); | ||
279 | checkModeButtonLayout_SidebarWidget_(d); | ||
280 | if (!isRefreshPending_App()) { | ||
281 | updateSize_DocumentWidget(document_App()); | ||
282 | refresh_Widget(w); | ||
283 | } | 307 | } |
308 | return iTrue; | ||
284 | } | 309 | } |
285 | return iTrue; | 310 | else if (equal_Command(cmd, "sidebar.width")) { |
286 | } | 311 | setWidth_SidebarWidget(d, arg_Command(cmd)); |
287 | else if (isCommand_Widget(w, ev, "scroll.moved")) { | 312 | return iTrue; |
288 | d->scrollY = arg_Command(command_UserEvent(ev)); | 313 | } |
289 | d->hoverItem = iInvalidPos; | 314 | else if (equal_Command(cmd, "sidebar.mode")) { |
290 | refresh_Widget(w); | ||
291 | return iTrue; | ||
292 | } | ||
293 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | ||
294 | const char *cmd = command_UserEvent(ev); | ||
295 | if (isCommand_Widget(w, ev, "sidebar.mode")) { | ||
296 | setMode_SidebarWidget(d, arg_Command(cmd)); | 315 | setMode_SidebarWidget(d, arg_Command(cmd)); |
297 | updateItems_SidebarWidget_(d); | 316 | updateItems_SidebarWidget_(d); |
298 | return iTrue; | 317 | return iTrue; |
299 | } | 318 | } |
319 | else if (equal_Command(cmd, "sidebar.toggle")) { | ||
320 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); | ||
321 | if (isVisible_Widget(w)) { | ||
322 | w->rect.size.x = d->width; | ||
323 | } | ||
324 | arrange_Widget(w->parent); | ||
325 | updateSize_DocumentWidget(document_App()); | ||
326 | refresh_Widget(w->parent); | ||
327 | return iTrue; | ||
328 | } | ||
329 | else if (equal_Command(cmd, "scroll.moved")) { | ||
330 | d->scrollY = arg_Command(command_UserEvent(ev)); | ||
331 | d->hoverItem = iInvalidPos; | ||
332 | refresh_Widget(w); | ||
333 | return iTrue; | ||
334 | } | ||
300 | else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) { | 335 | else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) { |
301 | updateItems_SidebarWidget_(d); | 336 | updateItems_SidebarWidget_(d); |
302 | } | 337 | } |
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h index 80f22fca..4d002eee 100644 --- a/src/ui/sidebarwidget.h +++ b/src/ui/sidebarwidget.h | |||
@@ -15,4 +15,6 @@ iDeclareObjectConstruction(SidebarWidget) | |||
15 | 15 | ||
16 | void setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode); | 16 | void setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode); |
17 | 17 | ||
18 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *); | 18 | enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *); |
19 | int width_SidebarWidget (const iSidebarWidget *); | ||
20 | void setWidth_SidebarWidget (iSidebarWidget *, int width); | ||
diff --git a/src/ui/widget.c b/src/ui/widget.c index 2a889683..07f1f17d 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -149,13 +149,13 @@ static int widestChild_Widget_(const iWidget *d) { | |||
149 | } | 149 | } |
150 | 150 | ||
151 | static void setWidth_Widget_(iWidget *d, int width) { | 151 | static void setWidth_Widget_(iWidget *d, int width) { |
152 | if (~d->flags & fixedWidth_WidgetFlag) { | 152 | if (~d->flags & fixedWidth_WidgetFlag || d->flags & collapse_WidgetFlag) { |
153 | d->rect.size.x = width; | 153 | d->rect.size.x = width; |
154 | } | 154 | } |
155 | } | 155 | } |
156 | 156 | ||
157 | static void setHeight_Widget_(iWidget *d, int height) { | 157 | static void setHeight_Widget_(iWidget *d, int height) { |
158 | if (~d->flags & fixedHeight_WidgetFlag) { | 158 | if (~d->flags & fixedHeight_WidgetFlag || d->flags & collapse_WidgetFlag) { |
159 | d->rect.size.y = height; | 159 | d->rect.size.y = height; |
160 | } | 160 | } |
161 | } | 161 | } |
diff --git a/src/ui/window.c b/src/ui/window.c index 3a6b6e30..599044aa 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -71,6 +71,10 @@ static const iMenuItem navMenuItems[] = { | |||
71 | { "New Tab", 't', KMOD_PRIMARY, "tabs.new" }, | 71 | { "New Tab", 't', KMOD_PRIMARY, "tabs.new" }, |
72 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, | 72 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, |
73 | { "---", 0, 0, NULL }, | 73 | { "---", 0, 0, NULL }, |
74 | { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" }, | ||
75 | { "---", 0, 0, NULL }, | ||
76 | { "Toggle Sidebar", SDLK_s, KMOD_PRIMARY | KMOD_ALT, "sidebar.toggle" }, | ||
77 | { "---", 0, 0, NULL }, | ||
74 | { "Preferences...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 78 | { "Preferences...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
75 | { "---", 0, 0, NULL }, | 79 | { "---", 0, 0, NULL }, |
76 | { "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" } | 80 | { "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" } |
@@ -85,9 +89,11 @@ static const iMenuItem fileMenuItems[] = { | |||
85 | }; | 89 | }; |
86 | 90 | ||
87 | static const iMenuItem editMenuItems[] = { | 91 | static const iMenuItem editMenuItems[] = { |
92 | { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" }, | ||
88 | }; | 93 | }; |
89 | 94 | ||
90 | static const iMenuItem viewMenuItems[] = { | 95 | static const iMenuItem viewMenuItems[] = { |
96 | { "Toggle Sidebar", SDLK_s, KMOD_PRIMARY | KMOD_ALT, "sidebar.toggle" }, | ||
91 | }; | 97 | }; |
92 | #endif | 98 | #endif |
93 | 99 | ||
@@ -265,6 +271,8 @@ static void setupUserInterface_Window(iWindow *d) { | |||
265 | addChild_Widget(navBar, iClob(navMenu)); | 271 | addChild_Widget(navBar, iClob(navMenu)); |
266 | #else | 272 | #else |
267 | insertMenuItems_MacOS("File", fileMenuItems, iElemCount(fileMenuItems)); | 273 | insertMenuItems_MacOS("File", fileMenuItems, iElemCount(fileMenuItems)); |
274 | insertMenuItems_MacOS("Edit", editMenuItems, iElemCount(editMenuItems)); | ||
275 | insertMenuItems_MacOS("View", viewMenuItems, iElemCount(viewMenuItems)); | ||
268 | #endif | 276 | #endif |
269 | } | 277 | } |
270 | /* Tab bar. */ { | 278 | /* Tab bar. */ { |