From f809b80a688b8cd0a5a8bcb578d4cce1562fff5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= jaakko.keranen@iki.fi
Date: Sat, 31 Oct 2020 14:18:09 +0200
Subject: [PATCH 1/1] Preferences: Basic key bindings UI
One can now bind keys in Preferences. The configured keys aren't yet saved, though.
CMakeLists.txt | 2 +
src/app.c | 1 +
src/ui/bindingswidget.c | 193 ++++++++++++++++++++++++++++++++++++++++
src/ui/bindingswidget.h | 28 ++++++
src/ui/keys.c | 123 +++++++++++++++++--------
src/ui/keys.h | 20 +++--
src/ui/listwidget.c | 14 +++
src/ui/listwidget.h | 2 +
src/ui/sidebarwidget.c | 2 -
src/ui/util.c | 27 ++++--
src/ui/util.h | 1 +
11 files changed, 366 insertions(+), 47 deletions(-)
create mode 100644 src/ui/bindingswidget.c
create mode 100644 src/ui/bindingswidget.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d73956d..cfd42044 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -123,6 +123,8 @@ set (SOURCES
src/audio/player.h
src/audio/stb_vorbis.c
# User interface:
src/ui/color.c
src/ui/color.h
src/ui/command.c
diff --git a/src/app.c b/src/app.c
index 10f5f0d2..311a88bb 100644
--- a/src/app.c
+++ b/src/app.c
@@ -430,6 +430,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
static void deinit_App(iApp *d) {
saveState_App_(d);
save_Keys(dataDir_App_);
savePrefs_App_(d);
deinit_Prefs(&d->prefs);
save_Bookmarks(d->bookmarks, dataDir_App_);
diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c
new file mode 100644
index 00000000..4ce6ea4d
--- /dev/null
+++ b/src/ui/bindingswidget.c
@@ -0,0 +1,193 @@
+/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#include "bindingswidget.h"
+#include "listwidget.h"
+#include "keys.h"
+#include "command.h"
+#include "util.h"
+#include "app.h"
+iDeclareType(BindingItem)
+typedef iListItemClass iBindingItemClass;
+struct Impl_BindingItem {
+};
+void init_BindingItem(iBindingItem *d) {
+}
+void deinit_BindingItem(iBindingItem *d) {
+}
+static void setKey_BindingItem_(iBindingItem *d, int key, int mods) {
+}
+static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
const iListWidget *list);
+iBeginDefineSubclass(BindingItem, ListItem)
+iEndDefineSubclass(BindingItem)
+iDefineObjectConstruction(BindingItem)
+/----------------------------------------------------------------------------------------------/
+struct Impl_BindingsWidget {
+};
+iDefineObjectConstruction(BindingsWidget)
+static int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {
+}
+static void updateItems_BindingsWidget_(iBindingsWidget *d) {
const iBinding *bind = i.ptr;
if (isEmpty_String(&bind->label)) {
/* Only the ones with label are user-changeable. */
continue;
}
iBindingItem *item = new_BindingItem();
item->id = bind->id;
set_String(&item->label, &bind->label);
toString_Sym(bind->key, bind->mods, &item->key);
addItem_ListWidget(d->list, item);
+}
+void init_BindingsWidget(iBindingsWidget *d) {
+}
+void deinit_BindingsWidget(iBindingsWidget *d) {
+}
+static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {
iBindingItem *item = item_ListWidget(d->list, d->activePos);
item->isWaitingForEvent = iFalse;
invalidateItem_ListWidget(d->list, d->activePos);
iBindingItem *item = item_ListWidget(d->list, d->activePos);
item->isWaitingForEvent = iTrue;
invalidateItem_ListWidget(d->list, d->activePos);
+}
+static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {
setActiveItem_BindingsWidget_(d, arg_Command(cmd));
return iTrue;
if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {
setKey_BindingItem_(item_ListWidget(d->list, d->activePos),
ev->key.keysym.sym,
keyMods_Sym(ev->key.keysym.mod));
setActiveItem_BindingsWidget_(d, iInvalidPos);
postCommand_App("bindings.changed");
return iTrue;
}
+}
+static void draw_BindingsWidget_(const iBindingsWidget *d) {
+}
+static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
const iListWidget *list) {
constHoverItem_ListWidget(list) == d);
fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;
fillRect_Paint(p,
itemRect,
isPressing ? uiBackgroundPressed_ColorId
: uiBackgroundFramelessHover_ColorId);
init_I2(left_Rect(itemRect) + 3 * gap_UI, y),
fg,
range_String(&d->label));
init_I2(right_Rect(itemRect) - 3 * gap_UI,
y - (lineHeight_Text(uiContent_FontId) - line) / 2),
fg,
right_Alignment,
"%s",
d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));
+}
+iBeginDefineSubclass(BindingsWidget, Widget)
+iEndDefineSubclass(BindingsWidget)
diff --git a/src/ui/bindingswidget.h b/src/ui/bindingswidget.h
new file mode 100644
index 00000000..e1ed402c
--- /dev/null
+++ b/src/ui/bindingswidget.h
@@ -0,0 +1,28 @@
+/* Copyright 2020 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#pragma once
+#include "widget.h"
+iDeclareWidgetClass(BindingsWidget)
+iDeclareObjectConstruction(BindingsWidget)
diff --git a/src/ui/keys.c b/src/ui/keys.c
index 85304ef7..cc7dabef 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -24,12 +24,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "util.h"
#include "app.h"
-#include <the_Foundation/sortedarray.h>
+#include <the_Foundation/ptrset.h>
iDeclareType(Keys)
-static int cmp_Binding_(const void *a, const void *b) {
+static int cmpPtr_Binding_(const void *a, const void *b) {
const int cmp = iCmp(d->key, other->key);
if (cmp == 0) {
return iCmp(d->mods, other->mods);
@@ -37,14 +37,17 @@ static int cmp_Binding_(const void *a, const void *b) {
return cmp;
}
+/----------------------------------------------------------------------------------------------/
struct Impl_Keys {
};
static iKeys keys_;
static void clear_Keys_(iKeys *d) {
iBinding *bind = i.value;
deinit_String(&bind->command);
deinit_String(&bind->label);
@@ -52,27 +55,41 @@ static void clear_Keys_(iKeys *d) {
}
static void bindDefaults_(void) {
would need to be similarly handled. */
}
static iBinding *find_Keys_(iKeys *d, int key, int mods) {
size_t pos;
return at_SortedArray(&d->bindings, pos);
return at_PtrSet(&d->lookup, pos);
+}
+static iBinding *findId_Keys_(iKeys *d, int id) {
iBinding *bind = i.value;
if (bind->id == id) {
return bind;
}
}
return NULL;
}
static iBinding *findCommand_Keys_(iKeys *d, const char *command) {
/* Note: O(n) */
iBinding *bind = i.value;
if (!cmp_String(&bind->command, command)) {
return bind;
@@ -81,18 +98,37 @@ static iBinding *findCommand_Keys_(iKeys *d, const char *command) {
return NULL;
}
+static void updateLookup_Keys_(iKeys *d) {
insert_PtrSet(&d->lookup, i.value);
+}
+void setKey_Binding(int id, int key, int mods) {
bind->key = key;
bind->mods = mods;
updateLookup_Keys_(&keys_);
+}
/----------------------------------------------------------------------------------------------/
void init_Keys(void) {
iKeys *d = &keys_;
bindDefaults_();
}
void deinit_Keys(void) {
iKeys *d = &keys_;
clear_Keys_(d);
}
void load_Keys(const char *saveDir) {
@@ -103,34 +139,42 @@ void save_Keys(const char *saveDir) {
}
-void bind_Keys(const char *command, int key, int mods) {
+void bind_Keys(int id, const char *command, int key, int mods) {
iKeys *d = &keys_;
setCStr_String(&bind->command, command);
iBinding elem = { .id = id, .key = key, .mods = mods };
initCStr_String(&elem.command, command);
init_String(&elem.label);
pushBack_Array(&d->bindings, &elem);
}
else {
iBinding bind;
bind.key = key;
bind.mods = mods;
initCStr_String(&bind.command, command);
init_String(&bind.label);
insert_SortedArray(&d->bindings, &bind);
setCStr_String(&bind->command, command);
bind->key = key;
bind->mods = mods;
}
}
-void setLabel_Keys(const char *command, const char *label) {
+void setLabel_Keys(int id, const char *label) {
if (bind) {
setCStr_String(&bind->label, label);
}
}
-//const iString *label_Keys(const char *command) {
-//}
-//const char *shortcutLabel_Keys(const char *command) {}
+#if 0
+const iString *label_Keys(const char *command) {
const iBinding *bind = *i.value;
if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {
return &bind->label;
}
+}
+#endif
iBool processEvent_Keys(const SDL_Event *ev) {
iKeys *d = &keys_;
@@ -147,3 +191,12 @@ iBool processEvent_Keys(const SDL_Event *ev) {
const iBinding *findCommand_Keys(const char *command) {
return findCommand_Keys_(&keys_, command);
}
+const iPtrArray *list_Keys(void) {
pushBack_PtrArray(list, i.value);
+}
diff --git a/src/ui/keys.h b/src/ui/keys.h
index 0892bd81..a4c8f348 100644
--- a/src/ui/keys.h
+++ b/src/ui/keys.h
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
#include <the_Foundation/string.h>
+#include <the_Foundation/ptrarray.h>
#include <SDL_events.h>
#if defined (iPlatformApple)
@@ -46,23 +47,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
iDeclareType(Binding)
struct Impl_Binding {
int key;
int mods;
iString command;
iString label;
};
+void setKey_Binding (int id, int key, int mods);
+/----------------------------------------------------------------------------------------------/
void init_Keys (void);
void deinit_Keys (void);
void load_Keys (const char *saveDir);
void save_Keys (const char *saveDir);
-void bind_Keys (const char *command, int key, int mods);
-void setLabel_Keys (const char *command, const char *label);
-const iBinding *findCommand_Keys (const char *command);
+void bind_Keys (int id, const char *command, int key, int mods);
+void setLabel_Keys (int id, const char *label);
-//const iString * label_Keys (const char *command);
-//const char * shortcutLabel_Keys (const char *command);
+iLocalDef void bindLabel_Keys(int id, const char *command, int key, int mods, const char *label) {
+}
+const iBinding *findCommand_Keys (const char *command);
iBool processEvent_Keys (const SDL_Event *);
+const iPtrArray *list_Keys (void);
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index fb328c2f..02e1c728 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -120,6 +120,9 @@ void updateVisible_ListWidget(iListWidget *d) {
const int contentSize = size_PtrArray(&d->items) * d->itemHeight;
const iRect bounds = innerBounds_Widget(as_Widget(d));
const iBool wasVisible = isVisible_Widget(d->scroll);
return;
setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) });
setThumb_ScrollWidget(d->scroll,
d->scrollY,
@@ -245,11 +248,21 @@ void updateMouseHover_ListWidget(iListWidget *d) {
setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse));
}
+void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {
+}
static void redrawHoverItem_ListWidget_(iListWidget *d) {
insert_IntSet(&d->invalidItems, d->hoverItem);
refresh_Widget(as_Widget(d));
}
+static void sizeChanged_ListWidget_(iListWidget *d) {
+}
static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
iWidget *w = as_Widget(d);
if (isCommand_SDLEvent(ev)) {
@@ -391,4 +404,5 @@ iBool isMouseDown_ListWidget(const iListWidget *d) {
iBeginDefineSubclass(ListWidget, Widget)
.processEvent = (iAny *) processEvent_ListWidget_,
.draw = (iAny *) draw_ListWidget_,
iEndDefineSubclass(ListWidget)
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h
index da6303e9..11f1672e 100644
--- a/src/ui/listwidget.h
+++ b/src/ui/listwidget.h
@@ -64,6 +64,8 @@ void scrollOffset_ListWidget (iListWidget *, int offset);
void updateVisible_ListWidget (iListWidget *);
void updateMouseHover_ListWidget (iListWidget *);
+void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2));
iAnyObject * item_ListWidget (iListWidget *, size_t index);
iAnyObject * hoverItem_ListWidget (iListWidget *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index d6292f5b..c8571589 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -485,8 +485,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
/* Handle commands. */
if (isResize_UserEvent(ev)) {
checkModeButtonLayout_SidebarWidget_(d);
updateVisible_ListWidget(d->list);
invalidate_ListWidget(d->list);
}
else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
const char *cmd = command_UserEvent(ev);
diff --git a/src/ui/util.c b/src/ui/util.c
index 44f7e089..ceab01b8 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "gmutil.h"
#include "labelwidget.h"
#include "inputwidget.h"
+#include "bindingswidget.h"
#include "keys.h"
#include "widget.h"
#include "text.h"
@@ -105,6 +106,11 @@ void toString_Sym(int key, int kmods, iString *str) {
}
}
+iBool isMod_Sym(int key) {
key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;
+}
int keyMods_Sym(int kmods) {
kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);
/* Don't treat left/right modifiers differently. */
@@ -920,12 +926,22 @@ iWidget *makeToggle_Widget(const char *id) {
return toggle;
}
+static void appendFramelessTabPage_(iWidget *tabs, iWidget *page, const char *title, int shortcut,
int kmods) {
(iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),
frameless_WidgetFlag,
iTrue);
+}
static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int shortcut, iWidget **headings,
iWidget **values) {
iWidget *page = new_Widget();
setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
resizeHeightOfChildren_WidgetFlag | borderTop_WidgetFlag, iTrue);
addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
iWidget *columns = new_Widget();
addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
*headings = addChildFlags_Widget(
@@ -933,11 +949,7 @@ static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, int short
*values = addChildFlags_Widget(
columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
(iWidget *) back_ObjectList(children_Widget(findChild_Widget(tabs, "tabs.buttons"))),
frameless_WidgetFlag,
iTrue);
return page;
}
@@ -1080,6 +1092,11 @@ iWidget *makePreferences_Widget(void) {
addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");
}
iBindingsWidget *bind = new_BindingsWidget();
setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);
appendFramelessTabPage_(tabs, iClob(bind), "Bindings", '5', KMOD_PRIMARY);
resizeToLargestPage_Widget(tabs);
arrange_Widget(dlg);
/* Set input field sizes. */ {
diff --git a/src/ui/util.h b/src/ui/util.h
index 9796b387..c0e3a04c 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -48,6 +48,7 @@ iLocalDef iBool isResize_UserEvent(const SDL_Event *d) {
#endif
+iBool isMod_Sym (int key);
int keyMods_Sym (int kmods); /* shift, alt, control, or gui */
void toString_Sym (int key, int kmods, iString *str);
--
2.25.1
text/plain
This content has been proxied by September (ba2dc).