=> f809b80a688b8cd0a5a8bcb578d4cce1562fff5e
[1mdiff --git a/CMakeLists.txt b/CMakeLists.txt[m [1mindex 7d73956d..cfd42044 100644[m [1m--- a/CMakeLists.txt[m [1m+++ b/CMakeLists.txt[m [36m@@ -123,6 +123,8 @@[m [mset (SOURCES[m src/audio/player.h[m src/audio/stb_vorbis.c[m # User interface:[m [32m+[m[32m src/ui/bindingswidget.c[m [32m+[m[32m src/ui/bindingswidget.h[m src/ui/color.c[m src/ui/color.h[m src/ui/command.c[m [1mdiff --git a/src/app.c b/src/app.c[m [1mindex 10f5f0d2..311a88bb 100644[m [1m--- a/src/app.c[m [1m+++ b/src/app.c[m [36m@@ -430,6 +430,7 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m static void deinit_App(iApp *d) {[m saveState_App_(d);[m save_Keys(dataDir_App_);[m [32m+[m[32m deinit_Keys();[m savePrefs_App_(d);[m deinit_Prefs(&d->prefs);[m save_Bookmarks(d->bookmarks, dataDir_App_);[m [1mdiff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c[m [1mnew file mode 100644[m [1mindex 00000000..4ce6ea4d[m [1m--- /dev/null[m [1m+++ b/src/ui/bindingswidget.c[m [36m@@ -0,0 +1,193 @@[m [32m+[m[32m/* Copyright 2020 Jaakko Keränen[m [32m+[m [32m+[m[32mRedistribution and use in source and binary forms, with or without[m [32m+[m[32mmodification, are permitted provided that the following conditions are met:[m [32m+[m [32m+[m[32m1. Redistributions of source code must retain the above copyright notice, this[m [32m+[m[32m list of conditions and the following disclaimer.[m [32m+[m[32m2. Redistributions in binary form must reproduce the above copyright notice,[m [32m+[m[32m this list of conditions and the following disclaimer in the documentation[m [32m+[m[32m and/or other materials provided with the distribution.[m [32m+[m [32m+[m[32mTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND[m [32m+[m[32mANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED[m [32m+[m[32mWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE[m [32m+[m[32mDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR[m [32m+[m[32mANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES[m [32m+[m[32m(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;[m [32m+[m[32mLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON[m [32m+[m[32mANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT[m [32m+[m[32m(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS[m [32m+[m[32mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m [32m+[m [32m+[m[32m#include "bindingswidget.h"[m [32m+[m[32m#include "listwidget.h"[m [32m+[m[32m#include "keys.h"[m [32m+[m[32m#include "command.h"[m [32m+[m[32m#include "util.h"[m [32m+[m[32m#include "app.h"[m [32m+[m [32m+[m[32miDeclareType(BindingItem)[m [32m+[m[32mtypedef iListItemClass iBindingItemClass;[m [32m+[m [32m+[m[32mstruct Impl_BindingItem {[m [32m+[m[32m iListItem listItem;[m [32m+[m[32m iString label;[m [32m+[m[32m iString key;[m [32m+[m[32m int id;[m [32m+[m[32m iBool isWaitingForEvent;[m [32m+[m[32m};[m [32m+[m [32m+[m[32mvoid init_BindingItem(iBindingItem *d) {[m [32m+[m[32m init_ListItem(&d->listItem);[m [32m+[m[32m init_String(&d->label);[m [32m+[m[32m init_String(&d->key);[m [32m+[m[32m d->id = 0;[m [32m+[m[32m d->isWaitingForEvent = iFalse;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid deinit_BindingItem(iBindingItem *d) {[m [32m+[m[32m deinit_String(&d->key);[m [32m+[m[32m deinit_String(&d->label);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void setKey_BindingItem_(iBindingItem *d, int key, int mods) {[m [32m+[m[32m setKey_Binding(d->id, key, mods);[m [32m+[m[32m clear_String(&d->key);[m [32m+[m[32m toString_Sym(key, mods, &d->key);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,[m [32m+[m[32m const iListWidget *list);[m [32m+[m [32m+[m[32miBeginDefineSubclass(BindingItem, ListItem)[m [32m+[m[32m .draw = (iAny *) draw_BindingItem_,[m [32m+[m[32miEndDefineSubclass(BindingItem)[m [32m+[m [32m+[m[32miDefineObjectConstruction(BindingItem)[m [32m+[m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m [32m+[m[32mstruct Impl_BindingsWidget {[m [32m+[m[32m iWidget widget;[m [32m+[m[32m iListWidget *list;[m [32m+[m[32m size_t activePos;[m [32m+[m[32m};[m [32m+[m [32m+[m[32miDefineObjectConstruction(BindingsWidget)[m [32m+[m [32m+[m[32mstatic int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {[m [32m+[m[32m const iBindingItem *d = (const iBindingItem *) *item1, *other = (const iBindingItem *) *item2;[m [32m+[m[32m return iCmp(d->id, other->id);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void updateItems_BindingsWidget_(iBindingsWidget *d) {[m [32m+[m[32m clear_ListWidget(d->list);[m [32m+[m[32m iConstForEach(PtrArray, i, list_Keys()) {[m [32m+[m[32m const iBinding *bind = i.ptr;[m [32m+[m[32m if (isEmpty_String(&bind->label)) {[m [32m+[m[32m /* Only the ones with label are user-changeable. */[m [32m+[m[32m continue;[m [32m+[m[32m }[m [32m+[m[32m iBindingItem *item = new_BindingItem();[m [32m+[m[32m item->id = bind->id;[m [32m+[m[32m set_String(&item->label, &bind->label);[m [32m+[m[32m toString_Sym(bind->key, bind->mods, &item->key);[m [32m+[m[32m addItem_ListWidget(d->list, item);[m [32m+[m[32m }[m [32m+[m[32m sort_ListWidget(d->list, cmpId_BindingItem_);[m [32m+[m[32m updateVisible_ListWidget(d->list);[m [32m+[m[32m invalidate_ListWidget(d->list);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid init_BindingsWidget(iBindingsWidget *d) {[m [32m+[m[32m iWidget *w = as_Widget(d);[m [32m+[m[32m init_Widget(w);[m [32m+[m[32m setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue);[m [32m+[m[32m d->activePos = iInvalidPos;[m [32m+[m[32m d->list = new_ListWidget();[m [32m+[m[32m setItemHeight_ListWidget(d->list, lineHeight_Text(uiLabel_FontId) * 1.5f);[m [32m+[m[32m addChild_Widget(w, iClob(d->list));[m [32m+[m[32m updateItems_BindingsWidget_(d);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid deinit_BindingsWidget(iBindingsWidget *d) {[m [32m+[m[32m /* nothing to do */[m [32m+[m[32m iUnused(d);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {[m [32m+[m[32m if (d->activePos != iInvalidPos) {[m [32m+[m[32m iBindingItem *item = item_ListWidget(d->list, d->activePos);[m [32m+[m[32m item->isWaitingForEvent = iFalse;[m [32m+[m[32m invalidateItem_ListWidget(d->list, d->activePos);[m [32m+[m[32m }[m [32m+[m[32m d->activePos = pos;[m [32m+[m[32m if (d->activePos != iInvalidPos) {[m [32m+[m[32m iBindingItem *item = item_ListWidget(d->list, d->activePos);[m [32m+[m[32m item->isWaitingForEvent = iTrue;[m [32m+[m[32m invalidateItem_ListWidget(d->list, d->activePos);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {[m [32m+[m[32m iWidget * w = as_Widget(d);[m [32m+[m[32m const char *cmd = command_UserEvent(ev);[m [32m+[m[32m if (isCommand_Widget(w, ev, "list.clicked")) {[m [32m+[m[32m setActiveItem_BindingsWidget_(d, arg_Command(cmd));[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m /* Waiting for a keypress? */[m [32m+[m[32m if (d->activePos != iInvalidPos) {[m [32m+[m[32m if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {[m [32m+[m[32m setKey_BindingItem_(item_ListWidget(d->list, d->activePos),[m [32m+[m[32m ev->key.keysym.sym,[m [32m+[m[32m keyMods_Sym(ev->key.keysym.mod));[m [32m+[m[32m setActiveItem_BindingsWidget_(d, iInvalidPos);[m [32m+[m[32m postCommand_App("bindings.changed");[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m return processEvent_Widget(w, ev);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void draw_BindingsWidget_(const iBindingsWidget *d) {[m [32m+[m[32m const iWidget *w = constAs_Widget(d);[m [32m+[m[32m drawChildren_Widget(w);[m [32m+[m[32m drawBackground_Widget(w); /* kludge to allow drawing a top border over the list */[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,[m [32m+[m[32m const iListWidget *list) {[m [32m+[m[32m const int font = uiLabel_FontId;[m [32m+[m[32m const int itemHeight = height_Rect(itemRect);[m [32m+[m[32m const int line = lineHeight_Text(font);[m [32m+[m[32m int fg = uiText_ColorId;[m [32m+[m[32m const iBool isPressing = isMouseDown_ListWidget(list) || d->isWaitingForEvent;[m [32m+[m[32m const iBool isHover = (isHover_Widget(constAs_Widget(list)) &&[m [32m+[m[32m constHoverItem_ListWidget(list) == d);[m [32m+[m[32m if (isHover || isPressing) {[m [32m+[m[32m fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;[m [32m+[m[32m fillRect_Paint(p,[m [32m+[m[32m itemRect,[m [32m+[m[32m isPressing ? uiBackgroundPressed_ColorId[m [32m+[m[32m : uiBackgroundFramelessHover_ColorId);[m [32m+[m[32m }[m [32m+[m[32m const int y = top_Rect(itemRect) + (itemHeight - line) / 2;[m [32m+[m[32m drawRange_Text(font,[m [32m+[m[32m init_I2(left_Rect(itemRect) + 3 * gap_UI, y),[m [32m+[m[32m fg,[m [32m+[m[32m range_String(&d->label));[m [32m+[m[32m drawAlign_Text(d->isWaitingForEvent ? uiContent_FontId : font,[m [32m+[m[32m init_I2(right_Rect(itemRect) - 3 * gap_UI,[m [32m+[m[32m y - (lineHeight_Text(uiContent_FontId) - line) / 2),[m [32m+[m[32m fg,[m [32m+[m[32m right_Alignment,[m [32m+[m[32m "%s",[m [32m+[m[32m d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));[m [32m+[m[32m}[m [32m+[m [32m+[m[32miBeginDefineSubclass(BindingsWidget, Widget)[m [32m+[m[32m .processEvent = (iAny *) processEvent_BindingsWidget_,[m [32m+[m[32m .draw = (iAny *) draw_BindingsWidget_,[m [32m+[m[32miEndDefineSubclass(BindingsWidget)[m [1mdiff --git a/src/ui/bindingswidget.h b/src/ui/bindingswidget.h[m [1mnew file mode 100644[m [1mindex 00000000..e1ed402c[m [1m--- /dev/null[m [1m+++ b/src/ui/bindingswidget.h[m [36m@@ -0,0 +1,28 @@[m [32m+[m[32m/* Copyright 2020 Jaakko Keränen [m [32m+[m [32m+[m[32mRedistribution and use in source and binary forms, with or without[m [32m+[m[32mmodification, are permitted provided that the following conditions are met:[m [32m+[m [32m+[m[32m1. Redistributions of source code must retain the above copyright notice, this[m [32m+[m[32m list of conditions and the following disclaimer.[m [32m+[m[32m2. Redistributions in binary form must reproduce the above copyright notice,[m [32m+[m[32m this list of conditions and the following disclaimer in the documentation[m [32m+[m[32m and/or other materials provided with the distribution.[m [32m+[m [32m+[m[32mTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND[m [32m+[m[32mANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED[m [32m+[m[32mWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE[m [32m+[m[32mDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR[m [32m+[m[32mANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES[m [32m+[m[32m(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;[m [32m+[m[32mLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON[m [32m+[m[32mANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT[m [32m+[m[32m(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS[m [32m+[m[32mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m [32m+[m [32m+[m[32m#pragma once[m [32m+[m [32m+[m[32m#include "widget.h"[m [32m+[m [32m+[m[32miDeclareWidgetClass(BindingsWidget)[m [32m+[m[32miDeclareObjectConstruction(BindingsWidget)[m [1mdiff --git a/src/ui/keys.c b/src/ui/keys.c[m [1mindex 85304ef7..cc7dabef 100644[m [1m--- a/src/ui/keys.c[m [1m+++ b/src/ui/keys.c[m [36m@@ -24,12 +24,12 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "util.h"[m #include "app.h"[m [m [31m-#include [m [32m+[m[32m#include [m [m iDeclareType(Keys)[m [m [31m-static int cmp_Binding_(const void *a, const void *b) {[m [31m- const iBinding *d = a, *other = b;[m [32m+[m[32mstatic int cmpPtr_Binding_(const void *a, const void *b) {[m [32m+[m[32m const iBinding *d = *(const void **) a, *other = *(const void **) b;[m const int cmp = iCmp(d->key, other->key);[m if (cmp == 0) {[m return iCmp(d->mods, other->mods);[m [36m@@ -37,14 +37,17 @@[m [mstatic int cmp_Binding_(const void *a, const void *b) {[m return cmp;[m }[m [m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m struct Impl_Keys {[m [31m- iSortedArray bindings;[m [32m+[m[32m iArray bindings;[m [32m+[m[32m iPtrSet lookup; /* quick key/mods lookup */[m };[m [m static iKeys keys_;[m [m static void clear_Keys_(iKeys *d) {[m [31m- iForEach(Array, i, &d->bindings.values) {[m [32m+[m[32m iForEach(Array, i, &d->bindings) {[m iBinding *bind = i.value;[m deinit_String(&bind->command);[m deinit_String(&bind->label);[m [36m@@ -52,27 +55,41 @@[m [mstatic void clear_Keys_(iKeys *d) {[m }[m [m static void bindDefaults_(void) {[m [31m- bind_Keys("scroll.top", SDLK_HOME, 0);[m [31m- bind_Keys("scroll.bottom", SDLK_END, 0);[m [31m- bind_Keys("scroll.step arg:-1", SDLK_UP, 0);[m [31m- bind_Keys("scroll.step arg:1", SDLK_DOWN, 0);[m [31m- bind_Keys("scroll.page arg:-1", SDLK_PAGEUP, 0);[m [31m- bind_Keys("scroll.page arg:1", SDLK_PAGEDOWN, 0);[m [31m- bind_Keys("scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);[m [31m- bind_Keys("scroll.page arg:1", SDLK_SPACE, 0);[m [32m+[m[32m /* TODO: This indirection could be used for localization, although all UI strings[m [32m+[m[32m would need to be similarly handled. */[m [32m+[m[32m bindLabel_Keys(1, "scroll.top", SDLK_HOME, 0, "Jump to top");[m [32m+[m[32m bindLabel_Keys(2, "scroll.bottom", SDLK_END, 0, "Jump to bottom");[m [32m+[m[32m bindLabel_Keys(10, "scroll.step arg:-1", SDLK_UP, 0, "Scroll up");[m [32m+[m[32m bindLabel_Keys(11, "scroll.step arg:1", SDLK_DOWN, 0, "Scroll down");[m [32m+[m[32m bindLabel_Keys(20, "scroll.page arg:-1", SDLK_PAGEUP, 0, "Scroll up half a page");[m [32m+[m[32m bindLabel_Keys(21, "scroll.page arg:1", SDLK_PAGEDOWN, 0, "Scroll down half a page");[m [32m+[m[32m /* The following cannot currently be changed (built-in duplicates). */[m [32m+[m[32m bind_Keys(1000, "scroll.page arg:-1", SDLK_SPACE, KMOD_SHIFT);[m [32m+[m[32m bind_Keys(1001, "scroll.page arg:1", SDLK_SPACE, 0);[m }[m [m static iBinding *find_Keys_(iKeys *d, int key, int mods) {[m size_t pos;[m [31m- if (locate_SortedArray(&d->bindings, &(iBinding){ .key = key, .mods = mods }, &pos)) {[m [31m- return at_SortedArray(&d->bindings, pos);[m [32m+[m[32m const iBinding elem = { .key = key, .mods = mods };[m [32m+[m[32m if (locate_PtrSet(&d->lookup, &elem, &pos)) {[m [32m+[m[32m return at_PtrSet(&d->lookup, pos);[m [32m+[m[32m }[m [32m+[m[32m return NULL;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic iBinding *findId_Keys_(iKeys *d, int id) {[m [32m+[m[32m iForEach(Array, i, &d->bindings) {[m [32m+[m[32m iBinding *bind = i.value;[m [32m+[m[32m if (bind->id == id) {[m [32m+[m[32m return bind;[m [32m+[m[32m }[m }[m return NULL;[m }[m [m static iBinding *findCommand_Keys_(iKeys *d, const char *command) {[m /* Note: O(n) */[m [31m- iForEach(Array, i, &d->bindings.values) {[m [32m+[m[32m iForEach(Array, i, &d->bindings) {[m iBinding *bind = i.value;[m if (!cmp_String(&bind->command, command)) {[m return bind;[m [36m@@ -81,18 +98,37 @@[m [mstatic iBinding *findCommand_Keys_(iKeys *d, const char *command) {[m return NULL;[m }[m [m [32m+[m[32mstatic void updateLookup_Keys_(iKeys *d) {[m [32m+[m[32m clear_PtrSet(&d->lookup);[m [32m+[m[32m iConstForEach(Array, i, &d->bindings) {[m [32m+[m[32m insert_PtrSet(&d->lookup, i.value);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32mvoid setKey_Binding(int id, int key, int mods) {[m [32m+[m[32m iBinding *bind = findId_Keys_(&keys_, id);[m [32m+[m[32m if (bind) {[m [32m+[m[32m bind->key = key;[m [32m+[m[32m bind->mods = mods;[m [32m+[m[32m updateLookup_Keys_(&keys_);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m /*----------------------------------------------------------------------------------------------*/[m [m void init_Keys(void) {[m iKeys *d = &keys_;[m [31m- init_SortedArray(&d->bindings, sizeof(iBinding), cmp_Binding_);[m [32m+[m[32m init_Array(&d->bindings, sizeof(iBinding));[m [32m+[m[32m initCmp_PtrSet(&d->lookup, cmpPtr_Binding_);[m bindDefaults_();[m [32m+[m[32m updateLookup_Keys_(d);[m }[m [m void deinit_Keys(void) {[m iKeys *d = &keys_;[m clear_Keys_(d);[m [31m- deinit_SortedArray(&d->bindings);[m [32m+[m[32m deinit_PtrSet(&d->lookup);[m [32m+[m[32m deinit_Array(&d->bindings);[m }[m [m void load_Keys(const char *saveDir) {[m [36m@@ -103,34 +139,42 @@[m [mvoid save_Keys(const char *saveDir) {[m [m }[m [m [31m-void bind_Keys(const char *command, int key, int mods) {[m [32m+[m[32mvoid bind_Keys(int id, const char *command, int key, int mods) {[m iKeys *d = &keys_;[m [31m- iBinding *bind = find_Keys_(d, key, mods);[m [31m- if (bind) {[m [31m- setCStr_String(&bind->command, command);[m [32m+[m[32m iBinding *bind = findId_Keys_(d, id);[m [32m+[m[32m if (!bind) {[m [32m+[m[32m iBinding elem = { .id = id, .key = key, .mods = mods };[m [32m+[m[32m initCStr_String(&elem.command, command);[m [32m+[m[32m init_String(&elem.label);[m [32m+[m[32m pushBack_Array(&d->bindings, &elem);[m }[m else {[m [31m- iBinding bind;[m [31m- bind.key = key;[m [31m- bind.mods = mods;[m [31m- initCStr_String(&bind.command, command);[m [31m- init_String(&bind.label);[m [31m- insert_SortedArray(&d->bindings, &bind);[m [32m+[m[32m setCStr_String(&bind->command, command);[m [32m+[m[32m bind->key = key;[m [32m+[m[32m bind->mods = mods;[m }[m }[m [m [31m-void setLabel_Keys(const char *command, const char *label) {[m [31m- iBinding *bind = findCommand_Keys_(&keys_, command);[m [32m+[m[32mvoid setLabel_Keys(int id, const char *label) {[m [32m+[m[32m iBinding *bind = findId_Keys_(&keys_, id);[m if (bind) {[m setCStr_String(&bind->label, label);[m }[m }[m [m [31m-//const iString *label_Keys(const char *command) {[m [31m-[m [31m-//}[m [31m-[m [31m-//const char *shortcutLabel_Keys(const char *command) {}[m [32m+[m[32m#if 0[m [32m+[m[32mconst iString *label_Keys(const char *command) {[m [32m+[m[32m iKeys *d = &keys_;[m [32m+[m[32m /* TODO: A hash wouldn't hurt here. */[m [32m+[m[32m iConstForEach(PtrSet, i, &d->bindings) {[m [32m+[m[32m const iBinding *bind = *i.value;[m [32m+[m[32m if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {[m [32m+[m[32m return &bind->label;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m return collectNew_String();[m [32m+[m[32m}[m [32m+[m[32m#endif[m [m iBool processEvent_Keys(const SDL_Event *ev) {[m iKeys *d = &keys_;[m [36m@@ -147,3 +191,12 @@[m [miBool processEvent_Keys(const SDL_Event *ev) {[m const iBinding *findCommand_Keys(const char *command) {[m return findCommand_Keys_(&keys_, command);[m }[m [32m+[m [32m+[m[32mconst iPtrArray *list_Keys(void) {[m [32m+[m[32m iKeys *d = &keys_;[m [32m+[m[32m iPtrArray *list = collectNew_PtrArray();[m [32m+[m[32m iConstForEach(Array, i, &d->bindings) {[m [32m+[m[32m pushBack_PtrArray(list, i.value);[m [32m+[m[32m }[m [32m+[m[32m return list;[m [32m+[m[32m}[m [1mdiff --git a/src/ui/keys.h b/src/ui/keys.h[m [1mindex 0892bd81..a4c8f348 100644[m [1m--- a/src/ui/keys.h[m [1m+++ b/src/ui/keys.h[m [36m@@ -23,6 +23,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #pragma once[m [m #include [m [32m+[m[32m#include [m #include [m [m #if defined (iPlatformApple)[m [36m@@ -46,23 +47,32 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m iDeclareType(Binding)[m [m struct Impl_Binding {[m [32m+[m[32m int id;[m int key;[m int mods;[m iString command;[m iString label;[m };[m [m [32m+[m[32mvoid setKey_Binding (int id, int key, int mods);[m [32m+[m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m void init_Keys (void);[m void deinit_Keys (void);[m [m void load_Keys (const char *saveDir);[m void save_Keys (const char *saveDir);[m [m [31m-void bind_Keys (const char *command, int key, int mods);[m [31m-void setLabel_Keys (const char *command, const char *label);[m [31m-const iBinding *findCommand_Keys (const char *command);[m [32m+[m[32mvoid bind_Keys (int id, const char *command, int key, int mods);[m [32m+[m[32mvoid setLabel_Keys (int id, const char *label);[m [m [31m-//const iString * label_Keys (const char *command);[m [31m-//const char * shortcutLabel_Keys (const char *command);[m [32m+[m[32miLocalDef void bindLabel_Keys(int id, const char *command, int key, int mods, const char *label) {[m [32m+[m[32m bind_Keys(id, command, key, mods);[m [32m+[m[32m setLabel_Keys(id, label);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mconst iBinding *findCommand_Keys (const char *command);[m [m iBool processEvent_Keys (const SDL_Event *);[m [32m+[m[32mconst iPtrArray *list_Keys (void);[m [1mdiff --git a/src/ui/listwidget.c b/src/ui/listwidget.c[m [1mindex fb328c2f..02e1c728 100644[m [1m--- a/src/ui/listwidget.c[m [1m+++ b/src/ui/listwidget.c[m [36m@@ -120,6 +120,9 @@[m [mvoid updateVisible_ListWidget(iListWidget *d) {[m const int contentSize = size_PtrArray(&d->items) * d->itemHeight;[m const iRect bounds = innerBounds_Widget(as_Widget(d));[m const iBool wasVisible = isVisible_Widget(d->scroll);[m [32m+[m[32m if (area_Rect(bounds) == 0) {[m [32m+[m[32m return;[m [32m+[m[32m }[m setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });[m setThumb_ScrollWidget(d->scroll,[m d->scrollY,[m [36m@@ -245,11 +248,21 @@[m [mvoid updateMouseHover_ListWidget(iListWidget *d) {[m setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));[m }[m [m [32m+[m[32mvoid sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {[m [32m+[m[32m sort_Array(&d->items, (iSortedArrayCompareElemFunc) cmp);[m [32m+[m[32m}[m [32m+[m static void redrawHoverItem_ListWidget_(iListWidget *d) {[m insert_IntSet(&d->invalidItems, d->hoverItem);[m refresh_Widget(as_Widget(d));[m }[m [m [32m+[m[32mstatic void sizeChanged_ListWidget_(iListWidget *d) {[m [32m+[m[32m printf("ListWidget %p size changed: %d x %d\n", d, d->widget.rect.size.x, d->widget.rect.size.y); fflush(stdout);[m [32m+[m[32m updateVisible_ListWidget(d);[m [32m+[m[32m invalidate_ListWidget(d);[m [32m+[m[32m}[m [32m+[m static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {[m iWidget *w = as_Widget(d);[m if (isCommand_SDLEvent(ev)) {[m [36m@@ -391,4 +404,5 @@[m [miBool isMouseDown_ListWidget(const iListWidget *d) {[m iBeginDefineSubclass(ListWidget, Widget)[m .processEvent = (iAny *) processEvent_ListWidget_,[m .draw = (iAny *) draw_ListWidget_,[m [32m+[m[32m .sizeChanged = (iAny *) sizeChanged_ListWidget_,[m iEndDefineSubclass(ListWidget)[m [1mdiff --git a/src/ui/listwidget.h b/src/ui/listwidget.h[m [1mindex da6303e9..11f1672e 100644[m [1m--- a/src/ui/listwidget.h[m [1m+++ b/src/ui/listwidget.h[m [36m@@ -64,6 +64,8 @@[m [mvoid scrollOffset_ListWidget (iListWidget *, int offset);[m void updateVisible_ListWidget (iListWidget *);[m void updateMouseHover_ListWidget (iListWidget *);[m [m [32m+[m[32mvoid sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2));[m [32m+[m iAnyObject * item_ListWidget (iListWidget *, size_t index);[m iAnyObject * hoverItem_ListWidget (iListWidget *);[m [m [1mdiff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c[m [1mindex d6292f5b..c8571589 100644[m [1m--- a/src/ui/sidebarwidget.c[m [1m+++ b/src/ui/sidebarwidget.c[m [36m@@ -485,8 +485,6 @@[m [mstatic iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)[m /* Handle commands. */[m if (isResize_UserEvent(ev)) {[m checkModeButtonLayout_SidebarWidget_(d);[m [31m- updateVisible_ListWidget(d->list);[m [31m- invalidate_ListWidget(d->list);[m }[m else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {[m const char *cmd = command_UserEvent(ev);[m [1mdiff --git a/src/ui/util.c b/src/ui/util.c[m [1mindex 44f7e089..ceab01b8 100644[m [1m--- a/src/ui/util.c[m [1m+++ b/src/ui/util.c[m [36m@@ -29,6 +29,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "gmutil.h"[m #include "labelwidget.h"[m #include "inputwidget.h"[m [32m+[m[32m#include "bindingswidget.h"[m #include "keys.h"[m #include "widget.h"[m #include "text.h"[m [36m@@ -105,6 +106,11 @@[m [mvoid toString_Sym(int key, int kmods, iString *str) {[m }[m }[m [m [32m+[m[32miBool isMod_Sym(int key) {[m [32m+[m[32m return key == SDLK_LALT || key == SDLK_RALT || key == SDLK_LCTRL || key == SDLK_RCTRL ||[m [32m+[m[32m key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;[m [32m+[m[32m}[m [32m+[m int keyMods_Sym(int kmods) {[m kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);[m /* Don't treat left/right modifiers differently. */[m [36m@@ -920,12 +926,22 @@[m [miWidget *makeToggle_Widget(const char *id) {[m return toggle;[m }[m [m [32m+[m[32mstatic void appendFramelessTabPage_(iWidget *tabs, iWidget *page, const char *title, int shortcut,[m [32m+[m[32m int kmods) {[m [32m+[m[32m appendTabPage_Widget(tabs, page, title, shortcut, kmods);[m [32m+[m[32m setFlags_Widget([m [32m+[m[32m (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),[m [32m+[m[32m frameless_WidgetFlag,[m [32m+[m[32m iTrue);[m [32m+[m[32m}[m [32m+[m static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings,[m iWidget **values) {[m iWidget *page = new_Widget();[m setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |[m resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue);[m addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);[m [32m+[m[32m setPadding_Widget(page, 0, gap_UI, 0, gap_UI);[m iWidget *columns = new_Widget();[m addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);[m *headings = addChildFlags_Widget([m [36m@@ -933,11 +949,7 @@[m [mstatic iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int short[m *values = addChildFlags_Widget([m columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);[m addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);[m [31m- appendTabPage_Widget(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);[m [31m- setFlags_Widget([m [31m- (iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),[m [31m- frameless_WidgetFlag,[m [31m- iTrue);[m [32m+[m[32m appendFramelessTabPage_(tabs, page, title, shortcut, shortcut ? KMOD_PRIMARY : 0);[m return page;[m }[m [m [36m@@ -1080,6 +1092,11 @@[m [miWidget *makePreferences_Widget(void) {[m addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));[m setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");[m }[m [32m+[m[32m /* Keybindings. */ {[m [32m+[m[32m iBindingsWidget *bind = new_BindingsWidget();[m [32m+[m[32m setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);[m [32m+[m[32m appendFramelessTabPage_(tabs, iClob(bind), "Bindings", '5', KMOD_PRIMARY);[m [32m+[m[32m }[m resizeToLargestPage_Widget(tabs);[m arrange_Widget(dlg);[m /* Set input field sizes. */ {[m [1mdiff --git a/src/ui/util.h b/src/ui/util.h[m [1mindex 9796b387..c0e3a04c 100644[m [1m--- a/src/ui/util.h[m [1m+++ b/src/ui/util.h[m [36m@@ -48,6 +48,7 @@[m [miLocalDef iBool isResize_UserEvent(const SDL_Event *d) {[m # define KMOD_SECONDARY KMOD_GUI[m #endif[m [m [32m+[m[32miBool isMod_Sym (int key);[m int keyMods_Sym (int kmods); /* shift, alt, control, or gui */[m void toString_Sym (int key, int kmods, iString *str);[m [m
text/gemini; charset=utf-8
This content has been proxied by September (ba2dc).