diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-31 14:18:09 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-31 14:18:09 +0200 |
commit | f809b80a688b8cd0a5a8bcb578d4cce1562fff5e (patch) | |
tree | bfecbefcf9f56f9fd4e44d89bb8977eb872b0bd5 /src/ui/bindingswidget.c | |
parent | 7d0d4a6821644a73fc52b54eba6774ba5a64cb54 (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.c | 193 |
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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | |||
30 | iDeclareType(BindingItem) | ||
31 | typedef iListItemClass iBindingItemClass; | ||
32 | |||
33 | struct Impl_BindingItem { | ||
34 | iListItem listItem; | ||
35 | iString label; | ||
36 | iString key; | ||
37 | int id; | ||
38 | iBool isWaitingForEvent; | ||
39 | }; | ||
40 | |||
41 | void 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 | |||
49 | void deinit_BindingItem(iBindingItem *d) { | ||
50 | deinit_String(&d->key); | ||
51 | deinit_String(&d->label); | ||
52 | } | ||
53 | |||
54 | static 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 | |||
60 | static void draw_BindingItem_(const iBindingItem *d, iPaint *p, iRect itemRect, | ||
61 | const iListWidget *list); | ||
62 | |||
63 | iBeginDefineSubclass(BindingItem, ListItem) | ||
64 | .draw = (iAny *) draw_BindingItem_, | ||
65 | iEndDefineSubclass(BindingItem) | ||
66 | |||
67 | iDefineObjectConstruction(BindingItem) | ||
68 | |||
69 | /*----------------------------------------------------------------------------------------------*/ | ||
70 | |||
71 | struct Impl_BindingsWidget { | ||
72 | iWidget widget; | ||
73 | iListWidget *list; | ||
74 | size_t activePos; | ||
75 | }; | ||
76 | |||
77 | iDefineObjectConstruction(BindingsWidget) | ||
78 | |||
79 | static 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 | |||
84 | static 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 | |||
103 | void 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 | |||
114 | void deinit_BindingsWidget(iBindingsWidget *d) { | ||
115 | /* nothing to do */ | ||
116 | iUnused(d); | ||
117 | } | ||
118 | |||
119 | static 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 | |||
133 | static 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 | |||
154 | static 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 | |||
160 | static 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 | |||
190 | iBeginDefineSubclass(BindingsWidget, Widget) | ||
191 | .processEvent = (iAny *) processEvent_BindingsWidget_, | ||
192 | .draw = (iAny *) draw_BindingsWidget_, | ||
193 | iEndDefineSubclass(BindingsWidget) | ||