summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ui/listwidget.c323
-rw-r--r--src/ui/listwidget.h69
-rw-r--r--src/ui/sidebarwidget.c607
-rw-r--r--src/ui/util.c4
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/widget.c6
6 files changed, 755 insertions, 255 deletions
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
new file mode 100644
index 00000000..3865becc
--- /dev/null
+++ b/src/ui/listwidget.c
@@ -0,0 +1,323 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "listwidget.h"
24#include "scrollwidget.h"
25#include "paint.h"
26#include "util.h"
27#include "command.h"
28
29#include <the_Foundation/intset.h>
30
31void init_ListItem(iListItem *d) {
32 d->isSeparator = iFalse;
33 d->isSelected = iFalse;
34}
35
36void deinit_ListItem(iListItem *d) {
37 iUnused(d);
38}
39
40iDefineObjectConstruction(ListItem)
41iDefineClass(ListItem)
42
43/*----------------------------------------------------------------------------------------------*/
44
45iDefineObjectConstruction(ListWidget)
46
47struct Impl_ListWidget {
48 iWidget widget;
49 iScrollWidget *scroll;
50 int scrollY;
51 int itemHeight;
52 iPtrArray items;
53 size_t hoverItem;
54 iClick click;
55 iIntSet invalidItems;
56 SDL_Texture *visBuffer;
57 iBool visBufferValid;
58};
59
60void init_ListWidget(iListWidget *d) {
61 iWidget *w = as_Widget(d);
62 init_Widget(w);
63 setId_Widget(w, "list");
64 setFlags_Widget(w, hover_WidgetFlag, iTrue);
65 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
66 setThumb_ScrollWidget(d->scroll, 0, 0);
67 d->scrollY = 0;
68 init_PtrArray(&d->items);
69 d->hoverItem = iInvalidPos;
70 init_Click(&d->click, d, SDL_BUTTON_LEFT);
71 init_IntSet(&d->invalidItems);
72 d->visBuffer = NULL;
73 d->visBufferValid = iFalse;
74}
75
76void deinit_ListWidget(iListWidget *d) {
77 clear_ListWidget(d);
78 deinit_PtrArray(&d->items);
79 SDL_DestroyTexture(d->visBuffer);
80}
81
82void invalidate_ListWidget(iListWidget *d) {
83 d->visBufferValid = iFalse;
84 clear_IntSet(&d->invalidItems); /* all will be drawn */
85 refresh_Widget(as_Widget(d));
86}
87
88void clear_ListWidget(iListWidget *d) {
89 iForEach(PtrArray, i, &d->items) {
90 deref_Object(i.ptr);
91 }
92 clear_PtrArray(&d->items);
93 d->hoverItem = iInvalidPos;
94}
95
96void addItem_ListWidget(iListWidget *d, iAnyObject *item) {
97 pushBack_PtrArray(&d->items, ref_Object(item));
98}
99
100iScrollWidget *scroll_ListWidget(iListWidget *d) {
101 return d->scroll;
102}
103
104size_t numItems_ListWidget(const iListWidget *d) {
105 return size_PtrArray(&d->items);
106}
107
108static int scrollMax_ListWidget_(const iListWidget *d) {
109 return iMax(0,
110 (int) size_PtrArray(&d->items) * d->itemHeight -
111 height_Rect(innerBounds_Widget(constAs_Widget(d))));
112}
113
114void updateVisible_ListWidget(iListWidget *d) {
115 const int contentSize = size_PtrArray(&d->items) * d->itemHeight;
116 const iRect bounds = innerBounds_Widget(as_Widget(d));
117 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });
118 setThumb_ScrollWidget(d->scroll,
119 d->scrollY,
120 contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) *
121 height_Rect(bounds) / contentSize
122 : 0);
123}
124
125void setItemHeight_ListWidget(iListWidget *d, int itemHeight) {
126 d->itemHeight = itemHeight;
127 invalidate_ListWidget(d);
128}
129
130int scrollPos_ListWidget(const iListWidget *d) {
131 return d->scrollY;
132}
133
134void setScrollPos_ListWidget(iListWidget *d, int pos) {
135 d->scrollY = pos;
136 d->hoverItem = iInvalidPos;
137 invalidate_ListWidget(d);
138}
139
140void scrollOffset_ListWidget(iListWidget *d, int offset) {
141 const int oldScroll = d->scrollY;
142 d->scrollY += offset;
143 if (d->scrollY < 0) {
144 d->scrollY = 0;
145 }
146 const int scrollMax = scrollMax_ListWidget_(d);
147 d->scrollY = iMin(d->scrollY, scrollMax);
148 if (oldScroll != d->scrollY) {
149 d->hoverItem = iInvalidPos;
150 updateVisible_ListWidget(d);
151 invalidate_ListWidget(d);
152 }
153}
154
155static int visCount_ListWidget_(const iListWidget *d) {
156 return iMin(height_Rect(innerBounds_Widget(constAs_Widget(d))) / d->itemHeight,
157 (int) size_PtrArray(&d->items));
158}
159
160static iRanges visRange_ListWidget_(const iListWidget *d) {
161 iRanges vis = { d->scrollY / d->itemHeight, 0 };
162 vis.end = iMin(size_PtrArray(&d->items), vis.start + visCount_ListWidget_(d));
163 return vis;
164}
165
166size_t itemIndex_ListWidget(const iListWidget *d, iInt2 pos) {
167 const iRect bounds = innerBounds_Widget(constAs_Widget(d));
168 pos.y -= top_Rect(bounds) - d->scrollY;
169 if (pos.y < 0) return iInvalidPos;
170 size_t index = pos.y / d->itemHeight;
171 if (index >= size_Array(&d->items)) return iInvalidPos;
172 return index;
173}
174
175const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) {
176 if (d->hoverItem < size_PtrArray(&d->items)) {
177 return constAt_PtrArray(&d->items, d->hoverItem);
178 }
179 return NULL;
180}
181
182iAnyObject *hoverItem_ListWidget(iListWidget *d) {
183 if (d->hoverItem < size_PtrArray(&d->items)) {
184 return at_PtrArray(&d->items, d->hoverItem);
185 }
186 return NULL;
187}
188
189static void setHoverItem_ListWidget_(iListWidget *d, size_t index) {
190 if (index < size_PtrArray(&d->items)) {
191 const iListItem *item = at_PtrArray(&d->items, index);
192 if (item->isSeparator) {
193 index = iInvalidPos;
194 }
195 }
196 if (d->hoverItem != index) {
197 insert_IntSet(&d->invalidItems, d->hoverItem);
198 insert_IntSet(&d->invalidItems, index);
199 d->hoverItem = index;
200 refresh_Widget(as_Widget(d));
201 }
202}
203
204void updateMouseHover_ListWidget(iListWidget *d) {
205 const iInt2 mouse = mouseCoord_Window(get_Window());
206 setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));
207}
208
209static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
210 iWidget *w = as_Widget(d);
211 if (isCommand_SDLEvent(ev)) {
212 const char *cmd = command_UserEvent(ev);
213 if (equal_Command(cmd, "theme.changed")) {
214 invalidate_ListWidget(d);
215 }
216 else if (isCommand_Widget(w, ev, "scroll.moved")) {
217 setScrollPos_ListWidget(d, arg_Command(cmd));
218 return iTrue;
219 }
220 }
221 if (ev->type == SDL_MOUSEMOTION) {
222 const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y);
223 size_t hover = iInvalidPos;
224 if (!contains_Widget(constAs_Widget(d->scroll), mouse) &&
225 contains_Widget(w, mouse)) {
226 hover = itemIndex_ListWidget(d, mouse);
227 }
228 setHoverItem_ListWidget_(d, hover);
229 }
230 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
231#if defined (iPlatformApple)
232 /* Momentum scrolling. */
233 scrollOffset_ListWidget(d, -ev->wheel.y * get_Window()->pixelRatio);
234#else
235 scrollOffset_ListWidget(d, -ev->wheel.y * 3 * d->itemHeight);
236#endif
237 return iTrue;
238 }
239 switch (processEvent_Click(&d->click, ev)) {
240 case started_ClickResult:
241 //invalidate_SidebarWidget_(d);
242 break;
243 case finished_ClickResult:
244 if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) &&
245 d->hoverItem != iInvalidSize) {
246 //itemClicked_SidebarWidget_(d, d->hoverItem);
247 postCommand_Widget(w, "list.clicked arg:%zu item:%p",
248 d->hoverItem, constHoverItem_ListWidget(d));
249 }
250 // invalidate_SidebarWidget_(d);
251 break;
252 default:
253 break;
254 }
255 return processEvent_Widget(w, ev);
256}
257
258static void allocVisBuffer_ListWidget_(iListWidget *d) {
259 const iInt2 size = innerBounds_Widget(as_Widget(d)).size;
260 if (!d->visBuffer || !isEqual_I2(size_SDLTexture(d->visBuffer), size)) {
261 if (d->visBuffer) {
262 SDL_DestroyTexture(d->visBuffer);
263 }
264 d->visBuffer = SDL_CreateTexture(renderer_Window(get_Window()),
265 SDL_PIXELFORMAT_RGBA8888,
266 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
267 size.x,
268 size.y);
269 SDL_SetTextureBlendMode(d->visBuffer, SDL_BLENDMODE_NONE);
270 d->visBufferValid = iFalse;
271 }
272}
273
274static void draw_ListWidget_(const iListWidget *d) {
275 const iWidget *w = constAs_Widget(d);
276 const iRect bounds = innerBounds_Widget(w);
277 draw_Widget(w); /* background */
278 iPaint p;
279 init_Paint(&p);
280 if (!d->visBufferValid || !isEmpty_IntSet(&d->invalidItems)) {
281 iListWidget *m = iConstCast(iListWidget *, d);
282 allocVisBuffer_ListWidget_(m);
283 beginTarget_Paint(&p, d->visBuffer);
284 const iRect bufBounds = (iRect){ zero_I2(), bounds.size };
285 if (!d->visBufferValid) {
286 fillRect_Paint(&p, bufBounds, w->bgColor);
287 }
288 /* Draw items. */ {
289 const iRanges visRange = visRange_ListWidget_(d);
290 iInt2 pos = init_I2(0, -(d->scrollY % d->itemHeight));
291 for (size_t i = visRange.start; i < visRange.end; i++) {
292 /* TODO: Refactor to loop through invalidItems only. */
293 if (!d->visBufferValid || contains_IntSet(&d->invalidItems, i)) {
294 const iListItem *item = constAt_PtrArray(&d->items, i);
295 const iRect itemRect = { pos, init_I2(width_Rect(bounds), d->itemHeight) };
296 setClip_Paint(&p, intersect_Rect(itemRect, bufBounds));
297 if (d->visBufferValid) {
298 fillRect_Paint(&p, itemRect, w->bgColor);
299 }
300 class_ListItem(item)->draw(item, &p, itemRect, d);
301 unsetClip_Paint(&p);
302 }
303 pos.y += d->itemHeight;
304 }
305 }
306 endTarget_Paint(&p);
307 /* Update state. */
308 m->visBufferValid = iTrue;
309 clear_IntSet(&m->invalidItems);
310 }
311 SDL_RenderCopy(
312 renderer_Window(get_Window()), d->visBuffer, NULL, (const SDL_Rect *) &bounds);
313}
314
315iBool isMouseDown_ListWidget(const iListWidget *d) {
316 return d->click.isActive &&
317 contains_Rect(innerBounds_Widget(constAs_Widget(d)), pos_Click(&d->click));
318}
319
320iBeginDefineSubclass(ListWidget, Widget)
321 .processEvent = (iAny *) processEvent_ListWidget_,
322 .draw = (iAny *) draw_ListWidget_,
323iEndDefineSubclass(ListWidget)
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h
new file mode 100644
index 00000000..38bdebc9
--- /dev/null
+++ b/src/ui/listwidget.h
@@ -0,0 +1,69 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "scrollwidget.h"
26#include "paint.h"
27
28#include <the_Foundation/ptrarray.h>
29
30iDeclareType(ListWidget)
31
32iBeginDeclareClass(ListItem)
33 void (*draw) (const iAnyObject *, iPaint *p, iRect rect, const iListWidget *list);
34iEndDeclareClass(ListItem)
35
36iDeclareType(ListItem)
37
38 struct Impl_ListItem {
39 iObject object;
40 iBool isSeparator;
41 iBool isSelected;
42};
43
44iDeclareObjectConstruction(ListItem)
45
46iDeclareWidgetClass(ListWidget)
47iDeclareObjectConstruction(ListWidget)
48
49void setItemHeight_ListWidget(iListWidget *, int itemHeight);
50
51void invalidate_ListWidget (iListWidget *);
52void clear_ListWidget (iListWidget *);
53void addItem_ListWidget (iListWidget *, iAnyObject *item);
54
55iScrollWidget * scroll_ListWidget (iListWidget *);
56
57int scrollPos_ListWidget (const iListWidget *);
58
59void setScrollPos_ListWidget (iListWidget *, int pos);
60void scrollOffset_ListWidget (iListWidget *, int offset);
61void updateVisible_ListWidget (iListWidget *);
62void updateMouseHover_ListWidget (iListWidget *);
63
64size_t numItems_ListWidget (const iListWidget *);
65size_t itemIndex_ListWidget(const iListWidget *, iInt2 pos);
66const iAnyObject *constHoverItem_ListWidget(const iListWidget *);
67iAnyObject *hoverItem_ListWidget(iListWidget *);
68
69iBool isMouseDown_ListWidget (const iListWidget *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index a919fca6..8dada9eb 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "gmdocument.h" 30#include "gmdocument.h"
31#include "inputwidget.h" 31#include "inputwidget.h"
32#include "labelwidget.h" 32#include "labelwidget.h"
33#include "listwidget.h"
33#include "paint.h" 34#include "paint.h"
34#include "scrollwidget.h" 35#include "scrollwidget.h"
35#include "util.h" 36#include "util.h"
@@ -41,27 +42,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
41#include <SDL_mouse.h> 42#include <SDL_mouse.h>
42 43
43iDeclareType(SidebarItem) 44iDeclareType(SidebarItem)
45typedef iListItemClass iSidebarItemClass;
44 46
45struct Impl_SidebarItem { 47struct Impl_SidebarItem {
46 uint32_t id; 48 iListItem listItem;
47 int indent; 49 uint32_t id;
48 iChar icon; 50 int indent;
49 iString label; 51 iChar icon;
50 iString meta; 52 iString label;
51 iString url; 53 iString meta;
52 iBool isSeparator; 54 iString url;
53 iBool isSelected;
54}; 55};
55 56
56void init_SidebarItem(iSidebarItem *d) { 57void init_SidebarItem(iSidebarItem *d) {
58 init_ListItem(&d->listItem);
57 d->id = 0; 59 d->id = 0;
58 d->indent = 0; 60 d->indent = 0;
59 d->icon = 0; 61 d->icon = 0;
60 init_String(&d->label); 62 init_String(&d->label);
61 init_String(&d->meta); 63 init_String(&d->meta);
62 init_String(&d->url); 64 init_String(&d->url);
63 d->isSeparator = iFalse;
64 d->isSelected = iFalse;
65} 65}
66 66
67void deinit_SidebarItem(iSidebarItem *d) { 67void deinit_SidebarItem(iSidebarItem *d) {
@@ -70,53 +70,60 @@ void deinit_SidebarItem(iSidebarItem *d) {
70 deinit_String(&d->label); 70 deinit_String(&d->label);
71} 71}
72 72
73iDefineTypeConstruction(SidebarItem) 73static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, const iListWidget *list);
74
75iBeginDefineSubclass(SidebarItem, ListItem)
76 .draw = (iAny *) draw_SidebarItem_,
77iEndDefineSubclass(SidebarItem)
78
79iDefineObjectConstruction(SidebarItem)
74 80
75/*----------------------------------------------------------------------------------------------*/ 81/*----------------------------------------------------------------------------------------------*/
76 82
77struct Impl_SidebarWidget { 83struct Impl_SidebarWidget {
78 iWidget widget; 84 iWidget widget;
79 enum iSidebarMode mode; 85 enum iSidebarMode mode;
80 iScrollWidget *scroll; 86 iListWidget *list;
81 int scrollY; 87// iScrollWidget *scroll;
88// int scrollY;
82 int modeScroll[max_SidebarMode]; 89 int modeScroll[max_SidebarMode];
83 int width; 90 int width;
84 iLabelWidget *modeButtons[max_SidebarMode]; 91 iLabelWidget *modeButtons[max_SidebarMode];
85 int itemHeight; 92 int itemHeight;
86 int maxButtonLabelWidth; 93 int maxButtonLabelWidth;
87 iArray items; 94// iArray items;
88 size_t hoverItem; 95// size_t hoverItem;
89 iClick click; 96// iClick click;
90 iWidget *resizer; 97 iWidget *resizer;
91 SDL_Cursor *resizeCursor; 98 SDL_Cursor *resizeCursor;
92 iWidget *menu; 99 iWidget *menu;
93 iIntSet invalidItems; 100// iIntSet invalidItems;
94 SDL_Texture *visBuffer; 101// SDL_Texture *visBuffer;
95 iBool visBufferValid; 102// iBool visBufferValid;
96}; 103};
97 104
98iDefineObjectConstruction(SidebarWidget) 105iDefineObjectConstruction(SidebarWidget)
99 106
100static void invalidate_SidebarWidget_(iSidebarWidget *d) { 107//static void invalidate_SidebarWidget_(iSidebarWidget *d) {
101 d->visBufferValid = iFalse; 108// d->visBufferValid = iFalse;
102 refresh_Widget(as_Widget(d)); 109// refresh_Widget(as_Widget(d));
103 clear_IntSet(&d->invalidItems); /* all will be drawn */ 110// clear_IntSet(&d->invalidItems); /* all will be drawn */
104} 111//}
105 112
106static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { 113static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) {
107 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; 114 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0;
108} 115}
109 116
110static void clearItems_SidebarWidget_(iSidebarWidget *d) { 117//static void clearItems_SidebarWidget_(iSidebarWidget *d) {
111 iForEach(Array, i, &d->items) { 118// iForEach(Array, i, &d->items) {
112 deinit_SidebarItem(i.value); 119// deinit_SidebarItem(i.value);
113 } 120// }
114 clear_Array(&d->items); 121// clear_Array(&d->items);
115} 122//}
116 123
117static iRect contentBounds_SidebarWidget_(const iSidebarWidget *d) { 124static iRect contentBounds_SidebarWidget_(const iSidebarWidget *d) {
118 iRect bounds = bounds_Widget(constAs_Widget(d)); 125 iRect bounds = bounds_Widget(constAs_Widget(d));
119 const iWidget *scroll = constAs_Widget(d->scroll); 126 const iWidget *scroll = constAs_Widget(scroll_ListWidget(d->list));
120 adjustEdges_Rect(&bounds, 127 adjustEdges_Rect(&bounds,
121 as_Widget(d->modeButtons[0])->rect.size.y + gap_UI, 128 as_Widget(d->modeButtons[0])->rect.size.y + gap_UI,
122 isVisible_Widget(scroll) ? -scroll->rect.size.x : 0, 129 isVisible_Widget(scroll) ? -scroll->rect.size.x : 0,
@@ -125,57 +132,60 @@ static iRect contentBounds_SidebarWidget_(const iSidebarWidget *d) {
125 return bounds; 132 return bounds;
126} 133}
127 134
128static int scrollMax_SidebarWidget_(const iSidebarWidget *d) { 135//static int scrollMax_SidebarWidget_(const iSidebarWidget *d) {
129 return iMax(0, 136// return iMax(0,
130 (int) size_Array(&d->items) * d->itemHeight - 137// (int) numItems_ListWidget(d->list) * d->itemHeight -
131 height_Rect(contentBounds_SidebarWidget_(d))); 138// height_Rect(contentBounds_SidebarWidget_(d)));
132} 139//}
133 140
134static void updateVisible_SidebarWidget_(iSidebarWidget *d) { 141//static void updateVisible_SidebarWidget_(iSidebarWidget *d) {
135 const int contentSize = size_Array(&d->items) * d->itemHeight; 142// const int contentSize = size_Array(&d->items) * d->itemHeight;
136 const iRect bounds = contentBounds_SidebarWidget_(d); 143// const iRect bounds = contentBounds_SidebarWidget_(d);
137 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_SidebarWidget_(d) }); 144// setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_SidebarWidget_(d) });
138 setThumb_ScrollWidget(d->scroll, 145// setThumb_ScrollWidget(d->scroll,
139 d->scrollY, 146// d->scrollY,
140 contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) * 147// contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) *
141 height_Rect(bounds) / contentSize 148// height_Rect(bounds) / contentSize
142 : 0); 149// : 0);
143} 150//}
144 151
145static int cmpTitle_Bookmark_(const iBookmark **a, const iBookmark **b) { 152static int cmpTitle_Bookmark_(const iBookmark **a, const iBookmark **b) {
146 return cmpStringCase_String(&(*a)->title, &(*b)->title); 153 return cmpStringCase_String(&(*a)->title, &(*b)->title);
147} 154}
148 155
149static void updateItems_SidebarWidget_(iSidebarWidget *d) { 156static void updateItems_SidebarWidget_(iSidebarWidget *d) {
150 clearItems_SidebarWidget_(d); 157// clearItems_SidebarWidget_(d);
158 clear_ListWidget(d->list);
151 destroy_Widget(d->menu); 159 destroy_Widget(d->menu);
152 d->menu = NULL; 160 d->menu = NULL;
153 d->hoverItem = iInvalidPos; 161// d->hoverItem = iInvalidPos;
154 switch (d->mode) { 162 switch (d->mode) {
155 case documentOutline_SidebarMode: { 163 case documentOutline_SidebarMode: {
156 const iGmDocument *doc = document_DocumentWidget(document_App()); 164 const iGmDocument *doc = document_DocumentWidget(document_App());
157 iConstForEach(Array, i, headings_GmDocument(doc)) { 165 iConstForEach(Array, i, headings_GmDocument(doc)) {
158 const iGmHeading *head = i.value; 166 const iGmHeading *head = i.value;
159 iSidebarItem item; 167 iSidebarItem *item = new_SidebarItem();
160 init_SidebarItem(&item); 168// init_SidebarItem(&item);
161 item.id = index_ArrayConstIterator(&i); 169 item->id = index_ArrayConstIterator(&i);
162 setRange_String(&item.label, head->text); 170 setRange_String(&item->label, head->text);
163 item.indent = head->level * 4 * gap_UI; 171 item->indent = head->level * 4 * gap_UI;
164 pushBack_Array(&d->items, &item); 172 addItem_ListWidget(d->list, item);
173 iRelease(item);
165 } 174 }
166 break; 175 break;
167 } 176 }
168 case bookmarks_SidebarMode: { 177 case bookmarks_SidebarMode: {
169 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, cmpTitle_Bookmark_)) { 178 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, cmpTitle_Bookmark_)) {
170 const iBookmark *bm = i.ptr; 179 const iBookmark *bm = i.ptr;
171 iSidebarItem item; 180 iSidebarItem *item = new_SidebarItem();
172 init_SidebarItem(&item); 181// init_SidebarItem(&item);
173 item.id = id_Bookmark(bm); 182 item->id = id_Bookmark(bm);
174 item.icon = bm->icon; 183 item->icon = bm->icon;
175 set_String(&item.url, &bm->url); 184 set_String(&item->url, &bm->url);
176 set_String(&item.label, &bm->title); 185 set_String(&item->label, &bm->title);
177 set_String(&item.meta, &bm->tags); 186 set_String(&item->meta, &bm->tags);
178 pushBack_Array(&d->items, &item); 187 addItem_ListWidget(d->list, item);
188 iRelease(item);
179 } 189 }
180 d->menu = makeMenu_Widget( 190 d->menu = makeMenu_Widget(
181 as_Widget(d), 191 as_Widget(d),
@@ -192,27 +202,32 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
192 const int thisYear = on.year; 202 const int thisYear = on.year;
193 iConstForEach(PtrArray, i, list_Visited(visited_App(), 200)) { 203 iConstForEach(PtrArray, i, list_Visited(visited_App(), 200)) {
194 const iVisitedUrl *visit = i.ptr; 204 const iVisitedUrl *visit = i.ptr;
195 iSidebarItem item; 205 iSidebarItem *item = new_SidebarItem();
196 init_SidebarItem(&item); 206// init_SidebarItem(&item);
197 set_String(&item.url, &visit->url); 207 set_String(&item->url, &visit->url);
198 iDate date; 208 iDate date;
199 init_Date(&date, &visit->when); 209 init_Date(&date, &visit->when);
200 if (date.day != on.day || date.month != on.month || date.year != on.year) { 210 if (date.day != on.day || date.month != on.month || date.year != on.year) {
201 on = date; 211 on = date;
202 /* Date separator. */ 212 /* Date separator. */
203 iSidebarItem sep; 213 iSidebarItem *sep = new_SidebarItem();
204 init_SidebarItem(&sep); 214// init_SidebarItem(&sep);
205 sep.isSeparator = iTrue; 215 sep->listItem.isSeparator = iTrue;
206 set_String(&sep.meta, 216 set_String(&sep->meta,
207 collect_String(format_Date( 217 collect_String(format_Date(
208 &date, date.year != thisYear ? "%b %d %Y" : "%b %d"))); 218 &date, date.year != thisYear ? "%b %d %Y" : "%b %d")));
209 pushBack_Array(&d->items, &sep); 219// pushBack_Array(&d->items, &sep);
220 addItem_ListWidget(d->list, sep);
221 iRelease(sep);
210 /* Date separators are two items tall. */ 222 /* Date separators are two items tall. */
211 init_SidebarItem(&sep); 223 sep = new_SidebarItem();
212 sep.isSeparator = iTrue; 224 sep->listItem.isSeparator = iTrue;
213 pushBack_Array(&d->items, &sep); 225 addItem_ListWidget(d->list, sep);
226 iRelease(sep);
214 } 227 }
215 pushBack_Array(&d->items, &item); 228// pushBack_Array(&d->items, &item);
229 addItem_ListWidget(d->list, item);
230 iRelease(item);
216 } 231 }
217 d->menu = makeMenu_Widget( 232 d->menu = makeMenu_Widget(
218 as_Widget(d), 233 as_Widget(d),
@@ -230,16 +245,16 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
230 const iString *tabUrl = url_DocumentWidget(document_App()); 245 const iString *tabUrl = url_DocumentWidget(document_App());
231 iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { 246 iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) {
232 const iGmIdentity *ident = i.ptr; 247 const iGmIdentity *ident = i.ptr;
233 iSidebarItem item; 248 iSidebarItem *item = new_SidebarItem();
234 init_SidebarItem(&item); 249// init_SidebarItem(&item);
235 item.id = index_PtrArrayConstIterator(&i); 250 item->id = index_PtrArrayConstIterator(&i);
236 item.icon = ident->icon; 251 item->icon = ident->icon;
237 set_String(&item.label, collect_String(subject_TlsCertificate(ident->cert))); 252 set_String(&item->label, collect_String(subject_TlsCertificate(ident->cert)));
238 iDate until; 253 iDate until;
239 validUntil_TlsCertificate(ident->cert, &until); 254 validUntil_TlsCertificate(ident->cert, &until);
240 const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); 255 const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl);
241 format_String( 256 format_String(
242 &item.meta, 257 &item->meta,
243 "%s", 258 "%s",
244 isActive ? "Using" 259 isActive ? "Using"
245 : isUsed_GmIdentity(ident) 260 : isUsed_GmIdentity(ident)
@@ -250,17 +265,19 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
250 ? "Temporary" 265 ? "Temporary"
251 : cstrCollect_String(format_Date(&until, "Expires %b %d, %Y")); 266 : cstrCollect_String(format_Date(&until, "Expires %b %d, %Y"));
252 if (isEmpty_String(&ident->notes)) { 267 if (isEmpty_String(&ident->notes)) {
253 appendFormat_String(&item.meta, "\n%s", expiry); 268 appendFormat_String(&item->meta, "\n%s", expiry);
254 } 269 }
255 else { 270 else {
256 appendFormat_String(&item.meta, 271 appendFormat_String(&item->meta,
257 " \u2014 %s\n%s%s", 272 " \u2014 %s\n%s%s",
258 expiry, 273 expiry,
259 escape_Color(uiHeading_ColorId), 274 escape_Color(uiHeading_ColorId),
260 cstr_String(&ident->notes)); 275 cstr_String(&ident->notes));
261 } 276 }
262 item.isSelected = isActive; 277 item->listItem.isSelected = isActive;
263 pushBack_Array(&d->items, &item); 278 //pushBack_Array(&d->items, &item);
279 addItem_ListWidget(d->list, item);
280 iRelease(item);
264 } 281 }
265 const iMenuItem menuItems[] = { 282 const iMenuItem menuItems[] = {
266 { "Use on This Page", 0, 0, "ident.use arg:1" }, 283 { "Use on This Page", 0, 0, "ident.use arg:1" },
@@ -280,39 +297,38 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
280 default: 297 default:
281 break; 298 break;
282 } 299 }
283 updateVisible_SidebarWidget_(d); 300 updateVisible_ListWidget(d->list);
284 invalidate_SidebarWidget_(d); 301 invalidate_ListWidget(d->list);
285} 302}
286 303
287static void scroll_SidebarWidget_(iSidebarWidget *d, int offset) { 304//static void scroll_SidebarWidget_(iSidebarWidget *d, int offset) {
288 const int oldScroll = d->scrollY; 305// const int oldScroll = d->scrollY;
289 d->scrollY += offset; 306// d->scrollY += offset;
290 if (d->scrollY < 0) { 307// if (d->scrollY < 0) {
291 d->scrollY = 0; 308// d->scrollY = 0;
292 } 309// }
293 const int scrollMax = scrollMax_SidebarWidget_(d); 310// const int scrollMax = scrollMax_SidebarWidget_(d);
294 d->scrollY = iMin(d->scrollY, scrollMax); 311// d->scrollY = iMin(d->scrollY, scrollMax);
295 if (oldScroll != d->scrollY) { 312// if (oldScroll != d->scrollY) {
296 d->hoverItem = iInvalidPos; 313// d->hoverItem = iInvalidPos;
297 updateVisible_SidebarWidget_(d); 314// updateVisible_SidebarWidget_(d);
298 invalidate_SidebarWidget_(d); 315// invalidate_SidebarWidget_(d);
299 } 316// }
300} 317//}
301 318
302void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { 319void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
303 if (d->mode == mode) return; 320 if (d->mode == mode) return;
304 if (d->mode >= 0 && d->mode < max_SidebarMode) { 321 if (d->mode >= 0 && d->mode < max_SidebarMode) {
305 d->modeScroll[d->mode] = d->scrollY; /* saved for later */ 322 d->modeScroll[d->mode] = scrollPos_ListWidget(d->list); /* saved for later */
306 } 323 }
307 d->mode = mode; 324 d->mode = mode;
308 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { 325 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
309 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); 326 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
310 } 327 }
311 const float heights[max_SidebarMode] = { 1.333f, 1.333f, 3.5f, 1.2f }; 328 const float heights[max_SidebarMode] = { 1.333f, 1.333f, 3.5f, 1.2f };
312 d->itemHeight = heights[mode] * lineHeight_Text(uiContent_FontId); 329 setItemHeight_ListWidget(d->list, heights[mode] * lineHeight_Text(uiContent_FontId));
313 invalidate_SidebarWidget_(d);
314 /* Restore previous scroll position. */ 330 /* Restore previous scroll position. */
315 d->scrollY = d->modeScroll[mode]; 331 setScrollPos_ListWidget(d->list, d->modeScroll[mode]);
316} 332}
317 333
318enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { 334enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) {
@@ -343,18 +359,15 @@ void init_SidebarWidget(iSidebarWidget *d) {
343 setId_Widget(w, "sidebar"); 359 setId_Widget(w, "sidebar");
344 setBackgroundColor_Widget(w, none_ColorId); 360 setBackgroundColor_Widget(w, none_ColorId);
345 setFlags_Widget(w, 361 setFlags_Widget(w,
346 hidden_WidgetFlag | hover_WidgetFlag | arrangeHorizontal_WidgetFlag | 362 hidden_WidgetFlag | arrangeVertical_WidgetFlag |
347 resizeWidthOfChildren_WidgetFlag | collapse_WidgetFlag, 363 resizeChildren_WidgetFlag | collapse_WidgetFlag,
348 iTrue); 364 iTrue);
349 d->scrollY = 0;
350 iZap(d->modeScroll); 365 iZap(d->modeScroll);
351 d->mode = -1; 366 d->mode = -1;
352 d->width = 60 * gap_UI; 367 d->width = 60 * gap_UI;
353 init_Array(&d->items, sizeof(iSidebarItem));
354 d->hoverItem = iInvalidPos;
355 init_Click(&d->click, d, SDL_BUTTON_LEFT);
356 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue); 368 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue);
357 d->maxButtonLabelWidth = 0; 369 d->maxButtonLabelWidth = 0;
370 /* TODO: Add a parent h-div for the mode buttons. */
358 for (int i = 0; i < max_SidebarMode; i++) { 371 for (int i = 0; i < max_SidebarMode; i++) {
359 d->modeButtons[i] = addChildFlags_Widget( 372 d->modeButtons[i] = addChildFlags_Widget(
360 w, 373 w,
@@ -365,8 +378,7 @@ void init_SidebarWidget(iSidebarWidget *d) {
365 iMaxi(d->maxButtonLabelWidth, 378 iMaxi(d->maxButtonLabelWidth,
366 3 * gap_UI + measure_Text(uiLabel_FontId, normalModeLabels_[i]).x); 379 3 * gap_UI + measure_Text(uiLabel_FontId, normalModeLabels_[i]).x);
367 } 380 }
368 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 381 addChildFlags_Widget(w, iClob(d->list = new_ListWidget()), expand_WidgetFlag);
369 setThumb_ScrollWidget(d->scroll, 0, 0);
370 setMode_SidebarWidget(d, documentOutline_SidebarMode); 382 setMode_SidebarWidget(d, documentOutline_SidebarMode);
371 d->resizer = addChildFlags_Widget( 383 d->resizer = addChildFlags_Widget(
372 w, 384 w,
@@ -378,55 +390,51 @@ void init_SidebarWidget(iSidebarWidget *d) {
378 setBackgroundColor_Widget(d->resizer, none_ColorId); 390 setBackgroundColor_Widget(d->resizer, none_ColorId);
379 d->resizeCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); 391 d->resizeCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
380 d->menu = NULL; 392 d->menu = NULL;
381 init_IntSet(&d->invalidItems);
382 d->visBuffer = NULL;
383 d->visBufferValid = iFalse;
384} 393}
385 394
386void deinit_SidebarWidget(iSidebarWidget *d) { 395void deinit_SidebarWidget(iSidebarWidget *d) {
387 SDL_FreeCursor(d->resizeCursor); 396 SDL_FreeCursor(d->resizeCursor);
388 clearItems_SidebarWidget_(d); 397 //clearItems_SidebarWidget_(d);
389 deinit_Array(&d->items); 398// deinit_Array(&d->items);
390 SDL_DestroyTexture(d->visBuffer);
391} 399}
392 400
393static int visCount_SidebarWidget_(const iSidebarWidget *d) { 401//static int visCount_SidebarWidget_(const iSidebarWidget *d) {
394 return iMin(height_Rect(bounds_Widget(constAs_Widget(d))) / d->itemHeight, 402// return iMin(height_Rect(bounds_Widget(constAs_Widget(d))) / d->itemHeight,
395 (int) size_Array(&d->items)); 403// (int) size_Array(&d->items));
396} 404//}
397 405
398static iRanges visRange_SidebarWidget_(const iSidebarWidget *d) { 406//static iRanges visRange_SidebarWidget_(const iSidebarWidget *d) {
399 iRanges vis = { d->scrollY / d->itemHeight, 0 }; 407// iRanges vis = { d->scrollY / d->itemHeight, 0 };
400 vis.end = iMin(size_Array(&d->items), vis.start + visCount_SidebarWidget_(d)); 408// vis.end = iMin(size_Array(&d->items), vis.start + visCount_SidebarWidget_(d));
401 return vis; 409// return vis;
402} 410//}
403 411
404static size_t itemIndex_SidebarWidget_(const iSidebarWidget *d, iInt2 pos) { 412//static size_t itemIndex_SidebarWidget_(const iSidebarWidget *d, iInt2 pos) {
405 const iRect bounds = contentBounds_SidebarWidget_(d); 413// const iRect bounds = contentBounds_SidebarWidget_(d);
406 pos.y -= top_Rect(bounds) - d->scrollY; 414// pos.y -= top_Rect(bounds) - d->scrollY;
407 if (pos.y < 0) return iInvalidPos; 415// if (pos.y < 0) return iInvalidPos;
408 size_t index = pos.y / d->itemHeight; 416// size_t index = pos.y / d->itemHeight;
409 if (index >= size_Array(&d->items)) return iInvalidPos; 417// if (index >= size_Array(&d->items)) return iInvalidPos;
410 return index; 418// return index;
411} 419//}
412 420
413static const iSidebarItem *constHoverItem_SidebarWidget_(const iSidebarWidget *d) { 421//static const iSidebarItem *constHoverItem_SidebarWidget_(const iSidebarWidget *d) {
414 if (d->hoverItem < size_Array(&d->items)) { 422// if (d->hoverItem < size_Array(&d->items)) {
415 return constAt_Array(&d->items, d->hoverItem); 423// return constAt_Array(&d->items, d->hoverItem);
416 } 424// }
417 return NULL; 425// return NULL;
418} 426//}
419 427
420static iSidebarItem *hoverItem_SidebarWidget_(iSidebarWidget *d) { 428//static iSidebarItem *hoverItem_SidebarWidget_(iSidebarWidget *d) {
421 if (d->hoverItem < size_Array(&d->items)) { 429// if (d->hoverItem < size_Array(&d->items)) {
422 return at_Array(&d->items, d->hoverItem); 430// return at_Array(&d->items, d->hoverItem);
423 } 431// }
424 return NULL; 432// return NULL;
425} 433//}
426 434
427static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { 435static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
428 if (d->mode == identities_SidebarMode) { 436 if (d->mode == identities_SidebarMode) {
429 const iSidebarItem *hoverItem = constHoverItem_SidebarWidget_(d); 437 const iSidebarItem *hoverItem = constHoverItem_ListWidget(d->list);
430 if (hoverItem) { 438 if (hoverItem) {
431 return identity_GmCerts(certs_App(), hoverItem->id); 439 return identity_GmCerts(certs_App(), hoverItem->id);
432 } 440 }
@@ -438,28 +446,28 @@ static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
438 return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d)); 446 return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d));
439} 447}
440 448
441static void setHoverItem_SidebarWidget_(iSidebarWidget *d, size_t index) { 449//static void setHoverItem_SidebarWidget_(iSidebarWidget *d, size_t index) {
442 if (index < size_Array(&d->items)) { 450// if (index < size_Array(&d->items)) {
443 if (constValue_Array(&d->items, index, iSidebarItem).isSeparator) { 451// if (constValue_Array(&d->items, index, iSidebarItem).isSeparator) {
444 index = iInvalidPos; 452// index = iInvalidPos;
445 } 453// }
446 } 454// }
447 if (d->hoverItem != index) { 455// if (d->hoverItem != index) {
448 insert_IntSet(&d->invalidItems, d->hoverItem); 456// insert_IntSet(&d->invalidItems, d->hoverItem);
449 insert_IntSet(&d->invalidItems, index); 457// insert_IntSet(&d->invalidItems, index);
450 d->hoverItem = index; 458// d->hoverItem = index;
451 refresh_Widget(as_Widget(d)); 459// refresh_Widget(as_Widget(d));
452 } 460// }
453} 461//}
454 462
455static void updateMouseHover_SidebarWidget_(iSidebarWidget *d) { 463//static void updateMouseHover_SidebarWidget_(iSidebarWidget *d) {
456 const iInt2 mouse = mouseCoord_Window(get_Window()); 464// const iInt2 mouse = mouseCoord_Window(get_Window());
457 setHoverItem_SidebarWidget_(d, itemIndex_SidebarWidget_(d, mouse)); 465// setHoverItem_SidebarWidget_(d, itemIndex_SidebarWidget_(d, mouse));
458} 466//}
459 467
460static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) { 468static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *item) {
461 setFocus_Widget(NULL); 469 setFocus_Widget(NULL);
462 const iSidebarItem *item = constAt_Array(&d->items, index); 470// const iSidebarItem *item = constAt_Array(&d->items, index);
463 switch (d->mode) { 471 switch (d->mode) {
464 case documentOutline_SidebarMode: { 472 case documentOutline_SidebarMode: {
465 const iGmDocument *doc = document_DocumentWidget(document_App()); 473 const iGmDocument *doc = document_DocumentWidget(document_App());
@@ -485,7 +493,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) {
485 signIn_GmCerts(certs_App(), ident, tabUrl); 493 signIn_GmCerts(certs_App(), ident, tabUrl);
486 } 494 }
487 updateItems_SidebarWidget_(d); 495 updateItems_SidebarWidget_(d);
488 updateMouseHover_SidebarWidget_(d); 496 updateMouseHover_ListWidget(d->list);
489 } 497 }
490 break; 498 break;
491 } 499 }
@@ -520,7 +528,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, int width) {
520 checkModeButtonLayout_SidebarWidget_(d); 528 checkModeButtonLayout_SidebarWidget_(d);
521 if (!isRefreshPending_App()) { 529 if (!isRefreshPending_App()) {
522 updateSize_DocumentWidget(document_App()); 530 updateSize_DocumentWidget(document_App());
523 invalidate_SidebarWidget_(d); 531 invalidate_ListWidget(d->list);
524 } 532 }
525} 533}
526 534
@@ -531,7 +539,7 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c
531 const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title")); 539 const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title"));
532 const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url")); 540 const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url"));
533 const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags")); 541 const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags"));
534 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 542 const iSidebarItem *item = hoverItem_ListWidget(d->list);
535 iAssert(item); /* hover item cannot have been changed */ 543 iAssert(item); /* hover item cannot have been changed */
536 iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); 544 iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id);
537 set_String(&bm->title, title); 545 set_String(&bm->title, title);
@@ -550,9 +558,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
550 iWidget *w = as_Widget(d); 558 iWidget *w = as_Widget(d);
551 /* Handle commands. */ 559 /* Handle commands. */
552 if (isResize_UserEvent(ev)) { 560 if (isResize_UserEvent(ev)) {
553 updateVisible_SidebarWidget_(d); 561 updateVisible_ListWidget(d->list);
554 checkModeButtonLayout_SidebarWidget_(d); 562 checkModeButtonLayout_SidebarWidget_(d);
555 invalidate_SidebarWidget_(d); 563 invalidate_ListWidget(d->list);
556 } 564 }
557 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 565 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
558 const char *cmd = command_UserEvent(ev); 566 const char *cmd = command_UserEvent(ev);
@@ -580,6 +588,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
580 } 588 }
581 return iTrue; 589 return iTrue;
582 } 590 }
591 else if (isCommand_Widget(w, ev, "list.clicked")) {
592 itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item"));
593 return iTrue;
594 }
583 else if (equal_Command(cmd, "sidebar.width")) { 595 else if (equal_Command(cmd, "sidebar.width")) {
584 setWidth_SidebarWidget(d, arg_Command(cmd)); 596 setWidth_SidebarWidget(d, arg_Command(cmd));
585 return iTrue; 597 return iTrue;
@@ -590,7 +602,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
590 if (argLabel_Command(cmd, "show") && !isVisible_Widget(w)) { 602 if (argLabel_Command(cmd, "show") && !isVisible_Widget(w)) {
591 postCommand_App("sidebar.toggle arg:1"); 603 postCommand_App("sidebar.toggle arg:1");
592 } 604 }
593 scroll_SidebarWidget_(d, 0); 605 scrollOffset_ListWidget(d->list, 0);
594 return iTrue; 606 return iTrue;
595 } 607 }
596 else if (equal_Command(cmd, "sidebar.toggle")) { 608 else if (equal_Command(cmd, "sidebar.toggle")) {
@@ -600,34 +612,25 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
600 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); 612 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w));
601 if (isVisible_Widget(w)) { 613 if (isVisible_Widget(w)) {
602 w->rect.size.x = d->width; 614 w->rect.size.x = d->width;
603 invalidate_SidebarWidget_(d); 615 invalidate_ListWidget(d->list);
604 } 616 }
605 arrange_Widget(w->parent); 617 arrange_Widget(w->parent);
606 updateSize_DocumentWidget(document_App()); 618 updateSize_DocumentWidget(document_App());
607 refresh_Widget(w->parent); 619 refresh_Widget(w->parent);
608 return iTrue; 620 return iTrue;
609 } 621 }
610 else if (equal_Command(cmd, "scroll.moved")) {
611 d->scrollY = arg_Command(command_UserEvent(ev));
612 d->hoverItem = iInvalidPos;
613 invalidate_SidebarWidget_(d);
614 return iTrue;
615 }
616 else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) { 622 else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) {
617 updateItems_SidebarWidget_(d); 623 updateItems_SidebarWidget_(d);
618 } 624 }
619 else if (equal_Command(cmd, "theme.changed")) {
620 invalidate_SidebarWidget_(d);
621 }
622 else if (equal_Command(cmd, "bookmark.copy")) { 625 else if (equal_Command(cmd, "bookmark.copy")) {
623 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 626 const iSidebarItem *item = hoverItem_ListWidget(d->list);
624 if (d->mode == bookmarks_SidebarMode && item) { 627 if (d->mode == bookmarks_SidebarMode && item) {
625 SDL_SetClipboardText(cstr_String(&item->url)); 628 SDL_SetClipboardText(cstr_String(&item->url));
626 } 629 }
627 return iTrue; 630 return iTrue;
628 } 631 }
629 else if (equal_Command(cmd, "bookmark.edit")) { 632 else if (equal_Command(cmd, "bookmark.edit")) {
630 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 633 const iSidebarItem *item = hoverItem_ListWidget(d->list);
631 if (d->mode == bookmarks_SidebarMode && item) { 634 if (d->mode == bookmarks_SidebarMode && item) {
632 setFlags_Widget(w, disabled_WidgetFlag, iTrue); 635 setFlags_Widget(w, disabled_WidgetFlag, iTrue);
633 iWidget *dlg = makeBookmarkEditor_Widget(); 636 iWidget *dlg = makeBookmarkEditor_Widget();
@@ -641,7 +644,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
641 return iTrue; 644 return iTrue;
642 } 645 }
643 else if (equal_Command(cmd, "bookmark.delete")) { 646 else if (equal_Command(cmd, "bookmark.delete")) {
644 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 647 const iSidebarItem *item = hoverItem_ListWidget(d->list);
645 if (d->mode == bookmarks_SidebarMode && item && remove_Bookmarks(bookmarks_App(), item->id)) { 648 if (d->mode == bookmarks_SidebarMode && item && remove_Bookmarks(bookmarks_App(), item->id)) {
646 postCommand_App("bookmarks.changed"); 649 postCommand_App("bookmarks.changed");
647 } 650 }
@@ -712,7 +715,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
712 return iTrue; 715 return iTrue;
713 } 716 }
714 else if (equal_Command(cmd, "ident.delete")) { 717 else if (equal_Command(cmd, "ident.delete")) {
715 iSidebarItem *item = hoverItem_SidebarWidget_(d); 718 iSidebarItem *item = hoverItem_ListWidget(d->list);
716 if (argLabel_Command(cmd, "confirm")) { 719 if (argLabel_Command(cmd, "confirm")) {
717 makeQuestion_Widget(uiTextCaution_ColorEscape "DELETE IDENTITY", 720 makeQuestion_Widget(uiTextCaution_ColorEscape "DELETE IDENTITY",
718 format_CStr("Do you really want to delete the identity\n" 721 format_CStr("Do you really want to delete the identity\n"
@@ -732,23 +735,23 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
732 return iTrue; 735 return iTrue;
733 } 736 }
734 else if (equal_Command(cmd, "history.delete")) { 737 else if (equal_Command(cmd, "history.delete")) {
735 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 738 const iSidebarItem *item = hoverItem_ListWidget(d->list);
736 if (item && !isEmpty_String(&item->url)) { 739 if (item && !isEmpty_String(&item->url)) {
737 removeUrl_Visited(visited_App(), &item->url); 740 removeUrl_Visited(visited_App(), &item->url);
738 updateItems_SidebarWidget_(d); 741 updateItems_SidebarWidget_(d);
739 scroll_SidebarWidget_(d, 0); 742 scrollOffset_ListWidget(d->list, 0);
740 } 743 }
741 return iTrue; 744 return iTrue;
742 } 745 }
743 else if (equal_Command(cmd, "history.copy")) { 746 else if (equal_Command(cmd, "history.copy")) {
744 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 747 const iSidebarItem *item = hoverItem_ListWidget(d->list);
745 if (item && !isEmpty_String(&item->url)) { 748 if (item && !isEmpty_String(&item->url)) {
746 SDL_SetClipboardText(cstr_String(&item->url)); 749 SDL_SetClipboardText(cstr_String(&item->url));
747 } 750 }
748 return iTrue; 751 return iTrue;
749 } 752 }
750 else if (equal_Command(cmd, "history.addbookmark")) { 753 else if (equal_Command(cmd, "history.addbookmark")) {
751 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 754 const iSidebarItem *item = hoverItem_ListWidget(d->list);
752 if (!isEmpty_String(&item->url)) { 755 if (!isEmpty_String(&item->url)) {
753 makeBookmarkCreation_Widget( 756 makeBookmarkCreation_Widget(
754 &item->url, 757 &item->url,
@@ -768,53 +771,55 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
768 else { 771 else {
769 clear_Visited(visited_App()); 772 clear_Visited(visited_App());
770 updateItems_SidebarWidget_(d); 773 updateItems_SidebarWidget_(d);
771 scroll_SidebarWidget_(d, 0); 774 scrollOffset_ListWidget(d->list, 0);
772 } 775 }
773 return iTrue; 776 return iTrue;
774 } 777 }
775 } 778 }
776 if (ev->type == SDL_MOUSEMOTION && !isVisible_Widget(d->menu)) { 779 if (ev->type == SDL_MOUSEMOTION && !isVisible_Widget(d->menu)) {
777 const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y); 780 const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y);
778 size_t hover = iInvalidPos; 781// size_t hover = iInvalidPos;
779 if (contains_Widget(d->resizer, mouse)) { 782 if (contains_Widget(d->resizer, mouse)) {
780 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_SIZEWE); 783 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_SIZEWE);
781 } 784 }
782 else if (contains_Widget(constAs_Widget(d->scroll), mouse)) { 785// else if (contains_Widget(constAs_Widget(d->scroll), mouse)) {
783 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 786// setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
784 } 787// }
785 else if (contains_Widget(w, mouse)) { 788// else if (contains_Widget(w, mouse)) {
786 hover = itemIndex_SidebarWidget_(d, mouse); 789// hover = itemIndex_SidebarWidget_(d, mouse);
787 } 790// }
788 setHoverItem_SidebarWidget_(d, hover); 791// setHoverItem_SidebarWidget_(d, hover);
789 /* Update cursor. */ 792 /* Update cursor. */
790 if (contains_Widget(w, mouse) && !contains_Widget(d->resizer, mouse) && 793 else if (contains_Widget(w, mouse)) /* && !contains_Widget(d->resizer, mouse) &&
791 !contains_Widget(constAs_Widget(d->scroll), mouse)) { 794 !contains_Widget(constAs_Widget(d->scroll), mouse))*/ {
792 const iSidebarItem *item = constHoverItem_SidebarWidget_(d); 795 const iSidebarItem *item = constHoverItem_ListWidget(d->list);
793 if (item && d->mode != identities_SidebarMode) { 796 if (item && d->mode != identities_SidebarMode) {
794 setCursor_Window(get_Window(), item->isSeparator ? SDL_SYSTEM_CURSOR_ARROW 797 setCursor_Window(get_Window(),
795 : SDL_SYSTEM_CURSOR_HAND); 798 item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW
799 : SDL_SYSTEM_CURSOR_HAND);
796 } 800 }
797 else { 801 else {
798 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 802 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
799 } 803 }
800 } 804 }
801 } 805 }
802 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 806// if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
803#if defined (iPlatformApple) 807//#if defined (iPlatformApple)
804 /* Momentum scrolling. */ 808// /* Momentum scrolling. */
805 scroll_SidebarWidget_(d, -ev->wheel.y * get_Window()->pixelRatio); 809// scroll_SidebarWidget_(d, -ev->wheel.y * get_Window()->pixelRatio);
806#else 810//#else
807 scroll_SidebarWidget_(d, -ev->wheel.y * 3 * d->itemHeight); 811// scroll_SidebarWidget_(d, -ev->wheel.y * 3 * d->itemHeight);
808#endif 812//#endif
809 return iTrue; 813// return iTrue;
810 } 814// }
811 if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { 815 if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) {
812 if (ev->button.button == SDL_BUTTON_RIGHT) { 816 if (ev->button.button == SDL_BUTTON_RIGHT) {
813 if (!isVisible_Widget(d->menu)) { 817 if (!isVisible_Widget(d->menu)) {
814 setHoverItem_SidebarWidget_( 818 updateMouseHover_ListWidget(d->list);
815 d, itemIndex_SidebarWidget_(d, init_I2(ev->button.x, ev->button.y))); 819// setHoverItem_ListWidget(
820// d->list, itemIndex_ListWidget(d->list, init_I2(ev->button.x, ev->button.y)));
816 } 821 }
817 if (d->hoverItem != iInvalidPos || isVisible_Widget(d->menu)) { 822 if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) {
818 /* Update menu items. */ 823 /* Update menu items. */
819 if (d->mode == identities_SidebarMode) { 824 if (d->mode == identities_SidebarMode) {
820 const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d); 825 const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d);
@@ -845,41 +850,27 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
845 } 850 }
846 } 851 }
847 processContextMenuEvent_Widget(d->menu, ev, {}); 852 processContextMenuEvent_Widget(d->menu, ev, {});
848 switch (processEvent_Click(&d->click, ev)) { 853// switch (processEvent_Click(&d->click, ev)) {
849 case started_ClickResult: 854// case started_ClickResult:
850 //invalidate_SidebarWidget_(d); 855// //invalidate_SidebarWidget_(d);
851 break; 856// break;
852 case finished_ClickResult: 857// case finished_ClickResult:
853 if (contains_Rect(contentBounds_SidebarWidget_(d), pos_Click(&d->click)) && 858// if (contains_Rect(contentBounds_SidebarWidget_(d), pos_Click(&d->click)) &&
854 d->hoverItem != iInvalidSize) { 859// d->hoverItem != iInvalidSize) {
855 itemClicked_SidebarWidget_(d, d->hoverItem); 860// itemClicked_SidebarWidget_(d, d->hoverItem);
856 } 861// }
857// invalidate_SidebarWidget_(d); 862// // invalidate_SidebarWidget_(d);
858 break; 863// break;
859 default: 864// default:
860 break; 865// break;
861 } 866// }
862 return processEvent_Widget(w, ev); 867 return processEvent_Widget(w, ev);
863} 868}
864 869
865static void allocVisBuffer_SidebarWidget_(iSidebarWidget *d) {
866 const iInt2 size = contentBounds_SidebarWidget_(d).size;
867 if (!d->visBuffer || !isEqual_I2(size_SDLTexture(d->visBuffer), size)) {
868 if (d->visBuffer) {
869 SDL_DestroyTexture(d->visBuffer);
870 }
871 d->visBuffer = SDL_CreateTexture(renderer_Window(get_Window()),
872 SDL_PIXELFORMAT_RGBA8888,
873 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
874 size.x,
875 size.y);
876 SDL_SetTextureBlendMode(d->visBuffer, SDL_BLENDMODE_NONE);
877 d->visBufferValid = iFalse;
878 }
879}
880
881static void draw_SidebarWidget_(const iSidebarWidget *d) { 870static void draw_SidebarWidget_(const iSidebarWidget *d) {
882 const iWidget *w = constAs_Widget(d); 871 const iWidget *w = constAs_Widget(d);
872 const iRect bounds = bounds_Widget(w);
873#if 0
883 const iRect bounds = contentBounds_SidebarWidget_(d); 874 const iRect bounds = contentBounds_SidebarWidget_(d);
884 const iBool isPressing = d->click.isActive && contains_Rect(bounds, pos_Click(&d->click)); 875 const iBool isPressing = d->click.isActive && contains_Rect(bounds, pos_Click(&d->click));
885 iPaint p; 876 iPaint p;
@@ -1033,18 +1024,124 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) {
1033 } 1024 }
1034 endTarget_Paint(&p); 1025 endTarget_Paint(&p);
1035 /* Update state. */ { 1026 /* Update state. */ {
1036 iSidebarWidget *m = iConstCast(iSidebarWidget *, d); 1027// iSidebarWidget *m = iConstCast(iSidebarWidget *, d);
1037 m->visBufferValid = iTrue; 1028// m->visBufferValid = iTrue;
1038 clear_IntSet(&m->invalidItems); 1029// clear_IntSet(&m->invalidItems);
1039 } 1030 }
1040 } 1031 }
1041 SDL_RenderCopy( 1032 SDL_RenderCopy(
1042 renderer_Window(get_Window()), d->visBuffer, NULL, (const SDL_Rect *) &bounds); 1033 renderer_Window(get_Window()), d->visBuffer, NULL, (const SDL_Rect *) &bounds);
1034#endif
1035 iPaint p;
1036 init_Paint(&p);
1043 draw_Widget(w); 1037 draw_Widget(w);
1044 drawVLine_Paint(&p, 1038 drawVLine_Paint(
1045 addX_I2(topRight_Rect(bounds_Widget(w)), -1), 1039 &p, addX_I2(topRight_Rect(bounds), -1), height_Rect(bounds), uiSeparator_ColorId);
1046 height_Rect(bounds_Widget(w)), 1040}
1047 uiSeparator_ColorId); 1041
1042static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1043 const iListWidget *list) {
1044 const iSidebarWidget *sidebar = (const iSidebarWidget *) constAs_Widget(list)->parent;
1045 const iBool isPressing = isMouseDown_ListWidget(list);
1046 const int font = uiContent_FontId;
1047 const iBool isHover =
1048 isHover_Widget(constAs_Widget(list)) && constHoverItem_ListWidget(list) == d;
1049 const int itemHeight = height_Rect(itemRect);
1050 const int iconColor =
1051 isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) : uiIcon_ColorId;
1052 if (isHover && !d->listItem.isSeparator) {
1053 fillRect_Paint(p,
1054 itemRect,
1055 isPressing ? uiBackgroundPressed_ColorId
1056 : uiBackgroundFramelessHover_ColorId);
1057 }
1058 iInt2 pos = itemRect.pos;
1059 if (sidebar->mode == documentOutline_SidebarMode) {
1060 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1061 : (tmHeading1_ColorId + d->indent / (4 * gap_UI));
1062 drawRange_Text(font,
1063 init_I2(pos.x + 3 * gap_UI + d->indent,
1064 mid_Rect(itemRect).y - lineHeight_Text(font) / 2),
1065 fg,
1066 range_String(&d->label));
1067 }
1068 else if (sidebar->mode == bookmarks_SidebarMode) {
1069 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1070 : uiText_ColorId;
1071 iString str;
1072 init_String(&str);
1073 appendChar_String(&str, d->icon ? d->icon : 0x1f588);
1074 const iRect iconArea = { addX_I2(pos, gap_UI), init_I2(7 * gap_UI, itemHeight) };
1075 drawCentered_Text(font, iconArea, iTrue, iconColor, "%s", cstr_String(&str));
1076 deinit_String(&str);
1077 iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2);
1078 drawRange_Text(font, textPos, fg, range_String(&d->label));
1079 }
1080 else if (sidebar->mode == history_SidebarMode) {
1081 iBeginCollect();
1082 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1083 : uiText_ColorId;
1084 if (d->listItem.isSeparator) {
1085 if (!isEmpty_String(&d->meta)) {
1086 unsetClip_Paint(p);
1087 iInt2 drawPos = addY_I2(topLeft_Rect(itemRect), itemHeight * 0.666f);
1088 drawHLine_Paint(p, drawPos, width_Rect(itemRect), uiIcon_ColorId);
1089 drawRange_Text(
1090 default_FontId,
1091 add_I2(drawPos,
1092 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(default_FontId)) / 2)),
1093 uiIcon_ColorId,
1094 range_String(&d->meta));
1095 }
1096 }
1097 else {
1098 iUrl parts;
1099 init_Url(&parts, &d->url);
1100 const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini");
1101 draw_Text(font,
1102 add_I2(topLeft_Rect(itemRect),
1103 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)),
1104 fg,
1105 "%s%s%s%s%s%s",
1106 isGemini ? "" : cstr_Rangecc(parts.scheme),
1107 isGemini ? "" : "://",
1108 escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId
1109 : uiTextFramelessHover_ColorId)
1110 : uiTextStrong_ColorId),
1111 cstr_Rangecc(parts.host),
1112 escape_Color(fg),
1113 cstr_Rangecc(parts.path));
1114 }
1115 iEndCollect();
1116 }
1117 else if (sidebar->mode == identities_SidebarMode) {
1118 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1119 : uiTextStrong_ColorId;
1120 if (d->listItem.isSelected) {
1121 drawRectThickness_Paint(p,
1122 adjusted_Rect(itemRect, zero_I2(), init_I2(-2, -1)),
1123 gap_UI / 4,
1124 isHover && isPressing ? uiTextPressed_ColorId : uiIcon_ColorId);
1125 }
1126 iString icon;
1127 initUnicodeN_String(&icon, &d->icon, 1);
1128 iInt2 cPos = topLeft_Rect(itemRect);
1129 addv_I2(&cPos,
1130 init_I2(3 * gap_UI,
1131 (itemHeight - lineHeight_Text(default_FontId) * 2 - lineHeight_Text(font)) /
1132 2));
1133 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId
1134 : uiTextFramelessHover_ColorId)
1135 : uiText_ColorId;
1136 drawRange_Text(
1137 font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon));
1138 deinit_String(&icon);
1139 drawRange_Text(font, add_I2(cPos, init_I2(6 * gap_UI, 0)), fg, range_String(&d->label));
1140 drawRange_Text(default_FontId,
1141 add_I2(cPos, init_I2(6 * gap_UI, lineHeight_Text(font))),
1142 metaFg,
1143 range_String(&d->meta));
1144 }
1048} 1145}
1049 1146
1050iBeginDefineSubclass(SidebarWidget, Widget) 1147iBeginDefineSubclass(SidebarWidget, Widget)
diff --git a/src/ui/util.c b/src/ui/util.c
index dfe364a5..120be3be 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -36,6 +36,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
36#include <the_Foundation/math.h> 36#include <the_Foundation/math.h>
37#include <the_Foundation/path.h> 37#include <the_Foundation/path.h>
38 38
39iBool isCommand_SDLEvent(const SDL_Event *d) {
40 return d->type == SDL_USEREVENT && d->user.code == command_UserEventCode;
41}
42
39iBool isCommand_UserEvent(const SDL_Event *d, const char *cmd) { 43iBool isCommand_UserEvent(const SDL_Event *d, const char *cmd) {
40 return d->type == SDL_USEREVENT && d->user.code == command_UserEventCode && 44 return d->type == SDL_USEREVENT && d->user.code == command_UserEventCode &&
41 equal_Command(d->user.data1, cmd); 45 equal_Command(d->user.data1, cmd);
diff --git a/src/ui/util.h b/src/ui/util.h
index 9ef166de..ae3742b5 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -32,6 +32,7 @@ iDeclareType(Click)
32iDeclareType(Widget) 32iDeclareType(Widget)
33iDeclareType(LabelWidget) 33iDeclareType(LabelWidget)
34 34
35iBool isCommand_SDLEvent (const SDL_Event *d);
35iBool isCommand_UserEvent (const SDL_Event *, const char *cmd); 36iBool isCommand_UserEvent (const SDL_Event *, const char *cmd);
36const char * command_UserEvent (const SDL_Event *); 37const char * command_UserEvent (const SDL_Event *);
37 38
diff --git a/src/ui/widget.c b/src/ui/widget.c
index b5ea3b0f..cd02d5e4 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -207,6 +207,12 @@ iLocalDef iRect innerRect_Widget_(const iWidget *d) {
207 height_Rect(d->rect) - d->padding[1] - d->padding[3]); 207 height_Rect(d->rect) - d->padding[1] - d->padding[3]);
208} 208}
209 209
210iRect innerBounds_Widget(const iWidget *d) {
211 return adjusted_Rect(bounds_Widget(d),
212 init_I2(d->padding[0], d->padding[1]),
213 init_I2(-d->padding[2], -d->padding[3]));
214}
215
210void arrange_Widget(iWidget *d) { 216void arrange_Widget(iWidget *d) {
211 if (isCollapsed_Widget_(d)) { 217 if (isCollapsed_Widget_(d)) {
212 setFlags_Widget(d, wasCollapsed_WidgetFlag, iTrue); 218 setFlags_Widget(d, wasCollapsed_WidgetFlag, iTrue);