HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
keyboard_bindings.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "keyboard_key.hpp"
8#include "gui_event.hpp"
9#include "../utility/utility.hpp"
10#include "../codec/codec.hpp"
11#include "../macros.hpp"
12#include <unordered_map>
13#include <tuple>
14#include <filesystem>
15#include <coroutine>
16
17hi_export_module(hikogui.GUI : keyboard_bindings);
18
19hi_export namespace hi { inline namespace v1 {
20
22public:
23 keyboard_bindings() noexcept : bindings() {}
24
25 static keyboard_bindings& global() noexcept;
26
27 void add_system_binding(keyboard_key key, gui_event_type command) noexcept
28 {
29 bindings[key].add_system_command(command);
30 }
31
32 void add_ignored_binding(keyboard_key key, gui_event_type command) noexcept
33 {
34 bindings[key].add_ignored_command(command);
35 }
36
37 void add_user_binding(keyboard_key key, gui_event_type command) noexcept
38 {
39 bindings[key].add_user_command(command);
40 }
41
47 [[nodiscard]] generator<gui_event> translate(gui_event event) const noexcept
48 {
49 if (event == gui_event_type::keyboard_down) {
50 auto const i = bindings.find(keyboard_key{event.keyboard_modifiers, event.key()});
51 if (i != bindings.cend()) {
52 for (auto& e : i->second.get_events()) {
53 co_yield e;
54 }
55 }
56 }
57 }
58
64 void clear() noexcept
65 {
66 bindings.clear();
67 }
68
71 void load_bindings(std::filesystem::path const& path, bool system_binding = false)
72 {
73 auto const data = parse_JSON(path);
74
75 try {
76 hi_check(data.contains("bindings"), "Missing key 'bindings' at top level.");
77
78 auto const binding_list = data["bindings"];
79 hi_check(
80 holds_alternative<datum::vector_type>(binding_list), "Expecting array value for key 'bindings' at top level.");
81
82 for (auto const& binding : binding_list) {
83 hi_check(holds_alternative<datum::map_type>(binding), "Expecting object for a binding, got {}", binding);
84
85 hi_check(
86 binding.contains("key") && binding.contains("command"),
87 "Expecting required 'key' and 'command' for a binding, got {}",
88 binding);
89
90 auto const key_name = static_cast<std::string>(binding["key"]);
91 auto const key = keyboard_key(key_name);
92
93 auto command_name = static_cast<std::string>(binding["command"]);
94
95 // Commands starting with '-' are ignored system-bindings.
96 bool ignored_binding = false;
97 if (command_name.size() >= 1 && command_name[0] == '-') {
98 ignored_binding = true;
99 command_name = command_name.substr(1);
100 }
101
102 auto command = to_gui_event_type(command_name);
103 if (command == gui_event_type::none) {
104 throw parse_error(std::format("Could not parse command '{}'", command_name));
105 }
106
107 if (ignored_binding) {
108 add_ignored_binding(key, command);
109 } else if (system_binding) {
110 add_system_binding(key, command);
111 } else {
112 add_user_binding(key, command);
113 }
114 }
115
116 } catch (std::exception const& e) {
117 throw io_error(std::format("{}: Could not load keyboard bindings.\n{}", path.string(), e.what()));
118 }
119 }
120
124 // void save_user_bindings(std::filesystem::path const &path);
125
126private:
127 struct commands_t {
129 std::vector<gui_event_type> system = {};
130
132 std::vector<gui_event_type> ignored = {};
133
136
138 std::vector<gui_event> cache = {};
139
140 [[nodiscard]] std::vector<gui_event> const& get_events() const noexcept
141 {
142 return cache;
143 }
144
145 void add_system_command(gui_event_type cmd) noexcept
146 {
147 auto const i = std::find(system.cbegin(), system.cend(), cmd);
148 if (i == system.cend()) {
149 system.push_back(cmd);
150 update_cache();
151 }
152 }
153
154 void add_ignored_command(gui_event_type cmd) noexcept
155 {
156 auto const i = std::find(ignored.cbegin(), ignored.cend(), cmd);
157 if (i == ignored.cend()) {
158 ignored.push_back(cmd);
159 update_cache();
160 }
161 }
162
163 void add_user_command(gui_event_type cmd) noexcept
164 {
165 auto const i = std::find(user.cbegin(), user.cend(), cmd);
166 if (i == user.cend()) {
167 user.push_back(cmd);
168 update_cache();
169 }
170 }
171
172 void update_cache() noexcept
173 {
174 cache.reserve(ssize(system) + ssize(user));
175
176 for (auto const cmd : system) {
177 auto const i = std::find(cache.cbegin(), cache.cend(), cmd);
178 if (i == cache.cend()) {
179 cache.emplace_back(cmd);
180 }
181 }
182
183 for (auto const cmd : ignored) {
184 auto const i = std::find(cache.cbegin(), cache.cend(), cmd);
185 if (i != cache.cend()) {
186 cache.erase(i);
187 }
188 }
189
190 for (auto const cmd : user) {
191 auto const i = std::find(cache.cbegin(), cache.cend(), cmd);
192 if (i == cache.cend()) {
193 cache.emplace_back(cmd);
194 }
195 }
196 }
197 };
198
202};
203
204namespace detail {
205inline std::unique_ptr<keyboard_bindings> keyboard_bindings_global;
206}
207
208inline keyboard_bindings& keyboard_bindings::global() noexcept
209{
210 if (not detail::keyboard_bindings_global) {
211 detail::keyboard_bindings_global = std::make_unique<keyboard_bindings>();
212 }
213 return *detail::keyboard_bindings_global;
214}
215
216inline void load_user_keyboard_bindings(std::filesystem::path const& path)
217{
218 return keyboard_bindings::global().load_bindings(path, false);
219}
220
221inline void load_system_keyboard_bindings(std::filesystem::path const& path)
222{
223 return keyboard_bindings::global().load_bindings(path, true);
224}
225
226inline generator<gui_event> translate_keyboard_event(gui_event event) noexcept
227{
228 for (auto& e : keyboard_bindings::global().translate(event)) {
229 co_yield e;
230 }
231}
232
233}} // namespace hi::v1
Definition of GUI event types.
gui_event_type
GUI event type.
Definition gui_event_type.hpp:24
The HikoGUI namespace.
Definition array_generic.hpp:20
constexpr gui_event_type to_gui_event_type(std::string_view name) noexcept
Convert a name to a GUI event type.
Definition gui_event_type.hpp:208
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
A user interface event.
Definition gui_event.hpp:82
Definition keyboard_bindings.hpp:21
void clear() noexcept
Clear all bindings.
Definition keyboard_bindings.hpp:64
void load_bindings(std::filesystem::path const &path, bool system_binding=false)
Load bindings from a JSON file.
Definition keyboard_bindings.hpp:71
generator< gui_event > translate(gui_event event) const noexcept
translate a key press in the empty-context to a command.
Definition keyboard_bindings.hpp:47
Exception thrown during parsing on an error.
Definition exception_intf.hpp:48
Exception thrown during I/O on an error.
Definition exception_intf.hpp:173
T cbegin(T... args)
T clear(T... args)
T emplace_back(T... args)
T cend(T... args)
T erase(T... args)
T find(T... args)
T push_back(T... args)
T reserve(T... args)
T substr(T... args)
T system(T... args)
T what(T... args)