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 "../coroutine/module.hpp"
12#include "../macros.hpp"
13#include <unordered_map>
14#include <tuple>
15#include <filesystem>
16
17namespace hi { inline namespace v1 {
18
20public:
21 keyboard_bindings() noexcept : bindings() {}
22
23 static keyboard_bindings& global() noexcept;
24
25 void add_system_binding(keyboard_key key, gui_event_type command) noexcept
26 {
27 bindings[key].add_system_command(command);
28 }
29
30 void add_ignored_binding(keyboard_key key, gui_event_type command) noexcept
31 {
32 bindings[key].add_ignored_command(command);
33 }
34
35 void add_user_binding(keyboard_key key, gui_event_type command) noexcept
36 {
37 bindings[key].add_user_command(command);
38 }
39
46 {
47 if (event == gui_event_type::keyboard_down) {
48 hilet i = bindings.find(keyboard_key{event.keyboard_modifiers, event.key()});
49 if (i != bindings.cend()) {
50 for (auto &e : i->second.get_events()) {
51 co_yield e;
52 }
53 }
54 }
55 }
56
63 {
64 bindings.clear();
65 }
66
69 void load_bindings(std::filesystem::path const& path, bool system_binding = false)
70 {
71 hilet data = parse_JSON(path);
72
73 try {
74 hi_check(data.contains("bindings"), "Missing key 'bindings' at top level.");
75
76 hilet binding_list = data["bindings"];
77 hi_check(
78 holds_alternative<datum::vector_type>(binding_list), "Expecting array value for key 'bindings' at top level.");
79
80 for (hilet& binding : binding_list) {
81 hi_check(holds_alternative<datum::map_type>(binding), "Expecting object for a binding, got {}", binding);
82
83 hi_check(
84 binding.contains("key") && binding.contains("command"),
85 "Expecting required 'key' and 'command' for a binding, got {}",
86 binding);
87
88 hilet key_name = static_cast<std::string>(binding["key"]);
89 hilet key = keyboard_key(key_name);
90
91 auto command_name = static_cast<std::string>(binding["command"]);
92
93 // Commands starting with '-' are ignored system-bindings.
94 bool ignored_binding = false;
95 if (command_name.size() >= 1 && command_name[0] == '-') {
96 ignored_binding = true;
98 }
99
101 if (command == gui_event_type::none) {
102 throw parse_error(std::format("Could not parse command '{}'", command_name));
103 }
104
105 if (ignored_binding) {
106 add_ignored_binding(key, command);
107 } else if (system_binding) {
108 add_system_binding(key, command);
109 } else {
110 add_user_binding(key, command);
111 }
112 }
113
114 } catch (std::exception const& e) {
115 throw io_error(std::format("{}: Could not load keyboard bindings.\n{}", path.string(), e.what()));
116 }
117 }
118
122 // void save_user_bindings(std::filesystem::path const &path);
123
124private:
125 struct commands_t {
127 std::vector<gui_event_type> system = {};
128
130 std::vector<gui_event_type> ignored = {};
131
134
136 std::vector<gui_event> cache = {};
137
138 [[nodiscard]] std::vector<gui_event> const& get_events() const noexcept
139 {
140 return cache;
141 }
142
143 void add_system_command(gui_event_type cmd) noexcept
144 {
145 hilet i = std::find(system.cbegin(), system.cend(), cmd);
146 if (i == system.cend()) {
147 system.push_back(cmd);
148 update_cache();
149 }
150 }
151
152 void add_ignored_command(gui_event_type cmd) noexcept
153 {
154 hilet i = std::find(ignored.cbegin(), ignored.cend(), cmd);
155 if (i == ignored.cend()) {
156 ignored.push_back(cmd);
157 update_cache();
158 }
159 }
160
161 void add_user_command(gui_event_type cmd) noexcept
162 {
163 hilet i = std::find(user.cbegin(), user.cend(), cmd);
164 if (i == user.cend()) {
165 user.push_back(cmd);
166 update_cache();
167 }
168 }
169
170 void update_cache() noexcept
171 {
172 cache.reserve(ssize(system) + ssize(user));
173
174 for (hilet cmd : system) {
175 hilet i = std::find(cache.cbegin(), cache.cend(), cmd);
176 if (i == cache.cend()) {
177 cache.emplace_back(cmd);
178 }
179 }
180
181 for (hilet cmd : ignored) {
182 hilet i = std::find(cache.cbegin(), cache.cend(), cmd);
183 if (i != cache.cend()) {
184 cache.erase(i);
185 }
186 }
187
188 for (hilet cmd : user) {
189 hilet i = std::find(cache.cbegin(), cache.cend(), cmd);
190 if (i == cache.cend()) {
191 cache.emplace_back(cmd);
192 }
193 }
194 }
195 };
196
197 inline static std::unique_ptr<keyboard_bindings> _global;
198
202};
203
204inline keyboard_bindings& keyboard_bindings::global() noexcept
205{
206 if (not _global) {
207 _global = std::make_unique<keyboard_bindings>();
208 }
209 return *_global;
210}
211
212inline void load_user_keyboard_bindings(std::filesystem::path const& path)
213{
214 return keyboard_bindings::global().load_bindings(path, false);
215}
216
217inline void load_system_keyboard_bindings(std::filesystem::path const& path)
218{
219 return keyboard_bindings::global().load_bindings(path, true);
220}
221
222inline generator<gui_event> translate_keyboard_event(gui_event event) noexcept
223{
224 for (auto &e : keyboard_bindings::global().translate(event)) {
225 co_yield e;
226 }
227}
228
229}} // namespace hi::v1
Definition of GUI event types.
gui_event_type
GUI event type.
Definition gui_event_type.hpp:24
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
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:202
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
A user interface event.
Definition gui_event.hpp:75
Definition keyboard_bindings.hpp:19
void clear() noexcept
Clear all bindings.
Definition keyboard_bindings.hpp:62
void load_bindings(std::filesystem::path const &path, bool system_binding=false)
Load bindings from a JSON file.
Definition keyboard_bindings.hpp:69
generator< gui_event > translate(gui_event event) const noexcept
translate a key press in the empty-context to a command.
Definition keyboard_bindings.hpp:45
Exception thrown during parsing on an error.
Definition exception_intf.hpp:47
Exception thrown during I/O on an error.
Definition exception_intf.hpp:172
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)