summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-04-27 11:59:28 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-04-27 11:59:28 +0300
commit9a7aa34e63132edb5906914ff99b861e64ee9099 (patch)
tree327e275b7f4fd6eb05150f7c03a87a72794d22a0
parent170209cf926e2f714d507fefaa6b30fd245811ad (diff)
Refactor: Added a proper Root object
`Root` encapsulates the root widget and the associated UI state.
-rw-r--r--src/app.c8
-rw-r--r--src/ui/paint.c4
-rw-r--r--src/ui/root.c78
-rw-r--r--src/ui/root.h27
-rw-r--r--src/ui/sidebarwidget.c2
-rw-r--r--src/ui/touch.c12
-rw-r--r--src/ui/util.c16
-rw-r--r--src/ui/widget.c63
-rw-r--r--src/ui/window.c40
-rw-r--r--src/ui/window.h6
10 files changed, 129 insertions, 127 deletions
diff --git a/src/app.c b/src/app.c
index c4511dc7..cefb707d 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1046,7 +1046,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
1046 handleCommand_MacOS(command_UserEvent(&ev)); 1046 handleCommand_MacOS(command_UserEvent(&ev));
1047#endif 1047#endif
1048 if (isMetricsChange_UserEvent(&ev)) { 1048 if (isMetricsChange_UserEvent(&ev)) {
1049 arrange_Widget(d->window->root); 1049 arrange_Widget(d->window->root.widget);
1050 } 1050 }
1051 if (!wasUsed) { 1051 if (!wasUsed) {
1052 /* No widget handled the command, so we'll do it. */ 1052 /* No widget handled the command, so we'll do it. */
@@ -1131,7 +1131,7 @@ static int run_App_(iApp *d) {
1131 1131
1132void refresh_App(void) { 1132void refresh_App(void) {
1133 iApp *d = &app_; 1133 iApp *d = &app_;
1134 destroyPending_RootData(data_Root()); 1134 destroyPending_Root(&d->window->root);
1135#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1135#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
1136 if (d->warmupFrames == 0 && d->isIdling) { 1136 if (d->warmupFrames == 0 && d->isIdling) {
1137 return; 1137 return;
@@ -1262,7 +1262,7 @@ void postCommandf_App(const char *command, ...) {
1262 1262
1263iAny *findWidget_App(const char *id) { 1263iAny *findWidget_App(const char *id) {
1264 if (!*id) return NULL; 1264 if (!*id) return NULL;
1265 return findChild_Widget(app_.window->root, id); 1265 return findChild_Widget(app_.window->root.widget, id);
1266} 1266}
1267 1267
1268void addTicker_App(iTickerFunc ticker, iAny *context) { 1268void addTicker_App(iTickerFunc ticker, iAny *context) {
@@ -2120,7 +2120,7 @@ iBool handleCommand_App(const char *cmd) {
2120 else if (equal_Command(cmd, "ident.import")) { 2120 else if (equal_Command(cmd, "ident.import")) {
2121 iCertImportWidget *imp = new_CertImportWidget(); 2121 iCertImportWidget *imp = new_CertImportWidget();
2122 setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); 2122 setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App()));
2123 addChild_Widget(d->window->root, iClob(imp)); 2123 addChild_Widget(d->window->root.widget, iClob(imp));
2124 finalizeSheet_Widget(as_Widget(imp)); 2124 finalizeSheet_Widget(as_Widget(imp));
2125 postRefresh_App(); 2125 postRefresh_App();
2126 return iTrue; 2126 return iTrue;
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 857bf20c..02914489 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -87,8 +87,8 @@ void unsetClip_Paint(iPaint *d) {
87void drawRect_Paint(const iPaint *d, iRect rect, int color) { 87void drawRect_Paint(const iPaint *d, iRect rect, int color) {
88 iInt2 br = bottomRight_Rect(rect); 88 iInt2 br = bottomRight_Rect(rect);
89 /* Keep the right/bottom edge visible in the window. */ 89 /* Keep the right/bottom edge visible in the window. */
90 if (br.x == d->dst->root->rect.size.x) br.x--; 90 if (br.x == d->dst->root.widget->rect.size.x) br.x--;
91 if (br.y == d->dst->root->rect.size.y) br.y--; 91 if (br.y == d->dst->root.widget->rect.size.y) br.y--;
92 const SDL_Point edges[] = { 92 const SDL_Point edges[] = {
93 { left_Rect(rect), top_Rect(rect) }, 93 { left_Rect(rect), top_Rect(rect) },
94 { br.x, top_Rect(rect) }, 94 { br.x, top_Rect(rect) },
diff --git a/src/ui/root.c b/src/ui/root.c
index f6b93abd..1fa36851 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -234,23 +234,27 @@ static const char *stopSeqCStr_[] = {
234static const int loadAnimIntervalMs_ = 133; 234static const int loadAnimIntervalMs_ = 133;
235static int loadAnimIndex_ = 0; 235static int loadAnimIndex_ = 0;
236 236
237static iWidget * activeRoot_ = NULL; 237static iRoot * activeRoot_ = NULL;
238static iRootData * activeRootData_ = NULL;
239 238
240void setCurrent_Root(iWidget *root, iRootData *rootData) { 239iDefineTypeConstruction(Root)
241 activeRoot_ = root; 240
242 activeRootData_ = rootData; 241void init_Root(iRoot *d) {
242 iZap(*d);
243} 243}
244 244
245iWidget *get_Root(void) { 245void deinit_Root(iRoot *d) {
246 return activeRoot_; 246 iReleasePtr(&d->widget);
247}
248
249void setCurrent_Root(iRoot *root) {
250 activeRoot_ = root;
247} 251}
248 252
249iRootData *data_Root(void) { 253iRoot *get_Root(void) {
250 return activeRootData_; 254 return activeRoot_;
251} 255}
252 256
253void destroyPending_RootData(iRootData *d) { 257void destroyPending_Root(iRoot *d) {
254 iForEach(PtrSet, i, d->pendingDestruction) { 258 iForEach(PtrSet, i, d->pendingDestruction) {
255 iWidget *widget = *i.value; 259 iWidget *widget = *i.value;
256 if (!isFinished_Anim(&widget->visualOffset)) { 260 if (!isFinished_Anim(&widget->visualOffset)) {
@@ -265,12 +269,11 @@ void destroyPending_RootData(iRootData *d) {
265 } 269 }
266} 270}
267 271
268iPtrArray *onTop_RootData(void) { 272iPtrArray *onTop_Root(iRoot *d) {
269 iAssert(activeRootData_); 273 if (!d->onTop) {
270 if (!activeRootData_->onTop) { 274 d->onTop = new_PtrArray();
271 activeRootData_->onTop = new_PtrArray();
272 } 275 }
273 return activeRootData_->onTop; 276 return d->onTop;
274} 277}
275 278
276static iBool handleRootCommands_(iWidget *root, const char *cmd) { 279static iBool handleRootCommands_(iWidget *root, const char *cmd) {
@@ -423,7 +426,7 @@ static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) {
423 426
424static void setReloadLabel_Window_(iWindow *d, iBool animating) { 427static void setReloadLabel_Window_(iWindow *d, iBool animating) {
425 const iBool isMobile = deviceType_App() != desktop_AppDeviceType; 428 const iBool isMobile = deviceType_App() != desktop_AppDeviceType;
426 iLabelWidget *label = findChild_Widget(d->root, "reload"); 429 iLabelWidget *label = findChild_Widget(d->root.widget, "reload");
427 updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : 430 updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() :
428 (isMobile ? pageMenuCStr_ : reloadCStr_)); 431 (isMobile ? pageMenuCStr_ : reloadCStr_));
429 if (isMobile) { 432 if (isMobile) {
@@ -444,13 +447,13 @@ static void checkLoadAnimation_Window_(iWindow *d) {
444 setReloadLabel_Window_(d, isOngoing); 447 setReloadLabel_Window_(d, isOngoing);
445} 448}
446 449
447void updatePadding_Root(iWidget *d) { 450void updatePadding_Root(iRoot *d) {
448#if defined (iPlatformAppleMobile) 451#if defined (iPlatformAppleMobile)
449 iWidget *toolBar = findChild_Widget(d, "toolbar"); 452 iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
450 float left, top, right, bottom; 453 float left, top, right, bottom;
451 safeAreaInsets_iOS(&left, &top, &right, &bottom); 454 safeAreaInsets_iOS(&left, &top, &right, &bottom);
452 /* Respect the safe area insets. */ { 455 /* Respect the safe area insets. */ {
453 setPadding_Widget(findChild_Widget(d, "navdiv"), left, top, right, 0); 456 setPadding_Widget(findChild_Widget(d->widget, "navdiv"), left, top, right, 0);
454 if (toolBar) { 457 if (toolBar) {
455 setPadding_Widget(toolBar, left, 0, right, bottom); 458 setPadding_Widget(toolBar, left, 0, right, bottom);
456 } 459 }
@@ -458,8 +461,8 @@ void updatePadding_Root(iWidget *d) {
458 if (toolBar) { 461 if (toolBar) {
459 /* TODO: get this from toolBar height, but it's buggy for some reason */ 462 /* TODO: get this from toolBar height, but it's buggy for some reason */
460 const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; 463 const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0;
461 setPadding_Widget(findChild_Widget(d, "sidebar"), 0, 0, 0, sidebarBottomPad); 464 setPadding_Widget(findChild_Widget(d->widget, "sidebar"), 0, 0, 0, sidebarBottomPad);
462 setPadding_Widget(findChild_Widget(d, "sidebar2"), 0, 0, 0, sidebarBottomPad); 465 setPadding_Widget(findChild_Widget(d->widget, "sidebar2"), 0, 0, 0, sidebarBottomPad);
463 /* TODO: There seems to be unrelated layout glitch in the sidebar where its children 466 /* TODO: There seems to be unrelated layout glitch in the sidebar where its children
464 are not arranged correctly until it's hidden and reshown. */ 467 are not arranged correctly until it's hidden and reshown. */
465 } 468 }
@@ -469,10 +472,10 @@ void updatePadding_Root(iWidget *d) {
469#endif 472#endif
470} 473}
471 474
472void dismissPortraitPhoneSidebars_Root(iWidget *root) { 475void dismissPortraitPhoneSidebars_Root(iRoot *d) {
473 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { 476 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
474 iWidget *sidebar = findChild_Widget(root, "sidebar"); 477 iWidget *sidebar = findChild_Widget(d->widget, "sidebar");
475 iWidget *sidebar2 = findChild_Widget(root, "sidebar2"); 478 iWidget *sidebar2 = findChild_Widget(d->widget, "sidebar2");
476 if (isVisible_Widget(sidebar)) { 479 if (isVisible_Widget(sidebar)) {
477 postCommand_App("sidebar.toggle"); 480 postCommand_App("sidebar.toggle");
478 setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); 481 setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
@@ -564,7 +567,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
564 updateSize_LabelWidget(btn); 567 updateSize_LabelWidget(btn);
565 } 568 }
566 } 569 }
567 arrange_Widget(get_Window()->root); 570 arrange_Widget(get_Window()->root.widget);
568 } 571 }
569 /* Resize the URL input field. */ { 572 /* Resize the URL input field. */ {
570 iWidget *urlBar = findChild_Widget(navBar, "url"); 573 iWidget *urlBar = findChild_Widget(navBar, "url");
@@ -716,7 +719,7 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) {
716 if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { 719 if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) {
717 if (!isVisible_Widget(searchBar)) { 720 if (!isVisible_Widget(searchBar)) {
718 setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); 721 setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
719 arrange_Widget(get_Window()->root); 722 arrange_Widget(get_Window()->root.widget);
720 postRefresh_App(); 723 postRefresh_App();
721 } 724 }
722 } 725 }
@@ -824,9 +827,9 @@ static int appIconSize_(void) {
824 return lineHeight_Text(uiContent_FontId); 827 return lineHeight_Text(uiContent_FontId);
825} 828}
826 829
827void updateMetrics_Root(iWidget *d) { 830void updateMetrics_Root(iRoot *d) {
828 /* Custom frame. */ 831 /* Custom frame. */
829 iWidget *winBar = findChild_Widget(d, "winbar"); 832 iWidget *winBar = findChild_Widget(d->widget, "winbar");
830 if (winBar) { 833 if (winBar) {
831 iWidget *appIcon = findChild_Widget(winBar, "winbar.icon"); 834 iWidget *appIcon = findChild_Widget(winBar, "winbar.icon");
832 iWidget *appTitle = findChild_Widget(winBar, "winbar.title"); 835 iWidget *appTitle = findChild_Widget(winBar, "winbar.title");
@@ -839,9 +842,9 @@ void updateMetrics_Root(iWidget *d) {
839 setFixedSize_Widget(appClose, appMin->rect.size); 842 setFixedSize_Widget(appClose, appMin->rect.size);
840 setFixedSize_Widget(appIcon, init_I2(appIconSize_(), appMin->rect.size.y)); 843 setFixedSize_Widget(appIcon, init_I2(appIconSize_(), appMin->rect.size.y));
841 } 844 }
842 iWidget *navBar = findChild_Widget(d, "navbar"); 845 iWidget *navBar = findChild_Widget(d->widget, "navbar");
843 iWidget *lock = findChild_Widget(navBar, "navbar.lock"); 846 iWidget *lock = findChild_Widget(navBar, "navbar.lock");
844 iWidget *url = findChild_Widget(d, "url"); 847 iWidget *url = findChild_Widget(d->widget, "url");
845 iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); 848 iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed");
846 iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); 849 iWidget *embedPad = findChild_Widget(navBar, "url.embedpad");
847 setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); 850 setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI);
@@ -852,12 +855,12 @@ void updateMetrics_Root(iWidget *d) {
852 width_Widget(lock) * 0.75); 855 width_Widget(lock) * 0.75);
853 rightEmbed->rect.pos.y = gap_UI; 856 rightEmbed->rect.pos.y = gap_UI;
854 updatePadding_Root(d); 857 updatePadding_Root(d);
855 arrange_Widget(d); 858 arrange_Widget(d->widget);
856 postRefresh_App(); 859 postRefresh_App();
857} 860}
858 861
859iWidget *createUserInterface_Root(void) { 862void createUserInterface_Root(iRoot *d) {
860 iWidget *root = new_Widget(); 863 iWidget *root = d->widget = new_Widget();
861 setId_Widget(root, "root"); 864 setId_Widget(root, "root");
862 /* Children of root cover the entire window. */ 865 /* Children of root cover the entire window. */
863 setFlags_Widget( 866 setFlags_Widget(
@@ -1180,7 +1183,7 @@ iWidget *createUserInterface_Root(void) {
1180 setId_Widget(menu, "toolbar.menu"); /* view menu */ 1183 setId_Widget(menu, "toolbar.menu"); /* view menu */
1181 } 1184 }
1182#endif 1185#endif
1183 updatePadding_Root(root); 1186 updatePadding_Root(d);
1184 /* Global context menus. */ { 1187 /* Global context menus. */ {
1185 iWidget *tabsMenu = makeMenu_Widget( 1188 iWidget *tabsMenu = makeMenu_Widget(
1186 root, 1189 root,
@@ -1226,14 +1229,13 @@ iWidget *createUserInterface_Root(void) {
1226 addAction_Widget(root, '4', rightSidebar_KeyModifier, "sidebar2.mode arg:3 toggle:1"); 1229 addAction_Widget(root, '4', rightSidebar_KeyModifier, "sidebar2.mode arg:3 toggle:1");
1227 addAction_Widget(root, '5', rightSidebar_KeyModifier, "sidebar2.mode arg:4 toggle:1"); 1230 addAction_Widget(root, '5', rightSidebar_KeyModifier, "sidebar2.mode arg:4 toggle:1");
1228 } 1231 }
1229 updateMetrics_Root(root); 1232 updateMetrics_Root(d);
1230 return root;
1231} 1233}
1232 1234
1233void showToolbars_Root(iWidget *root, iBool show) { 1235void showToolbars_Root(iRoot *d, iBool show) {
1234 /* The toolbar is only used on phone portrait layout. */ 1236 /* The toolbar is only used on phone portrait layout. */
1235 if (isLandscape_App()) return; 1237 if (isLandscape_App()) return;
1236 iWidget *toolBar = findChild_Widget(root, "toolbar"); 1238 iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
1237 if (!toolBar) return; 1239 if (!toolBar) return;
1238 const int height = rootSize_Window(get_Window()).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar)); 1240 const int height = rootSize_Window(get_Window()).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar));
1239 if (show && !isVisible_Widget(toolBar)) { 1241 if (show && !isVisible_Widget(toolBar)) {
diff --git a/src/ui/root.h b/src/ui/root.h
index fcb5b1b0..4b14b942 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -3,10 +3,10 @@
3#include "widget.h" 3#include "widget.h"
4#include <the_Foundation/ptrset.h> 4#include <the_Foundation/ptrset.h>
5 5
6iDeclareType(RootData) 6iDeclareType(Root)
7 7
8/* TODO: Rename to Root, include `iWidget *root` as well. */ 8struct Impl_Root {
9struct Impl_RootData { 9 iWidget * widget;
10 iWidget * hover; 10 iWidget * hover;
11 iWidget * mouseGrab; 11 iWidget * mouseGrab;
12 iWidget * focus; 12 iWidget * focus;
@@ -14,18 +14,19 @@ struct Impl_RootData {
14 iPtrSet * pendingDestruction; 14 iPtrSet * pendingDestruction;
15}; 15};
16 16
17iDeclareTypeConstruction(Root)
18
17/*----------------------------------------------------------------------------------------------*/ 19/*----------------------------------------------------------------------------------------------*/
18 20
19iWidget * createUserInterface_Root (void); 21void createUserInterface_Root (iRoot *);
20 22
21void setCurrent_Root (iWidget *root, iRootData *rootData); 23void setCurrent_Root (iRoot *);
22iWidget * get_Root (void); 24iRoot * get_Root (void);
23iRootData * data_Root (void);
24 25
25iPtrArray * onTop_RootData (void); 26iPtrArray * onTop_Root (iRoot *);
26void destroyPending_RootData (iRootData *); 27void destroyPending_Root (iRoot *);
27 28
28void updateMetrics_Root (iWidget *); 29void updateMetrics_Root (iRoot *);
29void updatePadding_Root (iWidget *); /* TODO: is part of metrics? */ 30void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */
30void dismissPortraitPhoneSidebars_Root (iWidget *); 31void dismissPortraitPhoneSidebars_Root (iRoot *);
31void showToolbars_Root (iWidget *, iBool show); 32void showToolbars_Root (iRoot *, iBool show);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 683f6436..8d83eed6 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -1170,7 +1170,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1170 else if (isCommand_Widget(w, ev, "ident.edit")) { 1170 else if (isCommand_Widget(w, ev, "ident.edit")) {
1171 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); 1171 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1172 if (ident) { 1172 if (ident) {
1173 makeValueInput_Widget(get_Window()->root, 1173 makeValueInput_Widget(get_Window()->root.widget,
1174 &ident->notes, 1174 &ident->notes,
1175 uiHeading_ColorEscape "${heading.ident.notes}", 1175 uiHeading_ColorEscape "${heading.ident.notes}",
1176 format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), 1176 format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))),
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 22f22f9b..75bbf765 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -161,7 +161,7 @@ static iBool clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) {
161 161
162static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) { 162static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) {
163 touchState_()->currentTouchPos = initF3_I2(pos); 163 touchState_()->currentTouchPos = initF3_I2(pos);
164 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseMotionEvent){ 164 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseMotionEvent){
165 .type = SDL_MOUSEMOTION, 165 .type = SDL_MOUSEMOTION,
166 .timestamp = SDL_GetTicks(), 166 .timestamp = SDL_GetTicks(),
167 .which = SDL_TOUCH_MOUSEID, 167 .which = SDL_TOUCH_MOUSEID,
@@ -185,12 +185,12 @@ static iBool dispatchClick_Touch_(const iTouch *d, int button) {
185 .x = x_F3(tapPos), 185 .x = x_F3(tapPos),
186 .y = y_F3(tapPos) 186 .y = y_F3(tapPos)
187 }; 187 };
188 iBool wasUsed = dispatchEvent_Widget(window->root, (SDL_Event *) &btn); 188 iBool wasUsed = dispatchEvent_Widget(window->root.widget, (SDL_Event *) &btn);
189 /* Immediately released, too. */ 189 /* Immediately released, too. */
190 btn.type = SDL_MOUSEBUTTONUP; 190 btn.type = SDL_MOUSEBUTTONUP;
191 btn.state = SDL_RELEASED; 191 btn.state = SDL_RELEASED;
192 btn.timestamp = SDL_GetTicks(); 192 btn.timestamp = SDL_GetTicks();
193 dispatchEvent_Widget(window->root, (SDL_Event *) &btn); 193 dispatchEvent_Widget(window->root.widget, (SDL_Event *) &btn);
194 if (!wasUsed && button == SDL_BUTTON_RIGHT) { 194 if (!wasUsed && button == SDL_BUTTON_RIGHT) {
195 postContextClick_Window(window, &btn); 195 postContextClick_Window(window, &btn);
196 } 196 }
@@ -199,7 +199,7 @@ static iBool dispatchClick_Touch_(const iTouch *d, int button) {
199 199
200static void dispatchButtonDown_Touch_(iFloat3 pos) { 200static void dispatchButtonDown_Touch_(iFloat3 pos) {
201 touchState_()->currentTouchPos = initF3_I2(pos); 201 touchState_()->currentTouchPos = initF3_I2(pos);
202 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ 202 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseButtonEvent){
203 .type = SDL_MOUSEBUTTONDOWN, 203 .type = SDL_MOUSEBUTTONDOWN,
204 .timestamp = SDL_GetTicks(), 204 .timestamp = SDL_GetTicks(),
205 .clicks = 1, 205 .clicks = 1,
@@ -213,7 +213,7 @@ static void dispatchButtonDown_Touch_(iFloat3 pos) {
213 213
214static void dispatchButtonUp_Touch_(iFloat3 pos) { 214static void dispatchButtonUp_Touch_(iFloat3 pos) {
215 touchState_()->currentTouchPos = initF3_I2(pos); 215 touchState_()->currentTouchPos = initF3_I2(pos);
216 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ 216 dispatchEvent_Widget(get_Window()->root.widget, (SDL_Event *) &(SDL_MouseButtonEvent){
217 .type = SDL_MOUSEBUTTONUP, 217 .type = SDL_MOUSEBUTTONUP,
218 .timestamp = SDL_GetTicks(), 218 .timestamp = SDL_GetTicks(),
219 .clicks = 1, 219 .clicks = 1,
@@ -458,7 +458,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
458 else if (x > rootSize.x - edgeWidth) { 458 else if (x > rootSize.x - edgeWidth) {
459 edge = right_TouchEdge; 459 edge = right_TouchEdge;
460 } 460 }
461 iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos)))); 461 iWidget *aff = hitChild_Widget(window->root.widget, init_I2(iRound(x), iRound(y_F3(pos))));
462 if (edge == left_TouchEdge) { 462 if (edge == left_TouchEdge) {
463 dragging = findSlidePanel_Widget_(aff); 463 dragging = findSlidePanel_Widget_(aff);
464 if (dragging) { 464 if (dragging) {
diff --git a/src/ui/util.c b/src/ui/util.c
index b80f7650..6b9a43d9 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -635,7 +635,7 @@ static iBool menuHandler_(iWidget *menu, const char *cmd) {
635 } 635 }
636 if ((equal_Command(cmd, "mouse.clicked") || equal_Command(cmd, "mouse.missed")) && 636 if ((equal_Command(cmd, "mouse.clicked") || equal_Command(cmd, "mouse.missed")) &&
637 arg_Command(cmd)) { 637 arg_Command(cmd)) {
638 if (hitChild_Widget(get_Window()->root, coord_Command(cmd)) == parentMenuButton_(menu)) { 638 if (hitChild_Widget(get_Window()->root.widget, coord_Command(cmd)) == parentMenuButton_(menu)) {
639 return iFalse; 639 return iFalse;
640 } 640 }
641 /* Dismiss open menus when clicking outside them. */ 641 /* Dismiss open menus when clicking outside them. */
@@ -1366,7 +1366,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
1366 easier to create phone versions of each dialog, but at least this works with any 1366 easier to create phone versions of each dialog, but at least this works with any
1367 future changes to the UI (..."works"). At least this way it is possible to enforce 1367 future changes to the UI (..."works"). At least this way it is possible to enforce
1368 a consistent styling. */ 1368 a consistent styling. */
1369 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == get_Window()->root) { 1369 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == get_Window()->root.widget) {
1370 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { 1370 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) {
1371 /* Already finalized. */ 1371 /* Already finalized. */
1372 arrange_Widget(sheet); 1372 arrange_Widget(sheet);
@@ -1573,7 +1573,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
1573 destroy_Widget(pageContent); 1573 destroy_Widget(pageContent);
1574 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); 1574 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue);
1575 } 1575 }
1576 destroyPending_RootData(data_Root()); 1576 destroyPending_Root(get_Root());
1577 /* Additional elements for preferences. */ 1577 /* Additional elements for preferences. */
1578 if (isPrefs) { 1578 if (isPrefs) {
1579 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); 1579 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
@@ -1966,7 +1966,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg,
1966 } 1966 }
1967 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); 1967 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1968 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(items, numItems))); 1968 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(items, numItems)));
1969 addChild_Widget(get_Window()->root, iClob(dlg)); 1969 addChild_Widget(get_Window()->root.widget, iClob(dlg));
1970 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't 1970 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't
1971 be arranged correctly unless it's here. */ 1971 be arranged correctly unless it's here. */
1972 finalizeSheet_Widget(dlg); 1972 finalizeSheet_Widget(dlg);
@@ -2412,7 +2412,7 @@ iWidget *makePreferences_Widget(void) {
2412 addChild_Widget(dlg, 2412 addChild_Widget(dlg,
2413 iClob(makeDialogButtons_Widget( 2413 iClob(makeDialogButtons_Widget(
2414 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); 2414 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1)));
2415 addChild_Widget(get_Window()->root, iClob(dlg)); 2415 addChild_Widget(get_Window()->root.widget, iClob(dlg));
2416 finalizeSheet_Widget(dlg); 2416 finalizeSheet_Widget(dlg);
2417 //printTree_Widget(dlg); 2417 //printTree_Widget(dlg);
2418 return dlg; 2418 return dlg;
@@ -2445,7 +2445,7 @@ iWidget *makeBookmarkEditor_Widget(void) {
2445 KMOD_PRIMARY, 2445 KMOD_PRIMARY,
2446 "bmed.accept" } }, 2446 "bmed.accept" } },
2447 2))); 2447 2)));
2448 addChild_Widget(get_Window()->root, iClob(dlg)); 2448 addChild_Widget(get_Window()->root.widget, iClob(dlg));
2449 finalizeSheet_Widget(dlg); 2449 finalizeSheet_Widget(dlg);
2450 return dlg; 2450 return dlg;
2451} 2451}
@@ -2578,7 +2578,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2578 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); 2578 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save");
2579 arrange_Widget(dlg); 2579 arrange_Widget(dlg);
2580 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; 2580 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x;
2581 addChild_Widget(get_Window()->root, iClob(dlg)); 2581 addChild_Widget(get_Window()->root.widget, iClob(dlg));
2582 finalizeSheet_Widget(dlg); 2582 finalizeSheet_Widget(dlg);
2583 /* Initialize. */ { 2583 /* Initialize. */ {
2584 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; 2584 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL;
@@ -2654,7 +2654,7 @@ iWidget *makeIdentityCreation_Widget(void) {
2654 KMOD_PRIMARY, 2654 KMOD_PRIMARY,
2655 "ident.accept" } }, 2655 "ident.accept" } },
2656 2))); 2656 2)));
2657 addChild_Widget(get_Window()->root, iClob(dlg)); 2657 addChild_Widget(get_Window()->root.widget, iClob(dlg));
2658 finalizeSheet_Widget(dlg); 2658 finalizeSheet_Widget(dlg);
2659 return dlg; 2659 return dlg;
2660} 2660}
diff --git a/src/ui/widget.c b/src/ui/widget.c
index b3f7b27f..47522dbe 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -81,7 +81,7 @@ void deinit_Widget(iWidget *d) {
81//#endif 81//#endif
82 deinit_String(&d->id); 82 deinit_String(&d->id);
83 if (d->flags & keepOnTop_WidgetFlag) { 83 if (d->flags & keepOnTop_WidgetFlag) {
84 removeAll_PtrArray(onTop_RootData(), d); 84 removeAll_PtrArray(onTop_Root(get_Root()), d);
85 } 85 }
86 if (d->flags & visualOffset_WidgetFlag) { 86 if (d->flags & visualOffset_WidgetFlag) {
87 removeTicker_App(visualOffsetAnimation_Widget_, d); 87 removeTicker_App(visualOffsetAnimation_Widget_, d);
@@ -95,10 +95,10 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) {
95 return; 95 return;
96 } 96 }
97 if (flags_Widget(d) & keepOnTop_WidgetFlag) { 97 if (flags_Widget(d) & keepOnTop_WidgetFlag) {
98 removeOne_PtrArray(onTop_RootData(), d); 98 removeOne_PtrArray(onTop_Root(get_Root()), d);
99 } 99 }
100 if (isHover_Widget(d)) { 100 if (isHover_Widget(d)) {
101 data_Root()->hover = NULL; 101 get_Root()->hover = NULL;
102 } 102 }
103 iForEach(ObjectList, i, d->children) { 103 iForEach(ObjectList, i, d->children) {
104 aboutToBeDestroyed_Widget_(as_Widget(i.object)); 104 aboutToBeDestroyed_Widget_(as_Widget(i.object));
@@ -111,10 +111,11 @@ void destroy_Widget(iWidget *d) {
111 postRefresh_App(); 111 postRefresh_App();
112 } 112 }
113 aboutToBeDestroyed_Widget_(d); 113 aboutToBeDestroyed_Widget_(d);
114 if (!data_Root()->pendingDestruction) { 114 iRoot *root = get_Root();
115 data_Root()->pendingDestruction = new_PtrSet(); 115 if (!root->pendingDestruction) {
116 root->pendingDestruction = new_PtrSet();
116 } 117 }
117 insert_PtrSet(data_Root()->pendingDestruction, d); 118 insert_PtrSet(root->pendingDestruction, d);
118 } 119 }
119} 120}
120 121
@@ -138,7 +139,7 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
138 } 139 }
139 iChangeFlags(d->flags, flags, set); 140 iChangeFlags(d->flags, flags, set);
140 if (flags & keepOnTop_WidgetFlag) { 141 if (flags & keepOnTop_WidgetFlag) {
141 iPtrArray *onTop = onTop_RootData(); 142 iPtrArray *onTop = onTop_Root(get_Root());
142 if (set) { 143 if (set) {
143 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos); 144 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);
144 pushBack_PtrArray(onTop, d); 145 pushBack_PtrArray(onTop, d);
@@ -186,7 +187,7 @@ void showCollapsed_Widget(iWidget *d, iBool show) {
186 if ((isVisible && !show) || (!isVisible && show)) { 187 if ((isVisible && !show) || (!isVisible && show)) {
187 setFlags_Widget(d, hidden_WidgetFlag, !show); 188 setFlags_Widget(d, hidden_WidgetFlag, !show);
188 /* The entire UI may be affected, if parents are resized due to the (un)collapsing. */ 189 /* The entire UI may be affected, if parents are resized due to the (un)collapsing. */
189 arrange_Widget(get_Window()->root); 190 arrange_Widget(get_Window()->root.widget);
190 postRefresh_App(); 191 postRefresh_App();
191 } 192 }
192} 193}
@@ -733,7 +734,7 @@ static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) {
733} 734}
734 735
735void unhover_Widget(void) { 736void unhover_Widget(void) {
736 data_Root()->hover = NULL; 737 get_Root()->hover = NULL;
737} 738}
738 739
739iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 740iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
@@ -743,14 +744,14 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
743 /* Hover widget may change. */ 744 /* Hover widget may change. */
744 setHover_Widget(NULL); 745 setHover_Widget(NULL);
745 } 746 }
746 if (data_Root()->focus && isKeyboardEvent_(ev)) { 747 if (get_Root()->focus && isKeyboardEvent_(ev)) {
747 /* Root dispatches keyboard events directly to the focused widget. */ 748 /* Root dispatches keyboard events directly to the focused widget. */
748 if (dispatchEvent_Widget(data_Root()->focus, ev)) { 749 if (dispatchEvent_Widget(get_Root()->focus, ev)) {
749 return iTrue; 750 return iTrue;
750 } 751 }
751 } 752 }
752 /* Root offers events first to widgets on top. */ 753 /* Root offers events first to widgets on top. */
753 iReverseForEach(PtrArray, i, data_Root()->onTop) { 754 iReverseForEach(PtrArray, i, get_Root()->onTop) {
754 iWidget *widget = *i.value; 755 iWidget *widget = *i.value;
755 if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) { 756 if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) {
756#if 0 757#if 0
@@ -774,7 +775,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
774 } 775 }
775 } 776 }
776 else if (ev->type == SDL_MOUSEMOTION && 777 else if (ev->type == SDL_MOUSEMOTION &&
777 (!data_Root()->hover || hasParent_Widget(d, data_Root()->hover)) && 778 (!get_Root()->hover || hasParent_Widget(d, get_Root()->hover)) &&
778 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 779 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
779 ~flags_Widget(d) & disabled_WidgetFlag) { 780 ~flags_Widget(d) & disabled_WidgetFlag) {
780 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 781 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -792,7 +793,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
792 handle the events first. */ 793 handle the events first. */
793 iReverseForEach(ObjectList, i, d->children) { 794 iReverseForEach(ObjectList, i, d->children) {
794 iWidget *child = as_Widget(i.object); 795 iWidget *child = as_Widget(i.object);
795 if (child == data_Root()->focus && isKeyboardEvent_(ev)) { 796 if (child == get_Root()->focus && isKeyboardEvent_(ev)) {
796 continue; /* Already dispatched. */ 797 continue; /* Already dispatched. */
797 } 798 }
798 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 799 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
@@ -1081,7 +1082,7 @@ void drawChildren_Widget(const iWidget *d) {
1081 } 1082 }
1082 /* Root draws the on-top widgets on top of everything else. */ 1083 /* Root draws the on-top widgets on top of everything else. */
1083 if (!d->parent) { 1084 if (!d->parent) {
1084 iConstForEach(PtrArray, i, onTop_RootData()) { 1085 iConstForEach(PtrArray, i, onTop_Root(get_Root())) {
1085 const iWidget *top = *i.value; 1086 const iWidget *top = *i.value;
1086 draw_Widget(top); 1087 draw_Widget(top);
1087 } 1088 }
@@ -1198,7 +1199,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
1198 } 1199 }
1199 /* Check for on-top widgets first. */ 1200 /* Check for on-top widgets first. */
1200 if (!d->parent) { 1201 if (!d->parent) {
1201 iReverseForEach(PtrArray, i, onTop_RootData()) { 1202 iReverseForEach(PtrArray, i, onTop_Root(get_Root())) {
1202 iWidget *child = i.ptr; 1203 iWidget *child = i.ptr;
1203// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)), 1204// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)),
1204// class_Widget(child)->name, 1205// class_Widget(child)->name,
@@ -1286,12 +1287,12 @@ iBool isDisabled_Widget(const iAnyObject *d) {
1286 1287
1287iBool isFocused_Widget(const iAnyObject *d) { 1288iBool isFocused_Widget(const iAnyObject *d) {
1288 iAssert(isInstance_Object(d, &Class_Widget)); 1289 iAssert(isInstance_Object(d, &Class_Widget));
1289 return data_Root()->focus == d; 1290 return get_Root()->focus == d;
1290} 1291}
1291 1292
1292iBool isHover_Widget(const iAnyObject *d) { 1293iBool isHover_Widget(const iAnyObject *d) {
1293 iAssert(isInstance_Object(d, &Class_Widget)); 1294 iAssert(isInstance_Object(d, &Class_Widget));
1294 return data_Root()->hover == d; 1295 return get_Root()->hover == d;
1295} 1296}
1296 1297
1297iBool isSelected_Widget(const iAnyObject *d) { 1298iBool isSelected_Widget(const iAnyObject *d) {
@@ -1337,12 +1338,12 @@ iBool isAffectedByVisualOffset_Widget(const iWidget *d) {
1337} 1338}
1338 1339
1339void setFocus_Widget(iWidget *d) { 1340void setFocus_Widget(iWidget *d) {
1340 if (data_Root()->focus != d) { 1341 if (get_Root()->focus != d) {
1341 if (data_Root()->focus) { 1342 if (get_Root()->focus) {
1342 iAssert(!contains_PtrSet(data_Root()->pendingDestruction, data_Root()->focus)); 1343 iAssert(!contains_PtrSet(get_Root()->pendingDestruction, get_Root()->focus));
1343 postCommand_Widget(data_Root()->focus, "focus.lost"); 1344 postCommand_Widget(get_Root()->focus, "focus.lost");
1344 } 1345 }
1345 data_Root()->focus = d; 1346 get_Root()->focus = d;
1346 if (d) { 1347 if (d) {
1347 iAssert(flags_Widget(d) & focusable_WidgetFlag); 1348 iAssert(flags_Widget(d) & focusable_WidgetFlag);
1348 postCommand_Widget(d, "focus.gained"); 1349 postCommand_Widget(d, "focus.gained");
@@ -1351,15 +1352,15 @@ void setFocus_Widget(iWidget *d) {
1351} 1352}
1352 1353
1353iWidget *focus_Widget(void) { 1354iWidget *focus_Widget(void) {
1354 return data_Root()->focus; 1355 return get_Root()->focus;
1355} 1356}
1356 1357
1357void setHover_Widget(iWidget *d) { 1358void setHover_Widget(iWidget *d) {
1358 data_Root()->hover = d; 1359 get_Root()->hover = d;
1359} 1360}
1360 1361
1361iWidget *hover_Widget(void) { 1362iWidget *hover_Widget(void) {
1362 return data_Root()->hover; 1363 return get_Root()->hover;
1363} 1364}
1364 1365
1365static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom, 1366static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom,
@@ -1403,7 +1404,7 @@ static const iWidget *findFocusRoot_Widget_(const iWidget *d) {
1403} 1404}
1404 1405
1405iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) { 1406iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) {
1406 const iWidget *root = findFocusRoot_Widget_(get_Window()->root); 1407 const iWidget *root = findFocusRoot_Widget_(get_Window()->root.widget);
1407 iAssert(root != NULL); 1408 iAssert(root != NULL);
1408 iBool getNext = (startFrom ? iFalse : iTrue); 1409 iBool getNext = (startFrom ? iFalse : iTrue);
1409 const iWidget *found = findFocusable_Widget_(root, startFrom, &getNext, focusDir); 1410 const iWidget *found = findFocusable_Widget_(root, startFrom, &getNext, focusDir);
@@ -1415,14 +1416,14 @@ iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusD
1415} 1416}
1416 1417
1417void setMouseGrab_Widget(iWidget *d) { 1418void setMouseGrab_Widget(iWidget *d) {
1418 if (data_Root()->mouseGrab != d) { 1419 if (get_Root()->mouseGrab != d) {
1419 data_Root()->mouseGrab = d; 1420 get_Root()->mouseGrab = d;
1420 SDL_CaptureMouse(d != NULL); 1421 SDL_CaptureMouse(d != NULL);
1421 } 1422 }
1422} 1423}
1423 1424
1424iWidget *mouseGrab_Widget(void) { 1425iWidget *mouseGrab_Widget(void) {
1425 return data_Root()->mouseGrab; 1426 return get_Root()->mouseGrab;
1426} 1427}
1427 1428
1428void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { 1429void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
@@ -1456,7 +1457,7 @@ void refresh_Widget(const iAnyObject *d) {
1456} 1457}
1457 1458
1458void raise_Widget(iWidget *d) { 1459void raise_Widget(iWidget *d) {
1459 iPtrArray *onTop = onTop_RootData(); 1460 iPtrArray *onTop = onTop_Root(get_Root());
1460 if (d->flags & keepOnTop_WidgetFlag) { 1461 if (d->flags & keepOnTop_WidgetFlag) {
1461 iAssert(indexOf_PtrArray(onTop, d) != iInvalidPos); 1462 iAssert(indexOf_PtrArray(onTop, d) != iInvalidPos);
1462 removeOne_PtrArray(onTop, d); 1463 removeOne_PtrArray(onTop, d);
diff --git a/src/ui/window.c b/src/ui/window.c
index ea2a4ec0..627f16b1 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -165,23 +165,21 @@ static void setupUserInterface_Window(iWindow *d) {
165#if defined (iPlatformAppleDesktop) 165#if defined (iPlatformAppleDesktop)
166 insertMacMenus_(); 166 insertMacMenus_();
167#endif 167#endif
168 static iRootData rootData_; 168 setCurrent_Root(&d->root);
169 setCurrent_Root(NULL, &rootData_); 169 createUserInterface_Root(&d->root);
170 d->root = createUserInterface_Root();
171 setCurrent_Root(d->root, &rootData_);
172} 170}
173 171
174static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { 172static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) {
175 iInt2 *size = &d->root->rect.size; 173 iInt2 *size = &d->root.widget->rect.size;
176 const iInt2 oldSize = *size; 174 const iInt2 oldSize = *size;
177 SDL_GetRendererOutputSize(d->render, &size->x, &size->y); 175 SDL_GetRendererOutputSize(d->render, &size->x, &size->y);
178 size->y -= d->keyboardHeight; 176 size->y -= d->keyboardHeight;
179 d->root->minSize = *size; 177 d->root.widget->minSize = *size;
180 if (notifyAlways || !isEqual_I2(oldSize, *size)) { 178 if (notifyAlways || !isEqual_I2(oldSize, *size)) {
181 updatePadding_Root(d->root); 179 updatePadding_Root(&d->root);
182 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); 180 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x);
183 const iBool isVert = (d->place.lastNotifiedSize.y != size->y); 181 const iBool isVert = (d->place.lastNotifiedSize.y != size->y);
184 arrange_Widget(d->root); 182 arrange_Widget(d->root.widget);
185 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d", 183 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d",
186 size->x, 184 size->x,
187 size->y, 185 size->y,
@@ -196,8 +194,8 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) {
196void drawWhileResizing_Window(iWindow *d, int w, int h) { 194void drawWhileResizing_Window(iWindow *d, int w, int h) {
197 /* This is called while a window resize is in progress, so we can be pretty confident 195 /* This is called while a window resize is in progress, so we can be pretty confident
198 the size has actually changed. */ 196 the size has actually changed. */
199 d->root->rect.size = coord_Window(d, w, h); 197 d->root.widget->rect.size = coord_Window(d, w, h);
200 arrange_Widget(d->root); 198 arrange_Widget(d->root.widget);
201 draw_Window(d); 199 draw_Window(d);
202} 200}
203 201
@@ -347,7 +345,7 @@ static SDL_Surface *loadImage_(const iBlock *data, int resized) {
347void init_Window(iWindow *d, iRect rect) { 345void init_Window(iWindow *d, iRect rect) {
348 theWindow_ = d; 346 theWindow_ = d;
349 d->win = NULL; 347 d->win = NULL;
350 d->root = NULL; 348 init_Root(&d->root);
351 iZap(d->cursors); 349 iZap(d->cursors);
352 d->place.initialPos = rect.pos; 350 d->place.initialPos = rect.pos;
353 d->place.normalRect = rect; 351 d->place.normalRect = rect;
@@ -460,7 +458,7 @@ void deinit_Window(iWindow *d) {
460 SDL_FreeCursor(d->cursors[i]); 458 SDL_FreeCursor(d->cursors[i]);
461 } 459 }
462 } 460 }
463 iReleasePtr(&d->root); 461 deinit_Root(&d->root);
464 deinit_Text(); 462 deinit_Text();
465 SDL_DestroyRenderer(d->render); 463 SDL_DestroyRenderer(d->render);
466 SDL_DestroyWindow(d->win); 464 SDL_DestroyWindow(d->win);
@@ -636,7 +634,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
636 return iTrue; 634 return iTrue;
637 } 635 }
638 case SDL_WINDOWEVENT_RESIZED: 636 case SDL_WINDOWEVENT_RESIZED:
639 updatePadding_Root(d->root); 637 updatePadding_Root(&d->root);
640 if (d->isMinimized) { 638 if (d->isMinimized) {
641 updateRootSize_Window_(d, iTrue); 639 updateRootSize_Window_(d, iTrue);
642 return iTrue; 640 return iTrue;
@@ -768,7 +766,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
768 event.button.x = pos.x; 766 event.button.x = pos.x;
769 event.button.y = pos.y; 767 event.button.y = pos.y;
770 } 768 }
771 iWidget *widget = d->root; 769 iWidget *widget = d->root.widget;
772 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL || 770 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL ||
773 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 771 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
774 if (mouseGrab_Widget()) { 772 if (mouseGrab_Widget()) {
@@ -796,7 +794,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
796 } 794 }
797 } 795 }
798 if (isMetricsChange_UserEvent(&event)) { 796 if (isMetricsChange_UserEvent(&event)) {
799 updateMetrics_Root(d->root); 797 updateMetrics_Root(&d->root);
800 } 798 }
801 if (isCommand_UserEvent(&event, "lang.changed")) { 799 if (isCommand_UserEvent(&event, "lang.changed")) {
802#if defined (iPlatformAppleDesktop) 800#if defined (iPlatformAppleDesktop)
@@ -805,8 +803,8 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
805 insertMacMenus_(); 803 insertMacMenus_();
806#endif 804#endif
807 invalidate_Window_(d); 805 invalidate_Window_(d);
808 updatePreferencesLayout_Widget(findChild_Widget(d->root, "prefs")); 806 updatePreferencesLayout_Widget(findChild_Widget(d->root.widget, "prefs"));
809 arrange_Widget(d->root); 807 arrange_Widget(d->root.widget);
810 //printTree_Widget(findChild_Widget(d->root, "prefs")); 808 //printTree_Widget(findChild_Widget(d->root, "prefs"));
811 } 809 }
812 if (oldHover != hover_Widget()) { 810 if (oldHover != hover_Widget()) {
@@ -823,7 +821,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
823 821
824iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) { 822iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {
825 /* A context menu may still get triggered here. */ 823 /* A context menu may still get triggered here. */
826 const iWidget *hit = hitChild_Widget(d->root, init_I2(ev->x, ev->y)); 824 const iWidget *hit = hitChild_Widget(d->root.widget, init_I2(ev->x, ev->y));
827 while (hit && isEmpty_String(id_Widget(hit))) { 825 while (hit && isEmpty_String(id_Widget(hit))) {
828 hit = parent_Widget(hit); 826 hit = parent_Widget(hit);
829 } 827 }
@@ -868,7 +866,7 @@ void draw_Window(iWindow *d) {
868 /* Draw widgets. */ 866 /* Draw widgets. */
869 d->frameTime = SDL_GetTicks(); 867 d->frameTime = SDL_GetTicks();
870 if (isExposed_Window(d)) { 868 if (isExposed_Window(d)) {
871 draw_Widget(d->root); 869 draw_Widget(d->root.widget);
872#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 870#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
873 /* App icon. */ 871 /* App icon. */
874 const iWidget *appIcon = findChild_Widget(d->root, "winbar.icon"); 872 const iWidget *appIcon = findChild_Widget(d->root, "winbar.icon");
@@ -907,7 +905,7 @@ void resize_Window(iWindow *d, int w, int h) {
907 905
908void setTitle_Window(iWindow *d, const iString *title) { 906void setTitle_Window(iWindow *d, const iString *title) {
909 SDL_SetWindowTitle(d->win, cstr_String(title)); 907 SDL_SetWindowTitle(d->win, cstr_String(title));
910 iLabelWidget *bar = findChild_Widget(d->root, "winbar.title"); 908 iLabelWidget *bar = findChild_Widget(d->root.widget, "winbar.title");
911 if (bar) { 909 if (bar) {
912 updateText_LabelWidget(bar, title); 910 updateText_LabelWidget(bar, title);
913 } 911 }
@@ -942,7 +940,7 @@ uint32_t id_Window(const iWindow *d) {
942} 940}
943 941
944iInt2 rootSize_Window(const iWindow *d) { 942iInt2 rootSize_Window(const iWindow *d) {
945 return d && d->root ? d->root->rect.size : zero_I2(); 943 return d && d->root.widget ? d->root.widget->rect.size : zero_I2();
946} 944}
947 945
948iRect safeRootRect_Window(const iWindow *d) { 946iRect safeRootRect_Window(const iWindow *d) {
diff --git a/src/ui/window.h b/src/ui/window.h
index 9d9a6aeb..f90bd863 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -22,7 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "widget.h" 25#include "root.h"
26 26
27#include <the_Foundation/rect.h> 27#include <the_Foundation/rect.h>
28#include <SDL_events.h> 28#include <SDL_events.h>
@@ -66,8 +66,8 @@ struct Impl_Window {
66 iBool ignoreClick; 66 iBool ignoreClick;
67 uint32_t focusGainedAt; 67 uint32_t focusGainedAt;
68 SDL_Renderer *render; 68 SDL_Renderer *render;
69 iWidget * root; 69 iRoot root; /* root widget and UI state */
70 float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */ 70 float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */
71 float displayScale; /* DPI-based scaling factor of current display, affects uiScale only */ 71 float displayScale; /* DPI-based scaling factor of current display, affects uiScale only */
72 float uiScale; 72 float uiScale;
73 uint32_t frameTime; 73 uint32_t frameTime;