summaryrefslogtreecommitdiff
path: root/src/ui/bindingswidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-10-31 14:18:09 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-10-31 14:18:09 +0200
commitf809b80a688b8cd0a5a8bcb578d4cce1562fff5e (patch)
treebfecbefcf9f56f9fd4e44d89bb8977eb872b0bd5 /src/ui/bindingswidget.c
parent7d0d4a6821644a73fc52b54eba6774ba5a64cb54 (diff)
Preferences: Basic key bindings UI
One can now bind keys in Preferences. The configured keys aren't yet saved, though.
Diffstat (limited to 'src/ui/bindingswidget.c')
-rw-r--r--src/ui/bindingswidget.c193
1 files changed, 193 insertions, 0 deletions
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 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "bindingswidget.h"
24#include "listwidget.h"
25#include "keys.h"
26#include "command.h"
27#include "util.h"
28#include "app.h"
29
30iDeclareType(BindingItem)
31typedef iListItemClass iBindingItemClass;
32
33struct Impl_BindingItem {
34 iListItem listItem;
35 iString label;
36 iString key;
37 int id;
38 iBool isWaitingForEvent;
39};
40
41void init_BindingItem(iBindingItem *d) {
42 init_ListItem(&d->listItem);
43 init_String(&d->label);
44 init_String(&d->key);
45 d->id = 0;
46 d->isWaitingForEvent = iFalse;
47}
48
49void deinit_BindingItem(iBindingItem *d) {
50 deinit_String(&d->key);
51 deinit_String(&d->label);
52}
53
54static void setKey_BindingItem_(iBindingItem *d, int key, int mods) {
55 setKey_Binding(d->id, key, mods);
56 clear_String(&d->key);
57 toString_Sym(key, mods, &d->key);
58}
59
60static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
61 const iListWidget *list);
62
63iBeginDefineSubclass(BindingItem, ListItem)
64 .draw = (iAny *) draw_BindingItem_,
65iEndDefineSubclass(BindingItem)
66
67iDefineObjectConstruction(BindingItem)
68
69/*----------------------------------------------------------------------------------------------*/
70
71struct Impl_BindingsWidget {
72 iWidget widget;
73 iListWidget *list;
74 size_t activePos;
75};
76
77iDefineObjectConstruction(BindingsWidget)
78
79static int cmpId_BindingItem_(const iListItem **item1, const iListItem **item2) {
80 const iBindingItem *d = (const iBindingItem *) *item1, *other = (const iBindingItem *) *item2;
81 return iCmp(d->id, other->id);
82}
83
84static void updateItems_BindingsWidget_(iBindingsWidget *d) {
85 clear_ListWidget(d->list);
86 iConstForEach(PtrArray, i, list_Keys()) {
87 const iBinding *bind = i.ptr;
88 if (isEmpty_String(&bind->label)) {
89 /* Only the ones with label are user-changeable. */
90 continue;
91 }
92 iBindingItem *item = new_BindingItem();
93 item->id = bind->id;
94 set_String(&item->label, &bind->label);
95 toString_Sym(bind->key, bind->mods, &item->key);
96 addItem_ListWidget(d->list, item);
97 }
98 sort_ListWidget(d->list, cmpId_BindingItem_);
99 updateVisible_ListWidget(d->list);
100 invalidate_ListWidget(d->list);
101}
102
103void init_BindingsWidget(iBindingsWidget *d) {
104 iWidget *w = as_Widget(d);
105 init_Widget(w);
106 setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue);
107 d->activePos = iInvalidPos;
108 d->list = new_ListWidget();
109 setItemHeight_ListWidget(d->list, lineHeight_Text(uiLabel_FontId) * 1.5f);
110 addChild_Widget(w, iClob(d->list));
111 updateItems_BindingsWidget_(d);
112}
113
114void deinit_BindingsWidget(iBindingsWidget *d) {
115 /* nothing to do */
116 iUnused(d);
117}
118
119static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {
120 if (d->activePos != iInvalidPos) {
121 iBindingItem *item = item_ListWidget(d->list, d->activePos);
122 item->isWaitingForEvent = iFalse;
123 invalidateItem_ListWidget(d->list, d->activePos);
124 }
125 d->activePos = pos;
126 if (d->activePos != iInvalidPos) {
127 iBindingItem *item = item_ListWidget(d->list, d->activePos);
128 item->isWaitingForEvent = iTrue;
129 invalidateItem_ListWidget(d->list, d->activePos);
130 }
131}
132
133static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *ev) {
134 iWidget * w = as_Widget(d);
135 const char *cmd = command_UserEvent(ev);
136 if (isCommand_Widget(w, ev, "list.clicked")) {
137 setActiveItem_BindingsWidget_(d, arg_Command(cmd));
138 return iTrue;
139 }
140 /* Waiting for a keypress? */
141 if (d->activePos != iInvalidPos) {
142 if (ev->type == SDL_KEYDOWN && !isMod_Sym(ev->key.keysym.sym)) {
143 setKey_BindingItem_(item_ListWidget(d->list, d->activePos),
144 ev->key.keysym.sym,
145 keyMods_Sym(ev->key.keysym.mod));
146 setActiveItem_BindingsWidget_(d, iInvalidPos);
147 postCommand_App("bindings.changed");
148 return iTrue;
149 }
150 }
151 return processEvent_Widget(w, ev);
152}
153
154static void draw_BindingsWidget_(const iBindingsWidget *d) {
155 const iWidget *w = constAs_Widget(d);
156 drawChildren_Widget(w);
157 drawBackground_Widget(w); /* kludge to allow drawing a top border over the list */
158}
159
160static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect,
161 const iListWidget *list) {
162 const int font = uiLabel_FontId;
163 const int itemHeight = height_Rect(itemRect);
164 const int line = lineHeight_Text(font);
165 int fg = uiText_ColorId;
166 const iBool isPressing = isMouseDown_ListWidget(list) || d->isWaitingForEvent;
167 const iBool isHover = (isHover_Widget(constAs_Widget(list)) &&
168 constHoverItem_ListWidget(list) == d);
169 if (isHover || isPressing) {
170 fg = isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId;
171 fillRect_Paint(p,
172 itemRect,
173 isPressing ? uiBackgroundPressed_ColorId
174 : uiBackgroundFramelessHover_ColorId);
175 }
176 const int y = top_Rect(itemRect) + (itemHeight - line) / 2;
177 drawRange_Text(font,
178 init_I2(left_Rect(itemRect) + 3 * gap_UI, y),
179 fg,
180 range_String(&d->label));
181 drawAlign_Text(d->isWaitingForEvent ? uiContent_FontId : font,
182 init_I2(right_Rect(itemRect) - 3 * gap_UI,
183 y - (lineHeight_Text(uiContent_FontId) - line) / 2),
184 fg,
185 right_Alignment,
186 "%s",
187 d->isWaitingForEvent ? "\U0001F449 \u2328" : cstr_String(&d->key));
188}
189
190iBeginDefineSubclass(BindingsWidget, Widget)
191 .processEvent = (iAny *) processEvent_BindingsWidget_,
192 .draw = (iAny *) draw_BindingsWidget_,
193iEndDefineSubclass(BindingsWidget)