=> a6f283838088921daa7fa29677e832400469c6dc
[1mdiff --git a/src/app.c b/src/app.c[m [1mindex 8245e5c3..f8fbbabd 100644[m [1m--- a/src/app.c[m [1m+++ b/src/app.c[m [36m@@ -42,6 +42,8 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "ui/inputwidget.h"[m #include "ui/keys.h"[m #include "ui/labelwidget.h"[m [32m+[m[32m#include "ui/listwidget.h"[m [32m+[m[32m#include "ui/lookupwidget.h"[m #include "ui/root.h"[m #include "ui/sidebarwidget.h"[m #include "ui/text.h"[m [36m@@ -2142,45 +2144,24 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m }[m #endif /* LAGRANGE_ENABLE_MOUSE_TOUCH_EMULATION */[m iBool wasUsed = iFalse;[m [31m- /* Focus navigation events take prioritity. */[m [32m+[m[32m /* Focus navigation events take priority. */[m if (!wasUsed) {[m /* Keyboard focus navigation with arrow keys. */[m [31m- iWidget *menubar = NULL;[m [31m- if (ev.type == SDL_KEYDOWN && ev.key.keysym.mod == 0 && focus_Widget() &&[m [31m- parentMenu_Widget(focus_Widget())) {[m [31m- setCurrent_Window(window_Widget(focus_Widget()));[m [31m- const int key = ev.key.keysym.sym;[m [31m- if (key == SDLK_DOWN || key == SDLK_UP) {[m [31m- iWidget *nextFocus = findFocusable_Widget(focus_Widget(),[m [31m- key == SDLK_UP[m [31m- ? backward_WidgetFocusDir[m [31m- : forward_WidgetFocusDir);[m [31m- if (nextFocus && parent_Widget(nextFocus) == parent_Widget(focus_Widget())) {[m [31m- setFocus_Widget(nextFocus);[m [31m- }[m [32m+[m[32m if (ev.type == SDL_KEYDOWN && keyMods_Sym(ev.key.keysym.mod) == 0 && focus_Widget()) {[m [32m+[m[32m if (moveFocusInsideMenu_App(&ev)) {[m wasUsed = iTrue;[m }[m [31m- else if ((key == SDLK_LEFT || key == SDLK_RIGHT)) {[m [31m- /* Arrow keys in the menubar will switch between top-level menus. */[m [31m- if ((menubar = findParent_Widget(focus_Widget(), "menubar")) != NULL) {[m [31m- iWidget *button = parent_Widget(parent_Widget(focus_Widget()));[m [31m- size_t index = indexOfChild_Widget(menubar, button);[m [31m- const size_t curIndex = index;[m [31m- if (key == SDLK_LEFT && index > 0) {[m [31m- index--;[m [31m- }[m [31m- else if (key == SDLK_RIGHT && index < childCount_Widget(menubar) - 1) {[m [31m- index++;[m [31m- }[m [31m- if (curIndex != index) {[m [31m- setFocus_Widget(child_Widget(menubar, index));[m [31m- postCommand_Widget(child_Widget(menubar, index), "trigger");[m [31m- }[m [31m- }[m [31m- else {[m [31m- postCommand_Widget(focus_Widget(), "cancel");[m [32m+[m[32m else {[m [32m+[m[32m const int key = ev.key.keysym.sym;[m [32m+[m[32m if ((key == SDLK_DOWN || key == SDLK_UP || key == SDLK_LEFT ||[m [32m+[m[32m key == SDLK_RIGHT) &&[m [32m+[m[32m /* some widgets handle arrow keys themselves: */[m [32m+[m[32m !isInstance_Object(focus_Widget(), &Class_DocumentWidget) &&[m [32m+[m[32m !isInstance_Object(focus_Widget(), &Class_ListWidget) &&[m [32m+[m[32m !isInstance_Object(focus_Widget(), &Class_InputWidget) &&[m [32m+[m[32m !isInstance_Object(focus_Widget(), &Class_LookupWidget)) {[m [32m+[m[32m wasUsed = moveFocusWithArrows_App(&ev);[m }[m [31m- wasUsed = iTrue;[m }[m }[m }[m [36m@@ -2720,6 +2701,116 @@[m [miAny *findWidget_App(const char *id) {[m return NULL;[m }[m [m [32m+[m[32miBool moveFocusInsideMenu_App(const void *sdlEvent) {[m [32m+[m[32m if (!focus_Widget()) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m const SDL_Event *event = sdlEvent;[m [32m+[m[32m if (event->type != SDL_KEYDOWN) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m const int key = event->key.keysym.sym;[m [32m+[m[32m /* The menubar has special behavior for focus changing to navigate between sibling menus. */[m [32m+[m[32m iWidget *menu = parentMenu_Widget(focus_Widget());[m [32m+[m[32m if (menu) {[m [32m+[m[32m const size_t focusIndex = indexOfChild_Widget(menu, focus_Widget());[m [32m+[m[32m if (key >= 'a' && key <= 'z') {[m [32m+[m[32m /* See if any menu item starts with a matching letter. */[m [32m+[m[32m iWidget *firstMatch = NULL;[m [32m+[m[32m size_t index = 0;[m [32m+[m[32m iForEach(ObjectList, i, children_Widget(menu)) {[m [32m+[m[32m if (isInstance_Object(i.object, &Class_LabelWidget)) {[m [32m+[m[32m iLabelWidget *item = i.object;[m [32m+[m[32m char prefix[2] = { key, 0 };[m [32m+[m[32m if (startsWithCase_String(text_LabelWidget(item), prefix)) {[m [32m+[m[32m if (!firstMatch) {[m [32m+[m[32m firstMatch = i.object;[m [32m+[m[32m }[m [32m+[m[32m if (focus_Widget() != i.object && index > focusIndex) {[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m setFocus_Widget(i.object);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m index++;[m [32m+[m[32m }[m [32m+[m[32m if (firstMatch) {[m [32m+[m[32m /* Loop back around. */[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m setFocus_Widget(firstMatch);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m else if (key == SDLK_PAGEUP || key == SDLK_PAGEDOWN || key == SDLK_HOME || key == SDLK_END) {[m [32m+[m[32m /* Move to top/bottom of menu. */[m [32m+[m[32m enum iDirection dir =[m [32m+[m[32m (key == SDLK_PAGEUP || key == SDLK_HOME ? up_Direction : down_Direction);[m [32m+[m[32m iWidget *next = focus_Widget();[m [32m+[m[32m for (;;) {[m [32m+[m[32m iWidget *adjacent = findAdjacentFocusable_Widget(next, dir);[m [32m+[m[32m if (!adjacent || adjacent == next) break;[m [32m+[m[32m next = adjacent;[m [32m+[m[32m }[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m setFocus_Widget(next);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m if (key == SDLK_LEFT || key == SDLK_RIGHT) {[m [32m+[m[32m /* Arrow keys in the menubar will switch between top-level menus. */[m [32m+[m[32m iWidget *menubar = findParent_Widget(focus_Widget(), "menubar");[m [32m+[m[32m if (menubar) {[m [32m+[m[32m iWidget *button = parent_Widget(parent_Widget(focus_Widget()));[m [32m+[m[32m size_t index = indexOfChild_Widget(menubar, button);[m [32m+[m[32m if (index == iInvalidPos) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m const size_t curIndex = index;[m [32m+[m[32m if (key == SDLK_LEFT && index > 0) {[m [32m+[m[32m index--;[m [32m+[m[32m }[m [32m+[m[32m else if (key == SDLK_RIGHT && index < childCount_Widget(menubar) - 1) {[m [32m+[m[32m index++;[m [32m+[m[32m }[m [32m+[m[32m if (curIndex != index) {[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m setFocus_Widget(child_Widget(menubar, index));[m [32m+[m[32m postCommand_Widget(child_Widget(menubar, index), "trigger");[m [32m+[m[32m }[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m postCommand_Widget(focus_Widget(), "cancel");[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m [32m+[m[32miBool moveFocusWithArrows_App(const void *sdlEvent) {[m [32m+[m[32m if (!focus_Widget()) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m const SDL_Event *event = sdlEvent;[m [32m+[m[32m if (event->type != SDL_KEYDOWN) {[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m const int key = event->key.keysym.sym;[m [32m+[m[32m iWidget *nextFocus = findAdjacentFocusable_Widget(focus_Widget(),[m [32m+[m[32m key == SDLK_UP ? up_Direction[m [32m+[m[32m : key == SDLK_DOWN ? down_Direction[m [32m+[m[32m : key == SDLK_LEFT ? left_Direction[m [32m+[m[32m : key == SDLK_RIGHT ? right_Direction[m [32m+[m[32m : none_Direction);[m [32m+[m[32m if (nextFocus) {[m [32m+[m[32m setCurrent_Window(window_Widget(focus_Widget()));[m [32m+[m[32m setFocus_Widget(nextFocus);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m void addTicker_App(iTickerFunc ticker, iAny *context) {[m iApp *d = &app_;[m insert_SortedArray(&d->tickers, &(iTicker){ context, get_Root(), ticker });[m [36m@@ -3035,6 +3126,10 @@[m [mstatic iBool handlePrefsCommands_(iWidget *d, const char *cmd) {[m return iTrue;[m }[m else if (equal_Command(cmd, "tabs.changed")) {[m [32m+[m[32m if (isTerminal_Platform()) {[m [32m+[m[32m iWidget *tabs = findChild_Widget(d, "prefs.tabs");[m [32m+[m[32m setFocus_Widget((iWidget *) tabPageButton_Widget(tabs, currentTabPage_Widget(tabs)));[m [32m+[m[32m }[m refresh_Widget(d);[m return iFalse;[m }[m [1mdiff --git a/src/app.h b/src/app.h[m [1mindex 16708127..a9acc149 100644[m [1m--- a/src/app.h[m [1m+++ b/src/app.h[m [36m@@ -186,6 +186,8 @@[m [miLocalDef void postCommand_App(const char *command) {[m iDocumentWidget *document_Command (const char *cmd);[m [m iAny * findWidget_App (const char *id);[m [32m+[m[32miBool moveFocusWithArrows_App (const void *sdlEvent);[m [32m+[m[32miBool moveFocusInsideMenu_App (const void *sdlEvent);[m void commitFile_App (const char *path, const char *tempPathWithNewContents); /* latter will be removed */[m void openInDefaultBrowser_App (const iString *url, const iString *mime);[m void revealPath_App (const iString *path);[m [1mdiff --git a/src/defs.h b/src/defs.h[m [1mindex 20898327..18c3aefa 100644[m [1m--- a/src/defs.h[m [1m+++ b/src/defs.h[m [36m@@ -142,6 +142,14 @@[m [menum iScrollType {[m max_ScrollType[m };[m [m [32m+[m[32menum iDirection {[m [32m+[m[32m none_Direction,[m [32m+[m[32m up_Direction,[m [32m+[m[32m right_Direction,[m [32m+[m[32m down_Direction,[m [32m+[m[32m left_Direction,[m [32m+[m[32m};[m [32m+[m enum iToolbarAction {[m back_ToolbarAction = 0,[m forward_ToolbarAction = 1,[m [1mdiff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c[m [1mindex 5e14bac2..66201119 100644[m [1m--- a/src/ui/inputwidget.c[m [1m+++ b/src/ui/inputwidget.c[m [36m@@ -2654,7 +2654,14 @@[m [mstatic iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {[m (checkAcceptMods_InputWidget_(d, mods) ||[m (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) {[m d->inFlags |= enterPressed_InputWidgetFlag;[m [31m- setFocus_Widget(NULL);[m [32m+[m[32m if (isTerminal_Platform() && cmp_String(id_Widget(w), "url")) {[m [32m+[m[32m /* In dialogs, Return moves to the next focusable field rather than[m [32m+[m[32m loosing focus entirely. */[m [32m+[m[32m setFocus_Widget(findFocusable_Widget(w, forward_WidgetFocusDir));[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m setFocus_Widget(NULL);[m [32m+[m[32m }[m return iTrue;[m }[m return iFalse;[m [36m@@ -2810,11 +2817,14 @@[m [mstatic iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {[m refresh_Widget(d);[m return iTrue;[m }[m [31m- if (isArrowUpDownConsumed_InputWidget_(d)) {[m [32m+[m[32m // if (isArrowUpDownConsumed_InputWidget_(d)) {[m [32m+[m[32m // return iTrue;[m [32m+[m[32m // }[m [32m+[m[32m /* For moving to lookup from url entry. */[m [32m+[m[32m if (processEvent_Widget(as_Widget(d), ev)) {[m return iTrue;[m }[m [31m- /* For moving to lookup from url entry. */[m [31m- return processEvent_Widget(as_Widget(d), ev);[m [32m+[m[32m return moveFocusWithArrows_App(ev);[m case SDLK_PAGEUP:[m case SDLK_PAGEDOWN:[m for (int count = 0; count < 5; count++) {[m [1mdiff --git a/src/ui/listwidget.c b/src/ui/listwidget.c[m [1mindex 51c89990..a1e4b72e 100644[m [1m--- a/src/ui/listwidget.c[m [1m+++ b/src/ui/listwidget.c[m [36m@@ -315,7 +315,7 @@[m [mvoid setHoverItem_ListWidget(iListWidget *d, size_t index) {[m }[m }[m [m [31m-static void moveCursor_ListWidget_(iListWidget *d, int dir, uint32_t animSpan) {[m [32m+[m[32mstatic iBool moveCursor_ListWidget_(iListWidget *d, int dir, uint32_t animSpan) {[m const size_t oldCursor = d->cursorItem;[m if (isEmpty_ListWidget(d)) {[m d->cursorItem = iInvalidPos;[m [36m@@ -338,6 +338,7 @@[m [mstatic void moveCursor_ListWidget_(iListWidget *d, int dir, uint32_t animSpan) {[m if (d->cursorItem != iInvalidPos) {[m scrollToItem_ListWidget(d, d->cursorItem, prefs_App()->uiAnimations ? animSpan : 0);[m }[m [32m+[m[32m return d->cursorItem != oldCursor;[m }[m [m void setCursorItem_ListWidget(iListWidget *d, size_t index) {[m [36m@@ -531,11 +532,17 @@[m [mstatic iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {[m case SDLK_END: {[m if (d->scrollMode == normal_ScrollMode) {[m const int step = cursorKeyStep_ListWidget_(d, key);[m [31m- moveCursor_ListWidget_(d, step, iAbs(step) == 1 ? 0 : 150);[m [32m+[m[32m const iBool wasChanged = moveCursor_ListWidget_(d, step, iAbs(step) == 1 ? 0 : 150);[m [32m+[m[32m if (!wasChanged && (key == SDLK_UP || key == SDLK_DOWN)) {[m [32m+[m[32m moveFocusWithArrows_App(ev);[m [32m+[m[32m }[m return iTrue;[m }[m return iFalse;[m }[m [32m+[m[32m case SDLK_LEFT:[m [32m+[m[32m case SDLK_RIGHT:[m [32m+[m[32m return moveFocusWithArrows_App(ev);[m case SDLK_RETURN:[m case SDLK_KP_ENTER:[m case SDLK_SPACE:[m [1mdiff --git a/src/ui/util.c b/src/ui/util.c[m [1mindex 5adf4151..bae92205 100644[m [1m--- a/src/ui/util.c[m [1m+++ b/src/ui/util.c[m [36m@@ -1466,13 +1466,15 @@[m [mvoid openMenuAnchorFlags_Widget(iWidget *d, iRect windowAnchorRect, int menuOpen[m arrange_Widget(d); /* need to know the height */[m iBool allowOverflow = (get_Window()->type == extra_WindowType);[m /* A vertical offset determined by a possible selected label in the menu. */[m [32m+[m[32m iWidget *focusedItem = NULL;[m if (deviceType_App() == desktop_AppDeviceType &&[m windowCoord.y < rootSize.y - lineHeight_Text(uiNormal_FontSize) * 3) {[m [31m- iConstForEach(ObjectList, child, children_Widget(d)) {[m [31m- const iWidget *item = constAs_Widget(child.object);[m [32m+[m[32m iForEach(ObjectList, child, children_Widget(d)) {[m [32m+[m[32m iWidget *item = as_Widget(child.object);[m if (flags_Widget(item) & selected_WidgetFlag) {[m windowCoord.y -= item->rect.pos.y;[m allowOverflow = iTrue;[m [32m+[m[32m focusedItem = item;[m }[m }[m }[m [36m@@ -1641,10 +1643,15 @@[m [mvoid openMenuAnchorFlags_Widget(iWidget *d, iRect windowAnchorRect, int menuOpen[m }[m setupMenuTransition_Mobile(d, iTrue);[m if (isMenuFocused) {[m [31m- iForEach(ObjectList, i, children_Widget(d)) {[m [31m- if (flags_Widget(i.object) & focusable_WidgetFlag) {[m [31m- setFocus_Widget(i.object);[m [31m- break;[m [32m+[m[32m if (focusedItem) {[m [32m+[m[32m setFocus_Widget(focusedItem);[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m iForEach(ObjectList, i, children_Widget(d)) {[m [32m+[m[32m if (flags_Widget(i.object) & focusable_WidgetFlag) {[m [32m+[m[32m setFocus_Widget(i.object);[m [32m+[m[32m break;[m [32m+[m[32m }[m }[m }[m }[m [36m@@ -1684,7 +1691,7 @@[m [mvoid closeMenu_Widget(iWidget *d) {[m postCommand_Widget(d, "menu.closed");[m setupMenuTransition_Mobile(d, iFalse);[m if (focus_Widget() && hasParent_Widget(focus_Widget(), d)) {[m [31m- setFocus_Widget(NULL);[m [32m+[m[32m setFocus_Widget(as_Widget(button));[m }[m }[m }[m [1mdiff --git a/src/ui/widget.c b/src/ui/widget.c[m [1mindex 3d839bea..0376661a 100644[m [1m--- a/src/ui/widget.c[m [1m+++ b/src/ui/widget.c[m [36m@@ -2470,28 +2470,28 @@[m [miWidget *hover_Widget(void) {[m }[m [m static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom,[m [31m- iBool *getNext, enum iWidgetFocusDir focusDir) {[m [32m+[m[32m iBool *getNext, enum iWidgetFocusDir cycleDir) {[m if (startFrom == d) {[m *getNext = iTrue;[m return NULL;[m }[m if ((d->flags & focusable_WidgetFlag) && isVisible_Widget(d) && !isDisabled_Widget(d) &&[m ~d->flags & destroyPending_WidgetFlag && *getNext) {[m [31m- if ((~focusDir & notInput_WidgetFocusFlag) || !isInstance_Object(d, &Class_InputWidget)) {[m [32m+[m[32m if ((~cycleDir & notInput_WidgetFocusFlag) || !isInstance_Object(d, &Class_InputWidget)) {[m return d;[m }[m }[m [31m- if ((focusDir & dirMask_WidgetFocusFlag) == forward_WidgetFocusDir) {[m [32m+[m[32m if ((cycleDir & dirMask_WidgetFocusFlag) == forward_WidgetFocusDir) {[m iConstForEach(ObjectList, i, d->children) {[m const iWidget *found =[m [31m- findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, focusDir);[m [32m+[m[32m findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, cycleDir);[m if (found) return found;[m }[m }[m else {[m iReverseConstForEach(ObjectList, i, d->children) {[m const iWidget *found =[m [31m- findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, focusDir);[m [32m+[m[32m findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, cycleDir);[m if (found) return found;[m }[m }[m [36m@@ -2536,14 +2536,14 @@[m [mconst iWidget *focusRoot_Widget(const iWidget *d) {[m return root_Widget(d);[m }[m [m [31m-iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) {[m [32m+[m[32miAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir cycleDir) {[m if (!get_Window()) {[m return NULL;[m }[m const iWidget *focusRoot = focusRoot_Widget(startFrom);[m iAssert(focusRoot != NULL);[m iBool getNext = (startFrom ? iFalse : iTrue);[m [31m- const iWidget *found = findFocusable_Widget_(focusRoot, startFrom, &getNext, focusDir);[m [32m+[m[32m const iWidget *found = findFocusable_Widget_(focusRoot, startFrom, &getNext, cycleDir);[m if (!found && startFrom) {[m getNext = iTrue;[m /* Switch to the next root, if available. */[m [36m@@ -2551,9 +2551,9 @@[m [miAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusD[m findTopmostFocusRoot_Widget_(otherRoot_Window(get_Window(), focusRoot->root)->widget),[m NULL,[m &getNext,[m [31m- focusDir);[m [32m+[m[32m cycleDir);[m }[m [31m- return iConstCast(iWidget *, found);[m [32m+[m[32m return as_Widget(iConstCast(iWidget *, found));[m }[m [m void setMouseGrab_Widget(iWidget *d) {[m [36m@@ -2651,11 +2651,145 @@[m [miBool hasVisibleChildOnTop_Widget(const iWidget *parent) {[m return iFalse;[m }[m [m [32m+[m[32mvoid addRecentlyDeleted_Widget(iAnyObject *obj) {[m [32m+[m[32m /* We sometimes include pointers to widgets in command events. Before an event is processed,[m [32m+[m[32m it is possible that the referened widget has been destroyed. Keeping track of recently[m [32m+[m[32m deleted widgets allows ignoring these events. */[m [32m+[m[32m maybeInit_RecentlyDeleted_(&recentlyDeleted_);[m [32m+[m[32m iGuardMutex(&recentlyDeleted_.mtx, insert_PtrSet(recentlyDeleted_.objs, obj));[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid clearRecentlyDeleted_Widget(void) {[m [32m+[m[32m if (recentlyDeleted_.objs) {[m [32m+[m[32m iGuardMutex(&recentlyDeleted_.mtx, clear_PtrSet(recentlyDeleted_.objs));[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32miBool isRecentlyDeleted_Widget(const iAnyObject *obj) {[m [32m+[m[32m return contains_RecentlyDeleted_(&recentlyDeleted_, obj);[m [32m+[m[32m}[m [32m+[m iBeginDefineClass(Widget)[m .processEvent = processEvent_Widget,[m .draw = draw_Widget,[m iEndDefineClass(Widget)[m [m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m [32m+[m[32miDeclareType(AdjacentFocusFinder);[m [32m+[m [32m+[m[32mstruct Impl_AdjacentFocusFinder {[m [32m+[m[32m iRect bounds;[m [32m+[m[32m enum iDirection direction;[m [32m+[m[32m float nearestDist;[m [32m+[m[32m const iWidget *nearest_out;[m [32m+[m[32m};[m [32m+[m [32m+[m[32mstatic iBool isContained_Rangei(iRangei large, iRangei small) {[m [32m+[m[32m return contains_Rangei(large, small.start) &&[m [32m+[m[32m (contains_Rangei(large, small.end) || large.end == small.end);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic int mid_Rangei(const iRangei d) {[m [32m+[m[32m return (d.start + d.end) / 2;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic float offAxisRangeDistance_(iRangei src, iRangei dst) {[m [32m+[m[32m if (equal_Rangei(src, dst)) {[m [32m+[m[32m return 0; /* Ideal match. */[m [32m+[m[32m }[m [32m+[m[32m if (isOverlapping_Rangei(src, dst)) {[m [32m+[m[32m return iAbs(src.start - dst.start); /* Prefer matching left/top edge. */[m [32m+[m[32m }[m [32m+[m[32m /* Source and destination are not overlapping. Off-axis non-overlapping differences are[m [32m+[m[32m unfavorable because this is used for navigating to a particular linear direction. */[m [32m+[m[32m const int dist = iAbs(mid_Rangei(src) - mid_Rangei(dst));[m [32m+[m[32m return dist * dist;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic float adjacentDistance_Rect(const iRect *d, const iRect *other, enum iDirection dir) {[m [32m+[m[32m float dist = 0;[m [32m+[m[32m switch (dir) {[m [32m+[m[32m case left_Direction:[m [32m+[m[32m dist = -right_Rect(*other) + left_Rect(*d);[m [32m+[m[32m if (dist < 0) {[m [32m+[m[32m return -1.0f;[m [32m+[m[32m }[m [32m+[m[32m dist += offAxisRangeDistance_(ySpan_Rect(*d), ySpan_Rect(*other));[m [32m+[m[32m break;[m [32m+[m[32m case right_Direction:[m [32m+[m[32m dist = left_Rect(*other) - right_Rect(*d);[m [32m+[m[32m if (dist < 0) {[m [32m+[m[32m return -1.0f;[m [32m+[m[32m }[m [32m+[m[32m dist += offAxisRangeDistance_(ySpan_Rect(*d), ySpan_Rect(*other));[m [32m+[m[32m break;[m [32m+[m[32m case up_Direction:[m [32m+[m[32m dist = -bottom_Rect(*other) + top_Rect(*d);[m [32m+[m[32m if (dist < 0) {[m [32m+[m[32m return -1.0f;[m [32m+[m[32m }[m [32m+[m[32m dist += offAxisRangeDistance_(xSpan_Rect(*d), xSpan_Rect(*other));[m [32m+[m[32m dist /= aspect_UI;[m [32m+[m[32m break;[m [32m+[m[32m case down_Direction:[m [32m+[m[32m dist = top_Rect(*other) - bottom_Rect(*d);[m [32m+[m[32m if (dist < 0) {[m [32m+[m[32m return -1.0f;[m [32m+[m[32m }[m [32m+[m[32m dist += offAxisRangeDistance_(xSpan_Rect(*d), xSpan_Rect(*other));[m [32m+[m[32m dist /= aspect_UI;[m [32m+[m[32m break;[m [32m+[m[32m default:[m [32m+[m[32m return -1.0f;[m [32m+[m[32m }[m [32m+[m[32m return dist;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void walkTree_AdjacentFocusFinder_(iAdjacentFocusFinder *d, const iWidget *parent) {[m [32m+[m[32m const iBool isMenu = findParent_Widget(parent, "menu") != NULL;[m [32m+[m[32m iConstForEach(ObjectList, i, parent->children) {[m [32m+[m[32m const iWidget *w = i.object;[m [32m+[m[32m if (isVisible_Widget(w) && !isDisabled_Widget(w) && ~w->flags & destroyPending_WidgetFlag) {[m [32m+[m[32m if (w->flags & focusable_WidgetFlag) {[m [32m+[m[32m const iRect bounds = bounds_Widget(w);[m [32m+[m[32m if (isOverlapping_Rect(d->bounds, bounds)) {[m [32m+[m[32m /* Overlapping means it isn't adjacent. */[m [32m+[m[32m continue;[m [32m+[m[32m }[m [32m+[m[32m const float dist = adjacentDistance_Rect(&d->bounds, &bounds, d->direction);[m [32m+[m[32m if (dist < 0) {[m [32m+[m[32m continue; /* wrong direction */[m [32m+[m[32m }[m [32m+[m[32m if (!d->nearest_out || dist < d->nearestDist) {[m [32m+[m[32m d->nearest_out = w;[m [32m+[m[32m d->nearestDist = dist;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m walkTree_AdjacentFocusFinder_(d, w);[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32miAny *findAdjacentFocusable_Widget(const iWidget *d, enum iDirection direction) {[m [32m+[m[32m if (!get_Window() || direction == none_Direction) {[m [32m+[m[32m return NULL;[m [32m+[m[32m }[m [32m+[m[32m const iWidget *focusRoot = focusRoot_Widget(d);[m [32m+[m[32m const iWidget *menu = parentMenu_Widget(d);[m [32m+[m[32m if (menu) {[m [32m+[m[32m /* Inside menus, don't allow exiting the current menu. */[m [32m+[m[32m // const iWidget *topMenu = findParent_Widget(menu, "menu");[m [32m+[m[32m focusRoot = menu;[m [32m+[m[32m }[m [32m+[m[32m iAdjacentFocusFinder args = { .bounds = bounds_Widget(d), .direction = direction };[m [32m+[m[32m walkTree_AdjacentFocusFinder_(&args, focusRoot);[m [32m+[m[32m if (!args.nearest_out && menu) {[m [32m+[m[32m args.nearest_out = d; /* Keep the focus unchanged in a menu. */[m [32m+[m[32m }[m [32m+[m[32m return as_Widget(iConstCast(iWidget *, args.nearest_out));[m [32m+[m[32m}[m [32m+[m /*----------------------------------------------------------------------------------------------[m Debug utilities for inspecting widget trees.[m */[m [36m@@ -2723,58 +2857,3 @@[m [mvoid identify_Widget(const iWidget *d) {[m printf("Root %d: %p\n", 1 + (d->root == get_Window()->roots[1]), d->root);[m fflush(stdout);[m }[m [31m-[m [31m-void addRecentlyDeleted_Widget(iAnyObject *obj) {[m [31m- /* We sometimes include pointers to widgets in command events. Before an event is processed,[m [31m- it is possible that the referened widget has been destroyed. Keeping track of recently[m [31m- deleted widgets allows ignoring these events. */[m [31m- maybeInit_RecentlyDeleted_(&recentlyDeleted_);[m [31m- iGuardMutex(&recentlyDeleted_.mtx, insert_PtrSet(recentlyDeleted_.objs, obj));[m [31m-}[m [31m-[m [31m-void clearRecentlyDeleted_Widget(void) {[m [31m- if (recentlyDeleted_.objs) {[m [31m- iGuardMutex(&recentlyDeleted_.mtx, clear_PtrSet(recentlyDeleted_.objs));[m [31m- }[m [31m-}[m [31m-[m [31m-iBool isRecentlyDeleted_Widget(const iAnyObject *obj) {[m [31m- return contains_RecentlyDeleted_(&recentlyDeleted_, obj);[m [31m-}[m [31m-[m [31m-#if 0[m [31m-static uint32_t callDelayed_Widget_(uint32_t interval, void *ctx) {[m [31m- iWidget *d = ctx;[m [31m- lock_Mutex(d->delayedMutex);[m [31m- d->delayedTimer = 0;[m [31m- if (d->delayedCallback) {[m [31m- d->delayedCallback(d);[m [31m- }[m [31m- unlock_Mutex(d->delayedMutex);[m [31m- return 0; /* single-shot */[m [31m-}[m [31m-[m [31m-void setDelayedCallback_Widget(iWidget *d, int delay, void (*callback)(iWidget *)) {[m [31m- if (!callback) {[m [31m- if (d->delayedMutex) {[m [31m- iGuardMutex(d->delayedMutex, {[m [31m- SDL_RemoveTimer(d->delayedTimer);[m [31m- d->delayedTimer = 0;[m [31m- d->delayedCallback = NULL;[m [31m- });[m [31m- delete_Mutex(d->delayedMutex);[m [31m- d->delayedMutex = NULL;[m [31m- }[m [31m- return;[m [31m- }[m [31m- if (!d->delayedMutex) {[m [31m- d->delayedMutex = new_Mutex();[m [31m- }[m [31m- lock_Mutex(d->delayedMutex);[m [31m- if (d->delayedTimer) {[m [31m- SDL_RemoveTimer(d->delayedTimer);[m [31m- }[m [31m- d->delayedTimer = SDL_AddTimer(delay, callDelayed_Widget_, d);[m [31m- unlock_Mutex(d->delayedMutex);[m [31m-}[m [31m-#endif[m [1mdiff --git a/src/ui/widget.h b/src/ui/widget.h[m [1mindex 69f46b92..28ebe856 100644[m [1m--- a/src/ui/widget.h[m [1m+++ b/src/ui/widget.h[m [36m@@ -232,6 +232,7 @@[m [mconst iPtrArray *findChildren_Widget (const iWidget *, const char *id);[m iAny * findParent_Widget (const iWidget *, const char *id);[m iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class);[m iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir);[m [32m+[m[32miAny * findAdjacentFocusable_Widget (const iWidget *, enum iDirection direction);[m iAny * findOverflowScrollable_Widget (iWidget *);[m size_t childCount_Widget (const iWidget *);[m void draw_Widget (const iWidget *);[m [1mdiff --git a/src/ui/window.c b/src/ui/window.c[m [1mindex 2c7b2ada..56c0d403 100644[m [1m--- a/src/ui/window.c[m [1m+++ b/src/ui/window.c[m [36m@@ -1309,32 +1309,6 @@[m [miBool processEvent_Window(iWindow *d, const SDL_Event *ev) {[m postCommand_App("media.player.update"); /* in case a player needs updating */[m return iFalse; /* unfreeze all frozen windows */[m }[m [31m-#if 0[m [31m- if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.sysframe") && mw) {[m [31m- /* This command is sent on Android to update the keyboard height. */[m [31m- const char *cmd = command_UserEvent(ev);[m [31m- /*[m [31m- 0[m [31m- |[m [31m- top[m [31m- | |[m [31m- | bottom (top of keyboard) :[m [31m- | | : keyboardHeight[m [31m- maxDrawableHeight :[m [31m- |[m [31m- fullheight[m [31m- */[m [31m- const int top = argLabel_Command(cmd, "top");[m [31m- const int bottom = argLabel_Command(cmd, "bottom");[m [31m- const int full = argLabel_Command(cmd, "fullheight");[m [31m- //if (!SDL_IsScreenKeyboardShown(mw->base.win)) {[m [31m- if (bottom == full) {[m [31m- mw->maxDrawableHeight = bottom - top;[m [31m- }[m [31m- setKeyboardHeight_MainWindow(mw, top + mw->maxDrawableHeight - bottom);[m [31m- return iTrue;[m [31m- }[m [31m-#endif[m if (processEvent_Touch(&event)) {[m return iTrue;[m }[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).