forked from FabricMC/Enigma
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add configurable keybinds (QuiltMC/enigma#8)
Closes FabricMC#319 and addresses FabricMC#52
- Loading branch information
1 parent
5ed0efd
commit a95819c
Showing
21 changed files
with
1,043 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
enigma-swing/src/main/java/cuchaz/enigma/gui/config/KeyBindsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package cuchaz.enigma.gui.config; | ||
|
||
import cuchaz.enigma.config.ConfigContainer; | ||
import cuchaz.enigma.config.ConfigSection; | ||
import cuchaz.enigma.gui.config.keybind.KeyBind; | ||
|
||
public final class KeyBindsConfig { | ||
private KeyBindsConfig() { | ||
} | ||
|
||
private static final ConfigContainer cfg = ConfigContainer.getOrCreate("enigma/enigmakeybinds"); | ||
|
||
public static void save() { | ||
cfg.save(); | ||
} | ||
|
||
private static ConfigSection getSection(KeyBind keyBind) { | ||
return keyBind.category().isEmpty() ? cfg.data() : cfg.data().section(keyBind.category()); | ||
} | ||
|
||
public static String[] getKeyBindCodes(KeyBind keyBind) { | ||
return getSection(keyBind).getArray(keyBind.name()).orElse(keyBind.serializeCombinations()); | ||
} | ||
|
||
public static void setKeyBind(KeyBind keyBind) { | ||
getSection(keyBind).setArray(keyBind.name(), keyBind.serializeCombinations()); | ||
} | ||
} |
143 changes: 143 additions & 0 deletions
143
enigma-swing/src/main/java/cuchaz/enigma/gui/config/keybind/KeyBind.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package cuchaz.enigma.gui.config.keybind; | ||
|
||
import cuchaz.enigma.utils.I18n; | ||
|
||
import javax.swing.KeyStroke; | ||
import java.awt.event.KeyEvent; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
public record KeyBind(String name, String category, List<Combination> combinations) { | ||
public record Combination(int keyCode, int keyModifiers) { | ||
public static final Combination EMPTY = new Combination(-1, 0); | ||
public boolean matches(KeyEvent e) { | ||
return e.getKeyCode() == keyCode && e.getModifiersEx() == keyModifiers; | ||
} | ||
|
||
public KeyStroke toKeyStroke(int modifiers) { | ||
modifiers = keyModifiers | modifiers; | ||
return KeyStroke.getKeyStroke(keyCode, modifiers); | ||
} | ||
|
||
public String serialize() { | ||
return keyCode + ";" + Integer.toString(keyModifiers, 16); | ||
} | ||
|
||
public static Combination deserialize(String str) { | ||
String[] parts = str.split(";", 2); | ||
return new Combination(Integer.parseInt(parts[0]), Integer.parseInt(parts[1], 16)); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Combination[keyCode=" + keyCode + ", keyModifiers=0x" + Integer.toString(keyModifiers, 16).toUpperCase(Locale.ROOT) + "]"; | ||
} | ||
} | ||
|
||
public void setFrom(KeyBind other) { | ||
this.combinations.clear(); | ||
this.combinations.addAll(other.combinations); | ||
} | ||
|
||
public boolean matches(KeyEvent e) { | ||
return combinations.stream().anyMatch(c -> c.matches(e)); | ||
} | ||
|
||
public KeyStroke toKeyStroke(int modifiers) { | ||
return isEmpty() ? null : combinations.get(0).toKeyStroke(modifiers); | ||
} | ||
|
||
public KeyStroke toKeyStroke() { | ||
return toKeyStroke(0); | ||
} | ||
|
||
public boolean isEmpty() { | ||
return combinations.isEmpty(); | ||
} | ||
|
||
public String[] serializeCombinations() { | ||
return combinations.stream().map(Combination::serialize).toArray(String[]::new); | ||
} | ||
|
||
public void deserializeCombinations(String[] serialized) { | ||
combinations.clear(); | ||
for (String serializedCombination : serialized) { | ||
if (!serializedCombination.isEmpty()) { | ||
combinations.add(Combination.deserialize(serializedCombination)); | ||
} else { | ||
System.out.println("warning: empty combination deserialized for keybind " + (category.isEmpty() ? "" : category + ".") + name); | ||
} | ||
} | ||
} | ||
|
||
private String getTranslationKey() { | ||
return "keybind." + (category.isEmpty() ? "" : category + ".") + this.name; | ||
} | ||
|
||
public String getTranslatedName() { | ||
return I18n.translate(getTranslationKey()); | ||
} | ||
|
||
public KeyBind copy() { | ||
return new KeyBind(name, category, new ArrayList<>(combinations)); | ||
} | ||
|
||
public KeyBind toImmutable() { | ||
return new KeyBind(name, category, List.copyOf(combinations)); | ||
} | ||
|
||
public boolean isSameKeyBind(KeyBind other) { | ||
return name.equals(other.name) && category.equals(other.category); | ||
} | ||
|
||
public static Builder builder(String name) { | ||
return new Builder(name); | ||
} | ||
|
||
public static Builder builder(String name, String category) { | ||
return new Builder(name, category); | ||
} | ||
|
||
public static class Builder { | ||
private final String name; | ||
private final String category; | ||
private final List<Combination> combinations = new ArrayList<>(); | ||
private int modifiers = 0; | ||
|
||
private Builder(String name) { | ||
this.name = name; | ||
this.category = ""; | ||
} | ||
|
||
private Builder(String name, String category) { | ||
this.name = name; | ||
this.category = category; | ||
} | ||
|
||
public KeyBind build() { | ||
return new KeyBind(name, category, combinations); | ||
} | ||
|
||
public Builder key(int keyCode, int keyModifiers) { | ||
combinations.add(new Combination(keyCode, keyModifiers | modifiers)); | ||
return this; | ||
} | ||
|
||
public Builder key(int keyCode) { | ||
return key(keyCode, 0); | ||
} | ||
|
||
public Builder keys(int... keyCodes) { | ||
for (int keyCode : keyCodes) { | ||
key(keyCode); | ||
} | ||
return this; | ||
} | ||
|
||
public Builder mod(int modifiers) { | ||
this.modifiers |= modifiers; | ||
return this; | ||
} | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
enigma-swing/src/main/java/cuchaz/enigma/gui/config/keybind/KeyBinds.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package cuchaz.enigma.gui.config.keybind; | ||
|
||
import cuchaz.enigma.gui.config.KeyBindsConfig; | ||
|
||
import java.awt.event.KeyEvent; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public final class KeyBinds { | ||
private static final String QUICK_FIND_DIALOG_CATEGORY = "quick_find_dialog"; | ||
private static final String SEARCH_DIALOG_CATEGORY = "search_dialog"; | ||
private static final String EDITOR_CATEGORY = "editor"; | ||
private static final String MENU_CATEGORY = "menu"; | ||
|
||
public static final KeyBind EXIT = KeyBind.builder("close").key(KeyEvent.VK_ESCAPE).build(); | ||
public static final KeyBind DIALOG_SAVE = KeyBind.builder("dialog_save").key(KeyEvent.VK_ENTER).build(); | ||
|
||
public static final KeyBind QUICK_FIND_DIALOG_NEXT = KeyBind.builder("next", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_ENTER).build(); | ||
public static final KeyBind QUICK_FIND_DIALOG_PREVIOUS = KeyBind.builder("previous", QUICK_FIND_DIALOG_CATEGORY).mod(KeyEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_ENTER).build(); | ||
public static final KeyBind SEARCH_DIALOG_NEXT = KeyBind.builder("next", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_DOWN).build(); | ||
public static final KeyBind SEARCH_DIALOG_PREVIOUS = KeyBind.builder("previous", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_UP).build(); | ||
|
||
public static final KeyBind EDITOR_RENAME = KeyBind.builder("rename", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_R).build(); | ||
public static final KeyBind EDITOR_PASTE = KeyBind.builder("paste", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_V).build(); | ||
public static final KeyBind EDITOR_EDIT_JAVADOC = KeyBind.builder("edit_javadoc", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_D).build(); | ||
public static final KeyBind EDITOR_SHOW_INHERITANCE = KeyBind.builder("show_inheritance", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_I).build(); | ||
public static final KeyBind EDITOR_SHOW_IMPLEMENTATIONS = KeyBind.builder("show_implementations", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_M).build(); | ||
public static final KeyBind EDITOR_SHOW_CALLS = KeyBind.builder("show_calls", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_C).build(); | ||
public static final KeyBind EDITOR_SHOW_CALLS_SPECIFIC = KeyBind.builder("show_calls_specific", EDITOR_CATEGORY).key(KeyEvent.VK_C).mod(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).build(); | ||
public static final KeyBind EDITOR_OPEN_ENTRY = KeyBind.builder("open_entry", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_N).build(); | ||
public static final KeyBind EDITOR_OPEN_PREVIOUS = KeyBind.builder("open_previous", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_P).build(); | ||
public static final KeyBind EDITOR_OPEN_NEXT = KeyBind.builder("open_next", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_E).build(); | ||
public static final KeyBind EDITOR_TOGGLE_MAPPING = KeyBind.builder("toggle_mapping", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_O).build(); | ||
public static final KeyBind EDITOR_ZOOM_IN = KeyBind.builder("zoom_in", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).keys(KeyEvent.VK_PLUS, KeyEvent.VK_ADD, KeyEvent.VK_EQUALS).build(); | ||
public static final KeyBind EDITOR_ZOOM_OUT = KeyBind.builder("zoom_out", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).keys(KeyEvent.VK_MINUS, KeyEvent.VK_SUBTRACT).build(); | ||
public static final KeyBind EDITOR_CLOSE_TAB = KeyBind.builder("close_tab", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_4).build(); | ||
public static final KeyBind EDITOR_RELOAD_CLASS = KeyBind.builder("reload_class", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_F5).build(); | ||
public static final KeyBind EDITOR_QUICK_FIND = KeyBind.builder("quick_find", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_F).build(); | ||
|
||
public static final KeyBind SAVE_MAPPINGS = KeyBind.builder("save", MENU_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_S).build(); | ||
public static final KeyBind DROP_MAPPINGS = KeyBind.builder("drop_mappings", MENU_CATEGORY).build(); | ||
public static final KeyBind RELOAD_MAPPINGS = KeyBind.builder("reload_mappings", MENU_CATEGORY).build(); | ||
public static final KeyBind RELOAD_ALL = KeyBind.builder("reload_all", MENU_CATEGORY).build(); | ||
public static final KeyBind MAPPING_STATS = KeyBind.builder("mapping_stats", MENU_CATEGORY).build(); | ||
public static final KeyBind SEARCH_CLASS = KeyBind.builder("search_class", MENU_CATEGORY).mod(KeyEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_SPACE).build(); | ||
public static final KeyBind SEARCH_METHOD = KeyBind.builder("search_method", MENU_CATEGORY).build(); | ||
public static final KeyBind SEARCH_FIELD = KeyBind.builder("search_field", MENU_CATEGORY).build(); | ||
|
||
private static final List<KeyBind> DEFAULT_KEY_BINDS = Stream.of(EXIT, DIALOG_SAVE, QUICK_FIND_DIALOG_NEXT, | ||
QUICK_FIND_DIALOG_PREVIOUS, SEARCH_DIALOG_NEXT, SEARCH_DIALOG_PREVIOUS, EDITOR_RENAME, EDITOR_PASTE, | ||
EDITOR_EDIT_JAVADOC, EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS, | ||
EDITOR_SHOW_CALLS_SPECIFIC, EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, | ||
EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN, EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, | ||
EDITOR_QUICK_FIND, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, RELOAD_ALL, MAPPING_STATS, SEARCH_CLASS, | ||
SEARCH_METHOD, SEARCH_FIELD).map(KeyBind::toImmutable).toList(); | ||
|
||
private static final List<KeyBind> CONFIGURABLE_KEY_BINDS = List.of(EDITOR_RENAME, EDITOR_PASTE, EDITOR_EDIT_JAVADOC, | ||
EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS, EDITOR_SHOW_CALLS_SPECIFIC, | ||
EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN, | ||
EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, | ||
RELOAD_ALL, MAPPING_STATS, SEARCH_CLASS, SEARCH_METHOD, SEARCH_FIELD); | ||
// Editing entries in CONFIGURABLE_KEY_BINDS directly wouldn't allow to revert the changes instead of saving | ||
private static List<KeyBind> EDITABLE_KEY_BINDS; | ||
|
||
private KeyBinds() { | ||
} | ||
|
||
public static boolean isConfigurable(KeyBind keyBind) { | ||
return CONFIGURABLE_KEY_BINDS.stream().anyMatch(bind -> bind.isSameKeyBind(keyBind)); | ||
} | ||
|
||
public static Map<String, List<KeyBind>> getEditableKeyBindsByCategory() { | ||
return EDITABLE_KEY_BINDS.stream() | ||
.collect(Collectors.groupingBy(KeyBind::category)); | ||
} | ||
|
||
public static void loadConfig() { | ||
for (KeyBind keyBind : CONFIGURABLE_KEY_BINDS) { | ||
keyBind.deserializeCombinations(KeyBindsConfig.getKeyBindCodes(keyBind)); | ||
} | ||
|
||
resetEditableKeyBinds(); | ||
} | ||
|
||
public static void saveConfig() { | ||
boolean modified = false; | ||
for (int i = 0; i < CONFIGURABLE_KEY_BINDS.size(); i++) { | ||
KeyBind keyBind = CONFIGURABLE_KEY_BINDS.get(i); | ||
KeyBind editedKeyBind = EDITABLE_KEY_BINDS.get(i); | ||
if (!editedKeyBind.equals(keyBind)) { | ||
modified = true; | ||
keyBind.setFrom(editedKeyBind); | ||
KeyBindsConfig.setKeyBind(editedKeyBind); | ||
} | ||
} | ||
if (modified) { | ||
KeyBindsConfig.save(); | ||
} | ||
} | ||
|
||
// Reset the key binds to the saved values | ||
public static void resetEditableKeyBinds() { | ||
EDITABLE_KEY_BINDS = CONFIGURABLE_KEY_BINDS.stream().map(KeyBind::copy) | ||
.collect(Collectors.toCollection(ArrayList::new)); | ||
} | ||
|
||
public static void resetToDefault(KeyBind keyBind) { | ||
// Ensure the key bind is editable | ||
if (!EDITABLE_KEY_BINDS.contains(keyBind)) { | ||
return; | ||
} | ||
|
||
KeyBind defaultKeyBind = DEFAULT_KEY_BINDS.stream().filter(bind -> bind.isSameKeyBind(keyBind)).findFirst().orElse(null); | ||
if (defaultKeyBind == null) { | ||
throw new IllegalStateException("Could not find default key bind for " + keyBind); | ||
} | ||
|
||
keyBind.setFrom(defaultKeyBind); | ||
} | ||
|
||
public static List<KeyBind> getEditableKeyBinds() { | ||
return EDITABLE_KEY_BINDS; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.