Skip to content

Commit e6f6317

Browse files
committed
import/export keybinds
1 parent da120a7 commit e6f6317

File tree

2 files changed

+160
-22
lines changed

2 files changed

+160
-22
lines changed

sources/include/cage-engine/keybinds.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ namespace cage
8989
CAGE_ENGINE_API void keybindsGuiTable(guiBuilder::GuiBuilder *g, const String &filterPrefix = {});
9090

9191
CAGE_ENGINE_API Holder<Ini> keybindsExport();
92-
CAGE_ENGINE_API void keybindsImport(const Ini *ini);
92+
CAGE_ENGINE_API void keybindsImport(const Ini *ini, bool updateGui = false);
93+
CAGE_ENGINE_API void keybindsResetAll(bool updateGui = false);
9394

9495
CAGE_ENGINE_API KeybindModesFlags inputKeybindMode(const GenericInput &in);
9596
CAGE_ENGINE_API ModifiersFlags inputModifiersFlags(const GenericInput &in);

sources/libengine/keybinds.cpp

+158-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <GLFW/glfw3.h>
77

88
#include <cage-core/hashString.h>
9+
#include <cage-core/ini.h>
910
#include <cage-core/string.h>
1011
#include <cage-core/texts.h>
1112
#include <cage-engine/guiBuilder.h>
@@ -135,6 +136,26 @@ namespace cage
135136

136137
using Matcher = std::variant<std::monostate, KeyboardMatcher, ModifiersMatcher, MouseMatcher, WheelMatcher>;
137138

139+
CAGE_FORCE_INLINE bool operator==(const Matcher &a, const Matcher &b)
140+
{
141+
if (a.index() != b.index())
142+
return false;
143+
return std::visit(
144+
[](const auto &a, const auto &b) -> bool
145+
{
146+
using T = std::decay_t<decltype(a)>;
147+
if constexpr (std::is_same_v<T, std::monostate>)
148+
return true;
149+
else
150+
{
151+
// using operator == leads to stack overflow
152+
static_assert(std::is_trivially_copyable_v<T>);
153+
return detail::memcmp(&a, &b, sizeof(T)) == 0;
154+
}
155+
},
156+
a, b);
157+
}
158+
138159
CAGE_FORCE_INLINE KeybindModesFlags matches(const GenericInput &input, const Matcher &matcher)
139160
{
140161
return std::visit(
@@ -238,24 +259,42 @@ namespace cage
238259
return maker.result;
239260
}
240261

262+
const KeybindCreateConfig &validateConfig(const KeybindCreateConfig &config)
263+
{
264+
CAGE_ASSERT(!config.id.empty());
265+
CAGE_ASSERT(!findKeybind(config.id)); // must be unique
266+
CAGE_ASSERT(none(config.requiredFlags & config.forbiddenFlags));
267+
CAGE_ASSERT(any(config.devices));
268+
CAGE_ASSERT(none(config.devices & KeybindDevicesFlags::WheelRoll) || none(config.devices & KeybindDevicesFlags::WheelScroll)); // these two flags are mutually exclusive
269+
CAGE_ASSERT(none(config.devices & KeybindDevicesFlags::Modifiers) || config.devices == KeybindDevicesFlags::Modifiers); // modifiers is exclusive with all other flags
270+
CAGE_ASSERT(any(config.modes));
271+
return config;
272+
}
273+
274+
std::vector<Matcher> makeDefaults(const KeybindCreateConfig &config, PointerRange<const GenericInput> defaults)
275+
{
276+
std::vector<Matcher> res;
277+
for (const auto &it : defaults)
278+
{
279+
const auto mt = makeMatcher(config, it);
280+
if (!std::holds_alternative<std::monostate>(mt))
281+
res.push_back(mt);
282+
}
283+
CAGE_ASSERT(res.size() == defaults.size());
284+
return res;
285+
}
286+
241287
bool guiUpdateGlobal(uintPtr ptr, const GenericInput &);
242288

243289
EventListener<bool(const GenericInput &)> assignmentListener;
244290

245291
class KeybindImpl : public Keybind
246292
{
247293
public:
248-
KeybindImpl(const KeybindCreateConfig &config, PointerRange<const GenericInput> defaults, Delegate<bool(const GenericInput &)> event) : config(config), defaults(defaults.begin(), defaults.end()), textId(HashString(config.id))
249-
{
250-
CAGE_ASSERT(!config.id.empty());
251-
CAGE_ASSERT(!findKeybind(config.id)); // must be unique
252-
CAGE_ASSERT(none(config.requiredFlags & config.forbiddenFlags));
253-
CAGE_ASSERT(any(config.devices));
254-
CAGE_ASSERT(none(config.devices & KeybindDevicesFlags::WheelRoll) || none(config.devices & KeybindDevicesFlags::WheelScroll)); // these two flags are mutually exclusive
255-
CAGE_ASSERT(none(config.devices & KeybindDevicesFlags::Modifiers) || config.devices == KeybindDevicesFlags::Modifiers); // modifiers is exclusive with all other flags
256-
CAGE_ASSERT(any(config.modes));
294+
KeybindImpl(const KeybindCreateConfig &config_, PointerRange<const GenericInput> defaults_, Delegate<bool(const GenericInput &)> event_) : config(validateConfig(config_)), defaults(makeDefaults(config_, defaults_)), textId(HashString(config.id))
295+
{
257296
reset(); // make matchers from the defaults
258-
this->event = event;
297+
this->event = event_;
259298
global().push_back(this);
260299
}
261300

@@ -318,12 +357,11 @@ namespace cage
318357

319358
const KeybindCreateConfig config;
320359
EventListener<bool(const GenericInput &)> listener;
321-
const std::vector<GenericInput> defaults;
360+
const std::vector<Matcher> defaults;
322361
std::vector<Matcher> matchers;
323362
const uint32 textId = 0;
324363
mutable bool active = false; // allows tick events
325364
mutable bool autoDeactivate = false; // automatically deactivates after first engine tick
326-
327365
Entity *guiEnt = nullptr;
328366
uint32 assigningIndex = m;
329367

@@ -347,7 +385,7 @@ namespace cage
347385
{
348386
auto _ = g->rightRow(0.5);
349387
g->button().disabled(count() == 0).event(Delegate<bool(const GenericInput &)>().bind<KeybindImpl, &KeybindImpl::guiClear>(this)).image(HashString("cage/texture/keybindClear.png")).tooltip<HashString("cage/keybinds/clear"), "Clear">().size(Vec2(28));
350-
g->button().event(Delegate<bool(const GenericInput &)>().bind<KeybindImpl, &KeybindImpl::guiReset>(this)).image(HashString("cage/texture/keybindReset.png")).tooltip<HashString("cage/keybinds/reset"), "Reset">().size(Vec2(28));
388+
g->button().disabled(defaults == matchers).event(Delegate<bool(const GenericInput &)>().bind<KeybindImpl, &KeybindImpl::guiReset>(this)).image(HashString("cage/texture/keybindReset.png")).tooltip<HashString("cage/keybinds/reset"), "Reset">().size(Vec2(28));
351389
}
352390
}
353391

@@ -537,10 +575,7 @@ namespace cage
537575
void Keybind::reset()
538576
{
539577
KeybindImpl *impl = (KeybindImpl *)this;
540-
clear();
541-
for (const auto &it : impl->defaults)
542-
add(it);
543-
CAGE_ASSERT(impl->matchers.size() == impl->defaults.size());
578+
impl->matchers = impl->defaults;
544579
}
545580

546581
Holder<Keybind> newKeybind(const KeybindCreateConfig &config, const GenericInput &defaults, Delegate<bool(const GenericInput &)> event)
@@ -601,15 +636,117 @@ namespace cage
601636
}
602637
}
603638

639+
namespace
640+
{
641+
String toString(const Matcher &mt)
642+
{
643+
return std::visit(
644+
[](const auto &a) -> String
645+
{
646+
using T = std::decay_t<decltype(a)>;
647+
//if constexpr (std::is_same_v<T, std::monostate>)
648+
// return "";
649+
if constexpr (std::is_same_v<T, KeyboardMatcher>)
650+
return Stringizer() + "key " + a.key + " " + (uint32)a.requiredFlags + " " + (uint32)~a.forbiddenFlags;
651+
if constexpr (std::is_same_v<T, ModifiersMatcher>)
652+
return Stringizer() + "mods " + (uint32)a.requiredFlags + " " + (uint32)~a.forbiddenFlags;
653+
if constexpr (std::is_same_v<T, MouseMatcher>)
654+
return Stringizer() + "mouse " + (uint32)a.button + " " + (uint32)a.requiredFlags + " " + (uint32)~a.forbiddenFlags;
655+
if constexpr (std::is_same_v<T, WheelMatcher>)
656+
return Stringizer() + "wheel " + a.direction + " " + (uint32)a.requiredFlags + " " + (uint32)~a.forbiddenFlags;
657+
},
658+
mt);
659+
}
660+
661+
MatcherBase baseFromString(String &s)
662+
{
663+
const uint32 r = toUint32(split(s));
664+
const uint32 f = toUint32(split(s));
665+
return MatcherBase{ (ModifiersFlags)r, ~(ModifiersFlags)f };
666+
}
667+
668+
Matcher fromString(const String &str)
669+
{
670+
if (str.empty())
671+
return {};
672+
String s = str;
673+
const String type = split(s);
674+
if (type == "key")
675+
{
676+
const uint32 k = toUint32(split(s));
677+
return KeyboardMatcher{ baseFromString(s), k };
678+
}
679+
else if (type == "mods")
680+
{
681+
return ModifiersMatcher{ baseFromString(s) };
682+
}
683+
else if (type == "mouse")
684+
{
685+
const uint32 b = toUint32(split(s));
686+
return MouseMatcher{ baseFromString(s), (MouseButtonsFlags)b };
687+
}
688+
else if (type == "wheel")
689+
{
690+
const sint32 d = toUint32(split(s));
691+
return WheelMatcher{ baseFromString(s), d };
692+
}
693+
else
694+
return {};
695+
}
696+
}
697+
604698
Holder<Ini> keybindsExport()
605699
{
606-
// todo
607-
return {};
700+
Holder<Ini> ini = newIni();
701+
for (KeybindImpl *k : global())
702+
{
703+
ini->set(k->config.id, "count", Stringizer() + k->matchers.size());
704+
uint32 i = 0;
705+
for (const Matcher &mt : k->matchers)
706+
{
707+
ini->set(k->config.id, Stringizer() + i, toString(mt));
708+
i++;
709+
}
710+
}
711+
return ini;
608712
}
609713

610-
void keybindsImport(const Ini *ini)
714+
void keybindsImport(const Ini *ini, bool updateGui)
611715
{
612-
// todo
716+
for (KeybindImpl *k : global())
717+
{
718+
try
719+
{
720+
if (ini->sectionExists(k->config.id))
721+
{
722+
k->clear();
723+
for (const String &it : ini->items(k->config.id))
724+
{
725+
if (it != "count")
726+
k->matchers.push_back(fromString(ini->get(k->config.id, it)));
727+
}
728+
}
729+
else
730+
k->reset();
731+
if (updateGui)
732+
k->makeGui();
733+
}
734+
catch (...)
735+
{
736+
CAGE_LOG(SeverityEnum::Warning, "keybinds", Stringizer() + "error importing keybinds for: " + k->config.id);
737+
k->reset();
738+
}
739+
}
740+
}
741+
742+
void keybindsResetAll(bool updateGui)
743+
{
744+
for (KeybindImpl *k : global())
745+
{
746+
k->reset();
747+
if (updateGui)
748+
k->makeGui();
749+
}
613750
}
614751

615752
KeybindModesFlags inputKeybindMode(const GenericInput &in)

0 commit comments

Comments
 (0)