mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Let players remap hotkeys in-game, fix default hotkeys being qwerty-specific.
- Provide a "Hotkey" screen to let players remap hotkeys in-game using a
convenient setup.
- Make all .cfg hotkeys refer to scancodes (i.e. position on the
keyboard), so that default hotkeys now translate correctly for AZERTY,
QWERTZ and other layouts.
- 'BackSpace' is now an alias for 'Delete', and works for killing units.
This fixes #1917, as macs don't have a proper delete key and would need
to use Fn+Del otherwise. This shifts "timewarp" to Shift+BackSpace.
Functionally, this switches hotkeys to scancodes, as that makes more
sense (they are combinations of key positions, not actual text output).
SDL includes are cleaned and key names are reused.
Fixes #2850, Fixes #2604, Refs #1810, Fixes #1917.
Follows work in 3d7784d2af.
Various diffs tested by: Angen, Stan, Freagarach
Comments by: Nescio
Differential Revision: https://code.wildfiregames.com/D2814
This was SVN commit r24215.
This commit is contained in:
parent
e6adfd01bf
commit
a4852c4c01
38 changed files with 1585 additions and 534 deletions
|
|
@ -141,7 +141,7 @@ menu = 60 ; Throttle FPS in menus only.
|
|||
; See keys.txt for the list of key names.
|
||||
|
||||
; > SYSTEM SETTINGS
|
||||
exit = "Ctrl+Break", "Super+Q" ; Exit to desktop
|
||||
exit = "Ctrl+Break", "Super+Q", "Alt+F4" ; Exit to desktop
|
||||
cancel = Escape ; Close or cancel the current dialog box/popup
|
||||
confirm = Return ; Confirm the current command
|
||||
pause = Pause ; Pause/unpause game
|
||||
|
|
@ -186,8 +186,8 @@ quickload = "Shift+F8"
|
|||
reset = "R" ; Reset camera rotation to default.
|
||||
follow = "F" ; Follow the first unit in the selection
|
||||
rallypointfocus = unused ; Focus the camera on the rally point of the selected building
|
||||
zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
|
||||
zoom.out = Minus, Underscore, NumMinus ; Zoom camera out (continuous control)
|
||||
zoom.in = Plus, NumPlus ; Zoom camera in (continuous control)
|
||||
zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
|
||||
zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
|
||||
zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
|
||||
rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
|
||||
|
|
@ -238,15 +238,15 @@ save = "Shift+F11" ; Save current profiler data to logs/profile.txt
|
|||
toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler
|
||||
|
||||
[hotkey.selection]
|
||||
cancel = Esc ; Un-select all units and cancel building placement
|
||||
add = Shift ; Add units to selection
|
||||
militaryonly = Alt ; Add only military units to the selection
|
||||
nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection
|
||||
idleonly = "I" ; Select only idle units
|
||||
woundedonly = "O" ; Select only wounded units
|
||||
remove = Ctrl ; Remove units from selection
|
||||
cancel = Esc ; Un-select all units and cancel building placement
|
||||
idleworker = Period, NumPoint ; Select next idle worker
|
||||
idlewarrior = ForwardSlash, NumDivide ; Select next idle warrior
|
||||
idleworker = Period, NumDecimal ; Select next idle worker
|
||||
idlewarrior = Slash, NumDivide ; Select next idle warrior
|
||||
idleunit = BackSlash ; Select next idle unit
|
||||
offscreen = Alt ; Include offscreen units in selection
|
||||
[hotkey.selection.group.add]
|
||||
|
|
@ -284,7 +284,7 @@ offscreen = Alt ; Include offscreen units in selection
|
|||
9 = 9, Num9
|
||||
|
||||
[hotkey.session]
|
||||
kill = Delete ; Destroy selected units
|
||||
kill = Delete, Backspace ; Destroy selected units
|
||||
stop = "H" ; Stop the current action
|
||||
backtowork = "Y" ; The unit will go back to work
|
||||
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
|
||||
|
|
@ -312,8 +312,8 @@ snaptoedges = Ctrl ; Modifier to align new structures with nearby exis
|
|||
; Overlays
|
||||
showstatusbars = Tab ; Toggle display of status bars
|
||||
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
|
||||
highlightguarding = PgDn ; Toggle highlight of guarding units
|
||||
highlightguarded = PgUp ; Toggle highlight of guarded units
|
||||
highlightguarding = PageDown ; Toggle highlight of guarding units
|
||||
highlightguarded = PageUp ; Toggle highlight of guarded units
|
||||
diplomacycolors = "Alt+X" ; Toggle diplomacy colors
|
||||
toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
|
||||
toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
|
||||
|
|
@ -328,7 +328,7 @@ objectives.toggle = "Ctrl+O" ; Toggle in-game objectives page
|
|||
tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel
|
||||
|
||||
[hotkey.session.savedgames]
|
||||
delete = Delete ; Delete the selected saved game asking confirmation
|
||||
delete = Delete, Backspace ; Delete the selected saved game asking confirmation
|
||||
noconfirmation = Shift ; Do not ask confirmation when deleting a game
|
||||
|
||||
[hotkey.session.queueunit] ; > UNIT TRAINING
|
||||
|
|
@ -343,7 +343,7 @@ noconfirmation = Shift ; Do not ask confirmation when deleting a game
|
|||
|
||||
[hotkey.session.timewarp]
|
||||
fastforward = Space ; If timewarp mode enabled, speed up the game
|
||||
rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game
|
||||
rewind = "Shift+Backspace" ; If timewarp mode enabled, go back to earlier point in the game
|
||||
|
||||
[hotkey.tab]
|
||||
next = "Tab", "Alt+S" ; Show the next tab
|
||||
|
|
|
|||
|
|
@ -1,107 +1,39 @@
|
|||
## This file documents keynames that can be used in .cfg files for specifying hotkeys
|
||||
## Note: all are case-insensitive.
|
||||
## Note: the keynames are not actually configured or implemented here
|
||||
## Also note: if your keyboard supports keys that do not appear here (such as the French é letter),
|
||||
## you should be able to use it directly.
|
||||
## Note: those names map to 'scancodes', i.e. positions on a classic QWERTY keyboard.
|
||||
## The in-game hotkey editor will show you what that corresponds to.
|
||||
|
||||
Backspace, BkSp
|
||||
Tab
|
||||
Clear
|
||||
Return, Ret
|
||||
Pause
|
||||
|
||||
MouseLeft
|
||||
MouseRight
|
||||
MouseMiddle
|
||||
MouseX1
|
||||
MouseX2
|
||||
WheelUp
|
||||
WheelDown
|
||||
WheelLeft
|
||||
WheelRight
|
||||
|
||||
A-Z
|
||||
0-9
|
||||
Return, Enter
|
||||
Break
|
||||
Escape, Esc
|
||||
Space, Spc
|
||||
!, Exclaim
|
||||
", DoubleQuote
|
||||
#, Hash
|
||||
$, Dollar
|
||||
&, Ampersand
|
||||
', SingleQuote
|
||||
(, LeftParen
|
||||
), RightParen
|
||||
*, Asterisk
|
||||
+, Plus
|
||||
,, Comma
|
||||
Backspace
|
||||
Tab
|
||||
Space
|
||||
-, Minus
|
||||
., Period
|
||||
/, ForwardSlash
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
:, Colon
|
||||
;, Semicolon
|
||||
<, LessThan
|
||||
=, Equals
|
||||
>, GreaterThan
|
||||
?, Question
|
||||
@, At
|
||||
=, Plus, Equals
|
||||
[, LeftBracket
|
||||
\, BackSlash
|
||||
], RightBracket
|
||||
^, Caret
|
||||
_, Underscore
|
||||
\\, Backslash ## You do need the espacing in the first case.
|
||||
;, Semicolon
|
||||
', Quote ## Maps to the quote/doubleQuote key on QWERTY.
|
||||
`, BackQuote
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
O
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
||||
Delete, Del
|
||||
Numpad 0, Num0
|
||||
Numpad 1, Num1
|
||||
Numpad 2, Num2
|
||||
Numpad 3, Num3
|
||||
Numpad 4, Num4
|
||||
Numpad 5, Num5
|
||||
Numpad 6, Num6
|
||||
Numpad 7, Num7
|
||||
Numpad 8, Num8
|
||||
Numpad 9, Num9
|
||||
Numpad ., NumPoint
|
||||
Numpad /, NumDivide
|
||||
Numpad *, NumMultiply
|
||||
Numpad -, NumMinus
|
||||
Numpad +, NumPlus
|
||||
Numpad Enter, NumEnter
|
||||
Numpad =, NumEquals
|
||||
|
||||
Arrow Up, UpArrow
|
||||
Arrow Down, DownArrow
|
||||
Arrow Right, RightArrow
|
||||
Arrow Left, LeftArrow
|
||||
Insert, Ins
|
||||
Home
|
||||
End
|
||||
Page Up, PgUp
|
||||
Page Down, PgDn
|
||||
|
||||
,, Comma
|
||||
., Period
|
||||
/, Slash
|
||||
F1
|
||||
F2
|
||||
F3
|
||||
|
|
@ -114,41 +46,155 @@ F9
|
|||
F10
|
||||
F11
|
||||
F12
|
||||
PrintScreen
|
||||
ScrollLock
|
||||
Pause
|
||||
Insert
|
||||
Home
|
||||
PageUp
|
||||
Delete, Del
|
||||
End
|
||||
PageDown
|
||||
Right, RightArrow
|
||||
Left, LeftArrow
|
||||
Down, DownArrow
|
||||
Up, UpArrow
|
||||
Numlock
|
||||
Keypad /, NumDivide
|
||||
Keypad *, NumMultiply
|
||||
Keypad -, NumMinus
|
||||
Keypad +, NumPlus
|
||||
Keypad Enter, NumEnter
|
||||
Keypad 1, Num1
|
||||
Keypad 2, Num2
|
||||
Keypad 3, Num3
|
||||
Keypad 4, Num4
|
||||
Keypad 5, Num5
|
||||
Keypad 6, Num6
|
||||
Keypad 7, Num7
|
||||
Keypad 8, Num8
|
||||
Keypad 9, Num9
|
||||
Keypad 0, Num0
|
||||
Keypad ., NumDecimal
|
||||
Application
|
||||
Power
|
||||
Keypad =
|
||||
F13
|
||||
F14
|
||||
F15
|
||||
|
||||
Num Lock, NumLock
|
||||
Caps Lock, CapsLock
|
||||
Scroll Lock, ScrlLock
|
||||
Right Shift, RightShift
|
||||
Left Shift, LeftShift
|
||||
Shift, AnyShift
|
||||
Right Ctrl, RightCtrl
|
||||
Left Ctrl, LeftCtrl
|
||||
Ctrl, AnyCtrl
|
||||
Right Alt, RightAlt
|
||||
Left Alt, LeftAlt
|
||||
Alt, AnyAlt
|
||||
Left Super, LeftWin
|
||||
Right Super, RightWin
|
||||
Super, AnyWindows ## Windows key, also Command/meta key on Macs
|
||||
Alt Gr, AltGr
|
||||
Compose
|
||||
|
||||
F16
|
||||
F17
|
||||
F18
|
||||
F19
|
||||
F20
|
||||
F21
|
||||
F22
|
||||
F23
|
||||
F24
|
||||
Execute
|
||||
Help
|
||||
Print Screen, PrtSc
|
||||
SysRq
|
||||
Break
|
||||
Menu
|
||||
Power
|
||||
Euro
|
||||
Select
|
||||
Stop
|
||||
Again
|
||||
Undo
|
||||
Left Mouse Button, MouseLeft
|
||||
Right Mouse Button, MouseRight
|
||||
Middle Mouse Button, MouseMiddle
|
||||
Mouse Wheel Up, WheelUp
|
||||
Mouse Wheel Down, WheelDown
|
||||
MouseButtonX, MouseNX # where X is a number 1-255, for extra mouse buttons
|
||||
## (note that some mice start numbering their buttons at higher numbers
|
||||
## so the first extra button on your mouse might be #8 here)
|
||||
Cut
|
||||
Copy
|
||||
Paste
|
||||
Find
|
||||
Mute
|
||||
VolumeUp
|
||||
VolumeDown
|
||||
Keypad ,
|
||||
AltErase
|
||||
SysReq
|
||||
Cancel
|
||||
Clear
|
||||
Prior
|
||||
Return
|
||||
Separator
|
||||
Out
|
||||
Oper
|
||||
Clear / Again
|
||||
CrSel
|
||||
ExSel
|
||||
Keypad 00
|
||||
Keypad 000
|
||||
ThousandsSeparator
|
||||
DecimalSeparator
|
||||
CurrencyUnit
|
||||
CurrencySubUnit
|
||||
Keypad (
|
||||
Keypad )
|
||||
Keypad {
|
||||
Keypad }
|
||||
Keypad Tab
|
||||
Keypad Backspace
|
||||
Keypad A
|
||||
Keypad B
|
||||
Keypad C
|
||||
Keypad D
|
||||
Keypad E
|
||||
Keypad F
|
||||
Keypad XOR
|
||||
Keypad ^
|
||||
Keypad %
|
||||
Keypad <
|
||||
Keypad >
|
||||
Keypad &
|
||||
Keypad &&
|
||||
Keypad |
|
||||
Keypad ||
|
||||
Keypad :
|
||||
Keypad #
|
||||
Keypad Space
|
||||
Keypad @
|
||||
Keypad !
|
||||
Keypad MemStore
|
||||
Keypad MemRecall
|
||||
Keypad MemClear
|
||||
Keypad MemAdd
|
||||
Keypad MemSubtract
|
||||
Keypad MemMultiply
|
||||
Keypad MemDivide
|
||||
Keypad +/-
|
||||
Keypad Clear
|
||||
Keypad ClearEntry
|
||||
Keypad Binary
|
||||
Keypad Octal
|
||||
Keypad Decimal
|
||||
Keypad Hexadecimal
|
||||
Left Ctrl, Ctrl
|
||||
Left Shift, Shift
|
||||
Left Alt, Alt
|
||||
Left GUI, Super
|
||||
Right Ctrl, Ctrl
|
||||
Right Shift, Shift
|
||||
Right Alt, Alt
|
||||
Right GUI, Super
|
||||
ModeSwitch
|
||||
AudioNext
|
||||
AudioPrev
|
||||
AudioStop
|
||||
AudioPlay
|
||||
AudioMute
|
||||
MediaSelect
|
||||
WWW
|
||||
Mail
|
||||
Calculator
|
||||
Computer
|
||||
AC Search
|
||||
AC Home
|
||||
AC Back
|
||||
AC Forward
|
||||
AC Stop
|
||||
AC Refresh
|
||||
AC Bookmarks
|
||||
BrightnessDown
|
||||
BrightnessUp
|
||||
DisplaySwitch
|
||||
KBDIllumToggle
|
||||
KBDIllumDown
|
||||
KBDIllumUp
|
||||
Eject
|
||||
Sleep
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ece26cb75be5a95695c229025e8234b80422c9652c0c3b9087bd0667d1b99ab1
|
||||
size 436
|
||||
33
binaries/data/mods/public/gui/common/hotkeys.js
Normal file
33
binaries/data/mods/public/gui/common/hotkeys.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Holds a map of scancode name -> user keyboard name
|
||||
*/
|
||||
var g_ScancodesMap;
|
||||
|
||||
function hotkeySort(a, b)
|
||||
{
|
||||
const specialKeys = ["Shift", "Alt", "Ctrl", "Super"];
|
||||
// Quick hack to put those first.
|
||||
if (specialKeys.indexOf(a) !== -1)
|
||||
a = ' ' + a;
|
||||
if (specialKeys.indexOf(b) !== -1)
|
||||
b = ' ' + b;
|
||||
return a.localeCompare(b, Engine.GetCurrentLocale().substr(0, 2), { "numeric": true });
|
||||
}
|
||||
|
||||
function formatHotkeyCombination(comb, translateScancodes = true)
|
||||
{
|
||||
if (!translateScancodes)
|
||||
return comb.sort(hotkeySort).join("+");
|
||||
|
||||
if (!g_ScancodesMap)
|
||||
g_ScancodesMap = Engine.GetScancodeKeyNames();
|
||||
|
||||
return comb.sort(hotkeySort).map(hk => g_ScancodesMap[hk]).join("+");
|
||||
}
|
||||
|
||||
function formatHotkeyCombinations(combinations, translateScancodes = true)
|
||||
{
|
||||
let combs = combinations.map(x => formatHotkeyCombination(x, translateScancodes));
|
||||
combs.sort((a, b) => a.length - b.length || a - b);
|
||||
return translateScancodes ? combs.join(", ") : combs;
|
||||
}
|
||||
113
binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
Normal file
113
binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Handle the interface to pick a hotkey combination.
|
||||
* The player must keep a key combination for 2s in the input field for it to be registered.
|
||||
*/
|
||||
class HotkeyPicker
|
||||
{
|
||||
constructor(onClose, name, combinations)
|
||||
{
|
||||
this.name = name;
|
||||
this.combinations = combinations;
|
||||
this.window = Engine.GetGUIObjectByName("hotkeyPicker");
|
||||
this.window.hidden = false;
|
||||
|
||||
this.enteringInput = -1;
|
||||
|
||||
Engine.GetGUIObjectByName("hotkeyPickerTitle").caption = translate(this.name);
|
||||
|
||||
this.setupCombinations();
|
||||
this.render();
|
||||
|
||||
Engine.GetGUIObjectByName("hotkeyPickerReset").onPress = () => {
|
||||
// This is a bit "using a bazooka to kill a fly"
|
||||
Engine.ConfigDB_RemoveValue("user", "hotkey." + this.name);
|
||||
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
|
||||
Engine.ReloadHotkeys();
|
||||
let data = Engine.GetHotkeyMap();
|
||||
this.combinations = data[this.name];
|
||||
this.setupCombinations();
|
||||
this.render();
|
||||
};
|
||||
Engine.GetGUIObjectByName("hotkeyPickerCancel").onPress = () => {
|
||||
onClose(this, false);
|
||||
};
|
||||
Engine.GetGUIObjectByName("hotkeyPickerSave").onPress = () => {
|
||||
onClose(this, true);
|
||||
};
|
||||
}
|
||||
|
||||
setupCombinations()
|
||||
{
|
||||
for (let i = 0; i < 4; ++i)
|
||||
{
|
||||
let s = Engine.GetGUIObjectByName("combination[" + i + "]").size;
|
||||
s.top = +i * 60 + 90;
|
||||
s.bottom = +i * 60 + 120;
|
||||
Engine.GetGUIObjectByName("combination[" + i + "]").size = s;
|
||||
Engine.GetGUIObjectByName("combNb[" + i + "]").caption = sprintf(translate("#%i"), i);
|
||||
|
||||
if (i == this.combinations.length)
|
||||
this.combinations.push([]);
|
||||
|
||||
let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
|
||||
|
||||
let picker = Engine.GetGUIObjectByName("picker[" + i + "]");
|
||||
Engine.GetGUIObjectByName("combMappingBtn[" + i + "]").onPress = () => {
|
||||
this.enteringInput = i;
|
||||
picker.focus();
|
||||
this.render();
|
||||
};
|
||||
|
||||
picker.onKeyChange = keys => {
|
||||
input.caption = (keys.length ?
|
||||
formatHotkeyCombination(keys) + translate(" (hold to register)") :
|
||||
translate("Enter new Hotkey, hold to register."));
|
||||
};
|
||||
|
||||
Engine.GetGUIObjectByName("deleteComb[" + i + "]").onPress = (j => () => {
|
||||
this.combinations[j] = [];
|
||||
this.render();
|
||||
})(i);
|
||||
|
||||
picker.onCombination = (j => keys => {
|
||||
this.combinations[j] = keys;
|
||||
this.enteringInput = -1;
|
||||
picker.blur();
|
||||
|
||||
this.render();
|
||||
})(i);
|
||||
}
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
this.window.hidden = true;
|
||||
for (let i = 0; i < 4; ++i)
|
||||
Engine.GetGUIObjectByName("picker[" + i + "]").blur();
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
for (let i = 0; i < 4; ++i)
|
||||
{
|
||||
let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
|
||||
if (i == this.enteringInput)
|
||||
input.caption = translate("Enter new Hotkey, hold to register.");
|
||||
else
|
||||
input.caption = formatHotkeyCombination(this.combinations[i]) || "(unused)";
|
||||
Engine.GetGUIObjectByName("conflicts[" + i + "]").caption = "";
|
||||
|
||||
Engine.GetGUIObjectByName("deleteComb[" + i + "]").hidden = !this.combinations[i].length;
|
||||
|
||||
let conflicts = (Engine.GetConflicts(this.combinations[i]) || [])
|
||||
.filter(name => name != this.name).map(translate);
|
||||
if (conflicts.length)
|
||||
Engine.GetGUIObjectByName("conflicts[" + i + "]").caption =
|
||||
coloredText(translate("May conflict with: "), "255 153 0") + conflicts.join(", ");
|
||||
}
|
||||
// Gray out buttons when entering an input so it's obvious clicking on them will do nothing.
|
||||
Engine.GetGUIObjectByName("hotkeyPickerReset").enabled = this.enteringInput == -1;
|
||||
Engine.GetGUIObjectByName("hotkeyPickerCancel").enabled = this.enteringInput == -1;
|
||||
Engine.GetGUIObjectByName("hotkeyPickerSave").enabled = this.enteringInput == -1;
|
||||
}
|
||||
}
|
||||
177
binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
Normal file
177
binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
class HotkeysPage
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
Engine.GetGUIObjectByName("hotkeyList").onMouseLeftDoubleClickItem = () => {
|
||||
let idx = Engine.GetGUIObjectByName("hotkeyList").selected;
|
||||
let picker = new HotkeyPicker(
|
||||
this.onHotkeyPicked.bind(this),
|
||||
Engine.GetGUIObjectByName("hotkeyList").list_data[idx],
|
||||
this.hotkeys[Engine.GetGUIObjectByName("hotkeyList").list_data[idx]]
|
||||
);
|
||||
};
|
||||
Engine.GetGUIObjectByName("hotkeyFilter").onSelectionChange = () => this.setupHotkeyList();
|
||||
|
||||
Engine.GetGUIObjectByName("hotkeyTextFilter").onTextEdit = () => this.setupHotkeyList();
|
||||
|
||||
Engine.GetGUIObjectByName("hotkeyClose").onPress = () => Engine.PopGuiPage();
|
||||
Engine.GetGUIObjectByName("hotkeyReset").onPress = () => this.resetUserHotkeys();
|
||||
Engine.GetGUIObjectByName("hotkeySave").onPress = () => {
|
||||
this.saveUserHotkeys();
|
||||
};
|
||||
|
||||
this.setupHotkeyData();
|
||||
this.setupFilters();
|
||||
this.setupHotkeyList();
|
||||
}
|
||||
|
||||
setupFilters()
|
||||
{
|
||||
let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
|
||||
let names = [];
|
||||
for (let cat in this.categories)
|
||||
names.push(this.categories[cat].label);
|
||||
dropdown.list = [translate("All Hotkeys")].concat(names);
|
||||
dropdown.list_data = [-1].concat(Object.keys(this.categories));
|
||||
dropdown.selected = 0;
|
||||
}
|
||||
|
||||
setupHotkeyList()
|
||||
{
|
||||
let hotkeyList = Engine.GetGUIObjectByName("hotkeyList");
|
||||
hotkeyList.selected = -1;
|
||||
let textFilter = Engine.GetGUIObjectByName("hotkeyTextFilter").caption;
|
||||
let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
|
||||
if (dropdown.selected && dropdown.selected !== 0)
|
||||
{
|
||||
let category = this.categories[dropdown.list_data[dropdown.selected]];
|
||||
// This is inefficient but it seems fast enough.
|
||||
let hotkeys = category.hotkeys.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
|
||||
hotkeyList.list_name = hotkeys.map(x => translate(x[0]));
|
||||
hotkeyList.list_mapping = hotkeys.map(x => formatHotkeyCombinations(x[1]));
|
||||
hotkeyList.list = hotkeys.map(() => 0);
|
||||
hotkeyList.list_data = hotkeys.map(x => x[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO SM62+ : refactor using flat()
|
||||
let flattened = [];
|
||||
for (let cat in this.categories)
|
||||
flattened = flattened.concat(this.categories[cat].hotkeys);
|
||||
flattened = flattened.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
|
||||
hotkeyList.list_name = flattened.map(x => translate(x[0]));
|
||||
hotkeyList.list_mapping = flattened.map(x => formatHotkeyCombinations(x[1]));
|
||||
hotkeyList.list = flattened.map(() => 0);
|
||||
hotkeyList.list_data = flattened.map(x => x[0]);
|
||||
}
|
||||
}
|
||||
|
||||
onHotkeyPicked(picker, success)
|
||||
{
|
||||
picker.close();
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
// Remove empty combinations which the picker added.
|
||||
picker.combinations = picker.combinations.filter(x => x.length);
|
||||
|
||||
this.hotkeys[picker.name] = picker.combinations;
|
||||
// Have to find the correct line.
|
||||
let panel = Engine.GetGUIObjectByName("hotkeyList");
|
||||
for (let cat in this.categories)
|
||||
{
|
||||
let idx = this.categories[cat].hotkeys.findIndex(([name, _]) => name == picker.name);
|
||||
if (idx === -1)
|
||||
continue;
|
||||
this.categories[cat].hotkeys[idx][1] = picker.combinations;
|
||||
}
|
||||
|
||||
this.setupHotkeyList();
|
||||
}
|
||||
|
||||
setupHotkeyData()
|
||||
{
|
||||
let hotkeydata = Engine.GetHotkeyMap();
|
||||
this.hotkeys = hotkeydata;
|
||||
|
||||
let categories = {
|
||||
"other": {
|
||||
"label": translate("Other hotkeys"),
|
||||
"hotkeys": []
|
||||
}
|
||||
};
|
||||
let n_categories = 1;
|
||||
for (let hotkeyName in this.hotkeys)
|
||||
{
|
||||
let category = "other";
|
||||
let firstdot = hotkeyName.indexOf('.');
|
||||
if (firstdot !== -1)
|
||||
category = hotkeyName.substr(0, firstdot);
|
||||
if (!(category in categories))
|
||||
{
|
||||
if (n_categories > 18)
|
||||
category = "other";
|
||||
categories[category] = {
|
||||
"label": category,
|
||||
"hotkeys": []
|
||||
};
|
||||
}
|
||||
categories[category].hotkeys.push([hotkeyName, this.hotkeys[hotkeyName]]);
|
||||
}
|
||||
// Remove categories that are too small to deserve a tab.
|
||||
for (let cat of Object.keys(categories))
|
||||
if (categories[cat].hotkeys.length < 3)
|
||||
{
|
||||
categories.other.hotkeys = categories.other.hotkeys.concat(categories[cat].hotkeys);
|
||||
delete categories[cat];
|
||||
}
|
||||
for (let cat in categories)
|
||||
categories[cat].hotkeys = categories[cat].hotkeys.sort();
|
||||
|
||||
this.categories = categories;
|
||||
}
|
||||
|
||||
resetUserHotkeys()
|
||||
{
|
||||
messageBox(
|
||||
400, 200,
|
||||
translate("Reset all hotkeys to default values?"),
|
||||
translate("Confirmation"),
|
||||
[translate("No"), translate("Yes")],
|
||||
[
|
||||
() => {},
|
||||
() => {
|
||||
for (let cat in this.categories)
|
||||
this.categories[cat].hotkeys.forEach(([name, _]) => {
|
||||
Engine.ConfigDB_RemoveValue("user", "hotkey." + name);
|
||||
});
|
||||
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
|
||||
Engine.ReloadHotkeys();
|
||||
this.setupHotkeyData();
|
||||
this.setupHotkeyList();
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
saveUserHotkeys()
|
||||
{
|
||||
for (let hotkey in this.hotkeys)
|
||||
Engine.ConfigDB_RemoveValue("user", "hotkey." + hotkey);
|
||||
Engine.ReloadHotkeys();
|
||||
let defaultData = Engine.GetHotkeyMap();
|
||||
for (let hotkey in this.hotkeys)
|
||||
{
|
||||
let keymap = formatHotkeyCombinations(this.hotkeys[hotkey], false);
|
||||
if (keymap.join("") !== formatHotkeyCombinations(defaultData[hotkey], false).join(""))
|
||||
Engine.ConfigDB_CreateValues("user", "hotkey." + hotkey, keymap);
|
||||
}
|
||||
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
|
||||
Engine.ReloadHotkeys();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function init(data)
|
||||
{
|
||||
let hotkeyPage = new HotkeysPage(data);
|
||||
}
|
||||
112
binaries/data/mods/public/gui/hotkeys/hotkeys.xml
Normal file
112
binaries/data/mods/public/gui/hotkeys/hotkeys.xml
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/hotkeys/"/>
|
||||
|
||||
<object type="image" sprite="BackgroundTranslucent"/>
|
||||
|
||||
<!-- Hotkey List Window -->
|
||||
<object name="hotkeys" type="image" style="ModernDialog" size="50%-350 50%-344 50%+350 50%+344">
|
||||
|
||||
<object style="ModernLabelText" type="text" size="50%-128 -16 50%+128 16">
|
||||
<translatableAttribute id="caption">Hotkeys</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object style="ModernLabelText" type="text" size="32 32 132 58" text_align="left">
|
||||
<translatableAttribute id="caption">Category:</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyFilter" type="dropdown" style="ModernDropDown" size="132 32 300 58"/>
|
||||
|
||||
<object style="ModernLabelText" type="text" size="100%-300 32 100%-200 58" text_align="left">
|
||||
<translatableAttribute id="caption">Filter:</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyTextFilter" type="input" style="ModernInput" size="100%-200 32 100%-32 58"/>
|
||||
|
||||
<object name="hotkeyList"
|
||||
style="ModernSortedList"
|
||||
selected_column="name"
|
||||
selected_column_order="-1"
|
||||
sortable="false"
|
||||
size="32 70 100%-32 100%-70"
|
||||
type="olist"
|
||||
auto_scroll="true"
|
||||
>
|
||||
<column id="name" color="255 255 255" width="60%">
|
||||
<translatableAttribute id="heading" context="hotkey list">Name</translatableAttribute>
|
||||
</column>
|
||||
|
||||
<column id="mapping" color="255 255 255" width="40%">
|
||||
<translatableAttribute id="heading" context="hotkey list">Mapping</translatableAttribute>
|
||||
</column>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyReset" type="button" size="32 100%-52 188 100%-24" style="ModernButtonRed">
|
||||
<translatableAttribute id="caption">Reset</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">Resets user settings to their game default</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeySave" type="button" size="100%-352 100%-52 100%-196 100%-24" style="ModernButtonRed">
|
||||
<translatableAttribute id="caption">Save</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">Saves changes</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyClose" type="button" size="100%-188 100%-52 100%-32 100%-24" style="ModernButtonRed" hotkey="cancel">
|
||||
<translatableAttribute id="caption">Close</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">Unsaved changes will be lost</translatableAttribute>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Hotkey Picker popup -->
|
||||
|
||||
<object name="hotkeyPicker" type="image" sprite="BackgroundTranslucent" hidden="true" z="100">
|
||||
<object type="image"
|
||||
style="ModernDialog"
|
||||
size="50%-300 50%-190 50%+300 50%+190"
|
||||
>
|
||||
<object name="hotkeyPickerTitle" style="ModernLabelText" type="text" size="50%-128 -16 50%+128 16">
|
||||
<translatableAttribute id="caption">Hotkey</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyPickerDesc" style="ModernLabelText" type="text" size="8 20 100%-8 66">
|
||||
<translatableAttribute id="caption">Click on any mapping to modify it.\n You may have up to 4 different hotkeys.</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<repeat count="4" var="n">
|
||||
<object name="combination[n]" size="8 40 100%-8 70" hidden="false">
|
||||
<object name="combNb[n]" style="ModernLabelText" type="text" size="0 0 20 100%" text_align="left"/>
|
||||
<object name="picker[n]" type="hotkeypicker"/>
|
||||
<object name="combMapping[n]" style="ModernInput" type="input" readonly="true" size="30 2 60% 100%-2"/>
|
||||
<!-- Used to detect clicks on the input. -->
|
||||
<object name="combMappingBtn[n]" type="button" size="30 2 60% 100%-2">
|
||||
<translatableAttribute id="tooltip">Click to set the hotkey</translatableAttribute>
|
||||
</object>
|
||||
<object name="deleteComb[n]" type="button" size="60%+10 5 60%+30 100%-5"
|
||||
sprite="crossRed">
|
||||
<translatableAttribute id="tooltip">Click to delete the hotkey</translatableAttribute>
|
||||
</object>
|
||||
<object name="conflicts[n]" style="ModernLabelText" type="text" size="60%+40 2 100%-8 100%-2" clip="false" text_valign="center"/>
|
||||
</object>
|
||||
</repeat>
|
||||
|
||||
<object name="hotkeyPickerReset" type="button" size="16 100%-52 172 100%-24" style="ModernButtonRed">
|
||||
<translatableAttribute id="caption">Reset</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">Resets user settings to their game default</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyPickerSave" type="button" size="100%-336 100%-52 100%-180 100%-24" style="ModernButtonRed">
|
||||
<translatableAttribute id="caption">Accept</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">Change the hotkeys and close</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="hotkeyPickerCancel" type="button" size="100%-172 100%-52 100%-16 100%-24" style="ModernButtonRed" hotkey="cancel">
|
||||
<translatableAttribute id="caption">Cancel</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">The hotkeys will not be modified</translatableAttribute>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</objects>
|
||||
15
binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
Normal file
15
binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<page>
|
||||
<include>common/modern/setup.xml</include>
|
||||
<include>common/modern/styles.xml</include>
|
||||
<include>common/modern/sprites.xml</include>
|
||||
|
||||
<include>common/setup.xml</include>
|
||||
<include>common/sprites.xml</include>
|
||||
<include>common/styles.xml</include>
|
||||
|
||||
<include>common/global.xml</include>
|
||||
|
||||
<include>hotkeys/sprites.xml</include>
|
||||
<include>hotkeys/hotkeys.xml</include>
|
||||
</page>
|
||||
11
binaries/data/mods/public/gui/hotkeys/sprites.xml
Normal file
11
binaries/data/mods/public/gui/hotkeys/sprites.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<sprites>
|
||||
<sprite name="crossRed">
|
||||
<effect add_color="180 0 0"/>
|
||||
<image
|
||||
texture="global/icon/cross.png"
|
||||
size="0 0 100% 100%"
|
||||
/>
|
||||
</sprite>
|
||||
</sprites>
|
||||
|
|
@ -7,13 +7,13 @@ You can switch between fullscreen and windowed mode by pressing Alt + Enter. In
|
|||
[font="sans-bold-16"]Playing the game[font="sans-14"]
|
||||
The controls and gameplay should be familiar to players of traditional real-time strategy (RTS) games. There are currently a lot of missing features and poorly-balanced stats – you will probably have to wait until a beta release for it to work well.
|
||||
|
||||
Basic controls:
|
||||
Basic controls (by default):
|
||||
• Left-click to select units
|
||||
• Left-click-and-drag to select groups of units
|
||||
• Right-click to order units to the target
|
||||
• Arrow keys or WASD keys to move the camera
|
||||
• Ctrl + arrow keys, or shift + mouse wheel, to rotate the camera
|
||||
• Mouse wheel, or “=”/“+” and “-”/“−” keys, to zoom
|
||||
• Mouse wheel, or “hotkey.camera.zoom.in” and ““hotkey.camera.zoom.out” keys, to zoom
|
||||
|
||||
[font="sans-bold-16"]Modes[font="sans-14"]
|
||||
The main menu gives access to two game modes:
|
||||
|
|
@ -37,122 +37,118 @@ Finally change the settings. For random maps this includes the number of players
|
|||
When you are ready to start, click the “Start game” button.
|
||||
|
||||
[font="sans-bold-16"]Hotkeys[font="sans-14"]
|
||||
You may change hotkeys in [font="sans-bold-14"]Options > Hotkeys[font="sans-14"] to suit your liking.
|
||||
|
||||
[font="sans-bold-14"]Program-wide[font="sans-14"]
|
||||
Alt + F4 – Immediately close the game, without asking for confirmation
|
||||
Alt + Enter (Return) – Toggle between fullscreen and windowed
|
||||
~ or F9 – Toggle console
|
||||
Alt + F – Toggle frame counter (FPS)
|
||||
F11 – Toggle real-time profiler (cycles through the displays of information)
|
||||
Shift + F11 – Save current profiler data to “logs/profile.txt”
|
||||
F2 – Take screenshot (in .png format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
|
||||
Shift + F2 – Take huge screenshot (6400×4800 pixels, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
|
||||
Tab, Alt + S – Switch to the next tab
|
||||
Shift + Tab, Alt + W – Switch to the previous tab
|
||||
hotkey.exit – Immediately close the game, without asking for confirmation
|
||||
hotkey.togglefullscreen – Toggle between fullscreen and windowed
|
||||
hotkey.console.toggle – Toggle console
|
||||
hotkey.fps.toggle – Toggle frame counter (FPS)
|
||||
hotkey.profile.toggle – Toggle real-time profiler (cycles through the displays of information)
|
||||
hotkey.profile.save – Save current profiler data to “logs/profile.txt”
|
||||
hotkey.screenshot – Take screenshot (in .png format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
|
||||
hotkey.bigscreenshot – Take huge screenshot (6400×4800 pixels, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
|
||||
hotkey.tab.next – Switch to the next tab
|
||||
hotkey.tab.prev – Switch to the previous tab
|
||||
|
||||
[font="sans-bold-14"]In Game[font="sans-14"]
|
||||
Double Left Click \[on unit] – Select all of your units of the same kind on the screen (even if they're different ranks)
|
||||
Triple Left Click \[on unit] – Select all of your units of the same kind and the same rank on the screen
|
||||
Alt + Double Left Click \[on unit] – Select all your units of the same kind on the entire map (even if they have different ranks)
|
||||
Alt + Triple Left Click \[on unit] – Select all your units of the same kind and rank on the entire map
|
||||
Shift + F5 – Quicksave
|
||||
Shift + F8 – Quickload
|
||||
F10 – Toggle menu
|
||||
F12 – Toggle time elapsed counter
|
||||
Esc – Close all dialogs (chat, menu) or clear selected units
|
||||
Enter (Return) – Open chat or send message
|
||||
T – Send team chat
|
||||
L – Chat with the previously selected private chat partner
|
||||
Pause – Pause or resume the game
|
||||
Delete – Delete currently selected unit(s)/structure(s), ask for confirmation
|
||||
Shift + Delete – Immediately delete currently selected unit(s)/structure(s), without asking for confirmation
|
||||
/ (Slash) – Select idle fighter
|
||||
Shift + / (Slash) – Add idle fighter to selection
|
||||
Alt + / (Slash) – Select all idle fighters
|
||||
. (Period) – Select idle worker (including citizen-soldiers)
|
||||
Shift + . (Period) – Add idle worker to selection (including citizen-soldiers)
|
||||
Alt + . (Period) – Select all idle workers (including citizen-soldiers)
|
||||
\\ (Backslash) – Select idle unit
|
||||
Shift + \\ (Backslash) – Add idle unit to selection
|
||||
Alt + \\ (Backslash) – Select all idle units
|
||||
H – Stop (halt) the currently selected units
|
||||
Y – The unit will go back to work
|
||||
U – Unload the garrisoned units of the selected structure(s)
|
||||
Ctrl + 1 (and so on up to Ctrl + 0) – Create control group 1 (to 0) from the selected unit(s)/structure(s)
|
||||
1 (and so on up to 0) – Select the unit(s)/structure(s) in control group 1 (to 0)
|
||||
Shift + 1 (to 0) – Add control group 1 (to 0) to the selected unit(s)/structure(s)
|
||||
Ctrl + F5 (and so on up to F8) – Mark the current camera position, for jumping back to later
|
||||
F5 (and so on up to F8) – Move the camera to a marked position. Jump back to the last location if the camera is already over the marked position
|
||||
Z, X, C, V, B, N, M – With training structure selected. Add the 1st, 2nd, … unit shown to the training queue for all the selected structures
|
||||
PageUp with unit(s) selected – Highlight the unit(s)/structure(s) guarded by the selection
|
||||
PageDown with unit(s)/structure(s) selected – Highlight the unit(s) guarding the selection
|
||||
Tab – See all status bars (which would also show the building progress)
|
||||
Ctrl + Tab – Toggle summary window
|
||||
Alt + L – Show the multiplayer lobby in a dialog window
|
||||
Ctrl + H – Toggle in-game diplomacy panel
|
||||
Ctrl + B – Toggle in-game barter and trade panel
|
||||
Ctrl + O – Toggle in-game objectives panel
|
||||
Ctrl + P – Toggle in-game tutorial panel
|
||||
Alt + Shift + T – Toggle structure tree panel
|
||||
Alt + Shift + H – Toggle civilization info panel
|
||||
hotkey.quicksave – Quicksave
|
||||
hotkey.quickload – Quickload
|
||||
hotkey.session.gui.menu.toggle – Toggle menu
|
||||
hotkey.timeelapsedcounter.toggle – Toggle time elapsed counter
|
||||
hotkey.cancel – Close all dialogs (chat, menu)
|
||||
hotkey.confirm – Open chat or send message
|
||||
hotkey.teamchat – Send team chat
|
||||
hotkey.privatechat – Chat with the previously selected private chat partner
|
||||
hotkey.pause – Pause or resume the game
|
||||
hotkey.session.kill – Delete currently selected unit(s)/structure(s), ask for confirmation
|
||||
• With hotkey.session.noconfirmation – Immediately delete currently selected unit(s)/structure(s), without asking for confirmation
|
||||
hotkey.selection.add – Modifier - add to selection (works with clicking and hotkeys, e.g. the idle hotkeys)
|
||||
hotkey.selection.remove – Modifier - remove from selection (works with clicking and hotkeys, e.g. the idle hotkeys)
|
||||
hotkey.selection.offscreen - Modifier - add all units, including offscreen units, to selection.
|
||||
hotkey.selection.cancel – Unselect all units, cancel building placement.
|
||||
hotkey.selection.idlewarrior – Select idle fighter
|
||||
hotkey.selection.idleworker – Select idle worker (including citizen-soldiers)
|
||||
hotkey.selection.idleunit – Select idle unit
|
||||
hotkey.session.stop – Stop (halt) the currently selected units
|
||||
hotkey.session.backtowork – The unit will go back to work
|
||||
hotkey.session.unload – Unload the garrisoned units of the selected structure(s)
|
||||
hotkey.selection.group.save.1 – Create control group 1 (by default, respectively 2,3, ...) from the selected unit(s)/structure(s)
|
||||
hotkey.selection.group.select.1 - Select the unit(s)/structure(s) in control group 1
|
||||
hotkey.selection.group.add.1 – Add control group 1 (to 0) to the selected unit(s)/structure(s)
|
||||
hotkey.camera.jump.set.1 – Mark the current camera position, for jumping back to later (by default, there are 4 possible positions)
|
||||
hotkey.camera.jump.1 – Move the camera to a marked position. Jump back to the last location if the camera is already over the marked position
|
||||
hotkey.session.queueunit.1, hotkey.session.queueunit.2, hotkey.session.queueunit.3, hotkey.session.queueunit.4, hotkey.session.queueunit.5, hotkey.session.queueunit.6, hotkey.session.queueunit.7 – With training structure selected. Add the 1st, 2nd, … unit shown to the training queue for all the selected structures
|
||||
hotkey.session.highlightguarded with unit(s) selected – Highlight the unit(s)/structure(s) guarded by the selection
|
||||
hotkey.session.highlightguarding with unit(s)/structure(s) selected – Highlight the unit(s) guarding the selection
|
||||
highlightguarding.showstatusbars – See all status bars (which would also show the building progress)
|
||||
hotkey.summary – Toggle summary window
|
||||
hotkey.lobby – Show the multiplayer lobby in a dialog window
|
||||
hotkey.session.gui.diplomacy.toggle – Toggle in-game diplomacy panel
|
||||
hotkey.session.gui.barter.toggle – Toggle in-game barter and trade panel
|
||||
hotkey.session.gui.objectives.toggle – Toggle in-game objectives panel
|
||||
hotkey.session.gui.tutorial.toggle – Toggle in-game tutorial panel
|
||||
hotkey.structree – Toggle structure tree panel
|
||||
hotkey.civinfo – Toggle civilization info panel
|
||||
|
||||
[font="sans-bold-14"]Modify mouse action[font="sans-14"]
|
||||
Ctrl + Right Click on structure – Garrison
|
||||
J + Right Click on structure – Repair
|
||||
P + Right Click – Patrol
|
||||
Shift + Right Click – Queue the move/build/gather/etc. order
|
||||
Alt + Right Click – Order one unit from the current selection to move/build/gather/etc. and unselect it. Used to quickly dispatch units with specific tasks
|
||||
Shift + Left Click when training units – Add units in batches (the batch size is 5 by default and can be changed in the options)
|
||||
Shift + Left Click or Left Drag over unit on map – Add unit to selection
|
||||
Ctrl + Left Click or Left Drag over unit on map – Remove unit from selection
|
||||
Alt + Left Drag over units on map – Only select military units
|
||||
Alt + Y + Left Drag over units on map – Only select non-military units
|
||||
I + Left Drag over units on map – Only select idle units
|
||||
O + Left Drag over units on map – Only select wounded units
|
||||
Ctrl + Left Click on unit/group icon with multiple units selected – Deselect
|
||||
hotkey.session.garrison + Right Click on structure – Garrison
|
||||
hotkey.session.repair + Right Click on structure – Repair
|
||||
hotkey.session.patrol + Right Click – Patrol
|
||||
hotkey.session.queue + Right Click – Queue the move/build/gather/etc. order
|
||||
hotkey.session.orderone + Right Click – Order one unit from the current selection to move/build/gather/etc. and unselect it. Used to quickly dispatch units with specific tasks
|
||||
hotkey.session.batchtrain + Left Click when training units – Add units in batches (the batch size is 5 by default and can be changed in the options)
|
||||
hotkey.selection.add + Left Click or Left Drag over unit on map – Add unit to selection
|
||||
hotkey.selection.remove + Left Click or Left Drag over unit on map – Remove unit from selection
|
||||
hotkey.selection.militaryonly + Left Drag over units on map – Only select military units
|
||||
hotkey.selection.nonmilitaryonly + Left Drag over units on map – Only select non-military units
|
||||
hotkey.selection.idleonly + Left Drag over units on map – Only select idle units
|
||||
hotkey.selection.woundedonly + Left Drag over units on map – Only select wounded units
|
||||
Right Click with a structure(s) selected – Set a rally point for units created/ungarrisoned from that structure
|
||||
Ctrl + Right Click with unit(s) selected:
|
||||
• If the cursor is over an own or allied structure – Garrison
|
||||
• If the cursor is over an enemy unit/structure – Attack (instead of capture or gather)
|
||||
• Otherwise – Attack move (by default all enemy units and structures along the way are targeted)
|
||||
Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
|
||||
Ctrl + Mouse Move near structures – Align the new structure with an existing nearby structure
|
||||
hotkey.session.garrison + Right Click with unit(s) selected - Garrison (If the cursor is over an own or allied structure)
|
||||
hotkey.session.attack + Right Click with unit(s) selected - Attack (instead of capture or gather)
|
||||
hotkey.session.attackmove + Right Click with unit(s) selected - Attack move (by default all enemy units and structures along the way are targeted)
|
||||
hotkey.session.attackmoveUnit + Right Click with unit(s) selected - Attack move, only units along the way are targeted
|
||||
hotkey.session.snaptoedges + Mouse Move near structures – Align the new structure with an existing nearby structure
|
||||
|
||||
[font="sans-bold-14"]Overlays[font="sans-14"]
|
||||
Alt + G – Toggle the GUI
|
||||
Alt + D – Toggle developer overlay (with developer options)
|
||||
Alt + Shift + W – Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode)
|
||||
Alt + Shift + S – Toggle unit silhouettes (might give a small performance boost)
|
||||
Alt + Z – Toggle sky
|
||||
Alt + X – Toggle diplomacy colors
|
||||
Alt + C – Toggle attack range visualizations of selected units and structures
|
||||
Alt + V – Toggle aura range visualizations of selected units and structures
|
||||
Alt + B – Toggle heal range visualizations of selected units
|
||||
hotkey.session.gui.toggle – Toggle the GUI
|
||||
hotkey.session.devcommands.toggle – Toggle developer overlay (with developer options)
|
||||
hotkey.wireframe – Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode)
|
||||
hotkey.silhouettes – Toggle unit silhouettes (might give a small performance boost)
|
||||
hotkey.showsky – Toggle sky
|
||||
hotkey.session.diplomacycolors – Toggle diplomacy colors
|
||||
hotkey.session.toggleattackrange – Toggle attack range visualizations of selected units and structures
|
||||
hotkey.session.toggleaurasrange – Toggle aura range visualizations of selected units and structures
|
||||
hotkey.session.togglehealrange – Toggle heal range visualizations of selected units
|
||||
|
||||
[font="sans-bold-14"]Camera manipulation[font="sans-14"]
|
||||
W or ↑ (Up) – Pan screen up
|
||||
S or ↓ (Down) – Pan screen down
|
||||
A or ← (Left) – Pan screen left
|
||||
D or → (Right) – Pan screen right
|
||||
Ctrl + W or Ctrl + ↑ (Up) – Rotate camera to look upward
|
||||
Ctrl + S or Ctrl + ↓ (Down) – Rotate camera to look downward
|
||||
Ctrl + A or Ctrl + ← (Left) – Rotate camera clockwise around terrain
|
||||
Ctrl + D or Ctrl + → (Right) – Rotate camera counter-clockwise around terrain
|
||||
Q – Rotate camera clockwise around terrain
|
||||
E – Rotate camera counter-clockwise around terrain
|
||||
Shift + Mouse Wheel Rotate Up – Rotate camera clockwise around terrain
|
||||
Shift + Mouse Wheel Rotate Down – Rotate camera counter-clockwise around terrain
|
||||
F – Follow the selected unit (move the camera to stop following the unit)
|
||||
R – Reset camera zoom and orientation
|
||||
= (Equals) or + (Plus) – Zoom in (keep pressed for continuous zoom)
|
||||
- (Hyphen) or − (Minus) – Zoom out (keep pressed for continuous zoom)
|
||||
Middle Mouse Button – Keep pressed and move the mouse to pan
|
||||
hotkey.camera.up – Pan screen up
|
||||
hotkey.camera.down – Pan screen down
|
||||
hotkey.camera.left – Pan screen left
|
||||
hotkey.camera.right – Pan screen right
|
||||
hotkey.camera.rotate.up – Rotate camera to look upward
|
||||
hotkey.camera.rotate.down – Rotate camera to look downward
|
||||
hotkey.camera.rotate.cw – Rotate camera clockwise around terrain
|
||||
hotkey.camera.rotate.ccw – Rotate camera counter-clockwise around terrain
|
||||
hotkey.camera.rotate.wheel.cw – Rotate camera clockwise around terrain
|
||||
hotkey.camera.rotate.wheel.ccw – Rotate camera counter-clockwise around terrain
|
||||
hotkey.camera.follow – Follow the selected unit (move the camera to stop following the unit)
|
||||
hotkey.camera.reset – Reset camera zoom and orientation
|
||||
hotkey.camera.zoom.in, hotkey.camera.zoom.wheel.in – Zoom in (keep pressed for continuous zoom)
|
||||
hotkey.camera.zoom.out, hotkey.camera.zoom.wheel.out – Zoom out (keep pressed for continuous zoom)
|
||||
hotkey.camera.pan – Keep pressed and move the mouse to pan
|
||||
|
||||
[font="sans-bold-14"]During Structure Placement[font="sans-14"]
|
||||
\[ (Left Bracket) – Rotate structure 15 degrees counter-clockwise
|
||||
] (Right Bracket) – Rotate structure 15 degrees clockwise
|
||||
hotkey.session.rotate.ccw – Rotate structure 15 degrees counter-clockwise
|
||||
hotkey.session.rotate.cw – Rotate structure 15 degrees clockwise
|
||||
Left Drag – Rotate structure using mouse (foundation will be placed on mouse release)
|
||||
|
||||
[font="sans-bold-14"]When loading a saved game[font="sans-14"]
|
||||
Esc – Cancel
|
||||
Delete – Delete the selected saved game, ask for confirmation
|
||||
Shift + Delete – Immediately delete the selected saved game, without asking for confirmation
|
||||
hotkey.cancel – Cancel
|
||||
hotkey.session.savedgames.delete – Delete the selected saved game, ask for confirmation
|
||||
• With hotkey.session.savedgames.noconfirmation – Don't ask for confirmation
|
||||
|
|
|
|||
10
binaries/data/mods/public/gui/manual/manual.js
Normal file
10
binaries/data/mods/public/gui/manual/manual.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
function init()
|
||||
{
|
||||
let mainText = Engine.GetGUIObjectByName("mainText");
|
||||
let text = Engine.TranslateLines(Engine.ReadFile("gui/manual/intro.txt"));
|
||||
|
||||
let hotkeys = Engine.GetHotkeyMap();
|
||||
|
||||
// Replace anything starting with 'hotkey.' with its hotkey.
|
||||
mainText.caption = text.replace(/hotkey\.([a-z0-9_\.]+)/g, (_, k) => formatHotkeyCombinations(hotkeys[k]));
|
||||
}
|
||||
|
|
@ -14,9 +14,7 @@
|
|||
</object>
|
||||
|
||||
<object type="image" sprite="ModernFade" size="20 20 100%-20 100%-58">
|
||||
<object name="mainText" type="text" style="ModernTextPanel">
|
||||
<action on="Load">this.caption = Engine.TranslateLines(Engine.ReadFile("gui/manual/intro.txt"));</action>
|
||||
</object>
|
||||
<object name="mainText" type="text" style="ModernTextPanel" />
|
||||
</object>
|
||||
|
||||
<object type="button" style="ModernButtonRed" tooltip_style="snToolTip" size="100%-408 100%-52 100%-218 100%-24" hotkey="cancel">
|
||||
|
|
|
|||
|
|
@ -148,6 +148,13 @@ var g_MainMenuItems = [
|
|||
fireConfigChangeHandlers);
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": translate("Hotkeys"),
|
||||
"tooltip": translate("Adjust hotkeys."),
|
||||
"onPress": () => {
|
||||
Engine.PushGuiPage("hotkeys/page_hotkeys.xml");
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": translate("Language"),
|
||||
"tooltip": translate("Choose the language of the game."),
|
||||
|
|
|
|||
|
|
@ -174,6 +174,27 @@ MenuButtons.prototype.Options = class
|
|||
}
|
||||
};
|
||||
|
||||
MenuButtons.prototype.Hotkeys = class
|
||||
{
|
||||
constructor(button, pauseControl)
|
||||
{
|
||||
this.button = button;
|
||||
this.button.caption = translate("Hotkeys");
|
||||
this.pauseControl = pauseControl;
|
||||
}
|
||||
|
||||
onPress()
|
||||
{
|
||||
closeOpenDialogs();
|
||||
this.pauseControl.implicitPause();
|
||||
|
||||
Engine.PushGuiPage(
|
||||
"hotkeys/page_hotkeys.xml",
|
||||
{},
|
||||
() => { resumeGame(); });
|
||||
}
|
||||
};
|
||||
|
||||
MenuButtons.prototype.Pause = class
|
||||
{
|
||||
constructor(button, pauseControl, playerViewControl)
|
||||
|
|
|
|||
|
|
@ -151,21 +151,19 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
|
|||
m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
|
||||
}
|
||||
|
||||
// Allow the focused object to pre-empt regular GUI events.
|
||||
if (GetFocusedObject())
|
||||
ret = GetFocusedObject()->PreemptEvent(ev);
|
||||
|
||||
// Only one object can be hovered
|
||||
IGUIObject* pNearest = nullptr;
|
||||
// pNearest will after this point at the hovered object, possibly nullptr
|
||||
IGUIObject* pNearest = FindObjectUnderMouse();
|
||||
|
||||
// TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
|
||||
if (ret == IN_PASS)
|
||||
{
|
||||
PROFILE("mouse events");
|
||||
// TODO Gee: Optimizations needed!
|
||||
// these two recursive function are quite overhead heavy.
|
||||
|
||||
// pNearest will after this point at the hovered object, possibly nullptr
|
||||
pNearest = FindObjectUnderMouse();
|
||||
|
||||
// Now we'll call UpdateMouseOver on *all* objects,
|
||||
// we'll input the one hovered, and they will each
|
||||
// update their own data and send messages accordingly
|
||||
// we'll input the one hovered, and they will each
|
||||
// update their own data and send messages accordingly
|
||||
m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast<IGUIObject* const&>(pNearest));
|
||||
|
||||
if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
|
||||
|
|
@ -253,19 +251,13 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
|
|||
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
|
||||
m_MousePos = oldMousePos;
|
||||
|
||||
// Handle keys for input boxes
|
||||
if (GetFocusedObject())
|
||||
// Let GUI items handle keys after everything else, e.g. for input boxes.
|
||||
if (ret == IN_PASS && GetFocusedObject())
|
||||
{
|
||||
if ((ev->ev.type == SDL_KEYDOWN &&
|
||||
ev->ev.key.keysym.sym != SDLK_ESCAPE &&
|
||||
!g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
|
||||
!g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) ||
|
||||
ev->ev.type == SDL_HOTKEYDOWN ||
|
||||
ev->ev.type == SDL_TEXTINPUT ||
|
||||
ev->ev.type == SDL_TEXTEDITING)
|
||||
{
|
||||
ret = GetFocusedObject()->ManuallyHandleEvent(ev);
|
||||
}
|
||||
if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN ||
|
||||
ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN ||
|
||||
ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING)
|
||||
ret = GetFocusedObject()->ManuallyHandleKeys(ev);
|
||||
// else will return IN_PASS because we never used the button.
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +266,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
|
|||
|
||||
void CGUI::TickObjects()
|
||||
{
|
||||
m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick);
|
||||
SendEventToAll(EventNameTick);
|
||||
m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "gui/ObjectTypes/CChart.h"
|
||||
#include "gui/ObjectTypes/CCheckBox.h"
|
||||
#include "gui/ObjectTypes/CDropDown.h"
|
||||
#include "gui/ObjectTypes/CHotkeyPicker.h"
|
||||
#include "gui/ObjectTypes/CImage.h"
|
||||
#include "gui/ObjectTypes/CInput.h"
|
||||
#include "gui/ObjectTypes/CList.h"
|
||||
|
|
@ -39,6 +40,7 @@ void CGUI::AddObjectTypes()
|
|||
AddObjectType("checkbox", &CCheckBox::ConstructObject);
|
||||
AddObjectType("dropdown", &CDropDown::ConstructObject);
|
||||
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
|
||||
AddObjectType("hotkeypicker", &CHotkeyPicker::ConstructObject);
|
||||
AddObjectType("image", &CImage::ConstructObject);
|
||||
AddObjectType("input", &CInput::ConstructObject);
|
||||
AddObjectType("list", &CList::ConstructObject);
|
||||
|
|
|
|||
|
|
@ -255,6 +255,12 @@ protected:
|
|||
//@{
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Called on every GUI tick unless the object or one of its parent is hidden/ghost.
|
||||
*/
|
||||
virtual void Tick() {};
|
||||
|
||||
/**
|
||||
* This function is called with different messages
|
||||
* for instance when the mouse enters the object.
|
||||
|
|
@ -290,7 +296,17 @@ protected:
|
|||
virtual void Draw() = 0;
|
||||
|
||||
/**
|
||||
* Some objects need to handle the SDL_Event_ manually.
|
||||
* Some objects need to be able to pre-emptively process SDL_Event_.
|
||||
*
|
||||
* Only the object with focus will have this function called.
|
||||
*
|
||||
* Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then
|
||||
* the event won't be passed on and processed by other handlers.
|
||||
*/
|
||||
virtual InReaction PreemptEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
|
||||
|
||||
/**
|
||||
* Some objects need to handle the text-related SDL_Event_ manually.
|
||||
* For instance the input box.
|
||||
*
|
||||
* Only the object with focus will have this function called.
|
||||
|
|
@ -299,7 +315,7 @@ protected:
|
|||
* the key won't be passed on and processed by other handlers.
|
||||
* This is used for keys that the GUI uses.
|
||||
*/
|
||||
virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
|
||||
virtual InReaction ManuallyHandleKeys(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
|
||||
|
||||
/**
|
||||
* Loads a style.
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ void CDropDown::HandleMessage(SGUIMessage& Message)
|
|||
SetupText();
|
||||
}
|
||||
|
||||
InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
|
||||
InReaction CDropDown::ManuallyHandleKeys(const SDL_Event_* ev)
|
||||
{
|
||||
InReaction result = IN_PASS;
|
||||
bool update_highlight = false;
|
||||
|
|
@ -298,7 +298,7 @@ InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
|
|||
if (!m_Open)
|
||||
return IN_PASS;
|
||||
// Set current selected item to highlighted, before
|
||||
// then really processing these in CList::ManuallyHandleEvent()
|
||||
// then really processing these in CList::ManuallyHandleKeys()
|
||||
SetSetting<i32>("selected", m_ElementHighlight, true);
|
||||
update_highlight = true;
|
||||
break;
|
||||
|
|
@ -356,7 +356,7 @@ InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
|
|||
}
|
||||
}
|
||||
|
||||
if (CList::ManuallyHandleEvent(ev) == IN_HANDLED)
|
||||
if (CList::ManuallyHandleKeys(ev) == IN_HANDLED)
|
||||
result = IN_HANDLED;
|
||||
|
||||
if (update_highlight)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public:
|
|||
/**
|
||||
* Handle events manually to catch keyboard inputting.
|
||||
*/
|
||||
virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
|
||||
virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
|
||||
|
||||
/**
|
||||
* Draws the Button
|
||||
|
|
|
|||
191
source/gui/ObjectTypes/CHotkeyPicker.cpp
Normal file
191
source/gui/ObjectTypes/CHotkeyPicker.cpp
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/* Copyright (C) 2020 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "CHotkeyPicker.h"
|
||||
|
||||
#include "lib/timer.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/KeyName.h"
|
||||
#include "scriptinterface/ScriptConversions.h"
|
||||
|
||||
const CStr CHotkeyPicker::EventNameCombination = "Combination";
|
||||
const CStr CHotkeyPicker::EventNameKeyChange = "KeyChange";
|
||||
|
||||
// Don't send the scancode, JS doesn't care.
|
||||
template<> void ScriptInterface::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const CHotkeyPicker::Key& val)
|
||||
{
|
||||
ScriptInterface::ToJSVal(rq, ret, val.scancodeName);
|
||||
}
|
||||
|
||||
// Unused, but JSVAL_VECTOR requires it.
|
||||
template<> bool ScriptInterface::FromJSVal(const ScriptRequest&, const JS::HandleValue, CHotkeyPicker::Key&)
|
||||
{
|
||||
LOGWARNING("FromJSVal<CHotkeyPicker>: Not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
JSVAL_VECTOR(CHotkeyPicker::Key);
|
||||
|
||||
CHotkeyPicker::CHotkeyPicker(CGUI& pGUI) : IGUIObject(pGUI), m_TimeToCombination(1.f)
|
||||
{
|
||||
RegisterSetting("time_to_combination", m_TimeToCombination);
|
||||
// 8 keys at the same time is probably more than we'll ever need.
|
||||
m_KeysPressed.reserve(8);
|
||||
}
|
||||
|
||||
CHotkeyPicker::~CHotkeyPicker()
|
||||
{
|
||||
}
|
||||
|
||||
void CHotkeyPicker::FireEvent(const CStr& event)
|
||||
{
|
||||
ScriptRequest rq(*m_pGUI.GetScriptInterface());
|
||||
|
||||
JS::AutoValueArray<1> args(rq.cx);
|
||||
JS::RootedValue keys(rq.cx);
|
||||
m_pGUI.GetScriptInterface()->ToJSVal(rq, &keys, m_KeysPressed);
|
||||
args[0].set(keys);
|
||||
ScriptEvent(event, args);
|
||||
}
|
||||
|
||||
void CHotkeyPicker::Tick()
|
||||
{
|
||||
if (m_KeysPressed.size() == 0)
|
||||
return;
|
||||
|
||||
double time = timer_Time();
|
||||
if (time - m_LastKeyChange < m_TimeToCombination)
|
||||
return;
|
||||
|
||||
FireEvent(EventNameCombination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void CHotkeyPicker::HandleMessage(SGUIMessage& Message)
|
||||
{
|
||||
IGUIObject::HandleMessage(Message);
|
||||
switch (Message.type)
|
||||
{
|
||||
case GUIM_GOT_FOCUS:
|
||||
case GUIM_LOST_FOCUS:
|
||||
{
|
||||
m_KeysPressed.clear();
|
||||
m_LastKeyChange = timer_Time();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InReaction CHotkeyPicker::PreemptEvent(const SDL_Event_* ev)
|
||||
{
|
||||
switch (ev->ev.type)
|
||||
{
|
||||
// Handle the same mouse events that hotkeys handle
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_MOUSEWHEEL:
|
||||
{
|
||||
SDL_Scancode scancode;
|
||||
|
||||
if (ev->ev.type != SDL_MOUSEWHEEL)
|
||||
{
|
||||
// Wait a little bit -> this gets triggered when clicking on a button,
|
||||
// but after the button click is processed, thus immediately triggering...
|
||||
if (timer_Time()-m_LastKeyChange < 0.2)
|
||||
return IN_HANDLED;
|
||||
// This is from hotkeyHandler - not sure what it does in all honesty.
|
||||
if(ev->ev.button.button >= SDL_BUTTON_X1)
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_BASE + (int)ev->ev.button.button + 2);
|
||||
else
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_BASE + (int)ev->ev.button.button);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ev->ev.wheel.y > 0)
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_WHEELUP);
|
||||
else if (ev->ev.wheel.y < 0)
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_WHEELDOWN);
|
||||
else if (ev->ev.wheel.x > 0)
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_X2);
|
||||
else if (ev->ev.wheel.x < 0)
|
||||
scancode = static_cast<SDL_Scancode>(MOUSE_X1);
|
||||
else
|
||||
return IN_HANDLED;
|
||||
}
|
||||
// Don't handle keys and mouse together except for modifiers.
|
||||
std::remove_if(m_KeysPressed.begin(), m_KeysPressed.end(), [](const Key& k) {
|
||||
return static_cast<int>(k.code) < UNIFIED_SHIFT || static_cast<int>(k.code) >= UNIFIED_LAST; } );
|
||||
m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
|
||||
// For mouse events, assume we immediately want to return.
|
||||
FireEvent(EventNameCombination);
|
||||
|
||||
return IN_HANDLED;
|
||||
}
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode scancode = ev->ev.key.keysym.scancode;
|
||||
|
||||
// Don't handle caps-lock, it doesn't really work in-game and it's a weird hotkey.
|
||||
if (scancode == SDL_SCANCODE_CAPSLOCK)
|
||||
return IN_PASS;
|
||||
|
||||
if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
|
||||
scancode = static_cast<SDL_Scancode>(UNIFIED_SHIFT);
|
||||
else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
|
||||
scancode = static_cast<SDL_Scancode>(UNIFIED_CTRL);
|
||||
else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
|
||||
scancode = static_cast<SDL_Scancode>(UNIFIED_ALT);
|
||||
else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
|
||||
scancode = static_cast<SDL_Scancode>(UNIFIED_SUPER);
|
||||
|
||||
if (ev->ev.type == SDL_KEYDOWN)
|
||||
{
|
||||
std::vector<Key>::const_iterator it = \
|
||||
std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
|
||||
// Can happen if multiple keys are mapped the same.
|
||||
if (it != m_KeysPressed.end())
|
||||
return IN_HANDLED;
|
||||
m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Key>::const_iterator it = \
|
||||
std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
|
||||
// Might happen if a key was down before this object is created.
|
||||
if (it == m_KeysPressed.end())
|
||||
return IN_HANDLED;
|
||||
m_KeysPressed.erase(it);
|
||||
}
|
||||
|
||||
FireEvent(EventNameKeyChange);
|
||||
|
||||
// Register after-JS in case this takes a while (probably not but it doesn't hurt).
|
||||
m_LastKeyChange = timer_Time();
|
||||
return IN_HANDLED;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return IN_PASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
source/gui/ObjectTypes/CHotkeyPicker.h
Normal file
78
source/gui/ObjectTypes/CHotkeyPicker.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* Copyright (C) 2020 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_CHOTKEYPICKER
|
||||
#define INCLUDED_CHOTKEYPICKER
|
||||
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/CStr.h"
|
||||
|
||||
class ScriptInterface;
|
||||
|
||||
/**
|
||||
* When in focus, returns all currently pressed keys.
|
||||
* After a set time without changes, it will trigger a "combination" event.
|
||||
*
|
||||
* Used to create new hotkey combinations in-game. Mostly custom.
|
||||
* This object does not draw anything.
|
||||
*
|
||||
* NB: because of how input is handled, mouse clicks
|
||||
*/
|
||||
class CHotkeyPicker : public IGUIObject
|
||||
{
|
||||
GUI_OBJECT(CHotkeyPicker)
|
||||
|
||||
friend class ScriptInterface;
|
||||
public:
|
||||
CHotkeyPicker(CGUI& pGUI);
|
||||
virtual ~CHotkeyPicker();
|
||||
|
||||
// Do nothing.
|
||||
virtual void Draw() {};
|
||||
|
||||
// Checks if the timer has passed and we need to fire a "combination" event.
|
||||
virtual void Tick();
|
||||
|
||||
// React to blur/focus.
|
||||
virtual void HandleMessage(SGUIMessage& Message);
|
||||
|
||||
// Pre-empt events: this is our sole purpose.
|
||||
virtual InReaction PreemptEvent(const SDL_Event_* ev);
|
||||
protected:
|
||||
// Fire an event with m_KeysPressed as argument.
|
||||
void FireEvent(const CStr& event);
|
||||
|
||||
// Time without changes until a "combination" event is sent.
|
||||
float m_TimeToCombination;
|
||||
// Time of the last registered key change.
|
||||
double m_LastKeyChange;
|
||||
|
||||
// Keep track of which keys we are pressing, and precompute their name for JS code.
|
||||
struct Key
|
||||
{
|
||||
// The scancode is used for fast comparisons.
|
||||
SDL_Scancode code;
|
||||
// This is the name ultimately stored in the config file.
|
||||
CStr scancodeName;
|
||||
};
|
||||
std::vector<Key> m_KeysPressed;
|
||||
|
||||
static const CStr EventNameCombination;
|
||||
static const CStr EventNameKeyChange;
|
||||
};
|
||||
|
||||
#endif // INCLUDED_CHOTKEYPICKER
|
||||
|
|
@ -114,7 +114,7 @@ void CInput::ClearComposedText()
|
|||
m_iComposedPos = 0;
|
||||
}
|
||||
|
||||
InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
|
||||
InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
|
||||
{
|
||||
ENSURE(m_iBufferPos != -1);
|
||||
|
||||
|
|
@ -226,6 +226,11 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
|
|||
// pointer and edit that.
|
||||
SDL_Keycode keyCode = ev->ev.key.keysym.sym;
|
||||
|
||||
// Escape is treated specially to let players close out windows even if an input is in focus.
|
||||
// TODO: there is maybe a better way to only handle text-like keys here?
|
||||
if (keyCode == SDLK_ESCAPE)
|
||||
return IN_PASS;
|
||||
|
||||
ManuallyImmutableHandleKeyDownEvent(keyCode);
|
||||
ManuallyMutableHandleKeyDownEvent(keyCode);
|
||||
|
||||
|
|
@ -1117,7 +1122,7 @@ void CInput::HandleMessage(SGUIMessage& Message)
|
|||
evt.ev.edit.length = 0;
|
||||
evt.ev.edit.start = 0;
|
||||
evt.ev.edit.text[0] = 0;
|
||||
ManuallyHandleEvent(&evt);
|
||||
ManuallyHandleKeys(&evt);
|
||||
}
|
||||
SDL_StopTextInput();
|
||||
|
||||
|
|
|
|||
|
|
@ -64,20 +64,20 @@ protected:
|
|||
/**
|
||||
* Handle events manually to catch keyboard inputting.
|
||||
*/
|
||||
virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
|
||||
virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
|
||||
|
||||
/**
|
||||
* Handle events manually to catch keys which change the text.
|
||||
*/
|
||||
* Handle events manually to catch keys which change the text.
|
||||
*/
|
||||
virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode);
|
||||
|
||||
/**
|
||||
* Handle events manually to catch keys which don't change the text.
|
||||
*/
|
||||
* Handle events manually to catch keys which don't change the text.
|
||||
*/
|
||||
virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode);
|
||||
|
||||
/**
|
||||
* Handle hotkey events (called by ManuallyHandleEvent)
|
||||
* Handle hotkey events (called by ManuallyHandleKeys)
|
||||
*/
|
||||
virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev);
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ void CList::HandleMessage(SGUIMessage& Message)
|
|||
IGUITextOwner::HandleMessage(Message);
|
||||
}
|
||||
|
||||
InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev)
|
||||
InReaction CList::ManuallyHandleKeys(const SDL_Event_* ev)
|
||||
{
|
||||
InReaction result = IN_PASS;
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ protected:
|
|||
/**
|
||||
* Handle events manually to catch keyboard inputting.
|
||||
*/
|
||||
virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
|
||||
virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
|
||||
|
||||
/**
|
||||
* Draws the List box
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "ps/scripting/JSInterface_Console.h"
|
||||
#include "ps/scripting/JSInterface_Debug.h"
|
||||
#include "ps/scripting/JSInterface_Game.h"
|
||||
#include "ps/scripting/JSInterface_Hotkey.h"
|
||||
#include "ps/scripting/JSInterface_Main.h"
|
||||
#include "ps/scripting/JSInterface_Mod.h"
|
||||
#include "ps/scripting/JSInterface_ModIo.h"
|
||||
|
|
@ -59,6 +60,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
|||
JSI_GUIManager::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_Game::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_GameView::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_Hotkey::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_L10n::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_Lobby::RegisterScriptFunctions(scriptInterface);
|
||||
JSI_Main::RegisterScriptFunctions(scriptInterface);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public:
|
|||
// Press 'a'.
|
||||
SDL_Event_ hotkeyNotification;
|
||||
hotkeyNotification.ev.type = SDL_KEYDOWN;
|
||||
hotkeyNotification.ev.key.keysym.sym = SDLK_a;
|
||||
hotkeyNotification.ev.key.keysym.scancode = SDL_SCANCODE_A;
|
||||
hotkeyNotification.ev.key.repeat = 0;
|
||||
|
||||
// Init input and poll the event.
|
||||
|
|
|
|||
|
|
@ -210,6 +210,18 @@ void CConfigDB::SetValueBool(EConfigNamespace ns, const CStr& name, const bool v
|
|||
SetValueString(ns, name, valueString);
|
||||
}
|
||||
|
||||
void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector<CStr> values)
|
||||
{
|
||||
CHECK_NS(;);
|
||||
|
||||
std::lock_guard<std::recursive_mutex> s(cfgdb_mutex);
|
||||
TConfigMap::iterator it = m_Map[ns].find(name);
|
||||
if (it == m_Map[ns].end())
|
||||
it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
|
||||
|
||||
it->second = values;
|
||||
}
|
||||
|
||||
void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
|
||||
{
|
||||
CHECK_NS(;);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ public:
|
|||
|
||||
void SetValueBool(EConfigNamespace ns, const CStr& name, const bool value);
|
||||
|
||||
void SetValueList(EConfigNamespace ns, const CStr& name, std::vector<CStr> values);
|
||||
|
||||
/**
|
||||
* Remove a config value in the specified namespace.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/CConsole.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CStr.h"
|
||||
|
|
@ -29,29 +30,11 @@
|
|||
|
||||
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
|
||||
|
||||
struct SKey
|
||||
{
|
||||
SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
|
||||
bool negated; // whether the key must be pressed (false) or unpressed (true)
|
||||
};
|
||||
std::unordered_map<int, KeyMapping> g_HotkeyMap;
|
||||
std::unordered_map<std::string, bool> g_HotkeyStatus;
|
||||
|
||||
// Hotkey data associated with an externally-specified 'primary' keycode
|
||||
struct SHotkeyMapping
|
||||
{
|
||||
CStr name; // name of the hotkey
|
||||
bool negated; // whether the primary key must be pressed (false) or unpressed (true)
|
||||
std::vector<SKey> requires; // list of non-primary keys that must also be active
|
||||
};
|
||||
|
||||
typedef std::vector<SHotkeyMapping> KeyMapping;
|
||||
|
||||
// A mapping of keycodes onto the hotkeys that are associated with that key.
|
||||
// (A hotkey triggered by a combination of multiple keys will be in this map
|
||||
// multiple times.)
|
||||
static std::map<int, KeyMapping> g_HotkeyMap;
|
||||
|
||||
// The current pressed status of hotkeys
|
||||
std::map<std::string, bool> g_HotkeyStatus;
|
||||
static_assert(std::is_integral<std::underlying_type<SDL_Scancode>::type>::value, "SDL_Scancode is not an integral enum.");
|
||||
static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT");
|
||||
|
||||
// Look up each key binding in the config file and set the mappings for
|
||||
// all key combinations that trigger it.
|
||||
|
|
@ -74,16 +57,14 @@ static void LoadConfigBindings()
|
|||
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
|
||||
{
|
||||
// Attempt decode as key name
|
||||
int mapping = FindKeyCode(*it);
|
||||
if (!mapping)
|
||||
mapping = SDL_GetKeyFromName(it->c_str());
|
||||
if (!mapping)
|
||||
SDL_Scancode scancode = FindScancode(it->c_str());
|
||||
if (!scancode)
|
||||
{
|
||||
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
SKey key = { (SDL_Keycode)mapping, false };
|
||||
SKey key = { scancode, false };
|
||||
keyCombination.push_back(key);
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +88,6 @@ static void LoadConfigBindings()
|
|||
|
||||
void LoadHotkeys()
|
||||
{
|
||||
InitKeyNameMap();
|
||||
|
||||
LoadConfigBindings();
|
||||
|
||||
// Set up the state of the hotkeys given no key is down.
|
||||
|
|
@ -146,7 +125,7 @@ bool isNegated(const SKey& key)
|
|||
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated)
|
||||
return false;
|
||||
// Modifier keycodes are between the normal keys and the mouse 'keys'
|
||||
else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
|
||||
else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
|
@ -163,13 +142,13 @@ InReaction HotkeyStateChange(const SDL_Event_* ev)
|
|||
|
||||
InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
||||
{
|
||||
int keycode = 0;
|
||||
int scancode = SDL_SCANCODE_UNKNOWN;
|
||||
|
||||
switch(ev->ev.type)
|
||||
{
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
keycode = (int)ev->ev.key.keysym.sym;
|
||||
scancode = ev->ev.key.keysym.scancode;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
|
|
@ -177,30 +156,30 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
|||
// Mousewheel events are no longer buttons, but we want to maintain the order
|
||||
// expected by g_mouse_buttons for compatibility
|
||||
if (ev->ev.button.button >= SDL_BUTTON_X1)
|
||||
keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
|
||||
scancode = MOUSE_BASE + (int)ev->ev.button.button + 2;
|
||||
else
|
||||
keycode = MOUSE_BASE + (int)ev->ev.button.button;
|
||||
scancode = MOUSE_BASE + (int)ev->ev.button.button;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (ev->ev.wheel.y > 0)
|
||||
{
|
||||
keycode = MOUSE_WHEELUP;
|
||||
scancode = MOUSE_WHEELUP;
|
||||
break;
|
||||
}
|
||||
else if (ev->ev.wheel.y < 0)
|
||||
{
|
||||
keycode = MOUSE_WHEELDOWN;
|
||||
scancode = MOUSE_WHEELDOWN;
|
||||
break;
|
||||
}
|
||||
else if (ev->ev.wheel.x > 0)
|
||||
{
|
||||
keycode = MOUSE_X2;
|
||||
scancode = MOUSE_X2;
|
||||
break;
|
||||
}
|
||||
else if (ev->ev.wheel.x < 0)
|
||||
{
|
||||
keycode = MOUSE_X1;
|
||||
scancode = MOUSE_X1;
|
||||
break;
|
||||
}
|
||||
return IN_PASS;
|
||||
|
|
@ -219,33 +198,33 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
|||
if (phantom.ev.type == SDL_KEYDOWN)
|
||||
phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0;
|
||||
|
||||
if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
|
||||
if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
|
||||
{
|
||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
|
||||
phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SHIFT);
|
||||
unified[0] = (phantom.ev.type == SDL_KEYDOWN);
|
||||
HotkeyInputHandler(&phantom);
|
||||
}
|
||||
else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
|
||||
else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
|
||||
{
|
||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
|
||||
phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_CTRL);
|
||||
unified[1] = (phantom.ev.type == SDL_KEYDOWN);
|
||||
HotkeyInputHandler(&phantom);
|
||||
}
|
||||
else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
|
||||
else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
|
||||
{
|
||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
|
||||
phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_ALT);
|
||||
unified[2] = (phantom.ev.type == SDL_KEYDOWN);
|
||||
HotkeyInputHandler(&phantom);
|
||||
}
|
||||
else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
|
||||
else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
|
||||
{
|
||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
|
||||
phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SUPER);
|
||||
unified[3] = (phantom.ev.type == SDL_KEYDOWN);
|
||||
HotkeyInputHandler(&phantom);
|
||||
}
|
||||
|
||||
// Check whether we have any hotkeys registered for this particular keycode
|
||||
if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
|
||||
if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end())
|
||||
return (IN_PASS);
|
||||
|
||||
// Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
|
||||
|
|
@ -253,7 +232,7 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
|||
|
||||
bool consoleCapture = false;
|
||||
|
||||
if (g_Console && g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
|
||||
if (g_Console && g_Console->IsActive() && scancode < SDL_NUM_SCANCODES)
|
||||
consoleCapture = true;
|
||||
|
||||
// Here's an interesting bit:
|
||||
|
|
@ -273,7 +252,7 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
|||
std::vector<const char*> closestMapNames;
|
||||
size_t closestMapMatch = 0;
|
||||
|
||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
|
||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
|
||||
{
|
||||
// If a key has been pressed, and this event triggers on its release, skip it.
|
||||
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
|
||||
|
|
@ -329,7 +308,7 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
|
|||
|
||||
// -- KEYUP SECTION --
|
||||
|
||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
|
||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
|
||||
{
|
||||
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
|
||||
// use this key negated => skip them
|
||||
|
|
|
|||
|
|
@ -33,15 +33,45 @@
|
|||
|
||||
#include "CStr.h"
|
||||
#include "lib/input.h"
|
||||
#include "lib/external_libraries/libsdl.h" // see note below
|
||||
|
||||
// note: we need the real SDL header - it defines SDL_USEREVENT, which is
|
||||
// required for our HOTKEY event type definition. this is OK since
|
||||
// hotkey.h is not included from any headers.
|
||||
#include <unordered_map>
|
||||
|
||||
const uint SDL_HOTKEYPRESS = SDL_USEREVENT;
|
||||
const uint SDL_HOTKEYDOWN = SDL_USEREVENT + 1;
|
||||
const uint SDL_HOTKEYUP = SDL_USEREVENT + 2;
|
||||
// SDL_Scancode is an enum, we'll use an explicit int to avoid including SDL in this header.
|
||||
using SDL_Scancode_ = int;
|
||||
|
||||
// 0x8000 is SDL_USEREVENT, this is static_asserted in Hotkey.cpp
|
||||
// We do this to avoid including SDL in this header.
|
||||
const uint SDL_USEREVENT_ = 0x8000;
|
||||
const uint SDL_HOTKEYPRESS = SDL_USEREVENT_;
|
||||
const uint SDL_HOTKEYDOWN = SDL_USEREVENT_ + 1;
|
||||
const uint SDL_HOTKEYUP = SDL_USEREVENT_ + 2;
|
||||
|
||||
struct SKey
|
||||
{
|
||||
SDL_Scancode_ code; // scancode or MOUSE_ or UNIFIED_ value
|
||||
bool negated; // whether the key must be pressed (false) or unpressed (true)
|
||||
|
||||
bool operator<(const SKey& o) const { return code < o.code && negated < o.negated; }
|
||||
bool operator==(const SKey& o) const { return code == o.code && negated == o.negated; }
|
||||
};
|
||||
|
||||
// Hotkey data associated with an externally-specified 'primary' keycode
|
||||
struct SHotkeyMapping
|
||||
{
|
||||
CStr name; // name of the hotkey
|
||||
bool negated; // whether the primary key must be pressed (false) or unpressed (true)
|
||||
std::vector<SKey> requires; // list of non-primary keys that must also be active
|
||||
};
|
||||
|
||||
typedef std::vector<SHotkeyMapping> KeyMapping;
|
||||
|
||||
// A mapping of scancodes onto the hotkeys that are associated with that key.
|
||||
// (A hotkey triggered by a combination of multiple keys will be in this map
|
||||
// multiple times.)
|
||||
extern std::unordered_map<SDL_Scancode_, KeyMapping> g_HotkeyMap;
|
||||
|
||||
// The current pressed status of hotkeys
|
||||
extern std::unordered_map<std::string, bool> g_HotkeyStatus;
|
||||
|
||||
extern void LoadHotkeys();
|
||||
extern void UnloadHotkeys();
|
||||
|
|
|
|||
|
|
@ -24,215 +24,198 @@
|
|||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/CStr.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
static std::map<CStr,int> keymap;
|
||||
// Some scancodes <-> names that SDL doesn't recognise.
|
||||
// Those are tested first so they override SDL defaults (useful for UNIFIED keys).
|
||||
static const std::unordered_map<int, std::vector<CStr>> scancodemap {{
|
||||
{ SDL_SCANCODE_DOWN, { "DownArrow" } },
|
||||
{ SDL_SCANCODE_UP, { "UpArrow" } },
|
||||
{ SDL_SCANCODE_LEFT, { "LeftArrow" } },
|
||||
{ SDL_SCANCODE_RIGHT, { "RightArrow" } },
|
||||
|
||||
struct SKeycodeMapping
|
||||
{ SDL_SCANCODE_EQUALS, { "Plus" } },
|
||||
{ SDL_SCANCODE_MINUS, { "Minus" } },
|
||||
|
||||
{ SDL_SCANCODE_KP_ENTER, { "NumEnter" } },
|
||||
{ SDL_SCANCODE_KP_DIVIDE, { "NumDivide" } },
|
||||
{ SDL_SCANCODE_KP_MULTIPLY, { "NumMultiply" } },
|
||||
{ SDL_SCANCODE_KP_EQUALS, { "NumEquals" } },
|
||||
{ SDL_SCANCODE_KP_PERIOD, { "NumDecimal" } },
|
||||
{ SDL_SCANCODE_KP_PLUS, { "NumPlus" } },
|
||||
{ SDL_SCANCODE_KP_MINUS, { "NumMinus" } },
|
||||
{ SDL_SCANCODE_KP_0, { "Num0" } },
|
||||
{ SDL_SCANCODE_KP_1, { "Num1" } },
|
||||
{ SDL_SCANCODE_KP_2, { "Num2" } },
|
||||
{ SDL_SCANCODE_KP_3, { "Num3" } },
|
||||
{ SDL_SCANCODE_KP_4, { "Num4" } },
|
||||
{ SDL_SCANCODE_KP_5, { "Num5" } },
|
||||
{ SDL_SCANCODE_KP_6, { "Num6" } },
|
||||
{ SDL_SCANCODE_KP_7, { "Num7" } },
|
||||
{ SDL_SCANCODE_KP_8, { "Num8" } },
|
||||
{ SDL_SCANCODE_KP_9, { "Num9" } },
|
||||
|
||||
{ SDL_SCANCODE_COMMA, { "Comma" } },
|
||||
{ SDL_SCANCODE_PERIOD, { "Period" } },
|
||||
{ SDL_SCANCODE_APOSTROPHE, { "Quote" } },
|
||||
{ SDL_SCANCODE_SEMICOLON, { "Semicolon" } },
|
||||
{ SDL_SCANCODE_GRAVE, { "Backquote" } },
|
||||
{ SDL_SCANCODE_LEFTBRACKET, { "LeftBracket" } },
|
||||
{ SDL_SCANCODE_RIGHTBRACKET, { "RightBracket" } },
|
||||
{ SDL_SCANCODE_BACKSLASH, { "Backslash" } },
|
||||
{ SDL_SCANCODE_SLASH, { "Slash" } },
|
||||
|
||||
{ SDL_SCANCODE_RETURN, { "Enter" } },
|
||||
{ SDL_SCANCODE_ESCAPE, { "Esc" } },
|
||||
{ SDL_SCANCODE_PAUSE, { "Break" } },
|
||||
{ SDL_SCANCODE_DELETE, { "Del" } },
|
||||
|
||||
{ MOUSE_LEFT, { "MouseLeft" } },
|
||||
{ MOUSE_RIGHT, { "MouseRight" } },
|
||||
{ MOUSE_MIDDLE, { "MouseMiddle" } },
|
||||
{ MOUSE_WHEELUP, { "WheelUp" } },
|
||||
{ MOUSE_WHEELDOWN, { "WheelDown" } },
|
||||
{ MOUSE_X1, { "WheelLeft", "MouseX1" } },
|
||||
{ MOUSE_X2, { "WheelRight", "MouseX2" } },
|
||||
|
||||
{ UNIFIED_SHIFT, { "Shift", "Left Shift", "Right Shift" } },
|
||||
{ UNIFIED_CTRL, { "Ctrl", "Left Ctrl", "Right Ctrl" } },
|
||||
{ UNIFIED_ALT, { "Alt", "Left Alt", "Right Alt" } },
|
||||
{ UNIFIED_SUPER, { "Super", "Left Gui", "Right Gui" } },
|
||||
}};
|
||||
|
||||
SDL_Scancode FindScancode(const CStr& keyname)
|
||||
{
|
||||
int keycode;
|
||||
const char* keyname;
|
||||
const char* altkeyname;
|
||||
};
|
||||
// Find (ignoring case) a corresponding scancode, if one exists.
|
||||
std::unordered_map<int, std::vector<CStr>>::const_iterator it =
|
||||
std::find_if(scancodemap.begin(), scancodemap.end(), [&keyname](const std::pair<int, std::vector<CStr>>& names) {
|
||||
return std::find_if(names.second.begin(), names.second.end(), [&keyname](const CStr& t) {
|
||||
return t.LowerCase() == keyname.LowerCase();
|
||||
})!= names.second.end();
|
||||
});
|
||||
|
||||
// You can use either key name in the config file...
|
||||
if (it != scancodemap.end())
|
||||
return static_cast<SDL_Scancode>(it->first);
|
||||
|
||||
static const SKeycodeMapping keycodeMapping[] =
|
||||
SDL_Scancode code = SDL_GetScancodeFromName(keyname.c_str());
|
||||
if (code != SDL_SCANCODE_UNKNOWN)
|
||||
return code;
|
||||
|
||||
// Parse SYM_XX codes, see below.
|
||||
if (keyname.size() > 4 && keyname.Left(4) == "SYM_")
|
||||
return static_cast<SDL_Scancode>(CStr(keyname.substr(4)).ToInt());
|
||||
|
||||
return SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
CStr FindScancodeName(SDL_Scancode scancode)
|
||||
{
|
||||
/* Just a tad friendlier than SDL_GetKeyName's name */
|
||||
{ SDLK_BACKSPACE, "Backspace", "BkSp" },
|
||||
{ SDLK_TAB, "Tab", 0 },
|
||||
{ SDLK_CLEAR, "Clear", 0 }, // ?
|
||||
{ SDLK_RETURN, "Return", "Ret" },
|
||||
{ SDLK_PAUSE, "Pause", 0 }, // ?
|
||||
{ SDLK_ESCAPE, "Escape", "Esc" },
|
||||
{ SDLK_SPACE, "Space", "Spc" },
|
||||
{ SDLK_EXCLAIM, "!", "Exclaim" },
|
||||
{ SDLK_QUOTEDBL, "\"", "DoubleQuote" },
|
||||
{ SDLK_HASH, "#", "Hash" },
|
||||
{ SDLK_DOLLAR, "$", "Dollar" },
|
||||
{ SDLK_AMPERSAND, "&", "Ampersand" },
|
||||
{ SDLK_QUOTE, "'", "SingleQuote" },
|
||||
{ SDLK_LEFTPAREN, "(", "LeftParen" },
|
||||
{ SDLK_RIGHTPAREN, ")", "RightParen" },
|
||||
{ SDLK_ASTERISK, "*", "Asterisk" },
|
||||
{ SDLK_PLUS, "+", "Plus" },
|
||||
{ SDLK_COMMA, ",", "Comma" },
|
||||
{ SDLK_MINUS, "-", "Minus" },
|
||||
{ SDLK_PERIOD, ".", "Period" },
|
||||
{ SDLK_SLASH, "/", "ForwardSlash" },
|
||||
{ SDLK_0, "0", 0 },
|
||||
{ SDLK_1, "1", 0 },
|
||||
{ SDLK_2, "2", 0 },
|
||||
{ SDLK_3, "3", 0 },
|
||||
{ SDLK_4, "4", 0 },
|
||||
{ SDLK_5, "5", 0 },
|
||||
{ SDLK_6, "6", 0 },
|
||||
{ SDLK_7, "7", 0 },
|
||||
{ SDLK_8, "8", 0 },
|
||||
{ SDLK_9, "9", 0 },
|
||||
{ SDLK_COLON, ":", "Colon" },
|
||||
{ SDLK_SEMICOLON, ";", "Semicolon" },
|
||||
{ SDLK_LESS, "<", "LessThan" },
|
||||
{ SDLK_EQUALS, "=", "Equals" },
|
||||
{ SDLK_GREATER, ">", "GreaterThan" },
|
||||
{ SDLK_QUESTION, "?", "Question" },
|
||||
{ SDLK_AT, "@", "At" },
|
||||
{ SDLK_LEFTBRACKET, "[", "LeftBracket" },
|
||||
{ SDLK_BACKSLASH, "\\", "BackSlash" },
|
||||
{ SDLK_RIGHTBRACKET, "]", "RightBracket" },
|
||||
{ SDLK_CARET, "^", "Caret", },
|
||||
{ SDLK_UNDERSCORE, "_", "Underscore" },
|
||||
{ SDLK_BACKQUOTE, "`", "BackQuote" },
|
||||
{ SDLK_a, "A", 0 },
|
||||
{ SDLK_b, "B", 0 },
|
||||
{ SDLK_c, "C", 0 },
|
||||
{ SDLK_d, "D", 0 },
|
||||
{ SDLK_e, "E", 0 },
|
||||
{ SDLK_f, "F", 0 },
|
||||
{ SDLK_g, "G", 0 },
|
||||
{ SDLK_h, "H", 0 },
|
||||
{ SDLK_i, "I", 0 },
|
||||
{ SDLK_j, "J", 0 },
|
||||
{ SDLK_k, "K", 0 },
|
||||
{ SDLK_l, "L", 0 },
|
||||
{ SDLK_m, "M", 0 },
|
||||
{ SDLK_n, "N", 0 },
|
||||
{ SDLK_o, "O", 0 },
|
||||
{ SDLK_p, "P", 0 },
|
||||
{ SDLK_q, "Q", 0 },
|
||||
{ SDLK_r, "R", 0 },
|
||||
{ SDLK_s, "S", 0 },
|
||||
{ SDLK_t, "T", 0 },
|
||||
{ SDLK_u, "U", 0 },
|
||||
{ SDLK_v, "V", 0 },
|
||||
{ SDLK_w, "W", 0 },
|
||||
{ SDLK_x, "X", 0 },
|
||||
{ SDLK_y, "Y", 0 },
|
||||
{ SDLK_z, "Z", 0 },
|
||||
{ SDLK_DELETE, "Delete", "Del" },
|
||||
if (scancodemap.find(scancode) != scancodemap.end())
|
||||
return scancodemap.at(scancode).front();
|
||||
|
||||
{ SDLK_KP_0, "Numpad 0", "Num0" },
|
||||
{ SDLK_KP_1, "Numpad 1", "Num1" },
|
||||
{ SDLK_KP_2, "Numpad 2", "Num2" },
|
||||
{ SDLK_KP_3, "Numpad 3", "Num3" },
|
||||
{ SDLK_KP_4, "Numpad 4", "Num4" },
|
||||
{ SDLK_KP_5, "Numpad 5", "Num5" },
|
||||
{ SDLK_KP_6, "Numpad 6", "Num6" },
|
||||
{ SDLK_KP_7, "Numpad 7", "Num7" },
|
||||
{ SDLK_KP_8, "Numpad 8", "Num8" },
|
||||
{ SDLK_KP_9, "Numpad 9", "Num9" },
|
||||
const char* name = SDL_GetScancodeName(scancode);
|
||||
// Some scancodes have no name, but we must have something to save/load/recognize it, so parse it as SYM_XX
|
||||
if (strlen(name) == 0)
|
||||
return CStr("SYM_") + CStr::FromInt(scancode);
|
||||
return name;
|
||||
}
|
||||
|
||||
{ SDLK_KP_PERIOD, "Numpad .", "NumPoint" },
|
||||
{ SDLK_KP_DIVIDE, "Numpad /", "NumDivide" },
|
||||
{ SDLK_KP_MULTIPLY, "Numpad *", "NumMultiply" },
|
||||
{ SDLK_KP_MINUS, "Numpad -", "NumMinus" },
|
||||
{ SDLK_KP_PLUS, "Numpad +", "NumPlus" },
|
||||
{ SDLK_KP_ENTER, "Numpad Enter", "NumEnter" },
|
||||
{ SDLK_KP_EQUALS, "Numpad =", "NumEquals" }, //?
|
||||
// Rename some SDL key names (!scancodes) for easier readability.
|
||||
// NB: this does not intend to be exhaustive, merely cover the usual suspects.
|
||||
static const std::unordered_map<SDL_Keycode, CStr> keyNames {{
|
||||
{ SDLK_COMMA, "Comma" },
|
||||
{ SDLK_SEMICOLON, "Semicolon" },
|
||||
{ SDLK_COLON, "Colon" },
|
||||
{ SDLK_PERIOD, "Period" },
|
||||
{ SDLK_EQUALS, "Equals" },
|
||||
{ SDLK_PLUS, "Plus" },
|
||||
{ SDLK_MINUS, "Minus" },
|
||||
|
||||
{ SDLK_UP, "Arrow Up", "UpArrow" },
|
||||
{ SDLK_DOWN, "Arrow Down", "DownArrow" },
|
||||
{ SDLK_RIGHT, "Arrow Right", "RightArrow" },
|
||||
{ SDLK_LEFT, "Arrow Left", "LeftArrow" },
|
||||
{ SDLK_INSERT, "Insert", "Ins" },
|
||||
{ SDLK_HOME, "Home", 0 },
|
||||
{ SDLK_END, "End", 0 },
|
||||
{ SDLK_PAGEUP, "Page Up", "PgUp" },
|
||||
{ SDLK_PAGEDOWN, "Page Down", "PgDn" },
|
||||
{ SDLK_QUOTE, "SingleQuote" },
|
||||
{ SDLK_QUOTEDBL, "DoubleQuote" },
|
||||
{ SDLK_BACKQUOTE, "BackQuote" },
|
||||
|
||||
{ SDLK_F1, "F1", 0 },
|
||||
{ SDLK_F2, "F2", 0 },
|
||||
{ SDLK_F3, "F3", 0 },
|
||||
{ SDLK_F4, "F4", 0 },
|
||||
{ SDLK_F5, "F5", 0 },
|
||||
{ SDLK_F6, "F6", 0 },
|
||||
{ SDLK_F7, "F7", 0 },
|
||||
{ SDLK_F8, "F8", 0 },
|
||||
{ SDLK_F9, "F9", 0 },
|
||||
{ SDLK_F10, "F10", 0 },
|
||||
{ SDLK_F11, "F11", 0 },
|
||||
{ SDLK_F12, "F12", 0 },
|
||||
{ SDLK_F13, "F13", 0 },
|
||||
{ SDLK_F14, "F14", 0 },
|
||||
{ SDLK_F15, "F15", 0 },
|
||||
{ SDLK_LEFTPAREN, { "LeftParen" } },
|
||||
|
||||
{ SDLK_NUMLOCKCLEAR, "Num Lock", "NumLock" },
|
||||
{ SDLK_LEFTBRACKET, { "LeftBracket" } },
|
||||
{ SDLK_RIGHTBRACKET, { "RightBracket" } },
|
||||
{ SDLK_BACKSLASH, { "Backslash" } },
|
||||
{ SDLK_SLASH, { "Slash" } },
|
||||
|
||||
{ SDLK_CAPSLOCK, "Caps Lock", "CapsLock" },
|
||||
{ SDLK_KP_ENTER, "NumEnter" },
|
||||
{ SDLK_KP_DIVIDE, "NumDivide" },
|
||||
{ SDLK_KP_MULTIPLY, "NumMultiply" },
|
||||
{ SDLK_KP_EQUALS, "NumEquals" },
|
||||
{ SDLK_KP_PERIOD, "NumDecimal" },
|
||||
{ SDLK_KP_PLUS, "NumPlus" },
|
||||
{ SDLK_KP_MINUS, "NumMinus" },
|
||||
{ SDLK_KP_0, "Num0" },
|
||||
{ SDLK_KP_1, "Num1" },
|
||||
{ SDLK_KP_2, "Num2" },
|
||||
{ SDLK_KP_3, "Num3" },
|
||||
{ SDLK_KP_4, "Num4" },
|
||||
{ SDLK_KP_5, "Num5" },
|
||||
{ SDLK_KP_6, "Num6" },
|
||||
{ SDLK_KP_7, "Num7" },
|
||||
{ SDLK_KP_8, "Num8" },
|
||||
{ SDLK_KP_9, "Num9" },
|
||||
|
||||
{ SDLK_SCROLLLOCK, "Scroll Lock", "ScrlLock" },
|
||||
{ SDLK_UP, "↑" },
|
||||
{ SDLK_DOWN, "↓" },
|
||||
{ SDLK_LEFT, "←" },
|
||||
{ SDLK_RIGHT, "→" },
|
||||
}};
|
||||
|
||||
{ SDLK_RSHIFT, "Right Shift", "RightShift" },
|
||||
{ SDLK_LSHIFT, "Left Shift", "LeftShift" },
|
||||
{ SDLK_RCTRL, "Right Ctrl", "RightCtrl" },
|
||||
{ SDLK_LCTRL, "Left Ctrl", "LeftCtrl" },
|
||||
{ SDLK_RALT, "Right Alt", "RightAlt" },
|
||||
{ SDLK_LALT, "Left Alt", "LeftAlt" },
|
||||
|
||||
{ SDLK_LGUI, "Left Super", "LeftWin" }, /* "Windows" keys */
|
||||
{ SDLK_RGUI, "Right Super", "RightWin" },
|
||||
|
||||
{ SDLK_MODE, "Alt Gr", "AltGr" },
|
||||
|
||||
{ SDLK_HELP, "Help", 0 }, // ?
|
||||
|
||||
{ SDLK_PRINTSCREEN, "Print Screen", "PrtSc" },
|
||||
|
||||
{ SDLK_SYSREQ, "SysRq", 0 },
|
||||
|
||||
{ SDLK_STOP, "Break", 0 },
|
||||
|
||||
{ SDLK_MENU, "Menu", 0 }, // ?
|
||||
{ SDLK_POWER, "Power", 0 }, // ?
|
||||
{ SDLK_UNDO, "Undo", 0 }, // ?
|
||||
{ MOUSE_LEFT, "Left Mouse Button", "MouseLeft" },
|
||||
{ MOUSE_RIGHT, "Right Mouse Button", "MouseRight" },
|
||||
{ MOUSE_MIDDLE, "Middle Mouse Button", "MouseMiddle" },
|
||||
{ MOUSE_WHEELUP, "Mouse Wheel Up", "WheelUp" },
|
||||
{ MOUSE_WHEELDOWN, "Mouse Wheel Down", "WheelDown" },
|
||||
{ MOUSE_X1, "Mouse X1", "MouseX1" },
|
||||
{ MOUSE_X2, "Mouse X2", "MouseX2" },
|
||||
{ UNIFIED_SHIFT, "Shift", "AnyShift" },
|
||||
{ UNIFIED_CTRL, "Ctrl", "AnyCtrl" },
|
||||
{ UNIFIED_ALT, "Alt", "AnyAlt" },
|
||||
{ UNIFIED_SUPER, "Super", "AnyWindows" },
|
||||
{ 0, 0, 0 },
|
||||
};
|
||||
|
||||
void InitKeyNameMap()
|
||||
CStr FindKeyName(SDL_Scancode scancode)
|
||||
{
|
||||
for (const SKeycodeMapping* it = keycodeMapping; it->keycode != 0; ++it)
|
||||
// Mouse and unified modifiers are harcoded.
|
||||
if (static_cast<int>(scancode) == UNIFIED_SHIFT)
|
||||
return "Shift";
|
||||
else if (static_cast<int>(scancode) == UNIFIED_ALT)
|
||||
return "Alt";
|
||||
else if (static_cast<int>(scancode) == UNIFIED_CTRL)
|
||||
return "Ctrl";
|
||||
else if (static_cast<int>(scancode) == UNIFIED_SUPER)
|
||||
return "Super";
|
||||
else if (static_cast<int>(scancode) == MOUSE_LEFT)
|
||||
return "MouseLeft";
|
||||
else if (static_cast<int>(scancode) == MOUSE_RIGHT)
|
||||
return "MouseRight";
|
||||
else if (static_cast<int>(scancode) == MOUSE_MIDDLE)
|
||||
return "MouseMiddle";
|
||||
else if (static_cast<int>(scancode) == MOUSE_WHEELUP)
|
||||
return "WheelUp";
|
||||
else if (static_cast<int>(scancode) == MOUSE_WHEELDOWN)
|
||||
return "WheelDown";
|
||||
else if (static_cast<int>(scancode) == MOUSE_X1)
|
||||
return "WheelLeft";
|
||||
else if (static_cast<int>(scancode) == MOUSE_X2)
|
||||
return "WheelRight";
|
||||
|
||||
SDL_Keycode code = SDL_GetKeyFromScancode(scancode);
|
||||
|
||||
if (keyNames.find(code) != keyNames.end())
|
||||
return keyNames.at(code);
|
||||
|
||||
if (code != SDLK_UNKNOWN)
|
||||
{
|
||||
keymap.insert(std::pair<CStr,int>(CStr(it->keyname).LowerCase(), it->keycode));
|
||||
if(it->altkeyname)
|
||||
keymap.insert(std::pair<CStr,int>(CStr(it->altkeyname).LowerCase(), it->keycode));
|
||||
const char* keyName = SDL_GetKeyName(code);
|
||||
if (strlen(keyName) != 0)
|
||||
return keyName;
|
||||
}
|
||||
|
||||
// Extra mouse buttons.
|
||||
for (int i = 1; i < 256; ++i) // There is no mouse 0
|
||||
{
|
||||
keymap.insert(std::pair<CStr,int>("mousebutton" + CStr::FromInt(i), MOUSE_BASE + i));
|
||||
keymap.insert(std::pair<CStr,int>("mousen" + CStr::FromInt(i), MOUSE_BASE + i));
|
||||
}
|
||||
}
|
||||
// Try the scancode name.
|
||||
const char* name = SDL_GetScancodeName(scancode);
|
||||
|
||||
int FindKeyCode(const CStr& keyname)
|
||||
{
|
||||
std::map<CStr,int>::iterator it;
|
||||
it = keymap.find(keyname.LowerCase());
|
||||
if (it != keymap.end())
|
||||
return it->second;
|
||||
return 0;
|
||||
}
|
||||
// xxtreme hack: some SDLKeycodes map to chars, and we need to escape [ and \ .
|
||||
if (keyNames.find(static_cast<SDL_Keycode>(*name)) != keyNames.end())
|
||||
return keyNames.at(static_cast<SDL_Keycode>(*name));
|
||||
|
||||
CStr FindKeyName(int keycode)
|
||||
{
|
||||
for (const SKeycodeMapping* it = keycodeMapping; it->keycode != 0; ++it)
|
||||
if (it->keycode == keycode)
|
||||
return CStr(it->keyname);
|
||||
if (strlen(name) != 0)
|
||||
return name;
|
||||
|
||||
return CStr("Unknown");
|
||||
// Else, show something regardless, so the player knows it's at least recognized.
|
||||
return CStr("SYM_") + CStr::FromInt(scancode);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,14 +23,16 @@
|
|||
|
||||
class CStr8;
|
||||
|
||||
extern void InitKeyNameMap();
|
||||
extern CStr8 FindKeyName(int keycode);
|
||||
extern int FindKeyCode(const CStr8& keyname);
|
||||
extern SDL_Scancode FindScancode(const CStr& keyname);
|
||||
// Map a scancode to a locale-independent scancode name.
|
||||
extern CStr8 FindScancodeName(SDL_Scancode scancode);
|
||||
// Map a scancode to a locale-dependent key name (to show the user).
|
||||
extern CStr8 FindKeyName(SDL_Scancode scancode);
|
||||
|
||||
enum {
|
||||
// Start sequential IDs in the right place
|
||||
// Pick a code which is greater than any keycodes used by SDL itself
|
||||
EXTRA_KEYS_BASE = SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES),
|
||||
// Pick a code which is greater than any scancodes used by SDL itself
|
||||
EXTRA_KEYS_BASE = SDL_NUM_SCANCODES,
|
||||
// 'Keycodes' for the unified modifier keys
|
||||
UNIFIED_SHIFT,
|
||||
UNIFIED_CTRL,
|
||||
|
|
|
|||
|
|
@ -112,6 +112,20 @@ bool JSI_ConfigDB::CreateValue(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate
|
|||
return true;
|
||||
}
|
||||
|
||||
bool JSI_ConfigDB::CreateValues(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::wstring& cfgNsString, const std::string& name, const std::vector<CStr>& values)
|
||||
{
|
||||
if (IsProtectedConfigName(name))
|
||||
return false;
|
||||
|
||||
EConfigNamespace cfgNs;
|
||||
if (!GetConfigNamespace(cfgNsString, cfgNs))
|
||||
return false;
|
||||
|
||||
g_ConfigDB.SetValueList(cfgNs, name, values);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool JSI_ConfigDB::RemoveValue(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::wstring& cfgNsString, const std::string& name)
|
||||
{
|
||||
if (IsProtectedConfigName(name))
|
||||
|
|
@ -177,6 +191,7 @@ void JSI_ConfigDB::RegisterScriptFunctions(const ScriptInterface& scriptInterfac
|
|||
scriptInterface.RegisterFunction<bool, std::wstring, bool, &JSI_ConfigDB::SetChanges>("ConfigDB_SetChanges");
|
||||
scriptInterface.RegisterFunction<std::string, std::wstring, std::string, &JSI_ConfigDB::GetValue>("ConfigDB_GetValue");
|
||||
scriptInterface.RegisterFunction<bool, std::wstring, std::string, std::string, &JSI_ConfigDB::CreateValue>("ConfigDB_CreateValue");
|
||||
scriptInterface.RegisterFunction<bool, std::wstring, std::string, std::vector<CStr>, &JSI_ConfigDB::CreateValues>("ConfigDB_CreateValues");
|
||||
scriptInterface.RegisterFunction<bool, std::wstring, std::string, &JSI_ConfigDB::RemoveValue>("ConfigDB_RemoveValue");
|
||||
scriptInterface.RegisterFunction<bool, std::wstring, Path, &JSI_ConfigDB::WriteFile>("ConfigDB_WriteFile");
|
||||
scriptInterface.RegisterFunction<bool, std::wstring, std::string, std::string, Path, &JSI_ConfigDB::WriteValueToFile>("ConfigDB_WriteValueToFile");
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace JSI_ConfigDB
|
|||
bool SetChanges(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, bool value);
|
||||
std::string GetValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name);
|
||||
bool CreateValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value);
|
||||
bool CreateValues(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::vector<CStr>& values);
|
||||
bool RemoveValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name);
|
||||
bool WriteFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const Path& path);
|
||||
bool WriteValueToFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value, const Path& path);
|
||||
|
|
|
|||
169
source/ps/scripting/JSInterface_Hotkey.cpp
Normal file
169
source/ps/scripting/JSInterface_Hotkey.cpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* Copyright (C) 2020 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "JSInterface_Hotkey.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/KeyName.h"
|
||||
#include "scriptinterface/ScriptConversions.h"
|
||||
|
||||
/**
|
||||
* Convert an unordered map to a JS object, mapping keys to values.
|
||||
* Assumes T to have a c_str() method that returns a const char*
|
||||
* NB: this is unordered since no particular effort is made to preserve order.
|
||||
* TODO: this could be moved to ScriptConversions.cpp if the need arises.
|
||||
*/
|
||||
template<typename T, typename U>
|
||||
static void ToJSVal_unordered_map(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<T, U>& val)
|
||||
{
|
||||
JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx));
|
||||
if (!obj)
|
||||
{
|
||||
ret.setUndefined();
|
||||
return;
|
||||
}
|
||||
for (const std::pair<T, U>& item : val)
|
||||
{
|
||||
JS::RootedValue el(rq.cx);
|
||||
ScriptInterface::ToJSVal<U>(rq, &el, item.second);
|
||||
JS_SetProperty(rq.cx, obj, item.first.c_str(), el);
|
||||
}
|
||||
ret.setObject(*obj);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ScriptInterface::ToJSVal<std::unordered_map<std::string, std::vector<std::vector<std::string>>>>(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<std::string, std::vector<std::vector<std::string>>>& val)
|
||||
{
|
||||
ToJSVal_unordered_map(rq, ret, val);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ScriptInterface::ToJSVal<std::unordered_map<std::string, std::string>>(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<std::string, std::string>& val)
|
||||
{
|
||||
ToJSVal_unordered_map(rq, ret, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a (js) object mapping hotkey name (from cfg files) to a list ofscancode names
|
||||
*/
|
||||
JS::Value GetHotkeyMap(ScriptInterface::CmptPrivate* pCmptPrivate)
|
||||
{
|
||||
ScriptRequest rq(*pCmptPrivate->pScriptInterface);
|
||||
|
||||
JS::RootedValue hotkeyMap(rq.cx);
|
||||
|
||||
std::unordered_map<std::string, std::vector<std::vector<std::string>>> hotkeys;
|
||||
for (const std::pair<SDL_Scancode_, KeyMapping>& key : g_HotkeyMap)
|
||||
for (const SHotkeyMapping& mapping : key.second)
|
||||
{
|
||||
std::vector<std::string> keymap;
|
||||
keymap.push_back(FindScancodeName(static_cast<SDL_Scancode>(key.first)));
|
||||
for (const SKey& secondary_key : mapping.requires)
|
||||
keymap.push_back(FindScancodeName(static_cast<SDL_Scancode>(secondary_key.code)));
|
||||
// All hotkey permutations are present so only push one (arbitrarily).
|
||||
if (keymap.size() == 1 || keymap[0] < keymap[1])
|
||||
hotkeys[mapping.name].emplace_back(keymap);
|
||||
}
|
||||
pCmptPrivate->pScriptInterface->ToJSVal(rq, &hotkeyMap, hotkeys);
|
||||
|
||||
return hotkeyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a (js) object mapping scancode names to their locale-dependent name.
|
||||
*/
|
||||
JS::Value GetScancodeKeyNames(ScriptInterface::CmptPrivate* pCmptPrivate)
|
||||
{
|
||||
ScriptRequest rq(*pCmptPrivate->pScriptInterface);
|
||||
|
||||
JS::RootedValue obj(rq.cx);
|
||||
std::unordered_map<std::string, std::string> map;
|
||||
|
||||
// Get the name of all scancodes.
|
||||
// This is slightly wasteful but should be fine overall, they are dense.
|
||||
for (int i = 0; i < MOUSE_LAST; ++i)
|
||||
map[FindScancodeName(static_cast<SDL_Scancode>(i))] = FindKeyName(static_cast<SDL_Scancode>(i));
|
||||
pCmptPrivate->pScriptInterface->ToJSVal(rq, &obj, map);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ReloadHotkeys(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
|
||||
{
|
||||
UnloadHotkeys();
|
||||
LoadHotkeys();
|
||||
}
|
||||
|
||||
JS::Value GetConflicts(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue combination)
|
||||
{
|
||||
ScriptInterface* scriptInterface = pCmptPrivate->pScriptInterface;
|
||||
ScriptRequest rq(*scriptInterface);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
if (!scriptInterface->FromJSVal(rq, combination, keys))
|
||||
{
|
||||
LOGERROR("Invalid hotkey combination");
|
||||
return JS::NullValue();
|
||||
}
|
||||
|
||||
if (keys.empty())
|
||||
return JS::NullValue();
|
||||
|
||||
// Pick a random code as a starting point of the hotkeys (they are all equivalent).
|
||||
SDL_Scancode_ startCode = FindScancode(keys.back());
|
||||
|
||||
std::unordered_map<SDL_Scancode_, KeyMapping>::const_iterator it = g_HotkeyMap.find(startCode);
|
||||
if (it == g_HotkeyMap.end())
|
||||
return JS::NullValue();
|
||||
|
||||
// Create a sorted vector with the remaining keys.
|
||||
keys.pop_back();
|
||||
|
||||
std::set<SKey> codes;
|
||||
for (const std::string& key : keys)
|
||||
codes.insert(SKey{ FindScancode(key), false });
|
||||
|
||||
std::vector<CStr> conflicts;
|
||||
// This isn't very efficient, but we shouldn't iterate too many hotkeys
|
||||
// since we at least have one matching key.
|
||||
for (const SHotkeyMapping& keymap : it->second)
|
||||
{
|
||||
std::set<SKey> match(keymap.requires.begin(), keymap.requires.end());
|
||||
if (codes == match)
|
||||
conflicts.emplace_back(keymap.name);
|
||||
}
|
||||
if (conflicts.empty())
|
||||
return JS::NullValue();
|
||||
|
||||
JS::RootedValue ret(rq.cx);
|
||||
scriptInterface->ToJSVal(rq, &ret, conflicts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void JSI_Hotkey::RegisterScriptFunctions(const ScriptInterface& scriptInterface)
|
||||
{
|
||||
scriptInterface.RegisterFunction<JS::Value, &GetHotkeyMap>("GetHotkeyMap");
|
||||
scriptInterface.RegisterFunction<JS::Value, &GetScancodeKeyNames>("GetScancodeKeyNames");
|
||||
scriptInterface.RegisterFunction<void, &ReloadHotkeys>("ReloadHotkeys");
|
||||
scriptInterface.RegisterFunction<JS::Value, JS::HandleValue, &GetConflicts>("GetConflicts");
|
||||
}
|
||||
28
source/ps/scripting/JSInterface_Hotkey.h
Normal file
28
source/ps/scripting/JSInterface_Hotkey.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* Copyright (C) 2020 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_JSI_HOTKEY
|
||||
#define INCLUDED_JSI_HOTKEY
|
||||
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
namespace JSI_Hotkey
|
||||
{
|
||||
void RegisterScriptFunctions(const ScriptInterface& ScriptInterface);
|
||||
}
|
||||
|
||||
#endif // INCLUDED_JSI_HOTKEY
|
||||
|
|
@ -193,6 +193,7 @@ MESSAGEHANDLER(GuiKeyEvent)
|
|||
SDL_Event_ ev = { { 0 } };
|
||||
ev.ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.ev.key.keysym.sym = (SDL_Keycode)(int)msg->sdlkey;
|
||||
ev.ev.key.keysym.scancode = SDL_GetScancodeFromKey((SDL_Keycode)(int)msg->sdlkey);
|
||||
in_dispatch_event(&ev);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue