diff --git a/data/raw/keybindings/keybindings.json b/data/raw/keybindings/keybindings.json index c7cc5aada769..22cc8fa1158f 100644 --- a/data/raw/keybindings/keybindings.json +++ b/data/raw/keybindings/keybindings.json @@ -18,35 +18,35 @@ "id": "UP", "category": "UILIST", "name": "Pan up", - "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] }, { "type": "keybinding", "id": "DOWN", "category": "UILIST", "name": "Pan down", - "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] }, { "type": "keybinding", "id": "FILTER", "category": "UILIST", "name": "Filter", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "QUIT", "category": "UILIST", "name": "Cancel menu", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "SHOW_DESCRIPTION", "category": "MELEE_STYLE_PICKER", "name": "Show description of melee style", - "bindings": [ { "input_method": "keyboard", "key": "F1" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "F1" } ] }, { "type": "keybinding", @@ -67,37 +67,38 @@ "id": "UP", "category": "MELEE_STYLE_PICKER", "name": "Pan up", - "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] }, { "type": "keybinding", "id": "DOWN", "category": "MELEE_STYLE_PICKER", "name": "Pan down", - "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] }, { "type": "keybinding", "id": "FILTER", "category": "MELEE_STYLE_PICKER", "name": "Filter", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "QUIT", "category": "MELEE_STYLE_PICKER", "name": "Cancel menu", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "UP", "name": "Pan up", "bindings": [ - { "input_method": "keyboard", "key": "k" }, - { "input_method": "keyboard", "key": "8" }, - { "input_method": "keyboard", "key": "UP" }, + { "input_method": "keyboard_any", "key": "k" }, + { "input_method": "keyboard_any", "key": "8" }, + { "input_method": "keyboard_code", "key": "KEYPAD_8" }, + { "input_method": "keyboard_any", "key": "UP" }, { "input_method": "gamepad", "key": "JOY_UP" } ] }, @@ -106,9 +107,10 @@ "id": "DOWN", "name": "Pan down", "bindings": [ - { "input_method": "keyboard", "key": "j" }, - { "input_method": "keyboard", "key": "DOWN" }, - { "input_method": "keyboard", "key": "2" }, + { "input_method": "keyboard_any", "key": "j" }, + { "input_method": "keyboard_any", "key": "DOWN" }, + { "input_method": "keyboard_any", "key": "2" }, + { "input_method": "keyboard_code", "key": "KEYPAD_2" }, { "input_method": "gamepad", "key": "JOY_DOWN" } ] }, @@ -117,9 +119,10 @@ "id": "LEFT", "name": "Pan left", "bindings": [ - { "input_method": "keyboard", "key": "h" }, - { "input_method": "keyboard", "key": "LEFT" }, - { "input_method": "keyboard", "key": "4" }, + { "input_method": "keyboard_any", "key": "h" }, + { "input_method": "keyboard_any", "key": "LEFT" }, + { "input_method": "keyboard_any", "key": "4" }, + { "input_method": "keyboard_code", "key": "KEYPAD_4" }, { "input_method": "gamepad", "key": "JOY_LEFT" } ] }, @@ -128,9 +131,10 @@ "id": "RIGHT", "name": "Pan right", "bindings": [ - { "input_method": "keyboard", "key": "l" }, - { "input_method": "keyboard", "key": "RIGHT" }, - { "input_method": "keyboard", "key": "6" }, + { "input_method": "keyboard_any", "key": "l" }, + { "input_method": "keyboard_any", "key": "RIGHT" }, + { "input_method": "keyboard_any", "key": "6" }, + { "input_method": "keyboard_code", "key": "KEYPAD_6" }, { "input_method": "gamepad", "key": "JOY_RIGHT" } ] }, @@ -139,8 +143,9 @@ "id": "LEFTUP", "name": "Pan up-left", "bindings": [ - { "input_method": "keyboard", "key": "y" }, - { "input_method": "keyboard", "key": "7" }, + { "input_method": "keyboard_any", "key": "y" }, + { "input_method": "keyboard_any", "key": "7" }, + { "input_method": "keyboard_code", "key": "KEYPAD_7" }, { "input_method": "gamepad", "key": "JOY_LEFTUP" } ] }, @@ -149,8 +154,9 @@ "id": "RIGHTUP", "name": "Pan up-right", "bindings": [ - { "input_method": "keyboard", "key": "u" }, - { "input_method": "keyboard", "key": "9" }, + { "input_method": "keyboard_any", "key": "u" }, + { "input_method": "keyboard_any", "key": "9" }, + { "input_method": "keyboard_code", "key": "KEYPAD_9" }, { "input_method": "gamepad", "key": "JOY_RIGHTUP" } ] }, @@ -159,8 +165,9 @@ "id": "LEFTDOWN", "name": "Pan down-left", "bindings": [ - { "input_method": "keyboard", "key": "b" }, - { "input_method": "keyboard", "key": "1" }, + { "input_method": "keyboard_any", "key": "b" }, + { "input_method": "keyboard_any", "key": "1" }, + { "input_method": "keyboard_code", "key": "KEYPAD_1" }, { "input_method": "gamepad", "key": "JOY_LEFTDOWN" } ] }, @@ -169,8 +176,9 @@ "id": "RIGHTDOWN", "name": "Pan down-right", "bindings": [ - { "input_method": "keyboard", "key": "n" }, - { "input_method": "keyboard", "key": "3" }, + { "input_method": "keyboard_any", "key": "n" }, + { "input_method": "keyboard_any", "key": "3" }, + { "input_method": "keyboard_code", "key": "KEYPAD_3" }, { "input_method": "gamepad", "key": "JOY_RIGHTDOWN" } ] }, @@ -178,13 +186,13 @@ "type": "keybinding", "id": "PAGE_UP", "name": "Page up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { "type": "keybinding", "id": "PAGE_DOWN", "name": "Page down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { "type": "keybinding", @@ -203,21 +211,21 @@ "id": "SELECT_ALL", "category": "PICKUP", "name": "Select all", - "bindings": [ { "input_method": "keyboard", "key": "," } ] + "bindings": [ { "input_method": "keyboard_any", "key": "," } ] }, { "type": "keybinding", "id": "SCROLL_UP", "category": "PICKUP", "name": "Scroll item info up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { "type": "keybinding", "id": "SCROLL_DOWN", "category": "PICKUP", "name": "Scroll item info down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { "type": "keybinding", @@ -225,8 +233,8 @@ "category": "PICKUP", "name": "Previous item", "bindings": [ - { "input_method": "keyboard", "key": "k" }, - { "input_method": "keyboard", "key": "UP" }, + { "input_method": "keyboard_any", "key": "k" }, + { "input_method": "keyboard_any", "key": "UP" }, { "input_method": "gamepad", "key": "JOY_UP" } ] }, @@ -236,8 +244,8 @@ "category": "PICKUP", "name": "Next item", "bindings": [ - { "input_method": "keyboard", "key": "j" }, - { "input_method": "keyboard", "key": "DOWN" }, + { "input_method": "keyboard_any", "key": "j" }, + { "input_method": "keyboard_any", "key": "DOWN" }, { "input_method": "gamepad", "key": "JOY_DOWN" } ] }, @@ -247,8 +255,8 @@ "category": "PICKUP", "name": "Unmark selected item", "bindings": [ - { "input_method": "keyboard", "key": "h" }, - { "input_method": "keyboard", "key": "LEFT" }, + { "input_method": "keyboard_any", "key": "h" }, + { "input_method": "keyboard_any", "key": "LEFT" }, { "input_method": "gamepad", "key": "JOY_LEFT" } ] }, @@ -258,8 +266,8 @@ "category": "PICKUP", "name": "Mark selected item", "bindings": [ - { "input_method": "keyboard", "key": "l" }, - { "input_method": "keyboard", "key": "RIGHT" }, + { "input_method": "keyboard_any", "key": "l" }, + { "input_method": "keyboard_any", "key": "RIGHT" }, { "input_method": "gamepad", "key": "JOY_RIGHT" } ] }, @@ -268,460 +276,585 @@ "id": "ENABLE_MAPEXTRA_NOTE", "category": "AUTO_NOTES", "name": "Enable auto note for map extra", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SWITCH_AUTO_NOTE_OPTION", "category": "AUTO_NOTES", "name": "Toggle auto notes option", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "DISABLE_MAPEXTRA_NOTE", "category": "AUTO_NOTES", "name": "Disable auto note for map extra", - "bindings": [ { "input_method": "keyboard", "key": "d" }, { "input_method": "keyboard", "key": "D" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "d" }, + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ADD_RULE", "category": "AUTO_PICKUP", "name": "Add rule", - "bindings": [ { "input_method": "keyboard", "key": "a" }, { "input_method": "keyboard", "key": "A" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "a" }, + { "input_method": "keyboard_char", "key": "A" }, + { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "REMOVE_RULE", "category": "AUTO_PICKUP", "name": "Remove rule", - "bindings": [ { "input_method": "keyboard", "key": "r" }, { "input_method": "keyboard", "key": "R" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "r" }, + { "input_method": "keyboard_char", "key": "R" }, + { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "COPY_RULE", "category": "AUTO_PICKUP", "name": "Copy rule", - "bindings": [ { "input_method": "keyboard", "key": "c" }, { "input_method": "keyboard", "key": "C" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "c" }, + { "input_method": "keyboard_char", "key": "C" }, + { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SWAP_RULE_GLOBAL_CHAR", "category": "AUTO_PICKUP", "name": "Move rule global <-> character", - "bindings": [ { "input_method": "keyboard", "key": "m" }, { "input_method": "keyboard", "key": "M" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "m" }, + { "input_method": "keyboard_char", "key": "M" }, + { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ENABLE_RULE", "category": "AUTO_PICKUP", "name": "Enable rule", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "DISABLE_RULE", "category": "AUTO_PICKUP", "name": "Disable rule", - "bindings": [ { "input_method": "keyboard", "key": "d" }, { "input_method": "keyboard", "key": "D" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "d" }, + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_RULE_UP", "category": "AUTO_PICKUP", "name": "Move rule up", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_RULE_DOWN", "category": "AUTO_PICKUP", "name": "Move rule down", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "TEST_RULE", "category": "AUTO_PICKUP", "name": "Test rule", - "bindings": [ { "input_method": "keyboard", "key": "t" }, { "input_method": "keyboard", "key": "T" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "t" }, + { "input_method": "keyboard_char", "key": "T" }, + { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SWITCH_AUTO_PICKUP_OPTION", "category": "AUTO_PICKUP", "name": "Enable auto pickup option", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ADD_DEFAULT_RULESET", "category": "SAFEMODE", "name": "Add default ruleset", - "bindings": [ { "input_method": "keyboard", "key": "~" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "~" }, { "input_method": "keyboard_code", "key": "`", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "ADD_RULE", "category": "SAFEMODE", "name": "Add rule", - "bindings": [ { "input_method": "keyboard", "key": "a" }, { "input_method": "keyboard", "key": "A" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "a" }, + { "input_method": "keyboard_char", "key": "A" }, + { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "REMOVE_RULE", "category": "SAFEMODE", "name": "Remove rule", - "bindings": [ { "input_method": "keyboard", "key": "r" }, { "input_method": "keyboard", "key": "R" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "r" }, + { "input_method": "keyboard_char", "key": "R" }, + { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "COPY_RULE", "category": "SAFEMODE", "name": "Copy rule", - "bindings": [ { "input_method": "keyboard", "key": "c" }, { "input_method": "keyboard", "key": "C" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "c" }, + { "input_method": "keyboard_char", "key": "C" }, + { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SWAP_RULE_GLOBAL_CHAR", "category": "SAFEMODE", "name": "Move rule global <-> character", - "bindings": [ { "input_method": "keyboard", "key": "m" }, { "input_method": "keyboard", "key": "M" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "m" }, + { "input_method": "keyboard_char", "key": "M" }, + { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ENABLE_RULE", "category": "SAFEMODE", "name": "Enable rule", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "DISABLE_RULE", "category": "SAFEMODE", "name": "Disable rule", - "bindings": [ { "input_method": "keyboard", "key": "d" }, { "input_method": "keyboard", "key": "D" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "d" }, + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_RULE_UP", "category": "SAFEMODE", "name": "Move rule up", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_RULE_DOWN", "category": "SAFEMODE", "name": "Move rule down", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "TEST_RULE", "category": "SAFEMODE", "name": "Test rule", - "bindings": [ { "input_method": "keyboard", "key": "t" }, { "input_method": "keyboard", "key": "T" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "t" }, + { "input_method": "keyboard_char", "key": "T" }, + { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SWITCH_SAFEMODE_OPTION", "category": "SAFEMODE", "name": "Enable safemode option", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "USAGE_HELP", "category": "SORT_ARMOR", "name": "Display Usage Help", - "bindings": [ { "input_method": "keyboard", "key": "F1" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "F1" } ] }, { "type": "keybinding", "id": "MOVE_ARMOR", "category": "SORT_ARMOR", "name": "Select armor for moving", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "RETURN" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_any", "key": "RETURN" }, + { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } + ] }, { "type": "keybinding", "id": "MOVE_PANEL", "category": "PANEL_MGMT", "name": "Select panel for moving", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "RETURN" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_any", "key": "RETURN" }, + { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } + ] }, { "type": "keybinding", "id": "TOGGLE_PANEL", "category": "PANEL_MGMT", "name": "Toggle panel off", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "CHANGE_SIDE", "category": "SORT_ARMOR", "name": "Change side armor is worn on", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "type": "keybinding", "id": "ASSIGN_INVLETS", "category": "SORT_ARMOR", "name": "Assign invlets to armor", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "SORT_ARMOR", "category": "SORT_ARMOR", "name": "Sort armor into natural layer order", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" }, { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "EQUIP_ARMOR", "category": "SORT_ARMOR", "name": "Equip armor from inventory", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "id": "EQUIP_ARMOR_HERE", "category": "SORT_ARMOR", "name": "Equip armor from inventory at this position", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "REMOVE_ARMOR", "category": "SORT_ARMOR", "name": "Unequip selected armor", - "bindings": [ { "input_method": "keyboard", "key": "u" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "u" } ] }, { "type": "keybinding", "id": "HELP_KEYBINDINGS", "name": "Display keybindings menu", - "bindings": [ { "input_method": "keyboard", "key": "?" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "?" }, { "input_method": "keyboard_code", "key": "/", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "FILTER", "name": "Filter", "bindings": [ - { "input_method": "keyboard", "key": "f" }, - { "input_method": "keyboard", "key": "F" }, - { "input_method": "keyboard", "key": "/" } + { "input_method": "keyboard_any", "key": "f" }, + { "input_method": "keyboard_char", "key": "F" }, + { "input_method": "keyboard_code", "key": "f", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "/" }, + { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "RESET_FILTER", "name": "Reset filter", - "bindings": [ { "input_method": "keyboard", "key": "r" }, { "input_method": "keyboard", "key": "R" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "r" }, + { "input_method": "keyboard_char", "key": "R" }, + { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } + ] + }, + { + "type": "keybinding", + "id": "SCROLL_RECIPE_INFO_UP", + "category": "CRAFTING", + "name": "Scroll recipe info up", + "bindings": [ { "input_method": "keyboard_any", "key": "[" } ] }, { "type": "keybinding", - "id": "CYCLE_MODE", + "id": "SCROLL_RECIPE_INFO_DOWN", "category": "CRAFTING", - "name": "Cycle display mode", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "name": "Scroll recipe info down", + "bindings": [ { "input_method": "keyboard_any", "key": "]" } ] }, { "type": "keybinding", "id": "HELP_RECIPE", "category": "CRAFTING", "name": "Show recipe result", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "HIDE_SHOW_RECIPE", "category": "CRAFTING", "name": "Hide/show recipe", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "CYCLE_BATCH", "category": "CRAFTING", "name": "Batch crafting", - "bindings": [ { "input_method": "keyboard", "key": [ "b" ] } ] + "bindings": [ { "input_method": "keyboard_any", "key": [ "b" ] } ] }, { "type": "keybinding", "id": "TOGGLE_UNAVAILABLE", "category": "CRAFTING", "name": "Hide/show unavailable", - "bindings": [ { "input_method": "keyboard", "key": "u" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "u" } ] }, { "type": "keybinding", "id": "SCROLL_UP", "category": "CRAFTING", "name": "Scroll item info up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { "type": "keybinding", "id": "SCROLL_DOWN", "category": "CRAFTING", "name": "Scroll item info down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { "type": "keybinding", "id": "RELATED_RECIPES", "category": "CRAFTING", "name": "Related recipes", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_FAVORITE", "category": "CRAFTING", "name": "Toggle recipes to be shown in favorite tab", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "LEVEL_UP", "name": "Go Up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" }, { "input_method": "keyboard", "key": "<" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "LEVEL_DOWN", "name": "Go Down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" }, { "input_method": "keyboard", "key": ">" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "NEXT", "category": "SOKOBAN", "name": "Next level", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PREV", "category": "SOKOBAN", "name": "Previous level", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "RESET", "category": "SOKOBAN", "name": "Reset level", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "UNDO", "category": "SOKOBAN", "name": "Undo move", - "bindings": [ { "input_method": "keyboard", "key": "u" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "u" } ] }, { "type": "keybinding", "id": "NEW", "category": "MINESWEEPER", "name": "Create New level", - "bindings": [ { "input_method": "keyboard", "key": "n" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "n" } ] }, { "type": "keybinding", "id": "FLAG", "category": "MINESWEEPER", "name": "Toggle Flag", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "TOGGLE_SPACE", "category": "LIGHTSON", "name": "Toggle lights", - "bindings": [ { "input_method": "keyboard", "key": " " } ] + "bindings": [ { "input_method": "keyboard_any", "key": " " } ] }, { "type": "keybinding", "id": "TOGGLE_5", "category": "LIGHTSON", "name": "Toggle lights", - "bindings": [ { "input_method": "keyboard", "key": "5" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "5" }, { "input_method": "keyboard_code", "key": "KEYPAD_5" } ] }, { "type": "keybinding", "id": "RESET", "category": "LIGHTSON", "name": "Reset level", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "CONFIRM", "name": "Confirm Choice", - "bindings": [ { "input_method": "keyboard", "key": "RETURN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "RETURN" }, { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } ] }, { "type": "keybinding", "id": "SAVE_DEFAULT_MODS", "category": "MODMANAGER_DIALOG", "name": "Save mods as default", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "VIEW_MOD_DESCRIPTION", "category": "MODMANAGER_DIALOG", "name": "View full mod description", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "type": "keybinding", "id": "ADD_MOD", "category": "MODMANAGER_DIALOG", "name": "Move selected mod up", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "REMOVE_MOD", "category": "MODMANAGER_DIALOG", "name": "Move selected mod down", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "TOGGLE_SHOW_OBSOLETE", "category": "MODMANAGER_DIALOG", "name": "Show obsolete mods", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "type": "keybinding", "id": "NEXT_CATEGORY_TAB", "category": "MODMANAGER_DIALOG", "name": "Move to next category tab", - "bindings": [ { "input_method": "keyboard", "key": ">" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "PREV_CATEGORY_TAB", "category": "MODMANAGER_DIALOG", "name": "Move to previous category tab", - "bindings": [ { "input_method": "keyboard", "key": "<" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "PICK_RANDOM_WORLDNAME", "category": "WORLDGEN_CONFIRM_DIALOG", "name": "Pick random world name", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", @@ -729,191 +862,192 @@ "category": "WORLDGEN_CONFIRM_DIALOG", "name": "Exit worldgen screen", "//": "separate entry, because the global entry also has 'q' and 'Q' listed, which conflicts with the world name entry feature of this dialog", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "ZOOM_OUT", "category": "OVERMAP", "name": "Zoom Out", - "bindings": [ { "input_method": "keyboard", "key": "Z" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "Z" }, { "input_method": "keyboard_code", "key": "z", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "ZOOM_IN", "category": "OVERMAP", "name": "Zoom In", - "bindings": [ { "input_method": "keyboard", "key": "z" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "z" } ] }, { "type": "keybinding", "id": "DELETE_NOTE", "category": "OVERMAP_NOTES", "name": "Delete Note", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "EDIT_NOTE", "category": "OVERMAP_NOTES", "name": "Edit Note", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "MARK_DANGER", "category": "OVERMAP_NOTES", "name": "Mark as Dangerous", - "bindings": [ { "input_method": "keyboard", "key": "M" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "CHANGE_SORT", "category": "OVERMAP_NOTES", "name": "Change sorting mode", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" } ] }, { "type": "keybinding", "id": "CLEAR_FILTER", "category": "OVERMAP_NOTES", "name": "Clear filter", - "bindings": [ { "input_method": "keyboard", "key": "R" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "R" } ] }, { "type": "keybinding", "id": "DELETE_NOTE", "category": "OVERMAP", "name": "Delete Note", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "CREATE_NOTE", "category": "OVERMAP", "name": "Create/Edit Note", - "bindings": [ { "input_method": "keyboard", "key": "N" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "N" }, { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "LIST_NOTES", "category": "OVERMAP", "name": "List Notes", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_BLINKING", "category": "OVERMAP", "name": "Toggle Blinking", - "bindings": [ { "input_method": "keyboard", "key": "B" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "B" }, { "input_method": "keyboard_code", "key": "b", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_OVERLAYS", "category": "OVERMAP", "name": "Toggle Overlays", - "bindings": [ { "input_method": "keyboard", "key": "O" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "O" }, { "input_method": "keyboard_code", "key": "o", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_LAND_USE_CODES", "category": "OVERMAP", "name": "Toggle Land Use Codes", - "bindings": [ { "input_method": "keyboard", "key": "A" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_MAP_NOTES", "category": "OVERMAP", "name": "Toggle Map Notes", - "bindings": [ { "input_method": "keyboard", "key": "G" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "G" }, { "input_method": "keyboard_code", "key": "g", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_CITY_LABELS", "category": "OVERMAP", "name": "Toggle City Labels", - "bindings": [ { "input_method": "keyboard", "key": "C" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "C" }, { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_HORDES", "category": "OVERMAP", "name": "Toggle Hordes", - "bindings": [ { "input_method": "keyboard", "key": "H" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "H" }, { "input_method": "keyboard_code", "key": "h", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_OVERMAP_WEATHER", "category": "OVERMAP", "name": "Toggle Visible Weather", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "w" } ] }, { "type": "keybinding", "id": "TOGGLE_FOREST_TRAILS", "category": "OVERMAP", "name": "Toggle Forest Trails", - "bindings": [ { "input_method": "keyboard", "key": "T" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "T" }, { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_EXPLORED", "category": "OVERMAP", "name": "Toggle Explored", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "PLACE_TERRAIN", "category": "OVERMAP", "name": "Place Overmap Terrain", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "id": "PLACE_SPECIAL", "category": "OVERMAP", "name": "Place Overmap Special", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "SET_SPECIAL_ARGS", "category": "OVERMAP", "name": "Set Overmap Special Arguments", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "name": "View Missions", "category": "OVERMAP", "id": "MISSIONS", - "bindings": [ { "input_method": "keyboard", "key": "M" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "ROTATE", "category": "OVERMAP_EDITOR", "name": "Rotate", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "SEARCH", "name": "Search", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "QUIT", "name": "Exit screen", "bindings": [ - { "input_method": "keyboard", "key": "ESC" }, - { "input_method": "keyboard", "key": "q" }, - { "input_method": "keyboard", "key": "Q" }, - { "input_method": "keyboard", "key": "SPACE" }, + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, { "input_method": "gamepad", "key": "JOY_3" } ] }, @@ -921,7 +1055,7 @@ "type": "keybinding", "id": "CHOOSE_DESTINATION", "name": "Choose destination", - "bindings": [ { "input_method": "keyboard", "key": "W" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "W" }, { "input_method": "keyboard_code", "key": "w", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -929,8 +1063,9 @@ "category": "TARGET", "name": "Fire Weapon", "bindings": [ - { "input_method": "keyboard", "key": "f" }, - { "input_method": "keyboard", "key": "RETURN" }, + { "input_method": "keyboard_any", "key": "f" }, + { "input_method": "keyboard_any", "key": "RETURN" }, + { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" }, { "input_method": "mouse", "key": "MOUSE_RIGHT" } ] }, @@ -940,9 +1075,10 @@ "category": "TARGET", "name": "Prev Target", "bindings": [ - { "input_method": "keyboard", "key": "BACKTAB" }, + { "input_method": "keyboard_char", "key": "BACKTAB" }, + { "input_method": "keyboard_code", "key": "TAB", "mod": [ "shift" ] }, { "input_method": "mouse", "key": "SCROLL_UP" }, - { "input_method": "keyboard", "key": "PPAGE" } + { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { @@ -951,9 +1087,9 @@ "category": "TARGET", "name": "Next Target", "bindings": [ - { "input_method": "keyboard", "key": "TAB" }, + { "input_method": "keyboard_any", "key": "TAB" }, { "input_method": "mouse", "key": "SCROLL_DOWN" }, - { "input_method": "keyboard", "key": "NPAGE" } + { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { @@ -961,63 +1097,83 @@ "id": "AIM", "category": "TARGET", "name": "Aim", - "bindings": [ { "input_method": "keyboard", "key": "." }, { "input_method": "keyboard", "key": "5" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "." }, + { "input_method": "keyboard_code", "key": "KEYPAD_PERIOD" }, + { "input_method": "keyboard_any", "key": "5" }, + { "input_method": "keyboard_code", "key": "KEYPAD_5" } + ] }, { "type": "keybinding", "id": "AIMED_SHOT", "category": "TARGET", "name": "Aimed Shot", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "id": "CAREFUL_SHOT", "category": "TARGET", "name": "Careful Shot", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "type": "keybinding", "id": "PRECISE_SHOT", "category": "TARGET", "name": "Precise Shot", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] + }, + { + "type": "keybinding", + "id": "SWITCH_AIM", + "category": "TARGET", + "name": "Switch Aiming Mode", + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", "id": "SWITCH_AMMO", "category": "TARGET", "name": "Switch ammo", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "type": "keybinding", "id": "SWITCH_MODE", "category": "TARGET", "name": "Switch Firing Mode", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "F" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "F" }, + { "input_method": "keyboard_code", "key": "f", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "TOGGLE_TURRET_LINES", "category": "TARGET", "name": "Toggle turret lines", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "id": "TOGGLE_SNAP_TO_TARGET", "category": "TARGET", "name": "Toggle Snap to Target", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "TOGGLE_MOVE_CURSOR_VIEW", "category": "TARGET", "name": "Toggle moving view / cursor", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "type": "keybinding", @@ -1041,106 +1197,113 @@ "type": "keybinding", "id": "NEXT_TAB", "name": "Go to next tab", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "PREV_TAB", "name": "Go to prev tab", - "bindings": [ { "input_method": "keyboard", "key": "BACKTAB" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "BACKTAB" }, + { "input_method": "keyboard_code", "key": "TAB", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "TOGGLE_FAST_SCROLL", "name": "Toggle Fast Scroll", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "EXTENDED_DESCRIPTION", "name": "Show extended description", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "id": "TRAVEL_TO", "name": "Travel to destination", - "bindings": [ { "input_method": "keyboard", "key": "T" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "T" }, { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "CENTER", "name": "Center On Character", - "bindings": [ { "input_method": "keyboard", "key": "0" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "0" }, { "input_method": "keyboard_code", "key": "KEYPAD_0" } ] }, { "type": "keybinding", "id": "HELP", "category": "CARAVAN", "name": "Display Help", - "bindings": [ { "input_method": "keyboard", "key": "0" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "0" }, { "input_method": "keyboard_code", "key": "KEYPAD_0" } ] }, { "type": "keybinding", "id": "CHANGE_GENDER", "name": "Change gender", - "bindings": [ { "input_method": "keyboard", "key": "@" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "@" }, { "input_method": "keyboard_code", "key": "2", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "SAVE_TEMPLATE", "category": "DEFENSE_SETUP", "name": "Save template", - "bindings": [ { "input_method": "keyboard", "key": "!" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "START", "category": "DEFENSE_SETUP", "name": "Start", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" }, { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "DELETE_TEMPLATE", "category": "MAIN_MENU", "name": "Delete template", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "type": "keybinding", "id": "SAVE_TEMPLATE", "category": "NEW_CHAR_DESCRIPTION", "name": "Save template", - "bindings": [ { "input_method": "keyboard", "key": "!" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "REROLL_CHARACTER", "category": "NEW_CHAR_DESCRIPTION", "name": "Reroll Random Character", - "bindings": [ { "input_method": "keyboard", "key": "%" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "%" }, { "input_method": "keyboard_code", "key": "5", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "REROLL_CHARACTER_WITH_SCENARIO", "category": "NEW_CHAR_DESCRIPTION", "name": "Reroll Random Character With Scenario", - "bindings": [ { "input_method": "keyboard", "key": "^" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "^" }, { "input_method": "keyboard_code", "key": "6", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "RANDOMIZE_CHAR_DESCRIPTION", "category": "NEW_CHAR_DESCRIPTION", "name": "Pick random character description", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "CHOOSE_LOCATION", "category": "NEW_CHAR_DESCRIPTION", "name": "Choose character start location", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", @@ -1148,209 +1311,212 @@ "category": "NEW_CHAR_DESCRIPTION", "name": "Exit new character screen", "//": "separate entry, because the global entry also has 'q' and 'Q' listed, which conflicts with the character name entry feature of this dialog", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "SORT", "category": "NEW_CHAR_PROFESSIONS", "name": "Toggle sorting order", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "SORT", "category": "NEW_CHAR_SCENARIOS", "name": "Toggle sorting order", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "SCROLL_UP", "category": "NEW_CHAR_SKILLS", "name": "Scroll description up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { "type": "keybinding", "id": "SCROLL_DOWN", "category": "NEW_CHAR_SKILLS", "name": "Scroll description down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { "type": "keybinding", "id": "INSTALL", "category": "VEH_INTERACT", "name": "Install part", - "bindings": [ { "input_method": "keyboard", "key": "i" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "i" } ] }, { "type": "keybinding", "id": "REPAIR", "category": "VEH_INTERACT", "name": "Repair part", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "MEND", "category": "VEH_INTERACT", "name": "Mend part", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", "id": "REFILL", "category": "VEH_INTERACT", "name": "Refill tank/battery", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "UNLOAD", "category": "VEH_INTERACT", "name": "Unload fuel bunker", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "type": "keybinding", "id": "REMOVE", "category": "VEH_INTERACT", "name": "Remove part", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "type": "keybinding", "id": "RENAME", "category": "VEH_INTERACT", "name": "Rename vehicle", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "id": "SIPHON", "category": "VEH_INTERACT", "name": "Siphon from tank", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "TIRE_CHANGE", "category": "VEH_INTERACT", "name": "Change tire", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "type": "keybinding", "id": "CHANGE_SHAPE", "category": "VEH_INTERACT", "name": "Change part shape", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "type": "keybinding", "id": "ASSIGN_CREW", "category": "VEH_INTERACT", "name": "Assign crew", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "type": "keybinding", "id": "RELABEL", "category": "VEH_INTERACT", "name": "Relabel a portion of a vehicle", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "id": "NEXT_TAB", "category": "VEH_INTERACT", "name": "Go to next tab", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "PREV_TAB", "category": "VEH_INTERACT", "name": "Go to prev tab", - "bindings": [ { "input_method": "keyboard", "key": "BACKTAB" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "BACKTAB" }, + { "input_method": "keyboard_code", "key": "TAB", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "FUEL_LIST_UP", "category": "VEH_INTERACT", "name": "Scroll up through fuel list", - "bindings": [ { "input_method": "keyboard", "key": "[" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "[" } ] }, { "type": "keybinding", "id": "FUEL_LIST_DOWN", "category": "VEH_INTERACT", "name": "Scroll down through fuel list", - "bindings": [ { "input_method": "keyboard", "key": "]" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "]" } ] }, { "type": "keybinding", "id": "DESC_LIST_UP", "category": "VEH_INTERACT", "name": "Scroll up through vehicle part descriptions", - "bindings": [ { "input_method": "keyboard", "key": "<" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "DESC_LIST_DOWN", "category": "VEH_INTERACT", "name": "Scroll down through vehicle part descriptions", - "bindings": [ { "input_method": "keyboard", "key": ">" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "OVERVIEW_UP", "category": "VEH_INTERACT", "name": "Scroll up through vehicle overview", - "bindings": [ { "input_method": "keyboard", "key": "{" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "{" }, { "input_method": "keyboard_code", "key": "[", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "OVERVIEW_DOWN", "category": "VEH_INTERACT", "name": "Scroll down through vehicle overview", - "bindings": [ { "input_method": "keyboard", "key": "}" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "}" }, { "input_method": "keyboard_code", "key": "]", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_UNAVAILABLE_CONSTRUCTIONS", "category": "CONSTRUCTION", "name": "Toggle unavailable constructions", - "bindings": [ { "input_method": "keyboard", "key": ";" } ] + "bindings": [ { "input_method": "keyboard_any", "key": ";" } ] }, { "type": "keybinding", "id": "TOGGLE_FAVORITE", "category": "CONSTRUCTION", "name": "Toggle construction as favorite", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "*" } ] }, { "type": "keybinding", "id": "SCROLL_STAGE_UP", "category": "CONSTRUCTION", "name": "Scroll to previous stage", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "type": "keybinding", "id": "SCROLL_STAGE_DOWN", "category": "CONSTRUCTION", "name": "Scroll to next stage", - "bindings": [ { "input_method": "keyboard", "key": "n" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "n" } ] }, { "type": "keybinding", "id": "EDITMAP_SHOW_ALL", "name": "Show all", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "type": "keybinding", @@ -1358,9 +1524,9 @@ "category": "EDITMAP_FEATURE", "name": "Confirm & quit", "bindings": [ - { "input_method": "keyboard", "key": "e" }, - { "input_method": "keyboard", "key": "u" }, - { "input_method": "keyboard", "key": "t" } + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_any", "key": "u" }, + { "input_method": "keyboard_any", "key": "t" } ] }, { @@ -1368,259 +1534,275 @@ "id": "RESIZE", "category": "EDITMAP_SHAPE", "name": "Resize", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "START", "category": "EDITMAP_SHAPE", "name": "To start", - "bindings": [ { "input_method": "keyboard", "key": "z" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "z" } ] }, { "type": "keybinding", "id": "SWAP", "category": "EDITMAP_SHAPE", "name": "Swap origin and target", - "bindings": [ { "input_method": "keyboard", "key": "y" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "y" } ] }, { "type": "keybinding", "id": "EDITMAP_MOVE", "name": "Move shape", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", "id": "EDITMAP_TAB", "name": "Switch to move point / confirm", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "EDIT_TRAPS", "category": "EDITMAP", "name": "Edit traps", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "id": "EDIT_FIELDS", "category": "EDITMAP", "name": "Edit fields", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "EDIT_TERRAIN", "category": "EDITMAP", "name": "Edit terrain", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "id": "EDIT_FURNITURE", "category": "EDITMAP", "name": "Edit furniture", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "EDIT_OVERMAP", "category": "EDITMAP", "name": "Edit overmap / mapgen", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "type": "keybinding", "id": "EDIT_ITEMS", "category": "EDITMAP", "name": "Edit items", - "bindings": [ { "input_method": "keyboard", "key": "i" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "i" } ] }, { "type": "keybinding", "id": "EDIT_MONSTER", "category": "EDITMAP", "name": "Edit creatures", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", "id": "SHOW_ALL", "category": "EDITMAP", "name": "Show whole map", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "type": "keybinding", "id": "LEFT_WIDE", "name": "Wide move left", - "bindings": [ { "input_method": "keyboard", "key": "H" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "H" }, { "input_method": "keyboard_code", "key": "h", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "RIGHT_WIDE", "name": "Wide move right", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "UP_WIDE", "name": "Wide move up", - "bindings": [ { "input_method": "keyboard", "key": "K" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "K" }, { "input_method": "keyboard_code", "key": "k", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "DOWN_WIDE", "name": "Wide move down", - "bindings": [ { "input_method": "keyboard", "key": "J" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "J" }, { "input_method": "keyboard_code", "key": "j", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "CATEGORY_SELECTION", "category": "INVENTORY", "name": "Toggle category selection mode", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] + }, + { + "type": "keybinding", + "id": "VIEW_CATEGORY_MODE", + "name": "Toggle inventory view to show item categories", + "bindings": [ { "input_method": "keyboard_any", "key": ";" } ] }, { "type": "keybinding", "id": "INVENTORY_FILTER", "category": "INVENTORY", "name": "Set item filter", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "TOGGLE_FAVORITE", "category": "INVENTORY", "name": "Toggle item as favorite", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "TOGGLE_EXAMINE", "category": "BIONICS", "name": "Toggle activate/examine", - "bindings": [ { "input_method": "keyboard", "key": "!" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_SAFE_FUEL", "category": "BIONICS", "name": "Toggle safe fuel mod", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" }, { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "TOGGLE_AUTO_START", "category": "BIONICS", "name": "Toggle auto start mod", - "bindings": [ { "input_method": "keyboard", "key": "A" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "SORT", "category": "BIONICS", "name": "Sort bionics list", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "TOGGLE_EXAMINE", "category": "MUTATIONS", "name": "Toggle activate/examine", - "bindings": [ { "input_method": "keyboard", "key": "!" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Pause", "category": "DEFAULTMODE", "id": "pause", - "bindings": [ { "input_method": "keyboard", "key": "." }, { "input_method": "keyboard", "key": "5" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "." }, + { "input_method": "keyboard_code", "key": "KEYPAD_PERIOD" }, + { "input_method": "keyboard_any", "key": "5" }, + { "input_method": "keyboard_code", "key": "KEYPAD_5" } + ] }, { "type": "keybinding", "name": "Toggle Map Memory", "category": "DEFAULTMODE", - "id": "toggle_map_memory" + "id": "toggle_map_memory", + "bindings": [ { "input_method": "keyboard_char", "key": "{" }, { "input_method": "keyboard_code", "key": "[", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Center View", "category": "DEFAULTMODE", "id": "center", - "bindings": [ { "input_method": "keyboard", "key": ":" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ":" }, { "input_method": "keyboard_code", "key": ";", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Move View North", "category": "DEFAULTMODE", "id": "shift_n", - "bindings": [ { "input_method": "keyboard", "key": "K" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "K" }, { "input_method": "keyboard_code", "key": "k", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Move View East", "category": "DEFAULTMODE", "id": "shift_e", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Move View South", "category": "DEFAULTMODE", "id": "shift_s", - "bindings": [ { "input_method": "keyboard", "key": "J" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "J" }, { "input_method": "keyboard_code", "key": "j", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Move View West", "category": "DEFAULTMODE", "id": "shift_w", - "bindings": [ { "input_method": "keyboard", "key": "H" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "H" }, { "input_method": "keyboard_code", "key": "h", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Open Door", "category": "DEFAULTMODE", "id": "open", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "type": "keybinding", "name": "Close Door", "category": "DEFAULTMODE", "id": "close", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "type": "keybinding", "name": "Smash Nearby Terrain", "category": "DEFAULTMODE", "id": "smash", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "name": "Examine Nearby Terrain", "category": "DEFAULTMODE", "id": "examine", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "name": "Advanced Inventory management", "category": "DEFAULTMODE", "id": "advinv", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "name": "Pick up Nearby Item(s)", "category": "DEFAULTMODE", "id": "pickup", - "bindings": [ { "input_method": "keyboard", "key": "g" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "g" } ] }, { "type": "keybinding", @@ -1633,42 +1815,42 @@ "name": "Grab something nearby", "category": "DEFAULTMODE", "id": "grab", - "bindings": [ { "input_method": "keyboard", "key": "G" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "G" }, { "input_method": "keyboard_code", "key": "g", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Haul items along the ground", "category": "DEFAULTMODE", "id": "haul", - "bindings": [ { "input_method": "keyboard", "key": "\\" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "\\" } ] }, { "type": "keybinding", "name": "Zone activities", "category": "DEFAULTMODE", "id": "loot", - "bindings": [ { "input_method": "keyboard", "key": "O" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "O" }, { "input_method": "keyboard_code", "key": "o", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Butcher", "category": "DEFAULTMODE", "id": "butcher", - "bindings": [ { "input_method": "keyboard", "key": "B" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "B" }, { "input_method": "keyboard_code", "key": "b", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Chat with NPC", "category": "DEFAULTMODE", "id": "chat", - "bindings": [ { "input_method": "keyboard", "key": "C" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "C" }, { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Look Around", "category": "DEFAULTMODE", "id": "look", - "bindings": [ { "input_method": "keyboard", "key": ";" }, { "input_method": "keyboard", "key": "x" } ] + "bindings": [ { "input_method": "keyboard_any", "key": ";" }, { "input_method": "keyboard_any", "key": "x" } ] }, { "type": "keybinding", @@ -1681,77 +1863,77 @@ "name": "Peek Around Corners", "category": "DEFAULTMODE", "id": "peek", - "bindings": [ { "input_method": "keyboard", "key": "X" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "X" }, { "input_method": "keyboard_code", "key": "x", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "List all items around the player", "category": "DEFAULTMODE", "id": "listitems", - "bindings": [ { "input_method": "keyboard", "key": "V" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "V" }, { "input_method": "keyboard_code", "key": "v", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Manage zones", "category": "DEFAULTMODE", "id": "zones", - "bindings": [ { "input_method": "keyboard", "key": "Y" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "Y" }, { "input_method": "keyboard_code", "key": "y", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Open Inventory", "category": "DEFAULTMODE", "id": "inventory", - "bindings": [ { "input_method": "keyboard", "key": "i" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "i" } ] }, { "type": "keybinding", "name": "Compare two Items", "category": "DEFAULTMODE", "id": "compare", - "bindings": [ { "input_method": "keyboard", "key": "I" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "I" }, { "input_method": "keyboard_code", "key": "i", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Swap Inventory Letters", "category": "DEFAULTMODE", "id": "organize", - "bindings": [ { "input_method": "keyboard", "key": "=" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "=" } ] }, { "type": "keybinding", "name": "Apply or Use Item", "category": "DEFAULTMODE", "id": "apply", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "name": "Apply or Use Wielded Item", "category": "DEFAULTMODE", "id": "apply_wielded", - "bindings": [ { "input_method": "keyboard", "key": "A" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Wear Item", "category": "DEFAULTMODE", "id": "wear", - "bindings": [ { "input_method": "keyboard", "key": "W" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "W" }, { "input_method": "keyboard_code", "key": "w", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Take Off Worn Item", "category": "DEFAULTMODE", "id": "take_off", - "bindings": [ { "input_method": "keyboard", "key": "T" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "T" }, { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Eat", "category": "DEFAULTMODE", "id": "eat", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -1764,21 +1946,21 @@ "name": "Read", "category": "DEFAULTMODE", "id": "read", - "bindings": [ { "input_method": "keyboard", "key": "R" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "R" }, { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Wield", "category": "DEFAULTMODE", "id": "wield", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "type": "keybinding", "name": "Select Martial Arts Style", "category": "DEFAULTMODE", "id": "pick_style", - "bindings": [ { "input_method": "keyboard", "key": "_" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "_" }, { "input_method": "keyboard_code", "key": "-", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -1797,126 +1979,134 @@ "name": "Unload or Empty Wielded Item", "category": "DEFAULTMODE", "id": "unload", - "bindings": [ { "input_method": "keyboard", "key": "U" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "U" }, { "input_method": "keyboard_code", "key": "u", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Throw Item", "category": "DEFAULTMODE", "id": "throw", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "name": "Blind Throw Item", "category": "LOOK", "id": "throw_blind", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "name": "Fire Wielded Item", "category": "DEFAULTMODE", "id": "fire", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "name": "Toggle attack mode of Wielded Item", "category": "DEFAULTMODE", "id": "select_fire_mode", - "bindings": [ { "input_method": "keyboard", "key": "F" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "F" }, { "input_method": "keyboard_code", "key": "f", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Select default ammo for Wielded Item", "category": "DEFAULTMODE", "id": "select_default_ammo", - "bindings": [ { "input_method": "keyboard", "key": "{" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "{" } ] }, { "type": "keybinding", "name": "Drop Item", "category": "DEFAULTMODE", "id": "drop", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "type": "keybinding", "name": "Drop Item to Adjacent Tile", "category": "DEFAULTMODE", "id": "drop_adj", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "View/Activate Bionics", "category": "DEFAULTMODE", "id": "bionics", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "type": "keybinding", "name": "View/Activate Mutations", "category": "DEFAULTMODE", "id": "mutations", - "bindings": [ { "input_method": "keyboard", "key": "[" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "[" } ] }, { "type": "keybinding", "name": "Re-layer armor/clothing", "category": "DEFAULTMODE", "id": "sort_armor", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "name": "Wait for Several Minutes", "category": "DEFAULTMODE", "id": "wait", - "bindings": [ { "input_method": "keyboard", "key": "|" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "|" }, { "input_method": "keyboard_code", "key": "", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Craft Items", "category": "DEFAULTMODE", "id": "craft", - "bindings": [ { "input_method": "keyboard", "key": "&" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "&" }, { "input_method": "keyboard_code", "key": "7", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Recraft last recipe", "category": "DEFAULTMODE", "id": "recraft", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "name": "Construct Terrain", "category": "DEFAULTMODE", "id": "construct", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "name": "Disassemble items", "category": "DEFAULTMODE", "id": "disassemble", - "bindings": [ { "input_method": "keyboard", "key": "(" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "(" }, { "input_method": "keyboard_code", "key": "9", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Sleep", "category": "DEFAULTMODE", "id": "sleep", - "bindings": [ { "input_method": "keyboard", "key": "$" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "$" }, { "input_method": "keyboard_code", "key": "4", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Control Vehicle", "category": "DEFAULTMODE", "id": "control_vehicle", - "bindings": [ { "input_method": "keyboard", "key": "^" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "^" }, { "input_method": "keyboard_code", "key": "6", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -1930,28 +2120,28 @@ "name": "Toggle Safe Mode", "category": "DEFAULTMODE", "id": "safemode", - "bindings": [ { "input_method": "keyboard", "key": "!" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Ignore Nearby Enemy", "category": "DEFAULTMODE", "id": "ignore_enemy", - "bindings": [ { "input_method": "keyboard", "key": "'" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "'" } ] }, { "type": "keybinding", "name": "Whitelist enemy", "category": "DEFAULTMODE", "id": "whitelist_enemy", - "bindings": [ { "input_method": "keyboard", "key": "~" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "~" }, { "input_method": "keyboard_code", "key": "`", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Save and Quit", "category": "DEFAULTMODE", "id": "save", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" }, { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -1964,14 +2154,14 @@ "name": "View Player Info", "category": "DEFAULTMODE", "id": "player_data", - "bindings": [ { "input_method": "keyboard", "key": "@" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "@" }, { "input_method": "keyboard_code", "key": "2", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "View Map", "category": "DEFAULTMODE", "id": "map", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", @@ -1984,61 +2174,61 @@ "name": "View Missions", "category": "DEFAULTMODE", "id": "missions", - "bindings": [ { "input_method": "keyboard", "key": "M" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "View Factions", "category": "DEFAULTMODE", "id": "factions", - "bindings": [ { "input_method": "keyboard", "key": "#" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "#" }, { "input_method": "keyboard_code", "key": "3", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "View Achievements, Scores, and Kills", "category": "DEFAULTMODE", "id": "scores", - "bindings": [ { "input_method": "keyboard", "key": ")" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ")" }, { "input_method": "keyboard_code", "key": "0", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "View Morale", "category": "DEFAULTMODE", "id": "morale", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "type": "keybinding", "name": "View Message Log", "category": "DEFAULTMODE", "id": "messages", - "bindings": [ { "input_method": "keyboard", "key": "P" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "P" }, { "input_method": "keyboard_code", "key": "p", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Open Wiki", "category": "DEFAULTMODE", "id": "open_wiki", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" }, { "input_method": "keyboard_code", "key": "w" } ] }, { "type": "keybinding", "name": "View Help", "category": "DEFAULTMODE", "id": "help", - "bindings": [ { "input_method": "keyboard", "key": "0" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "0" }, { "input_method": "keyboard_code", "key": "KEYPAD_0" } ] }, { "type": "keybinding", "name": "Zoom In", "id": "zoom_in", - "bindings": [ { "input_method": "keyboard", "key": "z" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "z" } ] }, { "type": "keybinding", "name": "Zoom Out", "id": "zoom_out", - "bindings": [ { "input_method": "keyboard", "key": "Z" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "Z" }, { "input_method": "keyboard_code", "key": "z", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -2051,14 +2241,14 @@ "name": "Debug Menu", "category": "DEFAULTMODE", "id": "debug", - "bindings": [ { "input_method": "keyboard", "key": "F11" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "F11" } ] }, { "type": "keybinding", "name": "Lua Console", "category": "DEFAULTMODE", "id": "lua_console", - "bindings": [ { "input_method": "keyboard", "key": "`" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "`" } ] }, { "type": "keybinding", @@ -2124,21 +2314,21 @@ "type": "keybinding", "name": "Toggle Minimap", "id": "toggle_pixel_minimap", - "bindings": [ { "input_method": "keyboard", "key": "N" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "N" }, { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Sidebar Options", "category": "DEFAULTMODE", "id": "toggle_panel_adm", - "bindings": [ { "input_method": "keyboard", "key": "}" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "}" }, { "input_method": "keyboard_code", "key": "]", "mod": [ "shift" ] } ] }, { "type": "keybinding", "name": "Reload Item", "category": "DEFAULTMODE", "id": "reload_item", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", @@ -2181,14 +2371,14 @@ "name": "Action Menu", "category": "DEFAULTMODE", "id": "action_menu", - "bindings": [ { "input_method": "keyboard", "key": "RETURN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "RETURN" }, { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } ] }, { "type": "keybinding", "name": "Item Action Menu", "category": "DEFAULTMODE", "id": "item_action_menu", - "bindings": [ { "input_method": "keyboard", "key": "%" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "%" }, { "input_method": "keyboard_code", "key": "5", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -2249,14 +2439,14 @@ "name": "Autoattack", "category": "DEFAULTMODE", "id": "autoattack", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "name": "Main Menu", "category": "DEFAULTMODE", "id": "main_menu", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", @@ -2329,21 +2519,24 @@ "name": "Movement Mode Menu", "category": "DEFAULTMODE", "id": "open_movement", - "bindings": [ { "input_method": "keyboard", "key": "\"" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "\"" }, + { "input_method": "keyboard_code", "key": "'", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "cast_spell", "name": "Spellcasting", "category": "DEFAULTMODE", - "bindings": [ { "input_method": "keyboard", "key": "]" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "]" } ] }, { "type": "keybinding", "id": "diary", "name": "Open Diary", "category": "DEFAULTMODE", - "bindings": [ { "input_method": "keyboard", "key": "HOME" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "HOME" } ] }, { "type": "keybinding", @@ -2351,9 +2544,11 @@ "category": "LIST_ITEMS", "name": "Compare", "bindings": [ - { "input_method": "keyboard", "key": "I" }, - { "input_method": "keyboard", "key": "c" }, - { "input_method": "keyboard", "key": "C" } + { "input_method": "keyboard_char", "key": "I" }, + { "input_method": "keyboard_code", "key": "i", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "c" }, + { "input_method": "keyboard_char", "key": "C" }, + { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } ] }, { @@ -2361,70 +2556,82 @@ "id": "EXAMINE", "category": "LIST_ITEMS", "name": "Examine", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PRIORITY_INCREASE", "category": "LIST_ITEMS", "name": "Increase priority", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PRIORITY_DECREASE", "category": "LIST_ITEMS", "name": "Decrease priority", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "SORT", "category": "LIST_ITEMS", "name": "Change sort order", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SAFEMODE_BLACKLIST_ADD", "category": "LIST_MONSTERS", "name": "Add to safemode blacklist", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "id": "SAFEMODE_BLACKLIST_REMOVE", "category": "LIST_MONSTERS", "name": "Remove from safemode blacklist", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "look", "category": "LIST_MONSTERS", "name": "look around", - "bindings": [ { "input_method": "keyboard", "key": "x" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "x" } ] }, { "type": "keybinding", "id": "fire", "category": "LIST_MONSTERS", "name": { "ctxt": "verb", "str": "fire" }, - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "LIST_ITEMS", "category": "LOOK", "name": "List items and monsters", - "bindings": [ { "input_method": "keyboard", "key": "V" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "V" }, { "input_method": "keyboard_code", "key": "v", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "REASSIGN", "category": "BIONICS", "name": "Reassign invlet", - "bindings": [ { "input_method": "keyboard", "key": "=" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "=" } ] }, { "type": "keybinding", @@ -2432,8 +2639,9 @@ "category": "BIONICS", "name": "Move cursor up", "bindings": [ - { "input_method": "keyboard", "key": "8" }, - { "input_method": "keyboard", "key": "UP" }, + { "input_method": "keyboard_any", "key": "8" }, + { "input_method": "keyboard_code", "key": "KEYPAD_8" }, + { "input_method": "keyboard_any", "key": "UP" }, { "input_method": "gamepad", "key": "JOY_UP" } ] }, @@ -2443,8 +2651,9 @@ "category": "BIONICS", "name": "Move cursor down", "bindings": [ - { "input_method": "keyboard", "key": "DOWN" }, - { "input_method": "keyboard", "key": "2" }, + { "input_method": "keyboard_any", "key": "DOWN" }, + { "input_method": "keyboard_any", "key": "2" }, + { "input_method": "keyboard_code", "key": "KEYPAD_2" }, { "input_method": "gamepad", "key": "JOY_DOWN" } ] }, @@ -2453,7 +2662,7 @@ "id": "REASSIGN", "category": "MUTATIONS", "name": "Reassign invlet", - "bindings": [ { "input_method": "keyboard", "key": "=" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "=" } ] }, { "type": "keybinding", @@ -2461,8 +2670,9 @@ "category": "MUTATIONS", "name": "Pan up", "bindings": [ - { "input_method": "keyboard", "key": "8" }, - { "input_method": "keyboard", "key": "UP" }, + { "input_method": "keyboard_any", "key": "8" }, + { "input_method": "keyboard_code", "key": "KEYPAD_8" }, + { "input_method": "keyboard_any", "key": "UP" }, { "input_method": "gamepad", "key": "JOY_UP" } ] }, @@ -2472,8 +2682,9 @@ "category": "MUTATIONS", "name": "Pan down", "bindings": [ - { "input_method": "keyboard", "key": "DOWN" }, - { "input_method": "keyboard", "key": "2" }, + { "input_method": "keyboard_any", "key": "DOWN" }, + { "input_method": "keyboard_any", "key": "2" }, + { "input_method": "keyboard_code", "key": "KEYPAD_2" }, { "input_method": "gamepad", "key": "JOY_DOWN" } ] }, @@ -2483,119 +2694,159 @@ "category": "HELP_KEYBINDINGS", "name": "Exit this keybinding screen", "//": "separate entry, because the global entry also has 'q', 'Q' and space listed, which conflicts with the search text input", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "UP", "category": "HELP_KEYBINDINGS", "name": "Pan up", - "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] }, { "type": "keybinding", "id": "DOWN", "category": "HELP_KEYBINDINGS", "name": "Pan down", - "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] }, { "type": "keybinding", "id": "REMOVE", "category": "HELP_KEYBINDINGS", "name": "Remove bindings", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "ADD_LOCAL", "category": "HELP_KEYBINDINGS", "name": "Add local keybinding", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ADD_GLOBAL", "category": "HELP_KEYBINDINGS", "name": "Add global keybinding", - "bindings": [ { "input_method": "keyboard", "key": "=" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "=" } ] }, { "type": "keybinding", "id": "EXECUTE", "category": "HELP_KEYBINDINGS", "name": "Execute action keybinding", - "bindings": [ { "input_method": "keyboard", "key": "." } ] + "bindings": [ { "input_method": "keyboard_any", "key": "." }, { "input_method": "keyboard_code", "key": "KEYPAD_PERIOD" } ] }, { "type": "keybinding", "id": "ADD_ZONE", "category": "ZONES_MANAGER", "name": "Add zone", - "bindings": [ { "input_method": "keyboard", "key": "a" }, { "input_method": "keyboard", "key": "A" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "a" }, + { "input_method": "keyboard_char", "key": "A" }, + { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "REMOVE_ZONE", "category": "ZONES_MANAGER", "name": "Remove zone", - "bindings": [ { "input_method": "keyboard", "key": "r" }, { "input_method": "keyboard", "key": "R" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "r" }, + { "input_method": "keyboard_char", "key": "R" }, + { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_ZONE_UP", "category": "ZONES_MANAGER", "name": "Move zone up", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "+" }, + { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" }, + { "input_method": "keyboard_code", "key": "=", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_ZONE_DOWN", "category": "ZONES_MANAGER", "name": "Move zone down", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "SHOW_ZONE_ON_MAP", "category": "ZONES_MANAGER", "name": "Show zone on map", - "bindings": [ { "input_method": "keyboard", "key": "m" }, { "input_method": "keyboard", "key": "M" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "m" }, + { "input_method": "keyboard_char", "key": "M" }, + { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ENABLE_ZONE", "category": "ZONES_MANAGER", "name": "Enable zone", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "DISABLE_ZONE", "category": "ZONES_MANAGER", "name": "Disable zone", - "bindings": [ { "input_method": "keyboard", "key": "d" }, { "input_method": "keyboard", "key": "D" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "d" }, + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SHOW_ALL_ZONES", "category": "ZONES_MANAGER", "name": "Show all zones / hide distant zones", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PAGE_DOWN", "category": "ADVANCED_INVENTORY", "name": "Page down", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "NPAGE" }, + { "input_method": "keyboard_char", "key": ">" }, + { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PAGE_UP", "category": "ADVANCED_INVENTORY", "name": "Page up", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "PPAGE" }, + { "input_method": "keyboard_char", "key": "<" }, + { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } + ] }, { "type": "keybinding", @@ -2603,8 +2854,8 @@ "category": "ADVANCED_INVENTORY", "name": "Pan up", "bindings": [ - { "input_method": "keyboard", "key": "k" }, - { "input_method": "keyboard", "key": "UP" }, + { "input_method": "keyboard_any", "key": "k" }, + { "input_method": "keyboard_any", "key": "UP" }, { "input_method": "gamepad", "key": "JOY_UP" } ] }, @@ -2614,8 +2865,8 @@ "category": "ADVANCED_INVENTORY", "name": "Pan down", "bindings": [ - { "input_method": "keyboard", "key": "j" }, - { "input_method": "keyboard", "key": "DOWN" }, + { "input_method": "keyboard_any", "key": "j" }, + { "input_method": "keyboard_any", "key": "DOWN" }, { "input_method": "gamepad", "key": "JOY_DOWN" } ] }, @@ -2625,8 +2876,8 @@ "category": "ADVANCED_INVENTORY", "name": "Select left inventory", "bindings": [ - { "input_method": "keyboard", "key": "h" }, - { "input_method": "keyboard", "key": "LEFT" }, + { "input_method": "keyboard_any", "key": "h" }, + { "input_method": "keyboard_any", "key": "LEFT" }, { "input_method": "gamepad", "key": "JOY_LEFT" } ] }, @@ -2636,8 +2887,8 @@ "category": "ADVANCED_INVENTORY", "name": "Select right inventory", "bindings": [ - { "input_method": "keyboard", "key": "l" }, - { "input_method": "keyboard", "key": "RIGHT" }, + { "input_method": "keyboard_any", "key": "l" }, + { "input_method": "keyboard_any", "key": "RIGHT" }, { "input_method": "gamepad", "key": "JOY_RIGHT" } ] }, @@ -2646,161 +2897,177 @@ "id": "TOGGLE_TAB", "category": "ADVANCED_INVENTORY", "name": "Toggle tab", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "TOGGLE_VEH", "category": "ADVANCED_INVENTORY", "name": "Toggle vehicle", - "bindings": [ { "input_method": "keyboard", "key": "v" }, { "input_method": "keyboard", "key": "V" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "v" }, + { "input_method": "keyboard_char", "key": "V" }, + { "input_method": "keyboard_code", "key": "v", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "TOGGLE_AUTO_PICKUP", "category": "ADVANCED_INVENTORY", "name": "Toggle auto-pickup for item", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "type": "keybinding", "id": "EXAMINE", "category": "ADVANCED_INVENTORY", "name": "Examine", - "bindings": [ { "input_method": "keyboard", "key": "e" }, { "input_method": "keyboard", "key": "E" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "e" }, + { "input_method": "keyboard_char", "key": "E" }, + { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "SORT", "category": "ADVANCED_INVENTORY", "name": "Change sorting mode", - "bindings": [ { "input_method": "keyboard", "key": "s" }, { "input_method": "keyboard", "key": "S" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "s" }, + { "input_method": "keyboard_char", "key": "S" }, + { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "MOVE_SINGLE_ITEM", "category": "ADVANCED_INVENTORY", "name": "Move a single item", - "bindings": [ { "input_method": "keyboard", "key": "RETURN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "RETURN" }, { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } ] }, { "type": "keybinding", "id": "MOVE_VARIABLE_ITEM", "category": "ADVANCED_INVENTORY", "name": "Move an amount of item", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "type": "keybinding", "id": "MOVE_ITEM_STACK", "category": "ADVANCED_INVENTORY", "name": "Move item stack", - "bindings": [ { "input_method": "keyboard", "key": "M" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "MOVE_ALL_ITEMS", "category": "ADVANCED_INVENTORY", "name": "Move all items", - "bindings": [ { "input_method": "keyboard", "key": "," } ] + "bindings": [ { "input_method": "keyboard_any", "key": "," } ] }, { "type": "keybinding", "id": "CATEGORY_SELECTION", "category": "ADVANCED_INVENTORY", "name": "Toggle category selection mode", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "id": "TOGGLE_FAVORITE", "category": "ADVANCED_INVENTORY", "name": "Toggle item as favorite", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "DROP_NON_FAVORITE", "category": "INVENTORY", "name": "Mark/unmark non-favorite items in multidrop menu", - "bindings": [ { "input_method": "keyboard", "key": "," } ] + "bindings": [ { "input_method": "keyboard_any", "key": "," } ] }, { "type": "keybinding", "id": "ITEMS_NW", "category": "ADVANCED_INVENTORY", "name": "Select items @ North-West", - "bindings": [ { "input_method": "keyboard", "key": "7" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "7" }, { "input_method": "keyboard_code", "key": "KEYPAD_7" } ] }, { "type": "keybinding", "id": "ITEMS_N", "category": "ADVANCED_INVENTORY", "name": "Select items @ North", - "bindings": [ { "input_method": "keyboard", "key": "8" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "8" }, { "input_method": "keyboard_code", "key": "KEYPAD_8" } ] }, { "type": "keybinding", "id": "ITEMS_NE", "category": "ADVANCED_INVENTORY", "name": "Select items @ North-East", - "bindings": [ { "input_method": "keyboard", "key": "9" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "9" }, { "input_method": "keyboard_code", "key": "KEYPAD_9" } ] }, { "type": "keybinding", "id": "ITEMS_W", "category": "ADVANCED_INVENTORY", "name": "Select items @ West", - "bindings": [ { "input_method": "keyboard", "key": "4" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "4" }, { "input_method": "keyboard_code", "key": "KEYPAD_4" } ] }, { "type": "keybinding", "id": "ITEMS_CE", "category": "ADVANCED_INVENTORY", "name": "Select items @ center", - "bindings": [ { "input_method": "keyboard", "key": "5" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "5" }, { "input_method": "keyboard_code", "key": "KEYPAD_5" } ] }, { "type": "keybinding", "id": "ITEMS_E", "category": "ADVANCED_INVENTORY", "name": "Select items @ East", - "bindings": [ { "input_method": "keyboard", "key": "6" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "6" }, { "input_method": "keyboard_code", "key": "KEYPAD_6" } ] }, { "type": "keybinding", "id": "ITEMS_SW", "category": "ADVANCED_INVENTORY", "name": "Select items @ South-West", - "bindings": [ { "input_method": "keyboard", "key": "1" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "1" }, { "input_method": "keyboard_code", "key": "KEYPAD_1" } ] }, { "type": "keybinding", "id": "ITEMS_S", "category": "ADVANCED_INVENTORY", "name": "Select items @ South", - "bindings": [ { "input_method": "keyboard", "key": "2" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "2" }, { "input_method": "keyboard_code", "key": "KEYPAD_2" } ] }, { "type": "keybinding", "id": "ITEMS_SE", "category": "ADVANCED_INVENTORY", "name": "Select items @ South-East", - "bindings": [ { "input_method": "keyboard", "key": "3" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "3" }, { "input_method": "keyboard_code", "key": "KEYPAD_3" } ] }, { "type": "keybinding", "id": "ITEMS_UP", "category": "ADVANCED_INVENTORY", "name": "Select items from above", - "bindings": [ { "input_method": "keyboard", "key": "<" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "ITEMS_DOWN", "category": "ADVANCED_INVENTORY", "name": "Select items from below", - "bindings": [ { "input_method": "keyboard", "key": ">" } ] + "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -2808,9 +3075,11 @@ "category": "ADVANCED_INVENTORY", "name": "Select items in inventory", "bindings": [ - { "input_method": "keyboard", "key": "i" }, - { "input_method": "keyboard", "key": "I" }, - { "input_method": "keyboard", "key": "0" } + { "input_method": "keyboard_any", "key": "i" }, + { "input_method": "keyboard_char", "key": "I" }, + { "input_method": "keyboard_code", "key": "i", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "0" }, + { "input_method": "keyboard_code", "key": "KEYPAD_0" } ] }, { @@ -2818,28 +3087,44 @@ "id": "ITEMS_AROUND", "category": "ADVANCED_INVENTORY", "name": "Select items @ all 9 fields", - "bindings": [ { "input_method": "keyboard", "key": "a" }, { "input_method": "keyboard", "key": "A" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "a" }, + { "input_method": "keyboard_char", "key": "A" }, + { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ITEMS_DRAGGED_CONTAINER", "category": "ADVANCED_INVENTORY", "name": "Select items in dragged container", - "bindings": [ { "input_method": "keyboard", "key": "d" }, { "input_method": "keyboard", "key": "D" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "d" }, + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ITEMS_CONTAINER", "category": "ADVANCED_INVENTORY", "name": "Select items in container", - "bindings": [ { "input_method": "keyboard", "key": "c" }, { "input_method": "keyboard", "key": "C" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "c" }, + { "input_method": "keyboard_char", "key": "C" }, + { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "ITEMS_WORN", "category": "ADVANCED_INVENTORY", "name": "Select items currently worn", - "bindings": [ { "input_method": "keyboard", "key": "w" }, { "input_method": "keyboard", "key": "W" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "w" }, + { "input_method": "keyboard_char", "key": "W" }, + { "input_method": "keyboard_code", "key": "w", "mod": [ "shift" ] } + ] }, { "type": "keybinding", @@ -2872,74 +3157,86 @@ "id": "UP", "category": "ITEM_ACTIONS", "name": "Pan up", - "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] }, { "type": "keybinding", "id": "DOWN", "category": "ITEM_ACTIONS", "name": "Pan down", - "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] }, { "type": "keybinding", "id": "FILTER", "category": "ITEM_ACTIONS", "name": "Filter", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "QUIT", "category": "ITEM_ACTIONS", "name": "Cancel menu", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "repair_fabric", "category": "ITEM_ACTIONS", "name": "Sew", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "type": "keybinding", "id": "repair_metal", "category": "ITEM_ACTIONS", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "type": "keybinding", "id": "firestarter", "category": "ITEM_ACTIONS", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "holster", "category": "ITEM_ACTIONS", - "bindings": [ { "input_method": "keyboard", "key": "h" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "h" } ] }, { "type": "keybinding", "id": "REMOVE_CUSTOM", "category": "COLORS", "name": "Remove custom color", - "bindings": [ { "input_method": "keyboard", "key": "r" }, { "input_method": "keyboard", "key": "R" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "r" }, + { "input_method": "keyboard_char", "key": "R" }, + { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "LOAD_TEMPLATE", "category": "COLORS", "name": "Load color template", - "bindings": [ { "input_method": "keyboard", "key": "t" }, { "input_method": "keyboard", "key": "T" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "t" }, + { "input_method": "keyboard_char", "key": "T" }, + { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "YES", "category": "YESNO", "name": "Yes", - "bindings": [ { "input_method": "keyboard", "key": "Y" }, { "input_method": "keyboard", "key": "y" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "Y" }, + { "input_method": "keyboard_code", "key": "y", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "y" } + ] }, { "type": "keybinding", @@ -2947,9 +3244,10 @@ "category": "YESNO", "name": "No", "bindings": [ - { "input_method": "keyboard", "key": "N" }, - { "input_method": "keyboard", "key": "n" }, - { "input_method": "keyboard", "key": "ESC" } + { "input_method": "keyboard_char", "key": "N" }, + { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "n" }, + { "input_method": "keyboard_any", "key": "ESC" } ] }, { @@ -2957,35 +3255,43 @@ "id": "LOAD", "category": "LOAD_DELETE_CANCEL", "name": "Load", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "DELETE", "category": "LOAD_DELETE_CANCEL", "name": "Delete", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "CANCEL", "category": "LOAD_DELETE_CANCEL", "name": "Cancel", - "bindings": [ { "input_method": "keyboard", "key": "C" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "C" }, { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "YES", "category": "YESNOQUIT", "name": "Yes", - "bindings": [ { "input_method": "keyboard", "key": "Y" }, { "input_method": "keyboard", "key": "y" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "Y" }, + { "input_method": "keyboard_code", "key": "y", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "y" } + ] }, { "type": "keybinding", "id": "NO", "category": "YESNOQUIT", "name": "No", - "bindings": [ { "input_method": "keyboard", "key": "N" }, { "input_method": "keyboard", "key": "n" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "N" }, + { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "n" } + ] }, { "type": "keybinding", @@ -2993,9 +3299,10 @@ "category": "YESNOQUIT", "name": "Quit", "bindings": [ - { "input_method": "keyboard", "key": "Q" }, - { "input_method": "keyboard", "key": "q" }, - { "input_method": "keyboard", "key": "ESC" } + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_any", "key": "ESC" } ] }, { @@ -3003,7 +3310,11 @@ "id": "YES", "category": "CANCEL_ACTIVITY_OR_IGNORE_QUERY", "name": "Yes", - "bindings": [ { "input_method": "keyboard", "key": "Y" }, { "input_method": "keyboard", "key": "y" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "Y" }, + { "input_method": "keyboard_code", "key": "y", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "y" } + ] }, { "type": "keybinding", @@ -3011,9 +3322,10 @@ "category": "CANCEL_ACTIVITY_OR_IGNORE_QUERY", "name": "No", "bindings": [ - { "input_method": "keyboard", "key": "N" }, - { "input_method": "keyboard", "key": "n" }, - { "input_method": "keyboard", "key": "ESC" } + { "input_method": "keyboard_char", "key": "N" }, + { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "n" }, + { "input_method": "keyboard_any", "key": "ESC" } ] }, { @@ -3021,21 +3333,29 @@ "id": "IGNORE", "category": "CANCEL_ACTIVITY_OR_IGNORE_QUERY", "name": "Ignore further distractions and finish", - "bindings": [ { "input_method": "keyboard", "key": "I" }, { "input_method": "keyboard", "key": "i" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "I" }, + { "input_method": "keyboard_code", "key": "i", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "i" } + ] }, { "type": "keybinding", "id": "MANAGER", "category": "CANCEL_ACTIVITY_OR_IGNORE_QUERY", "name": "Open manager", - "bindings": [ { "input_method": "keyboard", "key": "M" }, { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "YES", "category": "YES_NO_ALWAYS_NEVER", "name": "Yes", - "bindings": [ { "input_method": "keyboard", "key": [ "Y" ] }, { "input_method": "keyboard", "key": [ "y" ] } ] + "bindings": [ + { "input_method": "keyboard_char", "key": [ "Y" ] }, + { "input_method": "keyboard_code", "key": [ "y" ], "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": [ "y" ] } + ] }, { "type": "keybinding", @@ -3043,9 +3363,10 @@ "category": "YES_NO_ALWAYS_NEVER", "name": "No", "bindings": [ - { "input_method": "keyboard", "key": [ "N" ] }, - { "input_method": "keyboard", "key": [ "n" ] }, - { "input_method": "keyboard", "key": [ "ESC" ] } + { "input_method": "keyboard_char", "key": [ "N" ] }, + { "input_method": "keyboard_code", "key": [ "n" ], "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": [ "n" ] }, + { "input_method": "keyboard_any", "key": [ "ESC" ] } ] }, { @@ -3053,14 +3374,22 @@ "id": "ALWAYS", "category": "YES_NO_ALWAYS_NEVER", "name": "Always", - "bindings": [ { "input_method": "keyboard", "key": [ "A" ] }, { "input_method": "keyboard", "key": [ "a" ] } ] + "bindings": [ + { "input_method": "keyboard_char", "key": [ "A" ] }, + { "input_method": "keyboard_code", "key": [ "a" ], "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": [ "a" ] } + ] }, { "type": "keybinding", "id": "NEVER", "category": "YES_NO_ALWAYS_NEVER", "name": "Never", - "bindings": [ { "input_method": "keyboard", "key": [ "E" ] }, { "input_method": "keyboard", "key": [ "e" ] } ] + "bindings": [ + { "input_method": "keyboard_char", "key": [ "E" ] }, + { "input_method": "keyboard_code", "key": [ "e" ], "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": [ "e" ] } + ] }, { "type": "keybinding", @@ -3068,9 +3397,10 @@ "category": "POPUP_WAIT", "name": "Cancel popup", "bindings": [ - { "input_method": "keyboard", "key": "ESC" }, - { "input_method": "keyboard", "key": "SPACE" }, - { "input_method": "keyboard", "key": "RETURN" } + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "keyboard_any", "key": "RETURN" }, + { "input_method": "keyboard_code", "key": "KEYPAD_ENTER" } ] }, { @@ -3078,223 +3408,389 @@ "id": "CREATURE", "category": "EXTENDED_DESCRIPTION", "name": "Describe creature", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "type": "keybinding", "id": "FURNITURE", "category": "EXTENDED_DESCRIPTION", "name": "Describe furniture", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "type": "keybinding", "id": "TERRAIN", "category": "EXTENDED_DESCRIPTION", "name": "Describe terrain", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "type": "keybinding", "id": "SWITCH_LISTS", "category": "NPC_TRADE", "name": "Switch lists", - "bindings": [ { "input_method": "keyboard", "key": "TAB" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "TAB" } ] }, { "type": "keybinding", "id": "PAGE_UP", "category": "NPC_TRADE", "name": "Back", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" }, { "input_method": "keyboard", "key": "<" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "PPAGE" }, + { "input_method": "keyboard_char", "key": "<" }, + { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "PAGE_DOWN", "category": "NPC_TRADE", "name": "More", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" }, { "input_method": "keyboard", "key": ">" } ] + "bindings": [ + { "input_method": "keyboard_any", "key": "NPAGE" }, + { "input_method": "keyboard_char", "key": ">" }, + { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } + ] }, { "type": "keybinding", "id": "EXAMINE", "category": "NPC_TRADE", "name": "Examine item", - "bindings": [ { "input_method": "keyboard", "key": "/" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] }, { "type": "keybinding", "id": "QUIT", "category": "NPC_TRADE", "name": "Cancel trading", - "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] }, { "type": "keybinding", "id": "CHANGE_PROFESSION_NAME", "name": "Change profession name", - "bindings": [ { "input_method": "keyboard", "key": "*" } ] + "bindings": [ + { "input_method": "keyboard_char", "key": "*" }, + { "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" }, + { "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] } + ] }, { "id": "ACTIVATE", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Activate", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "id": "READ", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Read", - "bindings": [ { "input_method": "keyboard", "key": "R" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "R" }, { "input_method": "keyboard_code", "key": "r", "mod": [ "shift" ] } ] }, { "id": "EAT", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Eat", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "id": "WEAR", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Wear", - "bindings": [ { "input_method": "keyboard", "key": "W" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "W" }, { "input_method": "keyboard_code", "key": "w", "mod": [ "shift" ] } ] }, { "id": "WIELD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Wield", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "id": "UNWIELD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Unwield", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "id": "THROW", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Throw", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] }, { "id": "CHANGE_SIDE", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Change side", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "id": "TAKE_OFF", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Take off", - "bindings": [ { "input_method": "keyboard", "key": "T" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "T" }, { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } ] }, { "id": "DROP", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Drop", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "id": "UNLOAD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Unload", - "bindings": [ { "input_method": "keyboard", "key": "u" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "u" } ] }, { "id": "RELOAD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Reload", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "id": "PART_RELOAD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Part reload", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "id": "MEND", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Mend", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "id": "DISASSEMBLE", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Disassemble", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "id": "FAVORITE_ADD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Favorite", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "id": "FAVORITE_REMOVE", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Unfavorite", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "id": "REASSIGN", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Reassign", - "bindings": [ { "input_method": "keyboard", "key": "=" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "=" } ] }, { "id": "AUTOPICKUP_ADD", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Autopickup (add)", - "bindings": [ { "input_method": "keyboard", "key": "+" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "+" }, { "input_method": "keyboard_code", "key": "KEYPAD_PLUS" } ] }, { "id": "AUTOPICKUP_REMOVE", "type": "keybinding", "category": "INVENTORY_ITEM", "name": "Autopickup (remove)", - "bindings": [ { "input_method": "keyboard", "key": "-" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "DELETE PAGE", "name": "Delete page", "category": "DIARY", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "type": "keybinding", "id": "EXPORT_DIARY", "name": "Export diary to .txt", "category": "DIARY", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "type": "keybinding", "id": "NEW_PAGE", "name": "Add new page", "category": "DIARY", - "bindings": [ { "input_method": "keyboard", "key": "n" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "n" } ] + }, + { + "type": "keybinding", + "id": "TEXT.QUIT", + "name": "Cancel text input", + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CONFIRM", + "name": "Confirm text input", + "bindings": [ { "input_method": "keyboard_any", "key": "RETURN" }, { "input_method": "keyboard_any", "key": "KEYPAD_ENTER" } ] + }, + { + "type": "keybinding", + "id": "TEXT.UP", + "name": "Move cursor up", + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] + }, + { + "type": "keybinding", + "id": "TEXT.DOWN", + "name": "Move cursor down", + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] + }, + { + "type": "keybinding", + "id": "TEXT.LEFT", + "name": "Move cursor left", + "bindings": [ { "input_method": "keyboard_any", "key": "LEFT" } ] + }, + { + "type": "keybinding", + "id": "TEXT.RIGHT", + "name": "Move cursor right", + "bindings": [ { "input_method": "keyboard_any", "key": "RIGHT" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PAGE_UP", + "name": "Move cursor to previous page", + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PAGE_DOWN", + "name": "Move cursor to next page", + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CLEAR", + "name": "Clear text", + "bindings": [ + { "input_method": "keyboard_char", "key": "CTRL+U" }, + { "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "u" } + ] + }, + { + "type": "keybinding", + "id": "TEXT.BACKSPACE", + "name": "Remove previous character", + "bindings": [ { "input_method": "keyboard_any", "key": "BACKSPACE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.HOME", + "name": "Move cursor to start", + "bindings": [ { "input_method": "keyboard_any", "key": "HOME" } ] + }, + { + "type": "keybinding", + "id": "TEXT.END", + "name": "Move cursor to end", + "bindings": [ { "input_method": "keyboard_any", "key": "END" } ] + }, + { + "type": "keybinding", + "id": "TEXT.DELETE", + "name": "Remove current character", + "bindings": [ { "input_method": "keyboard_any", "key": "DELETE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PASTE", + "name": "Paste from clipboard", + "bindings": [ + { "input_method": "keyboard_char", "key": "CTRL+V" }, + { "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "v" } + ] + }, + { + "type": "keybinding", + "id": "TEXT.INPUT_FROM_FILE", + "name": "Input text from file", + "bindings": [ { "input_method": "keyboard_any", "key": "F2" } ] + }, + { + "type": "keybinding", + "id": "HISTORY_UP", + "name": "Show previous history", + "category": "STRING_INPUT", + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] + }, + { + "type": "keybinding", + "id": "HISTORY_DOWN", + "name": "Show next history", + "category": "STRING_INPUT", + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] + }, + { + "type": "keybinding", + "id": "HELP_KEYBINDINGS", + "name": "Display keybindings menu", + "category": "STRING_INPUT", + "//": "'?' is treated as text input, so a different key is used.", + "bindings": [ { "input_method": "keyboard_any", "key": "F1" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CONFIRM", + "name": "Confirm text input", + "category": "STRING_EDITOR", + "//": "RETURN is treated as text input, so use a different keybinding", + "//2": "CTRL+S is not available on curses, so add CTRL+Y as an alternative", + "bindings": [ + { "input_method": "keyboard_char", "key": "CTRL+S" }, + { "input_method": "keyboard_char", "key": "CTRL+Y" }, + { "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "s" }, + { "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "y" } + ] + }, + { + "type": "keybinding", + "id": "HELP_KEYBINDINGS", + "name": "Display keybindings menu", + "category": "STRING_EDITOR", + "//": "'?' is treated as text input, so a different key is used.", + "bindings": [ { "input_method": "keyboard_any", "key": "F1" } ] + }, + { + "type": "keybinding", + "id": "SCROLL_TRAIT_INFO_UP", + "category": "MUTATIONS", + "name": "Scroll mutation info up", + "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] + }, + { + "type": "keybinding", + "id": "SCROLL_TRAIT_INFO_DOWN", + "category": "MUTATIONS", + "name": "Scroll mutation info down", + "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", @@ -3308,48 +3804,48 @@ "name": "Reload Lua Code", "category": "LUA_CONSOLE", "id": "RELOAD", - "bindings": [ { "input_method": "keyboard", "key": "F4" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "F4" } ] }, { "type": "keybinding", "id": "HISTORY_UP", "name": "History up", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "UP" } ] }, { "type": "keybinding", "id": "HISTORY_DOWN", "name": "History down", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "DOWN" } ] }, { "type": "keybinding", "id": "SCROLL_UP", "name": "Scroll up", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "PPAGE" } ] }, { "type": "keybinding", "id": "SCROLL_DOWN", "name": "Scroll down", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "NPAGE" } ] }, { "type": "keybinding", "id": "SCROLL_TOP", "name": "Scroll to the top", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "HOME" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "HOME" } ] }, { "type": "keybinding", "id": "SCROLL_BOTTOM", "name": "Scroll to the bottom", "category": "LUA_CONSOLE", - "bindings": [ { "input_method": "keyboard", "key": "END" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "END" } ] } ] diff --git a/data/raw/keybindings/vehicle.json b/data/raw/keybindings/vehicle.json index 3f27928c6289..c68a5ba6485e 100644 --- a/data/raw/keybindings/vehicle.json +++ b/data/raw/keybindings/vehicle.json @@ -4,265 +4,272 @@ "type": "keybinding", "category": "VEHICLE", "name": "Control multiple electronics", - "bindings": [ { "input_method": "keyboard", "key": "E" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "E" }, { "input_method": "keyboard_code", "key": "e", "mod": [ "shift" ] } ] }, { "id": "CONTROL_ENGINES", "type": "keybinding", "category": "VEHICLE", "name": "Control individual engines", - "bindings": [ { "input_method": "keyboard", "key": "y" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "y" } ] }, { "id": "FOLD_VEHICLE", "type": "keybinding", "category": "VEHICLE", "name": "Fold vehicle", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "id": "RELEASE_CONTROLS", "type": "keybinding", "category": "VEHICLE", "name": "Release controls", - "bindings": [ { "input_method": "keyboard", "key": "l" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "l" } ] }, { "id": "SOUND_HORN", "type": "keybinding", "category": "VEHICLE", "name": "Sound horn", - "bindings": [ { "input_method": "keyboard", "key": "n" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "n" } ] }, { "id": "TOGGLE_AISLE_LIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle aisle lights", - "bindings": [ { "input_method": "keyboard", "key": "L" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "L" }, { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_ALARM", "type": "keybinding", "category": "VEHICLE", "name": "Toggle alarm", - "bindings": [ { "input_method": "keyboard", "key": "z" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "z" } ] }, { "id": "TOGGLE_ATOMIC_LIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle atomic lights", - "bindings": [ { "input_method": "keyboard", "key": "A" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] }, { "id": "CONTROL_AUTOPILOT", "type": "keybinding", "category": "VEHICLE", "name": "Control autopilot", - "bindings": [ { "input_method": "keyboard", "key": "a" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "id": "TOGGLE_CAMERA", "type": "keybinding", "category": "VEHICLE", "name": "Toggle camera system", - "bindings": [ { "input_method": "keyboard", "key": "M" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "M" }, { "input_method": "keyboard_code", "key": "m", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_CHIMES", "type": "keybinding", "category": "VEHICLE", "name": "Toggle chimes", - "bindings": [ { "input_method": "keyboard", "key": "i" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "i" } ] }, { "id": "TOGGLE_CRUISE_CONTROL", "type": "keybinding", "category": "VEHICLE", "name": "Toggle cruise control", - "bindings": [ { "input_method": "keyboard", "key": "c" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "c" } ] }, { "id": "TOGGLE_DOME_LIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle dome lights", - "bindings": [ { "input_method": "keyboard", "key": "d" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, { "id": "TOGGLE_DOORS", "type": "keybinding", "category": "VEHICLE", "name": "Toggle doors", - "bindings": [ { "input_method": "keyboard", "key": "D" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "D" }, { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_ENGINE", "type": "keybinding", "category": "VEHICLE", "name": "Toggle engine", - "bindings": [ { "input_method": "keyboard", "key": "e" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "e" } ] }, { "id": "TOGGLE_FRIDGE", "type": "keybinding", "category": "VEHICLE", "name": "Toggle fridge", - "bindings": [ { "input_method": "keyboard", "key": "f" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, { "id": "TOGGLE_FREEZER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle freezer", - "bindings": [ { "input_method": "keyboard", "key": "r" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "id": "TOGGLE_SPACE_HEATER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle space heater", - "bindings": [ { "input_method": "keyboard", "key": "s" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "s" } ] }, { "id": "TOGGLE_COOLER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle cooler", - "bindings": [ { "input_method": "keyboard", "key": "C" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "C" }, { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_HEADLIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle headlights", - "bindings": [ { "input_method": "keyboard", "key": "h" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "h" } ] }, { "id": "TOGGLE_WIDE_HEADLIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle wide-angle headlights", - "bindings": [ { "input_method": "keyboard", "key": "b" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "b" } ] }, { "id": "TOGGLE_HALF_OVERHEAD_LIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle directional overhead lights", - "bindings": [ { "input_method": "keyboard", "key": "O" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "O" }, { "input_method": "keyboard_code", "key": "o", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_OVERHEAD_LIGHT", "type": "keybinding", "category": "VEHICLE", "name": "Toggle overhead lights", - "bindings": [ { "input_method": "keyboard", "key": "o" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "o" } ] }, { "id": "TOGGLE_PLANTER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle planter", - "bindings": [ { "input_method": "keyboard", "key": "P" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "P" }, { "input_method": "keyboard_code", "key": "p", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_PLOW", "type": "keybinding", "category": "VEHICLE", "name": "Toggle plow", - "bindings": [ { "input_method": "keyboard", "key": "w" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] }, { "id": "TOGGLE_REACTOR", "type": "keybinding", "category": "VEHICLE", "name": "Toggle reactor", - "bindings": [ { "input_method": "keyboard", "key": "k" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "k" } ] }, { "id": "TOGGLE_REAPER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle reaper", - "bindings": [ { "input_method": "keyboard", "key": "H" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "H" }, { "input_method": "keyboard_code", "key": "h", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_RECHARGER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle recharger", - "bindings": [ { "input_method": "keyboard", "key": "g" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "g" } ] }, { "id": "TOGGLE_SCOOP", "type": "keybinding", "category": "VEHICLE", "name": "Toggle scoop", - "bindings": [ { "input_method": "keyboard", "key": "S" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "S" }, { "input_method": "keyboard_code", "key": "s", "mod": [ "shift" ] } ] }, { "id": "TOGGLE_STEREO", "type": "keybinding", "category": "VEHICLE", "name": "Toggle stereo", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "m" } ] }, { "id": "TOGGLE_WATER_PURIFIER", "type": "keybinding", "category": "VEHICLE", "name": "Toggle water purifiers", - "bindings": [ { "input_method": "keyboard", "key": "p" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] }, { "id": "TOGGLE_TRACKING", "type": "keybinding", "category": "VEHICLE", "name": "Toggle tracking", - "bindings": [ { "input_method": "keyboard", "key": "K" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "K" }, { "input_method": "keyboard_code", "key": "k", "mod": [ "shift" ] } ] + }, + { + "id": "TOGGLE_SMART_ENGINE_CONTROLLER", + "type": "keybinding", + "category": "VEHICLE", + "name": "Toggle smart engine controller", + "bindings": [ { "input_method": "keyboard_char", "key": "Y" }, { "input_method": "keyboard_code", "key": "y", "mod": [ "shift" ] } ] }, { "id": "TURRET_FIRE_MODE", "type": "keybinding", "category": "VEHICLE", "name": "Set turret firing modes", - "bindings": [ { "input_method": "keyboard", "key": "x" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "x" } ] }, { "id": "TURRET_MANUAL_AIM", "type": "keybinding", "category": "VEHICLE", "name": "Aim manual turrets", - "bindings": [ { "input_method": "keyboard", "key": "X" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "X" }, { "input_method": "keyboard_code", "key": "x", "mod": [ "shift" ] } ] }, { "id": "TURRET_MANUAL_OVERRIDE", "type": "keybinding", "category": "VEHICLE", "name": "Aim automatic turrets", - "bindings": [ { "input_method": "keyboard", "key": "V" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "V" }, { "input_method": "keyboard_code", "key": "v", "mod": [ "shift" ] } ] }, { "id": "TURRET_ALL_OVERRIDE", "type": "keybinding", "category": "VEHICLE", "name": "Aim all turrets", - "bindings": [ { "input_method": "keyboard", "key": "v" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "v" } ] }, { "id": "TURRET_SINGLE_FIRE", "type": "keybinding", "category": "VEHICLE", "name": "Aim individual turret", - "bindings": [ { "input_method": "keyboard", "key": "T" } ] + "bindings": [ { "input_method": "keyboard_char", "key": "T" }, { "input_method": "keyboard_code", "key": "t", "mod": [ "shift" ] } ] }, { "id": "TURRET_TARGET_MODE", "type": "keybinding", "category": "VEHICLE", "name": "Set turret targeting modes", - "bindings": [ { "input_method": "keyboard", "key": "t" } ] + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] } ] diff --git a/doc/src/content/docs/en/dev/explanation/accessibility.md b/doc/src/content/docs/en/dev/explanation/accessibility.md index d64fb6b36617..db6f1c234f96 100644 --- a/doc/src/content/docs/en/dev/explanation/accessibility.md +++ b/doc/src/content/docs/en/dev/explanation/accessibility.md @@ -2,37 +2,14 @@ title: Compatibility with screen readers --- -There are people who uses screen readers to play Cataclysm DDA. In order for screen readers to -announce the most important information in a UI, the terminal cursor has to be placed at the correct -location. This information may be text such as selected item names in a list, etc, and the cursor -has to be placed exactly at the beginning of the text for screen readers to announce it. - -`wmove` in `output.h|cpp` is the function to move the cursor to a specific location. After calling -`wmove` with the target `catacurses::window` and cursor position, `wrefresh` needs to be called -immediately afterwards for `wmove` to take effect. - -Here is an example of placing the cursor explicitly at the beginning of a piece of text: - -```cpp -catacurses::window win = ...; // target window - -... - -// display code -point cursor_position = ...; // default cursor position - -... - -cursor_position = point_zero; // record the start position of the text -fold_and_print( win, cursor_position, getmaxx( win ), c_white, _( "This text is important" ) ); - -... - -// at the end of display code -wmove( win, cursor_position ); -wrefresh( win ); -// no output code should follow as they might change the cursor position -``` - -As shown in the above example, it is preferable to record the intended cursor position in a variable -when the text is printed, and move the cursor later using the variable to ensure consisitency. +There are people who use screen readers to play Cataclysm Bright Nights. In order for screen readers +to announce the most important information in a UI, the terminal cursor has to be placed at the +correct location. This information may be text such as selected item names in a list, etc, and the +cursor has to be placed exactly at the beginning of the text for screen readers to announce it. + +The recommended way to place the cursor is to use `ui_adaptor`. This ensures the desired cursor +position is preserved when subsequent output code changes the cursor position. You can call +`ui_adaptor::set_cursor` and similar methods at any position in a redrawing callback, and the last +cursor position of the topmost UI set via the call will be used as the final cursor position. You +can also call `ui_adaptor::disable_cursor` to prevent a UI's cursor from being used as the final +cursor position. diff --git a/src/action.cpp b/src/action.cpp index 07181f50829e..7aef2946759e 100644 --- a/src/action.cpp +++ b/src/action.cpp @@ -61,7 +61,7 @@ std::vector keys_bound_to( action_id act, const bool restrict_to_printable action_id action_from_key( char ch ) { input_context ctxt = get_default_mode_input_context(); - const input_event event( ch, CATA_INPUT_KEYBOARD ); + const input_event event( ch, input_event_t::keyboard_char ); const std::string &action = ctxt.input_to_action( event ); return look_up_action( action ); } @@ -926,7 +926,8 @@ action_id handle_main_menu() const auto register_actions = make_register_actions( entries, ctxt ); register_actions( { - ACTION_OPEN_WIKI, ACTION_HELP, ACTION_KEYBINDINGS, ACTION_OPTIONS, ACTION_AUTOPICKUP, ACTION_AUTONOTES, + ACTION_OPEN_WIKI, ACTION_HELP, + ACTION_KEYBINDINGS, ACTION_OPTIONS, ACTION_AUTOPICKUP, ACTION_AUTONOTES, ACTION_SAFEMODE, ACTION_DISTRACTION_MANAGER, ACTION_COLOR, ACTION_WORLD_MODS, ACTION_ACTIONMENU, ACTION_QUICKSAVE, ACTION_SAVE, ACTION_DEBUG, ACTION_LUA_CONSOLE, ACTION_LUA_RELOAD @@ -947,7 +948,7 @@ action_id handle_main_menu() std::optional choose_direction( const std::string &message, const bool allow_vertical ) { - input_context ctxt( "DEFAULTMODE" ); + input_context ctxt( "DEFAULTMODE", keyboard_mode::keychar ); ctxt.set_iso( true ); ctxt.register_directions(); ctxt.register_action( "pause" ); diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index a2086b6388b1..dd7a257532a4 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -105,6 +105,8 @@ #include "vehicle_part.h" #include "vpart_position.h" +enum creature_size : int; + static const activity_id ACT_ADV_INVENTORY( "ACT_ADV_INVENTORY" ); static const activity_id ACT_ARMOR_LAYERS( "ACT_ARMOR_LAYERS" ); static const activity_id ACT_ATM( "ACT_ATM" ); @@ -595,7 +597,7 @@ butchery_setup consider_butchery( const item &corpse_item, player &u, butcher_ty inv.has_amount( itype_hd_tow_cable, 1 ) || inv.has_amount( itype_vine_30, 1 ) || inv.has_amount( itype_grapnel, 1 ); - const bool big_corpse = corpse.size >= MS_MEDIUM; + const bool big_corpse = corpse.size >= creature_size::medium; if( big_corpse ) { if( has_rope && !has_tree_nearby && !b_rack_present ) { @@ -635,7 +637,7 @@ butchery_setup consider_butchery( const item &corpse_item, player &u, butcher_ty } if( action == QUARTER ) { - if( corpse.size == MS_TINY ) { + if( corpse.size == creature_size::tiny ) { not_this_one( _( "This corpse is too small to quarter without damaging." ), butcherable_rating::too_small ); } @@ -721,22 +723,22 @@ static void set_up_butchery_activity( player_activity &act, player &u, const but act.index = false; } -static int size_factor_in_time_to_cut( m_size size ) +static int size_factor_in_time_to_cut( creature_size size ) { switch( size ) { // Time (roughly) in turns to cut up the corpse - case MS_TINY: + case creature_size::tiny: return 150; - case MS_SMALL: + case creature_size::small: return 300; - case MS_MEDIUM: + case creature_size::medium: return 450; - case MS_LARGE: + case creature_size::large: return 600; - case MS_HUGE: + case creature_size::huge: return 1800; default: - debugmsg( "Invalid m_size value for butchering corpse: %d", static_cast( size ) ); + debugmsg( "Invalid creature_size value for butchering corpse: %d", static_cast( size ) ); break; } return 0; @@ -950,7 +952,7 @@ static void butchery_drops_harvest( item *corpse_item, const mtype &mt, player & roll = roll / 4; } else if( entry.type == "bone" ) { roll /= 2; - } else if( corpse_item->get_mtype()->size >= MS_MEDIUM && ( entry.type == "skin" ) ) { + } else if( corpse_item->get_mtype()->size >= creature_size::medium && ( entry.type == "skin" ) ) { roll /= 2; } else if( entry.type == "offal" ) { roll /= 5; @@ -1094,7 +1096,8 @@ static void butchery_drops_harvest( item *corpse_item, const mtype &mt, player & // 20% of the original corpse weight is not an item, but liquid gore if( action != DISSECT ) { - p.practice( skill_survival, std::max( 0, practice ), std::max( mt.size - MS_MEDIUM, 0 ) + 4 ); + p.practice( skill_survival, std::max( 0, practice ), std::max( mt.size - creature_size::medium, + 0 ) + 4 ); } } @@ -1252,8 +1255,9 @@ void activity_handlers::butcher_finish( player_activity *act, player *p ) extract_or_wreck_cbms( cbms, roll, *p ); // those lines are for XP gain with dissecting. It depends on the size of the corpse, time to dissect the corpse and the amount of bionics you would gather. int time_to_cut = size_factor_in_time_to_cut( corpse->size ); - int level_cap = std::min( MAX_SKILL, ( corpse->size + ( cbms.size() * 2 + 1 ) ) ); - int size_mult = corpse->size > MS_MEDIUM ? ( corpse->size * corpse->size ) : 8; + int level_cap = std::min( MAX_SKILL, + ( static_cast( corpse->size ) + ( cbms.size() * 2 + 1 ) ) ); + int size_mult = corpse->size > creature_size::medium ? ( corpse->size * corpse->size ) : 8; int practice_amt = ( size_mult + 1 ) * ( ( time_to_cut / 150 ) + 1 ) * ( cbms.size() * cbms.size() / 2 + 1 ); p->practice( skill_firstaid, practice_amt, level_cap ); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 8d2530fab01f..5fb6161b15d4 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -1527,7 +1527,7 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe // make sure nobody else is working on that corpse right now if( i->is_corpse() && !i->has_var( "activity_var" ) ) { const mtype corpse = *i->get_mtype(); - if( corpse.size >= MS_MEDIUM ) { + if( corpse.size >= creature_size::medium ) { big_count += 1; } else { small_count += 1; @@ -2127,7 +2127,7 @@ static bool butcher_corpse_activity( player &p, const tripoint &src_loc, for( auto &elem : items ) { if( elem->is_corpse() && !elem->has_var( "activity_var" ) ) { const mtype corpse = *elem->get_mtype(); - if( corpse.size >= MS_MEDIUM && reason != do_activity_reason::NEEDS_BIG_BUTCHERING ) { + if( corpse.size >= creature_size::medium && reason != do_activity_reason::NEEDS_BIG_BUTCHERING ) { continue; } elem->set_var( "activity_var", p.name ); diff --git a/src/advanced_inv.cpp b/src/advanced_inv.cpp index 512b665fd5ef..d73811c1a3a7 100644 --- a/src/advanced_inv.cpp +++ b/src/advanced_inv.cpp @@ -26,7 +26,6 @@ #include "examine_item_menu.h" #include "game.h" #include "game_constants.h" -#include "ime.h" #include "input.h" #include "inventory.h" #include "item.h" @@ -1477,7 +1476,6 @@ void advanced_inventory::display() ui->mark_resize(); } - ime_sentry sentry; do { if( ui ) { diff --git a/src/armor_layers.cpp b/src/armor_layers.cpp index e58925d85413..be33418dba0f 100644 --- a/src/armor_layers.cpp +++ b/src/armor_layers.cpp @@ -559,7 +559,7 @@ void show_armor_layers_ui( Character &who ) int leftListSize = 0; int rightListSize = 0; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { draw_grid( w_sort_armor, left_w, middle_w ); werase( w_sort_cat ); @@ -638,7 +638,7 @@ void show_armor_layers_ui( Character &who ) } mvwprintz( w_encumb, point_east, c_white, _( "Encumbrance and Warmth" ) ); - character_display::print_encumbrance( w_encumb, who, -1, + character_display::print_encumbrance( ui, w_encumb, who, -1, ( leftListSize > 0 ) ? *access_tmp_worn( leftListIndex ) : nullptr ); // Right header diff --git a/src/ballistics.cpp b/src/ballistics.cpp index 9d2a37c89938..dc657a86afe0 100644 --- a/src/ballistics.cpp +++ b/src/ballistics.cpp @@ -108,10 +108,10 @@ static void drop_or_embed_projectile( dealt_projectile_attack &attack ) !proj.has_effect( ammo_effect_TANGLE ); // Don't embed in small creatures if( embed ) { - const m_size critter_size = mon->get_size(); + const creature_size critter_size = mon->get_size(); const units::volume vol = drop_item.volume(); - embed = embed && ( critter_size > MS_TINY || vol < 250_ml ); - embed = embed && ( critter_size > MS_SMALL || vol < 500_ml ); + embed = embed && ( critter_size > creature_size::tiny || vol < 250_ml ); + embed = embed && ( critter_size > creature_size::small || vol < 500_ml ); // And if we deal enough damage // Item volume bumps up the required damage too embed = embed && diff --git a/src/bionics_ui.cpp b/src/bionics_ui.cpp index a287dd6824f9..4b04690b09aa 100644 --- a/src/bionics_ui.cpp +++ b/src/bionics_ui.cpp @@ -186,7 +186,7 @@ char get_free_invlet( bionic_collection &bionics ) static void draw_bionics_titlebar( const catacurses::window &window, Character *p, bionic_menu_mode mode ) { - input_context ctxt( "BIONICS" ); + input_context ctxt( "BIONICS", keyboard_mode::keychar ); static const flag_id json_flag_PERPETUAL( "PERPETUAL" ); werase( window ); @@ -617,7 +617,7 @@ void show_bionics_ui( Character &who ) bionic_menu_mode menu_mode = ACTIVATING; int max_scroll_position = 0; - input_context ctxt( "BIONICS" ); + input_context ctxt( "BIONICS", keyboard_mode::keychar ); ctxt.register_updown(); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "TOGGLE_EXAMINE" ); diff --git a/src/catalua_bindings.cpp b/src/catalua_bindings.cpp index 939ae192f447..63bbaebd68ce 100644 --- a/src/catalua_bindings.cpp +++ b/src/catalua_bindings.cpp @@ -794,7 +794,7 @@ void cata::detail::reg_enums( sol::state &lua ) reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); - reg_enum( lua ); + reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); diff --git a/src/catalua_bindings_creature.cpp b/src/catalua_bindings_creature.cpp index 4b53b0e3d820..b95f950f6164 100644 --- a/src/catalua_bindings_creature.cpp +++ b/src/catalua_bindings_creature.cpp @@ -201,7 +201,7 @@ void cata::detail::reg_creature( sol::state &lua ) SET_FX_T( get_hit, float() const ); SET_FX_T( get_speed, int() const ); - SET_FX_T( get_size, m_size() const ); + SET_FX_T( get_size, creature_size() const ); luna::set_fx( ut, "get_hp", []( const Creature & cr, sol::optional bpid ) -> int { if( bpid.has_value() ) diff --git a/src/catalua_luna_doc.h b/src/catalua_luna_doc.h index 574a777c3c3e..142fc175ba3a 100644 --- a/src/catalua_luna_doc.h +++ b/src/catalua_luna_doc.h @@ -13,7 +13,7 @@ enum color_id : int; enum damage_type : int; enum game_message_type : int; enum m_flag : int; -enum m_size : int; +enum creature_size : int; enum mf_attitude : int; enum monster_attitude : int; enum npc_attitude : int; @@ -164,7 +164,7 @@ LUNA_ENUM( game_message_type, "MsgType" ) LUNA_ENUM( mf_attitude, "MonsterFactionAttitude" ) LUNA_ENUM( m_flag, "MonsterFlag" ) LUNA_ENUM( monster_attitude, "MonsterAttitude" ) -LUNA_ENUM( m_size, "MonsterSize" ) +LUNA_ENUM( creature_size, "MonsterSize" ) LUNA_ENUM( npc_attitude, "NpcAttitude" ) LUNA_ENUM( npc_need, "NpcNeed" ) LUNA_ENUM( sfx::channel, "SfxChannel" ) diff --git a/src/character.cpp b/src/character.cpp index 921453c25386..ebf26c93723f 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -909,7 +909,7 @@ void Character::mod_stat( const std::string &stat, float modifier ) } } -m_size Character::get_size() const +creature_size Character::get_size() const { return size_class; } @@ -1404,7 +1404,7 @@ bool Character::check_mount_is_spooked() // / 2 if horse has full tack and saddle. // Monster in spear reach monster and average stat (8) player on saddled horse, 14% -2% -0.8% / 2 = ~5% if( mounted_creature && mounted_creature->type->has_fear_trigger( mon_trigger::HOSTILE_CLOSE ) ) { - const m_size mount_size = mounted_creature->get_size(); + const creature_size mount_size = mounted_creature->get_size(); const bool saddled = mounted_creature->has_effect( effect_saddled ); for( const monster &critter : g->all_monsters() ) { double chance = 1.0; @@ -2495,7 +2495,7 @@ detached_ptr Character::wear_item( detached_ptr &&wear, } const bool was_deaf = is_deaf(); - const bool supertinymouse = get_size() == MS_TINY; + const bool supertinymouse = get_size() == creature_size::tiny; last_item = to_wear.typeId(); @@ -5402,7 +5402,7 @@ void Character::update_needs( int rate_multiplier ) } // Huge folks take penalties for cramming themselves in vehicles - if( in_vehicle && ( get_size() == MS_HUGE ) + if( in_vehicle && ( get_size() == creature_size::huge ) && !( has_trait( trait_NOPAIN ) || has_effect( effect_narcosis ) ) ) { vehicle *veh = veh_pointer_or_null( get_map().veh_at( pos() ) ); // it's painful to work the controls, but passengers in open topped vehicles are fine @@ -7341,15 +7341,15 @@ std::string Character::height_string() const int Character::height() const { switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: return init_height - 100; - case MS_SMALL: + case creature_size::small: return init_height - 50; - case MS_MEDIUM: + case creature_size::medium: return init_height; - case MS_LARGE: + case creature_size::large: return init_height + 50; - case MS_HUGE: + case creature_size::huge: return init_height + 100; default: break; @@ -10556,7 +10556,7 @@ float Character::power_rating() const } else if( dmg > 12 ) { ret = 3; // Melee weapon or weapon-y tool } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { ret += 1; } if( is_wearing_power_armor( nullptr ) ) { @@ -11569,7 +11569,8 @@ void Character::knock_back_to( const tripoint &to ) // First, see if we hit a monster if( monster *const critter = g->critter_at( to ) ) { - deal_damage( critter, bodypart_id( "torso" ), damage_instance( DT_BASH, critter->type->size ) ); + deal_damage( critter, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( critter->type->size ) ) ); add_effect( effect_stunned, 1_turns ); /** @EFFECT_STR_MAX allows knocked back player to knock back, damage, stun some monsters */ if( ( str_max - 6 ) / 4 > critter->type->size ) { @@ -11588,7 +11589,8 @@ void Character::knock_back_to( const tripoint &to ) } if( npc *const np = g->critter_at( to ) ) { - deal_damage( np, bodypart_id( "torso" ), damage_instance( DT_BASH, np->get_size() + 1 ) ); + deal_damage( np, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( np->get_size() + 1 ) ) ); add_effect( effect_stunned, 1_turns ); np->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, 3 ) ); add_msg_player_or_npc( _( "You bounce off %s!" ), _( " bounces off %s!" ), diff --git a/src/character.h b/src/character.h index 8cfdff40435d..70d93701cd1a 100644 --- a/src/character.h +++ b/src/character.h @@ -369,7 +369,7 @@ class Character : public Creature, public location_visitable void mod_stat( const std::string &stat, float modifier ) override; /** Get size class of character **/ - m_size get_size() const override; + creature_size get_size() const override; /** Recalculate size class of character **/ void recalculate_size(); @@ -2209,7 +2209,7 @@ class Character : public Creature, public location_visitable /**height at character creation*/ int init_height = 175; /** Size class of character. */ - m_size size_class = MS_MEDIUM; + creature_size size_class = creature_size::medium; trap_map known_traps; pimpl encumbrance_cache; diff --git a/src/character_display.cpp b/src/character_display.cpp index d88bdaa2494c..ee654f87ac5a 100644 --- a/src/character_display.cpp +++ b/src/character_display.cpp @@ -100,7 +100,28 @@ static std::vector> list_and_combine_bps( const Chara return bps; } -void character_display::print_encumbrance( const catacurses::window &win, const Character &ch, +static std::pair subindex_around_cursor( + const int num_entries, const int available_space, const int cursor_pos, const bool focused ) +/** + * Return indexes [start, end) that should be displayed from list long `num_entries`, + * given that cursor is at position `cursor_pos` and we have `available_space` spaces. + * + * Example: + * num_entries = 6, available_space = 3, cursor_pos = 2, focused = true; + * so choose 3 from indexes [0, 1, 2, 3, 4, 5] + * return {1, 4} + */ +{ + if( !focused || num_entries <= available_space ) { + return std::make_pair( 0, std::min( available_space, num_entries ) ); + } + int slice_start = std::min( std::max( 0, cursor_pos - available_space / 2 ), + num_entries - available_space ); + return std::make_pair( slice_start, slice_start + available_space ); +} + +void character_display::print_encumbrance( ui_adaptor &ui, const catacurses::window &win, + const Character &ch, const int line, const item *selected_clothing ) { // bool represents whether the part has been combined with its other half @@ -108,8 +129,8 @@ void character_display::print_encumbrance( const catacurses::window &win, const // width/height excluding title & scrollbar const int height = getmaxy( win ) - 1; - bool draw_scrollbar = height < static_cast( bps.size() ); - const int width = getmaxx( win ) - ( draw_scrollbar ? 1 : 0 ); + const bool do_draw_scrollbar = height < static_cast( bps.size() ); + const int width = getmaxx( win ) - ( do_draw_scrollbar ? 1 : 0 ); // index of the first printed bodypart from `bps` const int firstline = clamp( line - height / 2, 0, std::max( 0, static_cast( bps.size() ) - height ) ); @@ -139,31 +160,29 @@ void character_display::print_encumbrance( const catacurses::window &win, const // Two different highlighting schemes, highlight if the line is selected as per line being set. // Make the text green if this part is covered by the passed in item. - nc_color limb_color = thisline == line ? + const bool highlight_line = thisline == line; + nc_color limb_color = highlight_line ? ( highlighted ? h_green : h_light_gray ) : ( highlighted ? c_green : c_light_gray ); - mvwprintz( win, point( 1, 1 + i ), limb_color, "%s", out ); + const int y_pos = 1 + i; + if( highlight_line ) { + ui.set_cursor( win, point( 1, y_pos ) ); + } + mvwprintz( win, point( 1, y_pos ), limb_color, "%s", out ); // accumulated encumbrance from clothing, plus extra encumbrance from layering mvwprintz( win, point( 8, 1 + i ), encumb_color( e.encumbrance ), "%3d", e.encumbrance - e.layer_penalty ); // separator in low toned color - mvwprintz( win, point( 11, 1 + i ), c_light_gray, "+" ); + mvwprintz( win, point( 11, y_pos ), c_light_gray, "+" ); // take into account the new encumbrance system for layers - mvwprintz( win, point( 12, 1 + i ), encumb_color( e.encumbrance ), "%-3d", e.layer_penalty ); + mvwprintz( win, point( 12, y_pos ), encumb_color( e.encumbrance ), "%-3d", e.layer_penalty ); // print warmth, tethered to right hand side of the window - mvwprintz( win, point( width - 6, 1 + i ), ch.bodytemp_color( bp ), "(% 3d)", + mvwprintz( win, point( width - 6, y_pos ), ch.bodytemp_color( bp ), "(% 3d)", temperature_print_rescaling( ch.temp_conv[bp] ) ); } - if( draw_scrollbar ) { - scrollbar(). - offset_x( width ). - offset_y( 1 ). - content_size( bps.size() ). - viewport_pos( firstline ). - viewport_size( height ). - scroll_to_last( false ). - apply( win ); + if( do_draw_scrollbar ) { + draw_scrollbar( win, firstline, height, bps.size(), point( width, 1 ), c_white, true ); } } @@ -323,24 +342,39 @@ static player_display_tab prev_tab( const player_display_tab tab ) } } -static void draw_stats_tab( const catacurses::window &w_stats, - const Character &you, const unsigned int line, const player_display_tab curtab ) +static void draw_stats_tab( ui_adaptor &ui, const catacurses::window &w_stats, const Character &you, + const unsigned line, const player_display_tab curtab ) { werase( w_stats ); - const nc_color title_col = curtab == player_display_tab::stats ? h_light_gray : c_light_gray; + const bool is_current_tab = curtab == player_display_tab::stats; + const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_stats, point_zero ); + } center_print( w_stats, 0, title_col, _( title_STATS ) ); - const auto line_color = [curtab, line]( const unsigned int line_to_draw ) { - if( curtab == player_display_tab::stats && line == line_to_draw ) { + const auto highlight_line = [is_current_tab, line]( const unsigned line_to_draw ) { + return is_current_tab && line == line_to_draw; + }; + + const auto line_color = [&highlight_line]( const unsigned line_to_draw ) { + if( highlight_line( line_to_draw ) ) { return h_light_gray; } else { return c_light_gray; } }; + const auto set_highlight_cursor = [&highlight_line, &ui, &w_stats] + ( const unsigned line_to_draw ) { + if( highlight_line( line_to_draw ) ) { + ui.set_cursor( w_stats, point( 1, line_to_draw + 1 ) ); + } + }; + // Stats - const auto display_stat = [&w_stats]( const char *name, int cur, int max, int line_n, - const nc_color col ) { + const auto display_stat = [&line_color, &set_highlight_cursor, &w_stats] + ( const char *name, const int cur, const int max, const unsigned line_to_draw ) { nc_color cstatus; if( cur <= 0 ) { cstatus = c_dark_gray; @@ -356,18 +390,23 @@ static void draw_stats_tab( const catacurses::window &w_stats, cstatus = c_green; } - mvwprintz( w_stats, point( 1, line_n ), col, name ); - mvwprintz( w_stats, point( 18, line_n ), cstatus, "%2d", cur ); - mvwprintz( w_stats, point( 21, line_n ), c_light_gray, "(%2d)", max ); + set_highlight_cursor( line_to_draw ); + mvwprintz( w_stats, point( 1, line_to_draw + 1 ), line_color( line_to_draw ), name ); + mvwprintz( w_stats, point( 18, line_to_draw + 1 ), cstatus, "%2d", cur ); + mvwprintz( w_stats, point( 21, line_to_draw + 1 ), c_light_gray, "(%2d)", max ); }; - display_stat( _( "Strength:" ), you.get_str(), you.get_str_base(), 1, line_color( 0 ) ); - display_stat( _( "Dexterity:" ), you.get_dex(), you.get_dex_base(), 2, line_color( 1 ) ); - display_stat( _( "Intelligence:" ), you.get_int(), you.get_int_base(), 3, line_color( 2 ) ); - display_stat( _( "Perception:" ), you.get_per(), you.get_per_base(), 4, line_color( 3 ) ); + display_stat( _( "Strength:" ), you.get_str(), you.get_str_base(), 0 ); + display_stat( _( "Dexterity:" ), you.get_dex(), you.get_dex_base(), 1 ); + display_stat( _( "Intelligence:" ), you.get_int(), you.get_int_base(), 2 ); + display_stat( _( "Perception:" ), you.get_per(), you.get_per_base(), 3 ); + + set_highlight_cursor( 4 ); mvwprintz( w_stats, point( 1, 5 ), line_color( 4 ), _( "Height:" ) ); mvwprintz( w_stats, point( 25 - utf8_width( you.height_string() ), 5 ), line_color( 4 ), you.height_string() ); + + set_highlight_cursor( 5 ); mvwprintz( w_stats, point( 1, 6 ), line_color( 5 ), _( "Age:" ) ); mvwprintz( w_stats, point( 25 - utf8_width( you.age_string() ), 6 ), line_color( 5 ), you.age_string() ); @@ -375,8 +414,8 @@ static void draw_stats_tab( const catacurses::window &w_stats, wnoutrefresh( w_stats ); } -static void draw_stats_info( const catacurses::window &w_info, - const Character &you, const unsigned int line ) +static void draw_stats_info( const catacurses::window &w_info, const Character &you, + const unsigned line ) { werase( w_info ); nc_color col_temp = c_light_gray; @@ -450,23 +489,25 @@ static void draw_stats_info( const catacurses::window &w_info, wnoutrefresh( w_info ); } -static void draw_encumbrance_tab( const catacurses::window &w_encumb, - const Character &you, const unsigned int line, const player_display_tab curtab ) +static void draw_encumbrance_tab( ui_adaptor &ui, const catacurses::window &w_encumb, + const Character &you, + const unsigned line, const player_display_tab curtab ) { werase( w_encumb ); const bool is_current_tab = curtab == player_display_tab::encumbrance; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; center_print( w_encumb, 0, title_col, _( title_ENCUMB ) ); if( is_current_tab ) { - character_display::print_encumbrance( w_encumb, you, line ); + ui.set_cursor( w_encumb, point_zero ); + character_display::print_encumbrance( ui, w_encumb, you, line ); } else { - character_display::print_encumbrance( w_encumb, you ); + character_display::print_encumbrance( ui, w_encumb, you ); } wnoutrefresh( w_encumb ); } -static void draw_encumbrance_info( const catacurses::window &w_info, - const Character &you, const unsigned int line ) +static void draw_encumbrance_info( const catacurses::window &w_info, const Character &you, + const unsigned line ) { const std::vector> bps = list_and_combine_bps( you, nullptr ); @@ -483,46 +524,43 @@ static void draw_encumbrance_info( const catacurses::window &w_info, wnoutrefresh( w_info ); } -static void draw_traits_tab( const catacurses::window &w_traits, - const unsigned int line, const player_display_tab curtab, - const std::vector &traitslist, - const size_t trait_win_size_y ) +static void draw_traits_tab( ui_adaptor &ui, const catacurses::window &w_traits, + const unsigned line, const player_display_tab curtab, + const std::vector &traitslist ) { werase( w_traits ); const bool is_current_tab = curtab == player_display_tab::traits; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_traits, point_zero ); + } center_print( w_traits, 0, title_col, _( title_TRAITS ) ); - size_t min = 0; - size_t max = 0; - - if( !is_current_tab || line <= ( trait_win_size_y - 2 ) / 2 ) { - min = 0; - max = trait_win_size_y - 1; - if( traitslist.size() < max ) { - max = traitslist.size(); - } - } else if( line >= traitslist.size() - trait_win_size_y / 2 ) { - min = ( traitslist.size() < trait_win_size_y - 1 ? 0 : traitslist.size() - trait_win_size_y + 1 ); - max = traitslist.size(); - } else { - min = line - ( trait_win_size_y - 2 ) / 2; - max = line + ( trait_win_size_y + 1 ) / 2; - if( traitslist.size() < max ) { - max = traitslist.size(); - } - } + const int height = getmaxy( w_traits ) - 1; + const bool do_draw_scrollbar = height < static_cast( traitslist.size() ); + const int width = getmaxx( w_traits ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( traitslist.size(), height, + line, is_current_tab ); - for( size_t i = min; i < max; i++ ) { + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { const auto &mdata = traitslist[i].obj(); const auto color = mdata.get_display_color(); - trim_and_print( w_traits, point( 1, static_cast( 1 + i - min ) ), getmaxx( w_traits ) - 1, - is_current_tab && i == line ? hilite( color ) : color, mdata.name() ); + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 1 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_traits, pos ); + } + trim_and_print( w_traits, pos, width, + highlight_line ? hilite( color ) : color, mdata.name() ); + } + if( do_draw_scrollbar ) { + draw_scrollbar( w_traits, range.first, height, traitslist.size(), point( width + 1, 1 ), c_white, + true ); } wnoutrefresh( w_traits ); } -static void draw_traits_info( const catacurses::window &w_info, const unsigned int line, +static void draw_traits_info( const catacurses::window &w_info, const unsigned line, const std::vector &traitslist ) { werase( w_info ); @@ -535,45 +573,48 @@ static void draw_traits_info( const catacurses::window &w_info, const unsigned i wnoutrefresh( w_info ); } -static void draw_bionics_tab( const catacurses::window &w_bionics, - const Character &you, const unsigned int line, const player_display_tab curtab, - const std::vector &bionicslist, const size_t bionics_win_size_y ) +static void draw_bionics_tab( ui_adaptor &ui, const catacurses::window &w_bionics, + const Character &you, const unsigned line, + const player_display_tab curtab, + const std::vector &bionicslist ) { werase( w_bionics ); const bool is_current_tab = curtab == player_display_tab::bionics; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; center_print( w_bionics, 0, title_col, _( title_BIONICS ) ); // NOLINTNEXTLINE(cata-use-named-point-constants) - trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white, + const point pow_pos( 1, 1 ); + if( is_current_tab ) { + ui.set_cursor( w_bionics, pow_pos ); + } + trim_and_print( w_bionics, pow_pos, getmaxx( w_bionics ) - 1, c_white, string_format( _( "Bionic Power: %1$d" " / %2$d" ), units::to_kilojoule( you.get_power_level() ), units::to_kilojoule( you.get_max_power_level() ) ) ); - const size_t useful_y = bionics_win_size_y - 2; - const size_t half_y = useful_y / 2; - - size_t min = 0; - size_t max = 0; - - if( !is_current_tab || line <= half_y ) { // near the top - min = 0; - max = std::min( bionicslist.size(), useful_y ); - } else if( line >= bionicslist.size() - half_y ) { // near the bottom - min = ( bionicslist.size() <= useful_y ? 0 : bionicslist.size() - useful_y ); - max = bionicslist.size(); - } else { // scrolling - min = line - half_y; - max = std::min( bionicslist.size(), line + useful_y - half_y ); + const int height = getmaxy( w_bionics ) - 2; // -2 for headline and power_level + const bool do_draw_scrollbar = height < static_cast( bionicslist.size() ); + const int width = getmaxx( w_bionics ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( bionicslist.size(), height, + line, is_current_tab ); + + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 2 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_bionics, pos ); + } + trim_and_print( w_bionics, pos, width, + highlight_line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name ); } - - for( size_t i = min; i < max; i++ ) { - trim_and_print( w_bionics, point( 1, static_cast( 2 + i - min ) ), getmaxx( w_bionics ) - 1, - is_current_tab && i == line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name ); + if( do_draw_scrollbar ) { + draw_scrollbar( w_bionics, range.first, height, bionicslist.size(), point( width + 1, 2 ), c_white, + true ); } wnoutrefresh( w_bionics ); } -static void draw_bionics_info( const catacurses::window &w_info, const unsigned int line, +static void draw_bionics_info( const catacurses::window &w_info, const unsigned line, const std::vector &bionicslist ) { werase( w_info ); @@ -585,48 +626,41 @@ static void draw_bionics_info( const catacurses::window &w_info, const unsigned wnoutrefresh( w_info ); } -static void draw_effects_tab( const catacurses::window &w_effects, - const unsigned int line, const player_display_tab curtab, - const std::vector> &effect_name_and_text, - const size_t effect_win_size_y ) +static void draw_effects_tab( ui_adaptor &ui, const catacurses::window &w_effects, + const unsigned line, const player_display_tab curtab, + const std::vector> &effect_name_and_text ) { werase( w_effects ); const bool is_current_tab = curtab == player_display_tab::effects; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_effects, point_zero ); + } center_print( w_effects, 0, title_col, _( title_EFFECTS ) ); - const size_t half_y = ( effect_win_size_y - 1 ) / 2; - - size_t min = 0; - size_t max = 0; - - const size_t actual_size = effect_name_and_text.size(); - - if( !is_current_tab || line <= half_y ) { - min = 0; - max = effect_win_size_y - 1; - if( actual_size < max ) { - max = actual_size; - } - } else if( line >= actual_size - half_y ) { - min = ( actual_size < effect_win_size_y - 1 ? 0 : actual_size - effect_win_size_y + 1 ); - max = actual_size; - } else { - min = line - half_y; - max = line - half_y + effect_win_size_y - 1; - if( actual_size < max ) { - max = actual_size; + const int height = getmaxy( w_effects ) - 1; + const bool do_draw_scrollbar = height < static_cast( effect_name_and_text.size() ); + const int width = getmaxx( w_effects ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( effect_name_and_text.size(), + height, line, is_current_tab ); + + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 1 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_effects, pos ); } + trim_and_print( w_effects, pos, width, + highlight_line ? h_light_gray : c_light_gray, effect_name_and_text[i].first ); } - - for( size_t i = min; i < max; i++ ) { - trim_and_print( w_effects, point( 0, static_cast( 1 + i - min ) ), getmaxx( w_effects ) - 1, - is_current_tab && i == line ? h_light_gray : c_light_gray, effect_name_and_text[i].first ); + if( do_draw_scrollbar ) { + draw_scrollbar( w_effects, range.first, height, effect_name_and_text.size(), point( width + 1, 1 ), + c_white, true ); } wnoutrefresh( w_effects ); } -static void draw_effects_info( const catacurses::window &w_info, const unsigned int line, +static void draw_effects_info( const catacurses::window &w_info, const unsigned line, const std::vector> &effect_name_and_text ) { werase( w_info ); @@ -697,12 +731,12 @@ int character_display::display_empty_handed_base_damage( const Character &you ) } } -static void draw_skills_tab( const catacurses::window &w_skills, +static void draw_skills_tab( ui_adaptor &ui, const catacurses::window &w_skills, Character &you, unsigned int line, const player_display_tab curtab, std::vector &skillslist, const size_t skill_win_size_y ) { - const int col_width = 25; + const int col_width = getmaxx( w_skills ) - 1; if( line == 0 ) { //can't point to a header; line = 1; } @@ -710,6 +744,9 @@ static void draw_skills_tab( const catacurses::window &w_skills, werase( w_skills ); const bool is_current_tab = curtab == player_display_tab::skills; nc_color cstatus = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_skills, point_zero ); + } center_print( w_skills, 0, cstatus, _( title_SKILLS ) ); size_t min = 0; @@ -728,7 +765,7 @@ static void draw_skills_tab( const catacurses::window &w_skills, max = std::min( min + skill_win_size_y - 1, skillslist.size() ); int y_pos = 1; - for( size_t i = min; i < max; i++, y_pos++ ) { + for( size_t i = min; i < max; ++i, ++y_pos ) { const Skill *aSkill = skillslist[i].skill; const SkillLevel &level = you.get_skill_level_object( aSkill->ident() ); @@ -750,6 +787,7 @@ static void draw_skills_tab( const catacurses::window &w_skills, locked = true; } if( is_current_tab && i == line ) { + ui.set_cursor( w_skills, point( 1, y_pos ) ); if( locked ) { cstatus = h_yellow; } else if( !can_train ) { @@ -839,35 +877,35 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Overburdened (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Overburdened" ), 20 ), pen ); - line++; + ++line; } pen = character_effects::get_pain_penalty( you ).speed; if( pen >= 1 ) { //~ %s: Pain (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Pain" ), 20 ), pen ); - line++; + ++line; } if( you.get_thirst() > thirst_levels::very_thirsty ) { pen = std::abs( character_effects::get_thirst_speed_penalty( you.get_thirst() ) ); //~ %s: Thirsty/Parched (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Thirst" ), 20 ), pen ); - line++; + ++line; } if( character_effects::get_kcal_speed_penalty( you.get_kcal_percent() ) < 0 ) { pen = std::abs( character_effects::get_kcal_speed_penalty( you.get_kcal_percent() ) ); //~ %s: Starving/Underfed (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Starving" ), 20 ), pen ); - line++; + ++line; } if( you.has_trait( trait_id( "SUNLIGHT_DEPENDENT" ) ) && !g->is_in_sunlight( you.pos() ) ) { pen = ( g->light_level( you.posz() ) >= 12 ? 5 : 10 ); //~ %s: Out of Sunlight (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Out of Sunlight" ), 20 ), pen ); - line++; + ++line; } const float temperature_speed_modifier = you.mutation_value( "temperature_speed_modifier" ); @@ -887,7 +925,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Cold-Blooded (already left-justified), %s: sign of bonus/penalty, %2d%%: speed modifier mvwprintz( w_speed, point( 1, line ), pen_color, pgettext( "speed modifier", "%s%s%2d%%" ), left_justify( _( "Cold-Blooded" ), 20 ), pen_sign, std::abs( pen ) ); - line++; + ++line; } } @@ -900,12 +938,12 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Mutations (already left-justified), %s: sign of bonus/penalty, %2d%%: speed modifier mvwprintz( w_speed, point( 1, line ), pen_color, pgettext( "speed bonus", "%s%s%2d%%" ), left_justify( _( "Mutations" ), 20 ), pen_sign, std::abs( quick_bonus ) ); - line++; + ++line; } if( you.has_bionic( bionic_id( "bio_speed" ) ) ) { mvwprintz( w_speed, point( 1, line ), c_green, pgettext( "speed bonus", "Bionic Speed +%2d%%" ), bio_speed_bonus ); - line++; + ++line; } for( const std::pair &speed_effect : speed_effects ) { @@ -914,7 +952,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, mvwprintz( w_speed, point( 21, line ), col, ( speed_effect.second > 0 ? "+" : "-" ) ); mvwprintz( w_speed, point( std::abs( speed_effect.second ) >= 10 ? 22 : 23, line ), col, "%d%%", std::abs( speed_effect.second ) ); - line++; + ++line; } int runcost = you.run_cost( 100 ); @@ -928,7 +966,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, } static void draw_info_window( const catacurses::window &w_info, const Character &you, - const unsigned int line, const player_display_tab curtab, + const unsigned line, const player_display_tab curtab, const std::vector &traitslist, const std::vector &bionicslist, const std::vector> &effect_name_and_text, @@ -1130,6 +1168,30 @@ static bool handle_player_display_action( Character &you, unsigned int &line, return done; } +static std::pair calculate_shared_column_win_height( + const unsigned available_height, unsigned first_win_size_y_max, unsigned second_win_size_y_max ) +/** + * Calculate max allowed height of two windows sharing column space. + */ +{ + if( ( second_win_size_y_max + 1 + first_win_size_y_max ) > available_height ) { + // maximum space for either window if they're both the same size + unsigned max_shared_y = ( available_height - 1 ) / 2; + if( std::min( second_win_size_y_max, first_win_size_y_max ) > max_shared_y ) { + // both are larger than the shared size + second_win_size_y_max = max_shared_y; + first_win_size_y_max = available_height - 1 - second_win_size_y_max; + } else if( first_win_size_y_max <= max_shared_y ) { + // first window is less than the shared size, so give space to second window + second_win_size_y_max = available_height - 1 - first_win_size_y_max; + } else { + // second window is less than the shared size + first_win_size_y_max = available_height - 1 - second_win_size_y_max; + } + } + return std::make_pair( first_win_size_y_max, second_win_size_y_max ); +} + void character_display::disp_info( Character &ch ) { std::vector> effect_name_and_text; @@ -1214,7 +1276,7 @@ void character_display::disp_info( Character &ch ) } } - const unsigned int effect_win_size_y = 1 + static_cast( effect_name_and_text.size() ); + const unsigned int effect_win_size_y_max = 1 + static_cast( effect_name_and_text.size() ); std::vector traitslist = ch.get_mutations( false ); std::sort( traitslist.begin(), traitslist.end(), trait_display_sort ); @@ -1250,40 +1312,16 @@ void character_display::disp_info( Character &ch ) const unsigned int infooffsetytop = grid_height + 2; unsigned int infooffsetybottom = infooffsetytop + 1 + info_win_size_y; - const auto calculate_trait_and_bionic_height = [&]() { - const unsigned int maxy = static_cast( TERMY ); - unsigned int trait_win_size_y = trait_win_size_y_max; - unsigned int bionics_win_size_y = bionics_win_size_y_max; - if( ( bionics_win_size_y_max + 1 + trait_win_size_y_max + infooffsetybottom ) > maxy ) { - // maximum space for either window if they're both the same size - unsigned max_shared_y = ( maxy - infooffsetybottom - 1 ) / 2; - if( std::min( bionics_win_size_y_max, trait_win_size_y_max ) > max_shared_y ) { - // both are larger than the shared size - bionics_win_size_y = max_shared_y; - trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y; - } else if( trait_win_size_y_max <= max_shared_y ) { - // trait window is less than the shared size, so give space to bionics - bionics_win_size_y = maxy - infooffsetybottom - 1 - trait_win_size_y_max; - } else { - // bionics window is less than the shared size - trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y; - } - } - return std::make_pair( trait_win_size_y, bionics_win_size_y ); - }; - // Print name and header // Post-humanity trumps your pre-Cataclysm life // Unless you have a custom profession. std::string race; - if( ch.custom_profession.empty() ) { - if( ch.crossed_threshold() ) { - for( const trait_id &mut : ch.get_mutations() ) { - const mutation_branch &mdata = mut.obj(); - if( mdata.threshold ) { - race = mdata.name(); - break; - } + if( ch.custom_profession.empty() && ch.crossed_threshold() ) { + for( const trait_id &mut : ch.get_mutations() ) { + const mutation_branch &mdata = mut.obj(); + if( mdata.threshold ) { + race = mdata.name(); + break; } } } @@ -1322,10 +1360,12 @@ void character_display::disp_info( Character &ch ) ui_tip.position_from_window( w_tip ); } ); ui_tip.mark_resize(); - ui_tip.on_redraw( [&]( const ui_adaptor & ) { + ui_tip.on_redraw( [&]( ui_adaptor & ui_tip ) { + ui_tip.disable_cursor(); draw_tip( w_tip, ch, race, ctxt ); } ); + // STATS catacurses::window w_stats; catacurses::window w_stats_border; border_helper::border_info &border_stats = borders.add_border(); @@ -1344,58 +1384,66 @@ void character_display::disp_info( Character &ch ) ui_stats.position_from_window( w_stats_border ); } ); ui_stats.mark_resize(); - ui_stats.on_redraw( [&]( const ui_adaptor & ) { + ui_stats.on_redraw( [&]( ui_adaptor & ui_stats ) { borders.draw_border( w_stats_border ); wnoutrefresh( w_stats_border ); - draw_stats_tab( w_stats, ch, line, curtab ); + ui_stats.disable_cursor(); + draw_stats_tab( ui_stats, w_stats, ch, line, curtab ); } ); - unsigned int trait_win_size_y = 0; + // TRAITS & BIONICS + unsigned trait_win_size_y; + unsigned bionics_win_size_y; + // TRAITS catacurses::window w_traits; catacurses::window w_traits_border; border_helper::border_info &border_traits = borders.add_border(); ui_adaptor ui_traits; ui_traits.on_screen_resize( [&]( ui_adaptor & ui_traits ) { - trait_win_size_y = calculate_trait_and_bionic_height().first; + std::tie( trait_win_size_y, bionics_win_size_y ) = calculate_shared_column_win_height( + static_cast( TERMY ) - infooffsetybottom, trait_win_size_y_max, bionics_win_size_y_max ); w_traits = catacurses::newwin( trait_win_size_y, grid_width, point( grid_width + 1, infooffsetybottom ) ); - w_traits_border = catacurses::newwin( trait_win_size_y + 1, grid_width + 1, - point( grid_width + 1, infooffsetybottom ) ); + w_traits_border = catacurses::newwin( trait_win_size_y + 1, grid_width + 2, + point( grid_width, infooffsetybottom ) ); border_traits.set( point( grid_width, infooffsetybottom - 1 ), point( grid_width + 2, trait_win_size_y + 2 ) ); ui_traits.position_from_window( w_traits_border ); } ); ui_traits.mark_resize(); - ui_traits.on_redraw( [&]( const ui_adaptor & ) { + ui_traits.on_redraw( [&]( ui_adaptor & ui_traits ) { borders.draw_border( w_traits_border ); wnoutrefresh( w_traits_border ); - draw_traits_tab( w_traits, line, curtab, traitslist, trait_win_size_y ); + ui_traits.disable_cursor(); + draw_traits_tab( ui_traits, w_traits, line, curtab, traitslist ); } ); - unsigned int bionics_win_size_y = 0; + // BIONICS catacurses::window w_bionics; catacurses::window w_bionics_border; border_helper::border_info &border_bionics = borders.add_border(); ui_adaptor ui_bionics; ui_bionics.on_screen_resize( [&]( ui_adaptor & ui_bionics ) { - bionics_win_size_y = calculate_trait_and_bionic_height().second; + std::tie( trait_win_size_y, bionics_win_size_y ) = calculate_shared_column_win_height( + static_cast( TERMY ) - infooffsetybottom, trait_win_size_y_max, bionics_win_size_y_max ); w_bionics = catacurses::newwin( bionics_win_size_y, grid_width, point( grid_width + 1, infooffsetybottom + trait_win_size_y + 1 ) ); - w_bionics_border = catacurses::newwin( bionics_win_size_y + 1, grid_width + 1, - point( grid_width + 1, - infooffsetybottom + trait_win_size_y + 1 ) ); + w_bionics_border = catacurses::newwin( bionics_win_size_y + 1, grid_width + 2, + point( grid_width, infooffsetybottom + trait_win_size_y + 1 ) ); border_bionics.set( point( grid_width, infooffsetybottom + trait_win_size_y ), point( grid_width + 2, bionics_win_size_y + 2 ) ); ui_bionics.position_from_window( w_bionics_border ); } ); ui_bionics.mark_resize(); - ui_bionics.on_redraw( [&]( const ui_adaptor & ) { + ui_bionics.on_redraw( [&]( ui_adaptor & ui_bionics ) { borders.draw_border( w_bionics_border ); wnoutrefresh( w_bionics_border ); - draw_bionics_tab( w_bionics, ch, line, curtab, bionicslist, bionics_win_size_y ); + ui_bionics.disable_cursor(); + draw_bionics_tab( ui_bionics, w_bionics, ch, line, curtab, bionicslist ); } ); + // ENCUMBRANCE catacurses::window w_encumb; catacurses::window w_encumb_border; border_helper::border_info &border_encumb = borders.add_border(); @@ -1407,17 +1455,25 @@ void character_display::disp_info( Character &ch ) ui_encumb.position_from_window( w_encumb_border ); } ); ui_encumb.mark_resize(); - ui_encumb.on_redraw( [&]( const ui_adaptor & ) { + ui_encumb.on_redraw( [&]( ui_adaptor & ui_encumb ) { borders.draw_border( w_encumb_border ); wnoutrefresh( w_encumb_border ); - draw_encumbrance_tab( w_encumb, ch, line, curtab ); + ui_encumb.disable_cursor(); + draw_encumbrance_tab( ui_encumb, w_encumb, ch, line, curtab ); } ); + // EFFECTS + unsigned int effect_win_size_y = 0; catacurses::window w_effects; catacurses::window w_effects_border; border_helper::border_info &border_effects = borders.add_border(); ui_adaptor ui_effects; ui_effects.on_screen_resize( [&]( ui_adaptor & ui_effects ) { + const unsigned int maxy = static_cast( TERMY ); + effect_win_size_y = effect_win_size_y_max; + if( effect_win_size_y + infooffsetybottom > maxy ) { + effect_win_size_y = maxy - infooffsetybottom; + } w_effects = catacurses::newwin( effect_win_size_y, grid_width, point( grid_width * 2 + 2, infooffsetybottom ) ); w_effects_border = catacurses::newwin( effect_win_size_y + 1, grid_width + 1, @@ -1427,12 +1483,14 @@ void character_display::disp_info( Character &ch ) ui_effects.position_from_window( w_effects_border ); } ); ui_effects.mark_resize(); - ui_effects.on_redraw( [&]( const ui_adaptor & ) { + ui_effects.on_redraw( [&]( ui_adaptor & ui_effects ) { borders.draw_border( w_effects_border ); wnoutrefresh( w_effects_border ); - draw_effects_tab( w_effects, line, curtab, effect_name_and_text, effect_win_size_y ); + ui_effects.disable_cursor(); + draw_effects_tab( ui_effects, w_effects, line, curtab, effect_name_and_text ); } ); + // SPEED catacurses::window w_speed; catacurses::window w_speed_border; border_helper::border_info &border_speed = borders.add_border(); @@ -1446,12 +1504,14 @@ void character_display::disp_info( Character &ch ) ui_speed.position_from_window( w_speed_border ); } ); ui_speed.mark_resize(); - ui_speed.on_redraw( [&]( const ui_adaptor & ) { + ui_speed.on_redraw( [&]( ui_adaptor & ui_speed ) { borders.draw_border( w_speed_border ); wnoutrefresh( w_speed_border ); + ui_speed.disable_cursor(); draw_speed_tab( w_speed, ch, speed_effects ); } ); + // SKILLS unsigned int skill_win_size_y = 0; catacurses::window w_skills; catacurses::window w_skills_border; @@ -1472,12 +1532,14 @@ void character_display::disp_info( Character &ch ) ui_skills.position_from_window( w_skills_border ); } ); ui_skills.mark_resize(); - ui_skills.on_redraw( [&]( const ui_adaptor & ) { + ui_skills.on_redraw( [&]( ui_adaptor & ui_skills ) { borders.draw_border( w_skills_border ); wnoutrefresh( w_skills_border ); - draw_skills_tab( w_skills, ch, line, curtab, skillslist, skill_win_size_y ); + ui_skills.disable_cursor(); + draw_skills_tab( ui_skills, w_skills, ch, line, curtab, skillslist, skill_win_size_y ); } ); + // info panel catacurses::window w_info; catacurses::window w_info_border; border_helper::border_info &border_info = borders.add_border(); @@ -1492,9 +1554,10 @@ void character_display::disp_info( Character &ch ) ui_info.position_from_window( w_info_border ); } ); ui_info.mark_resize(); - ui_info.on_redraw( [&]( const ui_adaptor & ) { + ui_info.on_redraw( [&]( ui_adaptor & ui_info ) { borders.draw_border( w_info_border ); wnoutrefresh( w_info_border ); + ui_info.disable_cursor(); draw_info_window( w_info, ch, line, curtab, traitslist, bionicslist, effect_name_and_text, skillslist ); } ); diff --git a/src/character_display.h b/src/character_display.h index 3d45dadb7335..db5c1dda1f0a 100644 --- a/src/character_display.h +++ b/src/character_display.h @@ -7,6 +7,7 @@ class Character; class item; class avatar; +class ui_adaptor; namespace catacurses { @@ -19,7 +20,8 @@ namespace character_display /** * Formats and prints encumbrance info to specified window */ -void print_encumbrance( const catacurses::window &win, const Character &ch, int line = -1, +void print_encumbrance( ui_adaptor &ui, const catacurses::window &win, const Character &ch, + int line = -1, const item *selected_clothing = nullptr ); /** diff --git a/src/character_turn.cpp b/src/character_turn.cpp index b6dd4d9c47f6..1c6660883e1d 100644 --- a/src/character_turn.cpp +++ b/src/character_turn.cpp @@ -456,10 +456,10 @@ void Character::process_one_effect( effect &it, bool is_new ) if( has_trait( trait_FAT ) ) { mod *= 1.5; } - if( get_size() == MS_LARGE ) { + if( get_size() == creature_size::large ) { mod *= 2; } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { mod *= 3; } } @@ -480,10 +480,10 @@ void Character::process_one_effect( effect &it, bool is_new ) if( has_trait( trait_FAT ) ) { mod *= 1.5; } - if( get_size() == MS_LARGE ) { + if( get_size() == creature_size::large ) { mod *= 2; } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { mod *= 3; } } diff --git a/src/computer_session.cpp b/src/computer_session.cpp index 5404cc4d84a0..1479a3838fa4 100644 --- a/src/computer_session.cpp +++ b/src/computer_session.cpp @@ -1538,7 +1538,7 @@ computer_session::ynq computer_session::query_ynq( const std::string &text, Args const bool force_uc = get_option( "FORCE_CAPITAL_YN" ); const auto &allow_key = force_uc ? input_context::disallow_lower_case : input_context::allow_all_keys; - input_context ctxt( "YESNOQUIT" ); + input_context ctxt( "YESNOQUIT", keyboard_mode::keychar ); ctxt.register_action( "YES" ); ctxt.register_action( "NO" ); ctxt.register_action( "QUIT" ); diff --git a/src/construction.cpp b/src/construction.cpp index f69510285051..3c1742b96143 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -643,7 +643,7 @@ std::optional construction_menu( const bool blueprint ) } ); ui.mark_resize(); - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { draw_grid( w_con, w_list_width + w_list_x0 ); // Erase existing tab selection & list of constructions @@ -655,7 +655,6 @@ std::optional construction_menu( const bool blueprint ) // Determine where in the master list to start printing calcStartPos( offset, select, w_list_height, constructs.size() ); // Print the constructions between offset and max (or how many will fit) - std::optional cursor_pos; for( size_t i = 0; static_cast( i ) < w_list_height && ( i + offset ) < constructs.size(); i++ ) { int current = i + offset; @@ -663,7 +662,7 @@ std::optional construction_menu( const bool blueprint ) bool highlight = ( current == select ); const point print_from( 0, i ); if( highlight ) { - cursor_pos = print_from; + ui.set_cursor( w_list, print_from ); } const std::string group_name = is_favorite( group ) ? "* " + group->name() : group->name(); trim_and_print( w_list, print_from, w_list_width, @@ -721,10 +720,6 @@ std::optional construction_menu( const bool blueprint ) draw_scrollbar( w_con, select, w_list_height, constructs.size(), point( 0, 3 ) ); wnoutrefresh( w_con ); - // place the cursor at the selected construction name as expected by screen readers - if( cursor_pos ) { - wmove( w_list, cursor_pos.value() ); - } wnoutrefresh( w_list ); } ); diff --git a/src/consumption.cpp b/src/consumption.cpp index d965f3bccf4f..3e2d10a524f8 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -169,14 +169,14 @@ struct prepared_item_consumption { case item_consumption_t::tool: { comp_selection selection = comp_selection(); selection.comp = tool_comp( it.typeId(), it.type->charges_to_use() ); - selection.use_from = use_from_both; + selection.use_from = usage_from::both; p.consume_tools( selection, it.type->charges_to_use() ); return true; } case item_consumption_t::component: { comp_selection selection = comp_selection(); selection.comp = item_comp( it.typeId(), 1 ); - selection.use_from = use_from_both; + selection.use_from = usage_from::both; return !p.consume_items( selection, 1, is_crafting_component ).empty(); } } diff --git a/src/craft_command.cpp b/src/craft_command.cpp index b4b30fa8f90e..6e716df0b8ed 100644 --- a/src/craft_command.cpp +++ b/src/craft_command.cpp @@ -27,15 +27,15 @@ template std::string comp_selection::nname() const { switch( use_from ) { - case use_from_map: + case usage_from::map: return item::nname( comp.type, comp.count ) + _( " (nearby)" ); - case use_from_both: + case usage_from::both: return item::nname( comp.type, comp.count ) + _( " (person & nearby)" ); - case use_from_player: + case usage_from::player: // Is the same as the default return; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } @@ -46,17 +46,17 @@ namespace io { template<> -std::string enum_to_string( usage data ) +std::string enum_to_string( usage_from data ) { switch( data ) { // *INDENT-OFF* - case usage::use_from_map: return "map"; - case usage::use_from_player: return "player"; - case usage::use_from_both: return "both"; - case usage::use_from_none: return "none"; - case usage::cancel: return "cancel"; + case usage_from::map: return "map"; + case usage_from::player: return "player"; + case usage_from::both: return "both"; + case usage_from::none: return "none"; + case usage_from::cancel: return "cancel"; // *INDENT-ON* - case usage::num_usages: + case usage_from::num_usages_from: break; } debugmsg( "Invalid usage" ); @@ -84,7 +84,7 @@ void comp_selection::deserialize( JsonIn &jsin ) std::string use_from_str; data.read( "use_from", use_from_str ); - use_from = io::string_to_enum( use_from_str ); + use_from = io::string_to_enum( use_from_str ); data.read( "type", comp.type ); data.read( "count", comp.count ); } @@ -154,7 +154,7 @@ void craft_command::execute( const tripoint &new_loc ) for( const auto &it : needs->get_components() ) { comp_selection is = crafter->select_item_component( it, batch_size, map_inv, true, filter ); - if( is.use_from == cancel ) { + if( is.use_from == usage_from::cancel ) { return; } item_selections.push_back( is ); @@ -166,7 +166,7 @@ void craft_command::execute( const tripoint &new_loc ) it, batch_size, map_inv, crafter, true, DEFAULT_HOTKEYS, cost_adjustment::start_only ); - if( ts.use_from == cancel ) { + if( ts.use_from == usage_from::cancel ) { return; } tool_selections.push_back( ts ); @@ -301,49 +301,49 @@ std::vector> craft_command::check_item_components_miss if( item::count_by_charges( type ) && count > 0 ) { switch( item_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_charges( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_both: + case usage_from::both: if( !( crafter->charges_of( type, INT_MAX, filter ) + map_inv.charges_of( type, INT_MAX, filter ) >= count ) ) { missing.push_back( item_sel ); } break; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else { // Counting by units, not charges. switch( item_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_amount( type, count, false, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_components( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_both: + case usage_from::both: if( !( crafter->amount_of( type, false, std::numeric_limits::max(), filter ) + map_inv.amount_of( type, false, std::numeric_limits::max(), filter ) >= count ) ) { missing.push_back( item_sel ); } break; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } @@ -362,20 +362,20 @@ std::vector> craft_command::check_tool_components_miss if( tool_sel.comp.count > 0 ) { const int count = tool_sel.comp.count * batch_size; switch( tool_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_charges( type, count ) ) { missing.push_back( tool_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count ) ) { missing.push_back( tool_sel ); } break; - case use_from_both: - case use_from_none: - case cancel: - case num_usages: + case usage_from::both: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else if( !crafter->has_amount( type, 1 ) && !map_inv.has_tools( type, 1 ) ) { diff --git a/src/craft_command.h b/src/craft_command.h index cdae2a04729b..f57e56cc58cd 100644 --- a/src/craft_command.h +++ b/src/craft_command.h @@ -20,33 +20,39 @@ template struct enum_traits; /** * enum used by comp_selection to indicate where a component should be consumed from. */ -enum usage { - use_from_none = 0, - use_from_map = 1, - use_from_player = 2, - use_from_both = 1 | 2, +enum class usage_from : int { + none = 0, + map = 1, + player = 2, + both = 1 | 2, cancel = 4, // FIXME: hacky. - num_usages + num_usages_from }; template<> -struct enum_traits { - static constexpr usage last = usage::num_usages; +struct enum_traits { + static constexpr usage_from last = usage_from::num_usages_from; }; +inline bool operator&( usage_from l, usage_from r ) +{ + using I = std::underlying_type_t; + return static_cast( l ) & static_cast( r ); +} + /** * Struct that represents a selection of a component for crafting. */ template struct comp_selection { comp_selection() = default; - comp_selection( usage use_from, const CompType &comp = CompType() ) + comp_selection( usage_from use_from, const CompType &comp = CompType() ) : use_from( use_from ), comp( comp ) {} comp_selection( const comp_selection & ) = default; comp_selection &operator = ( const comp_selection & ) = default; /** Tells us where the selected component should be used from. */ - usage use_from = use_from_none; + usage_from use_from = usage_from::none; CompType comp; /** provides a translated name for 'comp', suffixed with it's location e.g '(nearby)'. */ diff --git a/src/crafting.cpp b/src/crafting.cpp index 7fde3e6ea528..6930ee2c633b 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -1229,7 +1229,7 @@ bool player::can_continue_craft( item &craft ) std::vector> item_selections; for( const auto &it : continue_reqs.get_components() ) { comp_selection is = select_item_component( it, batch_size, map_inv, true, filter ); - if( is.use_from == cancel ) { + if( is.use_from == usage_from::cancel ) { cancel_activity(); add_msg( _( "You stop crafting." ) ); return false; @@ -1285,7 +1285,7 @@ bool player::can_continue_craft( item &craft ) comp_selection selection = crafting::select_tool_component( alternatives, batch_size, map_inv, this, true, DEFAULT_HOTKEYS, cost_adjustment::continue_only ); - if( selection.use_from == cancel ) { + if( selection.use_from == usage_from::cancel ) { return false; } new_tool_selections.push_back( selection ); @@ -1349,7 +1349,7 @@ comp_selection player::select_item_component( const std::vector player::select_item_component( const std::vector player::select_item_component( const std::vector player::select_item_component( const std::vector( cmenu.ret ) >= map_has.size() + player_has.size() + mixed.size() ) { - selected.use_from = cancel; + selected.use_from = usage_from::cancel; return selected; } size_t uselection = static_cast( cmenu.ret ); if( uselection < map_has.size() ) { - selected.use_from = usage::use_from_map; + selected.use_from = usage_from::map; selected.comp = map_has[uselection]; } else if( uselection < map_has.size() + player_has.size() ) { uselection -= map_has.size(); - selected.use_from = usage::use_from_player; + selected.use_from = usage_from::player; selected.comp = player_has[uselection]; } else { uselection -= map_has.size() + player_has.size(); - selected.use_from = usage::use_from_both; + selected.use_from = usage_from::both; selected.comp = mixed[uselection]; } } @@ -1557,7 +1557,7 @@ std::vector> player::consume_items( map &m, const comp_select int real_count = ( selected_comp.count > 0 ) ? selected_comp.count * batch : std::abs( selected_comp.count ); // First try to get everything from the map, than (remaining amount) from player - if( is.use_from & use_from_map ) { + if( is.use_from & usage_from::map ) { if( by_charges ) { std::vector> tmp = m.use_charges( loc, radius, selected_comp.type, real_count, filter ); @@ -1576,7 +1576,7 @@ std::vector> player::consume_items( map &m, const comp_select std::make_move_iterator( tmp.end() ) ); } } - if( is.use_from & use_from_player ) { + if( is.use_from & usage_from::player ) { if( by_charges ) { std::vector> tmp = use_charges( selected_comp.type, real_count, filter ); ret.insert( ret.end(), std::make_move_iterator( tmp.begin() ), @@ -1671,28 +1671,28 @@ find_tool_component( const Character *player_with_inv, const std::vectorhas_charges( type, count ) ) { int total_charges = player_with_inv->charges_of( type ); - comp_selection sel( use_from_player, *it ); + comp_selection sel( usage_from::player, *it ); available_tools.emplace_back( sel, total_charges, ideal ); } } if( map_inv.has_charges( type, count ) ) { int total_charges = map_inv.charges_of( type ); - comp_selection sel( use_from_map, *it ); + comp_selection sel( usage_from::map, *it ); available_tools.emplace_back( sel, total_charges, ideal ); } } else if( ( player_with_inv && player_with_inv->has_amount( type, 1 ) ) || map_inv.has_tools( type, 1 ) ) { - comp_selection sel( use_from_none, *it ); + comp_selection sel( usage_from::none, *it ); available_tools.emplace_back( sel, 0, 0 ); } } std::sort( available_tools.begin(), available_tools.end(), []( const avail_tool_comp & lhs, const avail_tool_comp & rhs ) { - if( lhs.comp.use_from == use_from_none && rhs.comp.use_from != use_from_none ) { + if( lhs.comp.use_from == usage_from::none && rhs.comp.use_from != usage_from::none ) { return true; } - if( rhs.comp.use_from == use_from_none ) { + if( rhs.comp.use_from == usage_from::none ) { return false; } if( lhs.charges >= lhs.ideal && rhs.charges < rhs.ideal ) { @@ -1715,9 +1715,9 @@ query_tool_selection( const std::vector &available_tools, if( available_tools.empty() ) { // This SHOULD only happen if cooking with a fire, // and the fire goes out. - return comp_selection( use_from_none ); + return comp_selection( usage_from::none ); } - if( available_tools.front().comp.use_from == use_from_none ) { + if( available_tools.front().comp.use_from == usage_from::none ) { // Default to using a tool that doesn't require charges return available_tools.front().comp; } @@ -1727,7 +1727,7 @@ query_tool_selection( const std::vector &available_tools, if( is_npc ) { auto iter = std::find_if( available_tools.begin(), available_tools.end(), []( const avail_tool_comp & tool ) { - return tool.comp.use_from == use_from_player; + return tool.comp.use_from == usage_from::player; } ); if( iter != available_tools.end() ) { return iter->comp; @@ -1741,7 +1741,7 @@ query_tool_selection( const std::vector &available_tools, for( const avail_tool_comp &tool : available_tools ) { const itype_id &comp_type = tool.comp.comp.type; if( tool.ideal > 1 ) { - const char *format = tool.comp.use_from == use_from_map + const char *format = tool.comp.use_from == usage_from::map ? _( "%s (%d/%d charges nearby)" ) : _( "%s (%d/%d charges on person)" ); std::string str = string_format( format, @@ -1749,7 +1749,7 @@ query_tool_selection( const std::vector &available_tools, tool.charges ); tmenu.addentry( str ); } else { - std::string str = tool.comp.use_from == use_from_map + std::string str = tool.comp.use_from == usage_from::map ? item::nname( comp_type ) + _( " (nearby)" ) : item::nname( comp_type ); tmenu.addentry( str ); @@ -1763,7 +1763,7 @@ query_tool_selection( const std::vector &available_tools, tmenu.query(); if( tmenu.ret < 0 || static_cast( tmenu.ret ) >= available_tools.size() ) { - return comp_selection( cancel ); + return comp_selection( usage_from::cancel ); } return available_tools.at( static_cast( tmenu.ret ) ).comp; @@ -1837,7 +1837,7 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft if( tool_sel.comp.count > 0 ) { const int count = calc_charges( tool_sel.comp.count ); switch( tool_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !has_charges( type, count ) ) { add_msg_player_or_npc( _( "You have insufficient %s charges and can't continue crafting" ), @@ -1847,7 +1847,7 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft return false; } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count ) ) { add_msg_player_or_npc( _( "You have insufficient %s charges and can't continue crafting" ), @@ -1857,10 +1857,10 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft return false; } break; - case use_from_both: - case use_from_none: - case cancel: - case num_usages: + case usage_from::both: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else if( !has_amount( type, 1 ) && !map_inv.has_tools( type, 1 ) ) { @@ -1896,14 +1896,14 @@ void player::consume_tools( map &m, const comp_selection &tool, int b } int quantity = tool.comp.count * batch; - if( tool.use_from & use_from_player ) { + if( tool.use_from & usage_from::player ) { use_charges( tool.comp.type, quantity ); } - if( tool.use_from & use_from_map ) { + if( tool.use_from & usage_from::map ) { m.use_charges( origin, radius, tool.comp.type, quantity, return_true ); } - // else, use_from_none (or cancel), so we don't use up any tools; + // else, usage_from::none (or usage_from::cancel), so we don't use up any tools; } /* This call is in-efficient when doing it for multiple items with the same map inventory. diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index ad38959267e3..740e57ca891c 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -29,6 +29,7 @@ #include "json.h" #include "mod_manager.h" #include "output.h" +#include "player.h" #include "point.h" #include "recipe.h" #include "recipe_dictionary.h" @@ -133,35 +134,202 @@ void reset_recipe_categories() craft_subcat_list.clear(); } -static int print_items( const recipe &r, const catacurses::window &w, point pos, - nc_color col, int batch ) +namespace { - if( !r.has_byproducts() ) { - return 0; +struct availability { + explicit availability( const recipe *r, int batch_size, bool known ) { + this->known = known; + const inventory &inv = get_avatar().crafting_inventory(); + auto all_items_filter = r->get_component_filter( recipe_filter_flags::none ); + auto no_rotten_filter = r->get_component_filter( recipe_filter_flags::no_rotten ); + const deduped_requirement_data &req = r->deduped_requirements(); + could_craft_if_knew = req.can_make_with_inventory( + inv, all_items_filter, batch_size, cost_adjustment::start_only ); + can_craft = known && could_craft_if_knew; + can_craft_non_rotten = req.can_make_with_inventory( + inv, no_rotten_filter, batch_size, cost_adjustment::start_only ); + const requirement_data &simple_req = r->simple_requirements(); + apparently_craftable = simple_req.can_make_with_inventory( + inv, all_items_filter, batch_size, cost_adjustment::start_only ); + has_all_skills = r->skill_used.is_null() || + get_player_character().get_skill_level( r->skill_used ) >= r->difficulty; + for( const std::pair &e : r->required_skills ) { + if( get_player_character().get_skill_level( e.first ) < e.second ) { + has_all_skills = false; + break; + } + } + } + bool can_craft; + bool can_craft_non_rotten; + bool could_craft_if_knew; + bool apparently_craftable; + bool has_all_skills; + bool known; + + nc_color selected_color() const { + return can_craft + ? ( can_craft_non_rotten && has_all_skills ? h_white : h_brown ) + : ( could_craft_if_knew && has_all_skills ? h_yellow : h_dark_gray ); + } + + nc_color color( bool ignore_missing_skills = false ) const { + return can_craft + ? ( ( can_craft_non_rotten && has_all_skills ) || ignore_missing_skills ? c_white : c_brown ) + : ( ( could_craft_if_knew && has_all_skills ) || ignore_missing_skills ? c_yellow : c_dark_gray ); } - const int oldy = pos.y; +}; +} // namespace + +static std::vector recipe_info( + const recipe &recp, + const availability &avail, + player &u, + const std::string qry_comps, + const int batch_size, + const int fold_width, + const nc_color &color ) +{ + std::ostringstream oss; - mvwprintz( w, point( pos.x, pos.y++ ), col, _( "Byproducts:" ) ); - for( const auto &bp : r.byproducts ) { - const itype *t = &*bp.first; - int amount = bp.second * batch; - std::string desc; - if( t->count_by_charges() ) { - amount *= t->charges_default(); - desc = string_format( "> %s (%d)", t->nname( 1 ), amount ); - } else { - desc = string_format( "> %d %s", amount, - t->nname( static_cast( amount ) ) ); + oss << string_format( _( "Primary skill: %s\n" ), + recp.primary_skill_string( &u, false ) ); + + oss << string_format( _( "Other skills: %s\n" ), + recp.required_skills_string( &u, false, false ) ); + + + const int expected_turns = u.expected_time_to_craft( recp, batch_size ) + / to_moves( 1_turns ); + oss << string_format( _( "Time to complete: %s\n" ), + to_string( time_duration::from_turns( expected_turns ) ) ); + + oss << string_format( _( "Batch time savings: %s\n" ), + recp.batch_savings_string() ); + + const int makes = recp.makes_amount(); + if( makes > 1 ) { + oss << string_format( _( "Recipe makes: %d\n" ), makes ); + } + + oss << string_format( _( "Craftable in the dark? %s\n" ), + recp.has_flag( flag_BLIND_EASY ) ? _( "Easy" ) : + recp.has_flag( flag_BLIND_HARD ) ? _( "Hard" ) : + _( "Impossible" ) ); + + std::string nearby_string; + const inventory &crafting_inv = u.crafting_inventory(); + const int nearby_amount = crafting_inv.count_item( recp.result() ); + if( nearby_amount == 0 ) { + nearby_string = "0"; + } else if( nearby_amount > 9000 ) { + // at some point you get too many to count at a glance and just know you have a lot + nearby_string = _( "It's Over 9000!!!" ); + } else { + nearby_string = string_format( "%d", nearby_amount ); + } + oss << string_format( _( "Nearby: %s\n" ), nearby_string ); + + const bool can_craft_this = avail.can_craft; + if( can_craft_this && !avail.can_craft_non_rotten ) { + oss << _( "Will use rotten ingredients\n" ); + } + const bool too_complex = recp.deduped_requirements().is_too_complex(); + if( can_craft_this && too_complex ) { + oss << _( "Due to the complex overlapping requirements, this " + "recipe may appear to be craftable " + "when it is not.\n" ); + } + if( !can_craft_this && avail.apparently_craftable && avail.known ) { + oss << _( "Cannot be crafted because the same item is needed " + "for multiple components\n" ); + } + if( !avail.known ) { + oss << _( "Not known\n" ); + } + + if( recp.has_byproducts() ) { + oss << _( "Byproducts:\n" ); + for( const std::pair &bp : recp.byproducts ) { + const itype *t = &*bp.first; + int amount = bp.second * batch_size; + if( t->count_by_charges() ) { + amount *= t->charges_default(); + oss << string_format( "> %s (%d)\n", t->nname( 1 ), amount ); + } else { + oss << string_format( "> %d %s\n", amount, + t->nname( static_cast( amount ) ) ); + } } - mvwprintz( w, point( pos.x, pos.y++ ), col, desc ); } - return pos.y - oldy; + std::vector result = foldstring( oss.str(), fold_width ); + + bool show_unavailable = false; + const requirement_data &req = recp.simple_requirements(); + const std::vector tools = req.get_folded_tools_list( + fold_width, color, crafting_inv, batch_size ); + const std::vector comps = req.get_folded_components_list( + fold_width, color, crafting_inv, recp.get_component_filter(), batch_size, qry_comps ); + result.insert( result.end(), tools.begin(), tools.end() ); + result.insert( result.end(), comps.begin(), comps.end() ); + + oss = std::ostringstream(); + if( !u.knows_recipe( &recp ) ) { + oss << _( "Recipe not memorized yet\n" ); + const std::set books_with_recipe = show_unavailable + ? crafting::get_books_for_recipe( &recp ) + : crafting::get_books_for_recipe( u, crafting_inv, &recp ); + const std::string enumerated_books = + enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), + []( const itype_id & type_id ) { + return colorize( item::nname( type_id ), c_cyan ); + } ); + oss << string_format( _( "Written in: %s\n" ), enumerated_books ); + } + std::vector tmp = foldstring( oss.str(), fold_width ); + result.insert( result.end(), tmp.begin(), tmp.end() ); + + return result; } -const recipe *select_crafting_recipe( int &batch_size ) +const recipe *select_crafting_recipe( int &batch_size_out ) { + struct { + const recipe *recp = nullptr; + std::string qry_comps; + int batch_size; + int fold_width; + std::vector text; + } recipe_info_cache; + int recipe_info_scroll = 0; + + const auto cached_recipe_info = + [&]( + const recipe & recp, + const availability & avail, + player & u, + const std::string qry_comps, + const int batch_size, + const int fold_width, + const nc_color & color + ) -> const std::vector & { // *NOPAD* + if( recipe_info_cache.recp != &recp + || recipe_info_cache.qry_comps != qry_comps + || recipe_info_cache.batch_size != batch_size + || recipe_info_cache.fold_width != fold_width ) + { + recipe_info_cache.recp = &recp; + recipe_info_cache.qry_comps = qry_comps; + recipe_info_cache.batch_size = batch_size; + recipe_info_cache.fold_width = fold_width; + recipe_info_cache.text = recipe_info( + recp, avail, u, qry_comps, batch_size, fold_width, color ); + } + return recipe_info_cache.text; + }; + struct { const recipe *last_recipe = nullptr; detached_ptr dummy; @@ -198,12 +366,36 @@ const recipe *select_crafting_recipe( int &batch_size ) int dataLines = 0; int dataHalfLines = 0; int dataHeight = 0; - int componentPrintHeight = 0; int item_info_width = 0; + + input_context ctxt( "CRAFTING" ); + ctxt.register_cardinal(); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "SCROLL_RECIPE_INFO_UP" ); + ctxt.register_action( "SCROLL_RECIPE_INFO_DOWN" ); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "SCROLL_ITEM_INFO_UP" ); + ctxt.register_action( "SCROLL_ITEM_INFO_DOWN" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "FILTER" ); + ctxt.register_action( "RESET_FILTER" ); + ctxt.register_action( "TOGGLE_FAVORITE" ); + ctxt.register_action( "HELP_RECIPE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "CYCLE_BATCH" ); + ctxt.register_action( "RELATED_RECIPES" ); + ctxt.register_action( "HIDE_SHOW_RECIPE" ); + ctxt.register_action( "TOGGLE_UNAVAILABLE" ); + catacurses::window w_head; catacurses::window w_subhead; catacurses::window w_data; catacurses::window w_iteminfo; + std::vector keybinding_tips; + int keybinding_x = 0; ui_adaptor ui; ui.on_screen_resize( [&]( ui_adaptor & ui ) { const int freeWidth = TERMX - FULL_SCREEN_WIDTH; @@ -212,11 +404,39 @@ const recipe *select_crafting_recipe( int &batch_size ) width = isWide ? ( freeWidth > FULL_SCREEN_WIDTH ? FULL_SCREEN_WIDTH * 2 : TERMX ) : FULL_SCREEN_WIDTH; const int wStart = ( TERMX - width ) / 2; - const int tailHeight = isWide ? 3 : 4; + + // Keybinding tips + static const translation inline_fmt = to_translation( + //~ %1$s: action description text before key, + //~ %2$s: key description, + //~ %3$s: action description text after key. + "keybinding", "%1$s[%2$s]%3$s" ); + static const translation separate_fmt = to_translation( + //~ %1$s: key description, + //~ %2$s: action description. + "keybinding", "[%1$s]%2$s" ); + std::vector act_descs; + const auto add_action_desc = [&]( const std::string & act, const std::string & txt ) { + act_descs.emplace_back( ctxt.get_desc( act, txt, input_context::allow_all_keys, + inline_fmt, separate_fmt ) ); + }; + add_action_desc( "CONFIRM", pgettext( "crafting gui", "Craft" ) ); + add_action_desc( "HELP_RECIPE", pgettext( "crafting gui", "Describe" ) ); + add_action_desc( "FILTER", pgettext( "crafting gui", "Filter" ) ); + add_action_desc( "RESET_FILTER", pgettext( "crafting gui", "Reset filter" ) ); + add_action_desc( "HIDE_SHOW_RECIPE", pgettext( "crafting gui", "Show/hide" ) ); + add_action_desc( "RELATED_RECIPES", pgettext( "crafting gui", "Related" ) ); + add_action_desc( "TOGGLE_FAVORITE", pgettext( "crafting gui", "Favorite" ) ); + add_action_desc( "CYCLE_BATCH", pgettext( "crafting gui", "Batch" ) ); + add_action_desc( "HELP_KEYBINDINGS", pgettext( "crafting gui", "Keybindings" ) ); + keybinding_x = isWide ? 5 : 2; + keybinding_tips = foldstring( enumerate_as_string( act_descs, enumeration_conjunction::none ), + width - keybinding_x * 2 ); + + const int tailHeight = keybinding_tips.size() + 2; dataLines = TERMY - ( headHeight + subHeadHeight ) - tailHeight; dataHalfLines = dataLines / 2; dataHeight = TERMY - ( headHeight + subHeadHeight ); - componentPrintHeight = dataHeight - tailHeight - 1; w_head = catacurses::newwin( headHeight, width, point( wStart, 0 ) ); w_subhead = catacurses::newwin( subHeadHeight, width, point( wStart, 3 ) ); @@ -224,8 +444,8 @@ const recipe *select_crafting_recipe( int &batch_size ) headHeight + subHeadHeight ) ); if( isWide ) { - item_info_width = width - FULL_SCREEN_WIDTH - 2; - const int item_info_height = dataHeight - 3; + item_info_width = width - FULL_SCREEN_WIDTH - 1; + const int item_info_height = dataHeight - tailHeight; const point item_info( wStart + width - item_info_width, headHeight + subHeadHeight ); w_iteminfo = catacurses::newwin( item_info_height, item_info_width, @@ -242,48 +462,7 @@ const recipe *select_crafting_recipe( int &batch_size ) list_circularizer tab( craft_cat_list ); list_circularizer subtab( craft_subcat_list[tab.cur()] ); std::vector current; - - struct availability { - availability( const recipe *r, int batch_size, bool known ) { - this->known = known; - const inventory &inv = get_avatar().crafting_inventory(); - auto all_items_filter = r->get_component_filter( recipe_filter_flags::none ); - auto no_rotten_filter = r->get_component_filter( recipe_filter_flags::no_rotten ); - const deduped_requirement_data &req = r->deduped_requirements(); - could_craft_if_knew = req.can_make_with_inventory( - inv, all_items_filter, batch_size, cost_adjustment::start_only ); - can_craft = known && could_craft_if_knew; - can_craft_non_rotten = req.can_make_with_inventory( - inv, no_rotten_filter, batch_size, cost_adjustment::start_only ); - const requirement_data &simple_req = r->simple_requirements(); - apparently_craftable = simple_req.can_make_with_inventory( - inv, all_items_filter, batch_size, cost_adjustment::start_only ); - } - bool can_craft; - bool can_craft_non_rotten; - bool could_craft_if_knew; - bool apparently_craftable; - bool known; - - nc_color selected_color() const { - return can_craft - ? ( can_craft_non_rotten ? h_white : h_brown ) - : ( could_craft_if_knew ? h_yellow : h_dark_gray ); - } - - nc_color color() const { - return can_craft - ? ( can_craft_non_rotten ? c_white : c_brown ) - : ( could_craft_if_knew ? c_yellow : c_dark_gray ); - } - }; - std::vector available; - //preserves component color printout between mode rotations - nc_color rotated_color = c_white; - int previous_item_line = -1; - std::string previous_tab; - std::string previous_subtab; int line = 0; bool recalc = true; bool keepline = false; @@ -294,28 +473,8 @@ const recipe *select_crafting_recipe( int &batch_size ) size_t num_hidden = 0; int num_recipe = 0; int batch_line = 0; - int display_mode = 0; const recipe *chosen = nullptr; - input_context ctxt( "CRAFTING" ); - ctxt.register_cardinal(); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "CYCLE_MODE" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - ctxt.register_action( "PREV_TAB" ); - ctxt.register_action( "NEXT_TAB" ); - ctxt.register_action( "FILTER" ); - ctxt.register_action( "RESET_FILTER" ); - ctxt.register_action( "TOGGLE_FAVORITE" ); - ctxt.register_action( "HELP_RECIPE" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "CYCLE_BATCH" ); - ctxt.register_action( "RELATED_RECIPES" ); - ctxt.register_action( "HIDE_SHOW_RECIPE" ); - ctxt.register_action( "TOGGLE_UNAVAILABLE" ); - const inventory &crafting_inv = u.crafting_inventory(); const std::vector helpers = character_funcs::get_crafting_helpers( u ); std::string filterstring; @@ -330,7 +489,7 @@ const recipe *select_crafting_recipe( int &batch_size ) const auto &all_recipes = recipe_subset( {}, all_recipes_flat ); int recipe_scroll_window_min = 0; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { const TAB_MODE m = ( batch ) ? BATCH : ( filterstring.empty() ) ? NORMAL : FILTERED; draw_recipe_tabs( w_head, tab.cur(), m ); const auto &shown_recipes = show_unavailable ? all_recipes : available_recipes; @@ -343,39 +502,12 @@ const recipe *select_crafting_recipe( int &batch_size ) // Clear the screen of recipe data, and draw it anew werase( w_data ); - if( isWide ) { - if( !filterstring.empty() ) { - fold_and_print( w_data, point( 5, dataLines + 1 ), 0, c_white, - _( "Press [ENTER] to attempt to craft object. " - "D[e]scribe, [F]ind, " - "[R]eset, [m]ode, " - "[s]how/hide, Re[L]ated, " - "[*]Favorite, %s, [u]navailable, " - "[?]keybindings" ), - ( batch ) ? _( "cancel " - "[b]atch" ) : _( "[b]atch" ) ); - } else { - fold_and_print( w_data, point( 5, dataLines + 1 ), 0, c_white, - _( "Press [ENTER] to attempt to craft object. " - "D[e]scribe, [F]ind, " - "[m]ode, [s]how/hide, " - "Re[L]ated, [*]Favorite, " - "%s, [u]navailable, " - "[?]keybindings" ), - ( batch ) ? _( "cancel " - "[b]atch" ) : _( "[b]atch" ) ); - } - } else { - if( !filterstring.empty() ) { - mvwprintz( w_data, point( 2, dataLines + 2 ), c_white, - _( "[F]ind, [R]eset, [m]ode, [s]how/hide, Re[L]ated, [*]Fav, [b]atch." ) ); - } else { - mvwprintz( w_data, point( 2, dataLines + 2 ), c_white, - _( "[F]ind, [m]ode, [s]how/hide, Re[L]ated, [*]Fav, [b]atch." ) ); - } - mvwprintz( w_data, point( 2, dataLines + 1 ), c_white, - _( "Press [ENTER] to attempt to craft object. D[e]scribe, [?]keybindings," ) ); + for( size_t i = 0; i < keybinding_tips.size(); ++i ) { + nc_color dummy = c_white; + print_colored_text( w_data, point( keybinding_x, dataLines + 1 + i ), + dummy, c_white, keybinding_tips[i] ); } + // Draw borders for( int i = 1; i < width - 1; ++i ) { // - mvwputch( w_data, point( i, dataHeight - 1 ), BORDER_COLOR, LINE_OXOX ); @@ -387,7 +519,7 @@ const recipe *select_crafting_recipe( int &batch_size ) mvwputch( w_data, point( 0, dataHeight - 1 ), BORDER_COLOR, LINE_XXOO ); // |_ mvwputch( w_data, point( width - 1, dataHeight - 1 ), BORDER_COLOR, LINE_XOOX ); // _| - std::optional cursor_pos; + const int max_recipe_name_width = 27; int recmax = current.size(); // Draw recipes with scroll list @@ -404,187 +536,50 @@ const recipe *select_crafting_recipe( int &batch_size ) const bool highlight = i == line; const nc_color col = highlight ? available[i].selected_color() : available[i].color(); if( highlight ) { - cursor_pos = print_from; + ui.set_cursor( w_data, print_from ); } - mvwprintz( w_data, print_from, col, trim_by_length( tmp_name, 27 ) ); + mvwprintz( w_data, print_from, col, trim_by_length( tmp_name, max_recipe_name_width ) ); } - const int count = batch ? line + 1 : 1; // batch size + const int batch_size = batch ? line + 1 : 1; if( !current.empty() ) { - int pane = FULL_SCREEN_WIDTH - 30 - 1; - nc_color col = available[line].color(); - - const auto &req = current[line]->simple_requirements(); + const recipe &recp = *current[line]; - draw_can_craft_indicator( w_head, *current[line] ); + draw_can_craft_indicator( w_head, recp ); wnoutrefresh( w_head ); - int ypos = 0; - - auto qry = trim( filterstring ); + const availability &avail = available[line]; + // border + padding + name + padding + const int xpos = 1 + 1 + max_recipe_name_width + 3; + const int fold_width = FULL_SCREEN_WIDTH - xpos - 2; + const nc_color color = avail.color( true ); + const std::string qry = trim( filterstring ); std::string qry_comps; if( qry.compare( 0, 2, "c:" ) == 0 ) { qry_comps = qry.substr( 2 ); } - std::vector component_print_buffer; - auto tools = req.get_folded_tools_list( pane, col, crafting_inv, count ); - auto comps = req.get_folded_components_list( pane, col, crafting_inv, - current[line]->get_component_filter(), count, qry_comps ); - component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() ); - component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() ); - - if( !u.knows_recipe( current[line] ) ) { - component_print_buffer.emplace_back( _( "Recipe not memorized yet" ) ); - auto books_with_recipe = show_unavailable - ? crafting::get_books_for_recipe( current[line] ) - : crafting::get_books_for_recipe( u, crafting_inv, current[line] ); - std::string enumerated_books = - enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), - []( itype_id type_id ) { - return colorize( item::nname( type_id ), c_cyan ); - } ); - const std::string text = string_format( _( "Written in: %s" ), enumerated_books ); - std::vector folded_lines = foldstring( text, pane ); - component_print_buffer.insert( - component_print_buffer.end(), folded_lines.begin(), folded_lines.end() ); - } - - //handle positioning of component list if it needed to be scrolled - int componentPrintOffset = 0; - if( display_mode > 1 ) { - componentPrintOffset = ( display_mode - 1 ) * componentPrintHeight; - } - if( component_print_buffer.size() < static_cast( componentPrintOffset ) ) { - componentPrintOffset = 0; - if( previous_tab != tab.cur() || previous_subtab != subtab.cur() || previous_item_line != line ) { - display_mode = 1; - } else { - display_mode = 0; - } - } - - //only used to preserve mode position on components when - //moving to another item and the view is already scrolled - previous_tab = tab.cur(); - previous_subtab = subtab.cur(); - previous_item_line = line; - const int xpos = 30; - - if( display_mode == 0 ) { - if( display_mod_source ) { - const std::string source = enumerate_as_string( current[line]->src.begin(), - current[line]->src.end(), []( const std::pair &content_source ) { - return string_format( "'%s'", content_source.second->name() ); - }, enumeration_conjunction::arrow ); - - const std::string text = string_format( _( "Origin: %s" ), colorize( source, c_light_blue ) ); - print_colored_text( w_data, point( xpos, ypos++ ), col, col, text ); - } - if( display_object_ids ) { - const std::string ident = string_format( "[%s]", current[line]->ident() ); - const std::string text = string_format( _( "ID: %s" ), colorize( ident, c_light_blue ) ); - print_colored_text( w_data, point( xpos, ypos++ ), col, col, text ); - } + const std::vector &info = cached_recipe_info( + recp, avail, u, qry_comps, batch_size, fold_width, color ); - print_colored_text( - w_data, point( xpos, ypos++ ), col, col, - string_format( _( "Primary skill: %s" ), - current[line]->primary_skill_string( &u, false ) ) ); - - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Other skills: %s" ), - current[line]->required_skills_string( &u, false, false ) ); - - const int expected_turns = u.expected_time_to_craft( *current[line], - count ) / to_moves( 1_turns ); - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Time to complete: %s" ), - to_string( time_duration::from_turns( expected_turns ) ) ); - - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Batch time savings: %s" ), - current[line]->batch_savings_string() ); - - const int makes = current[line]->makes_amount(); - if( makes > 1 ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "One batch makes: %d" ), - makes ); - } - - print_colored_text( - w_data, point( xpos, ypos++ ), col, col, - string_format( _( "Dark craftable? %s" ), - current[line]->has_flag( flag_BLIND_EASY ) ? _( "Easy" ) : - current[line]->has_flag( flag_BLIND_HARD ) ? _( "Hard" ) : - _( "Impossible" ) ) ); - - std::string nearby_string; - const int nearby_amount = crafting_inv.count_item( current[line]->result() ); - - if( nearby_amount == 0 ) { - nearby_string = "0"; - } else if( nearby_amount > 9000 ) { - // at some point you get too many to count at a glance and just know you have a lot - nearby_string = _( "It's Over 9000!!!" ); - } else { - nearby_string = string_format( "%d", nearby_amount ); - } - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Nearby: %s" ), nearby_string ); - - if( available[line].can_craft && !available[line].can_craft_non_rotten ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Will use rotten ingredients" ) ); - } - const bool too_complex = current[line]->deduped_requirements().is_too_complex(); - if( available[line].can_craft && too_complex ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Due to the complex overlapping requirements, this " - "recipe may appear to be craftable " - "when it is not." ) ); - } - if( !available[line].can_craft && - available[line].apparently_craftable && - available[line].known ) { - ypos += fold_and_print( - w_data, point( xpos, ypos ), pane, col, - _( "Cannot be crafted because the same item is needed " - "for multiple components" ) ); - } - if( !available[line].known ) { - ypos += fold_and_print( - w_data, point( xpos, ypos ), pane, col, - _( "Not known" ) ); - } - ypos += print_items( *current[line], w_data, point( xpos, ypos ), col, batch ? line + 1 : 1 ); + const int total_lines = info.size(); + if( recipe_info_scroll < 0 ) { + recipe_info_scroll = 0; + } else if( recipe_info_scroll + dataLines > total_lines ) { + recipe_info_scroll = std::max( 0, total_lines - dataLines ); } - - //color needs to be preserved in case part of the previous page was cut off - nc_color stored_color = col; - if( display_mode > 1 ) { - stored_color = rotated_color; - } else { - rotated_color = col; - } - int components_printed = 0; - for( size_t i = static_cast( componentPrintOffset ); - i < component_print_buffer.size(); i++ ) { - if( ypos >= componentPrintHeight ) { - break; - } - - components_printed++; - print_colored_text( w_data, point( xpos, ypos++ ), stored_color, col, component_print_buffer[i] ); + for( int i = recipe_info_scroll; + i < std::min( recipe_info_scroll + dataLines, total_lines ); + ++i ) { + nc_color dummy = color; + print_colored_text( w_data, point( xpos, i - recipe_info_scroll ), + dummy, color, info[i] ); } - if( ypos >= componentPrintHeight && - component_print_buffer.size() > static_cast( components_printed ) ) { - mvwprintz( w_data, point( xpos, ypos++ ), col, - _( "v (%s for more)" ), - ctxt.press_x( "CYCLE_MODE" ) ); - rotated_color = stored_color; + if( total_lines > dataLines ) { + scrollbar().offset_x( xpos + fold_width + 1 ).content_size( total_lines ) + .viewport_pos( recipe_info_scroll ).viewport_size( dataLines ) + .apply( w_data ); } } @@ -592,19 +587,13 @@ const recipe *select_crafting_recipe( int &batch_size ) wnoutrefresh( w_data ); if( isWide && !current.empty() ) { - item_info_data data = item_info_data_from_recipe( current[line], count, item_info_scroll ); + item_info_data data = item_info_data_from_recipe( current[line], batch_size, item_info_scroll ); data.without_getch = true; data.without_border = true; data.scrollbar_left = false; data.use_full_win = true; draw_item_info( w_iteminfo, data ); } - - if( cursor_pos ) { - // place the cursor at the selected item name as expected by screen readers - wmove( w_data, cursor_pos.value() ); - wnoutrefresh( w_data ); - } } ); do { @@ -618,10 +607,6 @@ const recipe *select_crafting_recipe( int &batch_size ) keepline = false; } - if( display_mode > 1 ) { - display_mode = 1; - } - show_hidden = false; available.clear(); @@ -764,11 +749,10 @@ const recipe *select_crafting_recipe( int &batch_size ) ui_manager::redraw(); const std::string action = ctxt.handle_input(); - if( action == "CYCLE_MODE" ) { - display_mode = display_mode + 1; - if( display_mode <= 0 ) { - display_mode = 0; - } + if( action == "SCROLL_RECIPE_INFO_UP" ) { + recipe_info_scroll -= dataLines; + } else if( action == "SCROLL_RECIPE_INFO_DOWN" ) { + recipe_info_scroll += dataLines; } else if( action == "LEFT" ) { std::string start = subtab.cur(); do { @@ -809,7 +793,7 @@ const recipe *select_crafting_recipe( int &batch_size ) // popup is already inside check } else { chosen = current[line]; - batch_size = ( batch ) ? line + 1 : 1; + batch_size_out = ( batch ) ? line + 1 : 1; done = true; } } else if( action == "HELP_RECIPE" ) { @@ -876,7 +860,6 @@ const recipe *select_crafting_recipe( int &batch_size ) description += _( "\nUse up/down arrow to go through your search history." ); - description += "\n\n\n"; string_input_popup() .title( _( "Search:" ) ) @@ -947,6 +930,10 @@ const recipe *select_crafting_recipe( int &batch_size ) } recalc = true; + } else if( action == "HELP_KEYBINDINGS" ) { + // Regenerate keybinding tips + ui.mark_resize(); + } else if( action == "TOGGLE_UNAVAILABLE" ) { show_unavailable = !show_unavailable; diff --git a/src/crafting_gui.h b/src/crafting_gui.h index a7e087b85e9a..b77e5416c747 100644 --- a/src/crafting_gui.h +++ b/src/crafting_gui.h @@ -5,7 +5,7 @@ class recipe; class JsonObject; -const recipe *select_crafting_recipe( int &batch_size ); +const recipe *select_crafting_recipe( int &batch_size_out ); void load_recipe_category( const JsonObject &jsobj ); void reset_recipe_categories(); diff --git a/src/creature.cpp b/src/creature.cpp index 3cc4d0ed0b77..ad1af01bb4f6 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -82,9 +82,12 @@ static const efftype_id effect_stunned( "stunned" ); static const efftype_id effect_tied( "tied" ); static const efftype_id effect_zapped( "zapped" ); -const std::map Creature::size_map = { - {"TINY", MS_TINY}, {"SMALL", MS_SMALL}, {"MEDIUM", MS_MEDIUM}, - {"LARGE", MS_LARGE}, {"HUGE", MS_HUGE} +const std::map Creature::size_map = { + {"TINY", creature_size::tiny}, + {"SMALL", creature_size::small}, + {"MEDIUM", creature_size::medium}, + {"LARGE", creature_size::large}, + {"HUGE", creature_size::huge} }; const std::set Creature::cmat_flesh{ @@ -305,18 +308,18 @@ bool Creature::sees( const Creature &critter ) const } float size_modifier = 1.0; switch( ch->get_size() ) { - case MS_TINY: + case creature_size::tiny: size_modifier = 2.0; break; - case MS_SMALL: + case creature_size::small: size_modifier = 1.4; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: size_modifier = 0.6; break; - case MS_HUGE: + case creature_size::huge: size_modifier = 0.15; break; default: @@ -538,15 +541,15 @@ Creature *Creature::auto_find_hostile_target( int range, int &boo_hoo, int area int Creature::size_melee_penalty() const { switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: return 30; - case MS_SMALL: + case creature_size::small: return 15; - case MS_MEDIUM: + case creature_size::medium: return 0; - case MS_LARGE: + case creature_size::large: return -10; - case MS_HUGE: + case creature_size::huge: return -20; default: break; @@ -726,23 +729,23 @@ dealt_damage_instance hit_with_aoe( Creature &target, Creature *source, const da namespace { -auto get_stun_srength( const projectile &proj, m_size size ) -> int +auto get_stun_srength( const projectile &proj, creature_size size ) -> int { const int stun_strength = proj.has_effect( ammo_effect_BEANBAG ) ? 4 : proj.has_effect( ammo_effect_LARGE_BEANBAG ) ? 16 : 0; switch( size ) { - case MS_TINY: + case creature_size::tiny: return stun_strength * 4; - case MS_SMALL: + case creature_size::small: return stun_strength * 2; - case MS_MEDIUM: + case creature_size::medium: default: return stun_strength; - case MS_LARGE: + case creature_size::large: return stun_strength / 2; - case MS_HUGE: + case creature_size::huge: return stun_strength / 4; } } @@ -1953,19 +1956,19 @@ units::mass Creature::weight_capacity() const { units::mass base_carry = 13_kilogram; switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: base_carry /= 4; break; - case MS_SMALL: + case creature_size::small: base_carry /= 2; break; - case MS_MEDIUM: + case creature_size::medium: default: break; - case MS_LARGE: + case creature_size::large: base_carry *= 2; break; - case MS_HUGE: + case creature_size::huge: base_carry *= 4; break; } @@ -2155,19 +2158,19 @@ void Creature::describe_infrared( std::vector &buf ) const { std::string size_str; switch( get_size() ) { - case m_size::MS_TINY: + case creature_size::tiny: size_str = pgettext( "infrared size", "tiny" ); break; - case m_size::MS_SMALL: + case creature_size::small: size_str = pgettext( "infrared size", "small" ); break; - case m_size::MS_MEDIUM: + case creature_size::medium: size_str = pgettext( "infrared size", "medium" ); break; - case m_size::MS_LARGE: + case creature_size::large: size_str = pgettext( "infrared size", "large" ); break; - case m_size::MS_HUGE: + case creature_size::huge: size_str = pgettext( "infrared size", "huge" ); break; default: diff --git a/src/creature.h b/src/creature.h index 0e69a7597b57..0b46c7183fb6 100644 --- a/src/creature.h +++ b/src/creature.h @@ -55,6 +55,132 @@ struct trap; template struct enum_traits; +using I = std::underlying_type_t; +constexpr I operator+( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) + static_cast( rhs ); +} + +constexpr I operator+( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) + rhs; +} + +constexpr I operator+( const I lhs, const creature_size rhs ) +{ + return lhs + static_cast( rhs ); +} + +constexpr I operator-( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) - static_cast( rhs ); +} + +constexpr I operator-( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) - rhs; +} + +constexpr I operator-( const I lhs, const creature_size rhs ) +{ + return lhs - static_cast( rhs ); +} + +constexpr I operator*( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) * static_cast( rhs ); +} + +constexpr I operator*( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) * rhs; +} + +constexpr I operator*( const I lhs, const creature_size rhs ) +{ + return lhs * static_cast( rhs ); +} + +constexpr I operator/( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) / static_cast( rhs ); +} + +constexpr I operator/( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) / rhs; +} + +constexpr bool operator<=( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) <= static_cast( rhs ); +} + +constexpr bool operator<=( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) <= rhs; +} + +constexpr bool operator<=( const I lhs, const creature_size rhs ) +{ + return lhs <= static_cast( rhs ); +} + +constexpr bool operator<( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) < static_cast( rhs ); +} + +constexpr bool operator<( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) < rhs; +} + +constexpr bool operator<( const I lhs, const creature_size rhs ) +{ + return lhs < static_cast( rhs ); +} + +constexpr bool operator>=( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) >= static_cast( rhs ); +} + +constexpr bool operator>=( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) >= rhs; +} + +constexpr bool operator>=( const I lhs, const creature_size rhs ) +{ + return lhs >= static_cast( rhs ); +} + +constexpr bool operator>( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) > static_cast( rhs ); +} + +constexpr bool operator>( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) > rhs; +} + +constexpr bool operator>( const I lhs, const creature_size rhs ) +{ + return lhs > static_cast( rhs ); +} + +constexpr bool operator==( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) == rhs; +} + +constexpr bool operator==( const I lhs, const creature_size rhs ) +{ + return lhs == static_cast( rhs ); +} + enum FacingDirection { FD_NONE = 0, FD_LEFT = 1, @@ -66,7 +192,7 @@ class Creature public: virtual ~Creature(); - static const std::map size_map; + static const std::map size_map; // Like disp_name, but without any "the" virtual std::string get_name() const = 0; @@ -446,7 +572,7 @@ class Creature virtual float get_hit() const; virtual int get_speed() const; - virtual m_size get_size() const = 0; + virtual creature_size get_size() const = 0; virtual int get_hp( const bodypart_id &bp ) const; virtual int get_hp() const; virtual int get_hp_max( const bodypart_id &bp ) const; diff --git a/src/cursesdef.h b/src/cursesdef.h index 9fd142872950..27b56c133bf8 100644 --- a/src/cursesdef.h +++ b/src/cursesdef.h @@ -89,6 +89,7 @@ enum base_color : short { using chtype = int; using attr_t = unsigned short; +extern window newscr; extern window stdscr; window newwin( int nlines, int ncols, point begin ); diff --git a/src/debug.cpp b/src/debug.cpp index 7dac97713c66..6f9fbc3959dd 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -325,7 +325,7 @@ static void debug_error_prompt( } ); #if defined(__ANDROID__) - input_context ctxt( "DEBUG_MSG" ); + input_context ctxt( "DEBUG_MSG", keyboard_mode::keychar ); ctxt.register_manual_key( 'C' ); ctxt.register_manual_key( 'I' ); ctxt.register_manual_key( ' ' ); diff --git a/src/editmap.cpp b/src/editmap.cpp index bfa1fdd0a287..c7bddd5835c1 100644 --- a/src/editmap.cpp +++ b/src/editmap.cpp @@ -1139,7 +1139,7 @@ void editmap::edit_feature() draw_target_override = nullptr; } - input_context ctxt( emenu.input_category ); + input_context ctxt( emenu.input_category, keyboard_mode::keychar ); info_txt_curr = string_format( pgettext( "keybinding descriptions", "%s, %s, %s, %s, %s" ), ctxt.describe_key_and_name( "CONFIRM" ), ctxt.describe_key_and_name( "CONFIRM_QUIT" ), @@ -1256,7 +1256,7 @@ void editmap::edit_fld() draw_target_override = nullptr; } - input_context ctxt( fmenu.input_category ); + input_context ctxt( fmenu.input_category, keyboard_mode::keychar ); // \u00A0 is the non-breaking space info_txt_curr = string_format( pgettext( "keybinding descriptions", "%s, %s, [%s,%s]\u00A0intensity, %s, %s, %s" ), @@ -1853,7 +1853,7 @@ void editmap::mapgen_preview( const real_coords &tc, uilist &gmenu ) } else { tmpmap_ptr = nullptr; } - input_context ctxt( gpmenu.input_category ); + input_context ctxt( gpmenu.input_category, keyboard_mode::keychar ); // \u00A0 is the non-breaking space info_txt_curr = string_format( pgettext( "keybinding descriptions", "[%s,%s]\u00A0prev/next oter type, [%s,%s]\u00A0select, %s, %s" ), @@ -2122,7 +2122,7 @@ void editmap::edit_mapgen() blink = true; - input_context ctxt( gmenu.input_category ); + input_context ctxt( gmenu.input_category, keyboard_mode::keychar ); info_txt_curr = string_format( pgettext( "keybinding descriptions", "%s, %s, %s" ), ctxt.describe_key_and_name( "EDITMAP_MOVE" ), ctxt.describe_key_and_name( "CONFIRM" ), diff --git a/src/enum_conversions.cpp b/src/enum_conversions.cpp index f258ef10cc2b..c2f45937fcc1 100644 --- a/src/enum_conversions.cpp +++ b/src/enum_conversions.cpp @@ -25,20 +25,20 @@ std::string io::enum_to_string( Attitude att ) } template<> -std::string io::enum_to_string( m_size data ) +std::string io::enum_to_string( creature_size data ) { switch( data ) { - case m_size::MS_TINY: + case creature_size::tiny: return "TINY"; - case m_size::MS_SMALL: + case creature_size::small: return "SMALL"; - case m_size::MS_MEDIUM: + case creature_size::medium: return "MEDIUM"; - case m_size::MS_LARGE: + case creature_size::large: return "LARGE"; - case m_size::MS_HUGE: + case creature_size::huge: return "HUGE"; - case m_size::num_m_size: + case creature_size::num_creature_size: break; } debugmsg( "Invalid body_size value: %d", static_cast( data ) ); diff --git a/src/enums.h b/src/enums.h index da4806916927..50f1f3e80701 100644 --- a/src/enums.h +++ b/src/enums.h @@ -61,18 +61,18 @@ struct enum_traits { static constexpr holiday last = holiday::num_holiday; }; -enum m_size : int { - MS_TINY = 0, // Squirrel - MS_SMALL, // Dog - MS_MEDIUM, // Human - MS_LARGE, // Cow - MS_HUGE, // TAAAANK - num_m_size // last +enum creature_size : int { + tiny = 0, // Squirrel + small, // Dog + medium, // Human + large, // Cow + huge, // TAAAANK + num_creature_size // last }; template<> -struct enum_traits { - static constexpr m_size last = m_size::num_m_size; +struct enum_traits { + static constexpr creature_size last = creature_size::num_creature_size; }; enum class temperature_flag : int { @@ -100,7 +100,7 @@ enum visibility_type { }; // Matching rules for comparing a string to an overmap terrain id. -enum class ot_match_type { +enum class ot_match_type : int { // The provided string must completely match the overmap terrain id, including // linear direction suffixes for linear terrain types or rotation suffixes // for rotated terrain types. diff --git a/src/examine_item_menu.cpp b/src/examine_item_menu.cpp index 9d521aae5f6a..327337aa8f36 100644 --- a/src/examine_item_menu.cpp +++ b/src/examine_item_menu.cpp @@ -245,9 +245,9 @@ bool run( } }; ui->mark_resize(); - ui->on_redraw( [&]( const ui_adaptor & ) { + ui->on_redraw( [&]( ui_adaptor & ui ) { draw_item_info( w_info, data ); - action_list.show(); + action_list.show( ui ); } ); bool exit = false; diff --git a/src/explosion.cpp b/src/explosion.cpp index 06b2caf4bfa6..79c803cd78b9 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -100,15 +100,15 @@ static float critter_blast_percentage( Creature *c, float range, float distance const float radius_reduction = distance > range ? 0.0f : distance > range / 2 ? 0.5f : 1.0f; switch( c->get_size() ) { - case( m_size::MS_TINY ): + case( creature_size::tiny ): return 0.5 * radius_reduction; - case( m_size::MS_SMALL ): + case( creature_size::small ): return 0.8 * radius_reduction; - case( m_size::MS_MEDIUM ): + case( creature_size::medium ): return 1.0 * radius_reduction; - case( m_size::MS_LARGE ): + case( creature_size::large ): return 1.5 * radius_reduction; - case( m_size::MS_HUGE ): + case( creature_size::huge ): return 2.0 * radius_reduction; default: return 1.0 * radius_reduction; @@ -1685,10 +1685,10 @@ void emp_blast( const tripoint &p ) int deact_chance = 0; const auto mon_item_id = critter.type->revert_to_itype; switch( critter.get_size() ) { - case MS_TINY: + case creature_size::tiny: deact_chance = 6; break; - case MS_SMALL: + case creature_size::small: deact_chance = 3; break; default: diff --git a/src/game.cpp b/src/game.cpp index f5e9b1e2d2dc..d1547f316cd3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1719,6 +1719,7 @@ bool game::cancel_activity_or_ignore_query( const distraction_type type, const s : input_context::allow_all_keys; const auto &action = query_popup() + .preferred_keyboard_mode( keyboard_mode::keychar ) .context( "CANCEL_ACTIVITY_OR_IGNORE_QUERY" ) .message( force_uc ? pgettext( "cancel_activity_or_ignore_query", @@ -2034,7 +2035,7 @@ std::pair game::mouse_edge_scrolling( input_context &ctxt, c last_mouse_edge_scroll = now; } const input_event event = ctxt.get_raw_input(); - if( event.type == CATA_INPUT_MOUSE ) { + if( event.type == input_event_t::mouse ) { const point threshold( projected_window_width() / 100, projected_window_height() / 100 ); if( event.mouse_pos.x <= threshold.x ) { ret.first.x -= speed; @@ -2059,7 +2060,7 @@ std::pair game::mouse_edge_scrolling( input_context &ctxt, c } } ret.second = ret.first; - } else if( event.type == CATA_INPUT_TIMEOUT ) { + } else if( event.type == input_event_t::timeout ) { ret.first = ret.second; } #endif @@ -2086,7 +2087,7 @@ tripoint game::mouse_edge_scrolling_overmap( input_context &ctxt ) input_context get_default_mode_input_context() { - input_context ctxt( "DEFAULTMODE" ); + input_context ctxt( "DEFAULTMODE", keyboard_mode::keychar ); // Because those keys move the character, they don't pan, as their original name says ctxt.set_iso( true ); ctxt.register_action( "UP", to_translation( "Move North" ) ); @@ -2920,8 +2921,8 @@ shared_ptr_fast game::create_or_get_main_ui_adaptor() shared_ptr_fast ui = main_ui_adaptor.lock(); if( !ui ) { main_ui_adaptor = ui = make_shared_fast(); - ui->on_redraw( []( const ui_adaptor & ) { - g->draw(); + ui->on_redraw( []( ui_adaptor & ui ) { + g->draw( ui ); } ); ui->on_screen_resize( [this]( ui_adaptor & ui ) { // remove some space for the sidebar, this is the maximal space @@ -3090,7 +3091,7 @@ static shared_ptr_fast create_trail_callback( } ); } -void game::draw() +void game::draw( ui_adaptor &ui ) { if( test_mode ) { return; @@ -3115,6 +3116,12 @@ void game::draw() wnoutrefresh( w_terrain ); draw_panels( true ); + + // Ensure that the cursor lands on the character when everything is drawn. + // This allows screen readers to describe the area around the player, making it + // much easier to play with them + // (e.g. for blind players) + ui.set_cursor( w_terrain, -u.view_offset.xy() + point( POSX, POSY ) ); } void game::draw_panels( bool force_draw ) @@ -3264,8 +3271,6 @@ void game::draw_ter( const tripoint ¢er, const bool looking, const bool draw draw_veh_dir_indicator( false ); draw_veh_dir_indicator( true ); } - // Place the cursor over the player as is expected by screen readers. - wmove( w_terrain, -center.xy() + g->u.pos().xy() + point( POSX, POSY ) ); } std::optional game::get_veh_dir_indicator_location( bool next ) const @@ -5063,13 +5068,13 @@ bool game::forced_door_closing( const tripoint &p, const ter_id &door_type, int if( can_see ) { add_msg( _( "The %1$s hits the %2$s." ), door_name, critter.name() ); } - if( critter.type->size <= MS_SMALL ) { + if( critter.type->size <= creature_size::small ) { critter.die_in_explosion( nullptr ); } else { critter.apply_damage( nullptr, bodypart_id( "torso" ), bash_dmg ); critter.check_dead_state(); } - if( !critter.is_dead() && critter.type->size >= MS_HUGE ) { + if( !critter.is_dead() && critter.type->size >= creature_size::huge ) { // big critters simply prevent the gate from closing // TODO: perhaps damage/destroy the gate // if the critter was really big? @@ -7397,14 +7402,14 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) std::optional filter_type; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { reset_item_list_state( w_items_border, iInfoHeight, sort_radius ); + int iStartPos = 0; if( ground_items.empty() ) { wnoutrefresh( w_items_border ); mvwprintz( w_items, point( 2, 10 ), c_white, _( "You don't see any items around you!" ) ); } else { - int iStartPos = 0; werase( w_items ); calcStartPos( iStartPos, iActive, iMaxRows, iItemNum ); int iNum = 0; @@ -7448,15 +7453,15 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) sText += string_format( "[%d]", iter->vIG[iThisPage].count ); } - nc_color col = c_light_green; - if( iNum != iActive ) { - if( high ) { - col = c_yellow; - } else if( low ) { - col = c_red; - } else { - col = iter->example->color_in_inventory(); - } + nc_color col = c_light_gray; + if( iNum == iActive ) { + col = hilite( c_white ); + } else if( high ) { + col = c_yellow; + } else if( low ) { + col = c_red; + } else { + col = iter->example->color_in_inventory(); } trim_and_print( w_items, point( 1, iNum - iStartPos ), width - 9, col, sText ); const int numw = iItemNum > 9 ? 2 : 1; @@ -7509,6 +7514,8 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) trim_and_print( w_item_info, point( 4, 0 ), width - 8, activeItem->example->color_in_inventory(), activeItem->example->display_name() ); wprintw( w_item_info, " >" ); + // move the cursor to the selected item (for screen readers) + ui.set_cursor( w_items, point( 1, iActive - iStartPos ) ); } wnoutrefresh( w_items ); @@ -8742,13 +8749,13 @@ std::vector game::get_dangerous_tile( const tripoint &dest_loc ) co bool game::walk_move( const tripoint &dest_loc, const bool via_ramp ) { if( m.has_flag_ter( TFLAG_SMALL_PASSAGE, dest_loc ) ) { - if( u.get_size() > MS_MEDIUM ) { + if( u.get_size() > creature_size::medium ) { add_msg( m_warning, _( "You can't fit there." ) ); return false; // character too large to fit through a tight passage } if( u.is_mounted() ) { monster *mount = u.mounted_creature.get(); - if( mount->get_size() > MS_MEDIUM ) { + if( mount->get_size() > creature_size::medium ) { add_msg( m_warning, _( "Your mount can't fit there." ) ); return false; // char's mount is too large for tight passages } @@ -8996,18 +9003,18 @@ bool game::walk_move( const tripoint &dest_loc, const bool via_ramp ) if( u.is_mounted() ) { auto mons = u.mounted_creature.get(); switch( mons->get_size() ) { - case MS_TINY: + case creature_size::tiny: volume = 0; // No sound for the tinies break; - case MS_SMALL: + case creature_size::small: volume /= 3; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: volume *= 1.5; break; - case MS_HUGE: + case creature_size::huge: volume *= 2; break; default: @@ -10701,12 +10708,12 @@ void game::vertical_notes( int z_before, int z_after ) } const oter_id &ter = overmap_buffer.ter( cursp_before ); const oter_id &ter2 = overmap_buffer.ter( cursp_after ); - if( z_after > z_before && ter->has_flag( known_up ) && - !ter2->has_flag( known_down ) ) { + if( z_after > z_before && ter->has_flag( oter_flags::known_up ) && + !ter2->has_flag( oter_flags::known_down ) ) { overmap_buffer.set_seen( cursp_after, true ); overmap_buffer.add_note( cursp_after, string_format( ">:W;%s", _( "AUTO: goes down" ) ) ); - } else if( z_after < z_before && ter->has_flag( known_down ) && - !ter2->has_flag( known_up ) ) { + } else if( z_after < z_before && ter->has_flag( oter_flags::known_down ) && + !ter2->has_flag( oter_flags::known_up ) ) { overmap_buffer.set_seen( cursp_after, true ); overmap_buffer.add_note( cursp_after, string_format( "<:W;%s", _( "AUTO: goes up" ) ) ); } diff --git a/src/game.h b/src/game.h index c87604bfdf3c..5ce0b31a05e0 100644 --- a/src/game.h +++ b/src/game.h @@ -196,7 +196,7 @@ class game shared_ptr_fast create_or_get_main_ui_adaptor(); void invalidate_main_ui_adaptor() const; void mark_main_ui_adaptor_resize() const; - void draw(); + void draw( ui_adaptor &ui ); void draw_ter( bool draw_sounds = true ); void draw_ter( const tripoint ¢er, bool looking = false, bool draw_sounds = true ); diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 7b839d995b4a..d762ad0981f4 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -246,7 +246,7 @@ input_context game::get_player_input( std::string &action ) { input_context ctxt; if( uquit == QUIT_WATCH ) { - ctxt = input_context( "DEFAULTMODE" ); + ctxt = input_context( "DEFAULTMODE", keyboard_mode::keychar ); ctxt.set_iso( true ); // The list of allowed actions in death-cam mode in game::handle_action // *INDENT-OFF* diff --git a/src/help.cpp b/src/help.cpp index bf83736d1b55..6bceb19024e8 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -158,6 +158,7 @@ void help::display_help() init_windows( ui ); ui.on_screen_resize( init_windows ); + ctxt = input_context( "default", keyboard_mode::keychar ); ctxt.register_cardinal(); ctxt.register_action( "QUIT" ); ctxt.register_action( "CONFIRM" ); diff --git a/src/ime.cpp b/src/ime.cpp deleted file mode 100644 index 99732b18276a..000000000000 --- a/src/ime.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "ime.h" - -#ifdef __ANDROID__ -#include "options.h" -#include "sdltiles.h" -#endif - -#ifdef _WIN32 - -#if 1 // Prevent IWYU reordering this below -#include "platform_win.h" -#endif -#include - -class imm_wrapper -{ - private: - HMODULE hImm; - using pImmGetContext_t = HIMC( WINAPI * )( HWND ); - using pImmGetOpenStatus_t = BOOL( WINAPI * )( HIMC ); - using pImmSetOpenStatus_t = BOOL( WINAPI * )( HIMC, BOOL ); - using pImmReleaseContext_t = BOOL( WINAPI * )( HWND, HIMC ); - pImmGetContext_t pImmGetContext; - pImmGetOpenStatus_t pImmGetOpenStatus; - pImmSetOpenStatus_t pImmSetOpenStatus; - pImmReleaseContext_t pImmReleaseContext; - - template - static T fun_ptr_cast( FARPROC p ) { - // workaround function cast warning - return reinterpret_cast( reinterpret_cast( p ) ); - } - public: - imm_wrapper() { - // Check if East Asian support is available - hImm = LoadLibraryW( L"imm32.dll" ); - if( hImm ) { - pImmGetContext = fun_ptr_cast( - GetProcAddress( hImm, "ImmGetContext" ) ); - pImmGetOpenStatus = fun_ptr_cast( - GetProcAddress( hImm, "ImmGetOpenStatus" ) ); - pImmSetOpenStatus = fun_ptr_cast( - GetProcAddress( hImm, "ImmSetOpenStatus" ) ); - pImmReleaseContext = fun_ptr_cast( - GetProcAddress( hImm, "ImmReleaseContext" ) ); - if( !pImmGetContext || !pImmGetOpenStatus || - !pImmSetOpenStatus || !pImmReleaseContext ) { - - FreeLibrary( hImm ); - hImm = nullptr; - pImmGetContext = nullptr; - pImmGetOpenStatus = nullptr; - pImmSetOpenStatus = nullptr; - pImmReleaseContext = nullptr; - } - } - } - - ~imm_wrapper() { - if( hImm ) { - FreeLibrary( hImm ); - } - } - - bool ime_enabled() { - if( hImm ) { - // NOLINTNEXTLINE(misc-misplaced-const) - const HWND hwnd = getWindowHandle(); - // NOLINTNEXTLINE(misc-misplaced-const) - const HIMC himc = pImmGetContext( hwnd ); - bool enabled = pImmGetOpenStatus( himc ); - pImmReleaseContext( hwnd, himc ); - return enabled; - } - return false; - } - - void enable_ime() { - if( hImm ) { - // NOLINTNEXTLINE(misc-misplaced-const) - const HWND hwnd = getWindowHandle(); - // NOLINTNEXTLINE(misc-misplaced-const) - const HIMC himc = pImmGetContext( hwnd ); - pImmSetOpenStatus( himc, TRUE ); - pImmReleaseContext( hwnd, himc ); - } - } - - void disable_ime() { - if( hImm ) { - // NOLINTNEXTLINE(misc-misplaced-const) - const HWND hwnd = getWindowHandle(); - // NOLINTNEXTLINE(misc-misplaced-const) - const HIMC himc = pImmGetContext( hwnd ); - pImmSetOpenStatus( himc, FALSE ); - pImmReleaseContext( hwnd, himc ); - } - } -}; - -static imm_wrapper imm; -#endif - -static bool ime_enabled() -{ -#if defined( __ANDROID__ ) - return false; // always call disable_ime() (i.e. do nothing) on return -#elif defined( _WIN32 ) - return imm.ime_enabled(); -#endif - return false; - // TODO: other platforms? -} - -void enable_ime() -{ -#if defined( __ANDROID__ ) - if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { - SDL_StartTextInput(); - } -#elif defined( _WIN32 ) - imm.enable_ime(); -#endif - // TODO: other platforms? -} - -void disable_ime() -{ -#if defined( __ANDROID__ ) - // the original android code did nothing, so don't change it -#elif defined( _WIN32 ) - imm.disable_ime(); -#endif - // TODO: other platforms? -} - -ime_sentry::ime_sentry( ime_sentry::mode m ) : previously_enabled( ime_enabled() ) -{ - switch( m ) { - case enable: - enable_ime(); - break; - case disable: - disable_ime(); - break; - case keep: - // do nothing - break; - } -} - -ime_sentry::~ime_sentry() -{ - if( previously_enabled ) { - enable_ime(); - } else { - disable_ime(); - } -} diff --git a/src/ime.h b/src/ime.h deleted file mode 100644 index c6daf0032aac..000000000000 --- a/src/ime.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#ifndef CATA_SRC_IME_H -#define CATA_SRC_IME_H - -/** - * Enable or disable IME for text/keyboard input - */ -void enable_ime(); -void disable_ime(); - -/** - * used before text input to change IME mode and auto-restore IME mode when leaving the scope - */ -class ime_sentry -{ - public: - enum mode { - enable = 0, - disable = 1, - keep = 2, - }; - - ime_sentry( mode m = enable ); - ~ime_sentry(); - private: - bool previously_enabled; -}; - -#endif // CATA_SRC_IME_H diff --git a/src/input.cpp b/src/input.cpp index f0a682af52ce..4476d732ea05 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -21,7 +21,6 @@ #include "fstream_utils.h" #include "game.h" #include "help.h" -#include "ime.h" #include "json.h" #include "options.h" #include "output.h" @@ -80,6 +79,15 @@ bool is_mouse_enabled() #endif } +static bool is_keycode_mode_supported() +{ +#if defined(TILES) and !defined(__ANDROID__) + return true; +#else + return false; +#endif +} + //helper function for those have problem inputting certain characters. std::string get_input_string_from_file( const std::string &fname ) { @@ -97,6 +105,15 @@ std::string get_input_string_from_file( const std::string &fname ) return ret; } +input_event::input_event( const std::set &mod, const int s, const input_event_t t ) + : type( t ), modifiers( mod ), edit_refresh( false ) +{ + sequence.emplace_back( s ); +#if defined(__ANDROID__) + shortcut_last_used_action_counter = 0; +#endif +} + int input_event::get_first_input() const { if( sequence.empty() ) { @@ -157,7 +174,7 @@ void input_manager::init() action_contexts[action_id].clear(); touched.insert( a->second ); } - add_input_for_action( action_id, context, input_event( a->first, CATA_INPUT_KEYBOARD ) ); + add_input_for_action( action_id, context, input_event( a->first, input_event_t::keyboard_char ) ); } // Unmap actions that are explicitly not mapped for( const auto &elem : unbound_keymap ) { @@ -179,6 +196,8 @@ void input_manager::init() } } +static constexpr int current_keybinding_version = 1; + void input_manager::load( const std::string &file_name, bool is_user_preferences ) { std::ifstream data_file( file_name.c_str(), std::ifstream::in | std::ifstream::binary ); @@ -200,6 +219,13 @@ void input_manager::load( const std::string &file_name, bool is_user_preferences // JSON object representing the action JsonObject action = jsin.get_object(); + int version = current_keybinding_version; + if( is_user_preferences ) { + // if there isn't a "version" value it means the object was written before + // introduction of keybinding version, which is denoted by version 0. + version = action.get_int( "version", 0 ); + } + const std::string type = action.get_string( "type", "keybinding" ); if( type != "keybinding" ) { debugmsg( "Only objects of type 'keybinding' (not %s) should appear in the " @@ -222,25 +248,57 @@ void input_manager::load( const std::string &file_name, bool is_user_preferences for( const JsonObject keybinding : action.get_array( "bindings" ) ) { std::string input_method = keybinding.get_string( "input_method" ); input_event new_event; - if( input_method == "keyboard" ) { - new_event.type = CATA_INPUT_KEYBOARD; + std::vector new_events( 1 ); + if( input_method == "keyboard_any" ) { + new_events.resize( 2 ); + new_events[0].type = input_event_t::keyboard_char; + new_events[1].type = input_event_t::keyboard_code; + } else if( input_method == "keyboard_char" || input_method == "keyboard" ) { + new_events[0].type = input_event_t::keyboard_char; + } else if( input_method == "keyboard_code" ) { + new_events[0].type = input_event_t::keyboard_code; } else if( input_method == "gamepad" ) { - new_event.type = CATA_INPUT_GAMEPAD; + new_events[0].type = input_event_t::gamepad; } else if( input_method == "mouse" ) { - new_event.type = CATA_INPUT_MOUSE; + new_events[0].type = input_event_t::mouse; + } else { + keybinding.throw_error( "unknown input_method", "input_method" ); + } + + if( keybinding.has_member( "mod" ) ) { + for( const JsonValue &val : keybinding.get_array( "mod" ) ) { + const std::string str = val; + keymod_t mod = keymod_t::ctrl; + if( str == "ctrl" ) { + mod = keymod_t::ctrl; + } else if( str == "alt" ) { + mod = keymod_t::alt; + } else if( str == "shift" ) { + mod = keymod_t::shift; + } else { + val.throw_error( "unknown modifier name" ); + } + for( input_event &new_event : new_events ) { + new_event.modifiers.emplace( mod ); + } + } } if( keybinding.has_array( "key" ) ) { for( const std::string line : keybinding.get_array( "key" ) ) { - new_event.sequence.push_back( get_keycode( line ) ); + for( input_event &new_event : new_events ) { + new_event.sequence.push_back( get_keycode( new_event.type, line ) ); + } } } else { // assume string if not array, and throw if not string - new_event.sequence.push_back( - get_keycode( keybinding.get_string( "key" ) ) - ); + for( input_event &new_event : new_events ) { + new_event.sequence.push_back( + get_keycode( new_event.type, keybinding.get_string( "key" ) ) + ); + } } - events.push_back( new_event ); + events.insert( events.end(), new_events.begin(), new_events.end() ); } // An invariant of this class is that user-created, local keybindings @@ -260,6 +318,17 @@ void input_manager::load( const std::string &file_name, bool is_user_preferences // In case this is the second file containing user preferences, // this replaces the default bindings with the user's preferences. action_attributes &attributes = actions[action_id]; + if( is_user_preferences && version == 0 ) { + // version 0 means the keybinding was written prior to the division + // of `input_event_t::keyboard_char` and `input_event_t::keyboard_code`, + // so we copy any `input_event_t::keyboard_code` event from the default + // keybindings to be compatible with old user keybinding files. + for( const input_event &evt : attributes.input_events ) { + if( evt.type == input_event_t::keyboard_code ) { + events.emplace_back( evt ); + } + } + } attributes.input_events = events; if( action.has_member( "is_user_created" ) ) { attributes.is_user_created = action.get_bool( "is_user_created" ); @@ -282,6 +351,7 @@ void input_manager::save() jsout.start_object(); jsout.member( "id", action.first ); + jsout.member( "version", current_keybinding_version ); jsout.member( "category", a->first ); bool is_user_created = action.second.is_user_created; if( is_user_created ) { @@ -293,18 +363,40 @@ void input_manager::save() for( const auto &event : events ) { jsout.start_object(); switch( event.type ) { - case CATA_INPUT_KEYBOARD: - jsout.member( "input_method", "keyboard" ); + case input_event_t::keyboard_char: + jsout.member( "input_method", "keyboard_char" ); + break; + case input_event_t::keyboard_code: + jsout.member( "input_method", "keyboard_code" ); break; - case CATA_INPUT_GAMEPAD: + case input_event_t::gamepad: jsout.member( "input_method", "gamepad" ); break; - case CATA_INPUT_MOUSE: + case input_event_t::mouse: jsout.member( "input_method", "mouse" ); break; default: throw std::runtime_error( "unknown input_event_t" ); } + jsout.member( "mod" ); + jsout.start_array(); + for( const keymod_t mod : event.modifiers ) { + switch( mod ) { + case keymod_t::ctrl: + jsout.write( "ctrl" ); + break; + case keymod_t::alt: + jsout.write( "alt" ); + break; + case keymod_t::shift: + jsout.write( "shift" ); + break; + default: + throw std::runtime_error( "unknown keymod_t" ); + } + } + jsout.end_array(); + jsout.member( "key" ); jsout.start_array(); for( size_t i = 0; i < event.sequence.size(); i++ ) { @@ -322,16 +414,28 @@ void input_manager::save() }, _( "key bindings configuration" ) ); } -void input_manager::add_keycode_pair( int ch, const std::string &name ) +void input_manager::add_keyboard_char_keycode_pair( int ch, const std::string &name ) { - keycode_to_keyname[ch] = name; - keyname_to_keycode[name] = ch; + keyboard_char_keycode_to_keyname[ch] = name; + keyboard_char_keyname_to_keycode[name] = ch; +} + +void input_manager::add_keyboard_code_keycode_pair( const int ch, const std::string &name ) +{ + keyboard_code_keycode_to_keyname[ch] = name; + keyboard_code_keyname_to_keycode[name] = ch; } void input_manager::add_gamepad_keycode_pair( int ch, const std::string &name ) { gamepad_keycode_to_keyname[ch] = name; - keyname_to_keycode[name] = ch; + gamepad_keyname_to_keycode[name] = ch; +} + +void input_manager::add_mouse_keycode_pair( const int ch, const std::string &name ) +{ + mouse_keycode_to_keyname[ch] = name; + mouse_keyname_to_keycode[name] = ch; } constexpr int char_key_beg = ' '; @@ -343,30 +447,91 @@ void input_manager::init_keycode_mapping() // to themselves(see ASCII table) for( char c = char_key_beg; c <= char_key_end; c++ ) { std::string name( 1, c ); - add_keycode_pair( c, name ); - } - - add_keycode_pair( '\t', translate_marker_context( "key name", "TAB" ) ); - add_keycode_pair( KEY_BTAB, translate_marker_context( "key name", "BACKTAB" ) ); - add_keycode_pair( ' ', translate_marker_context( "key name", "SPACE" ) ); - add_keycode_pair( KEY_UP, translate_marker_context( "key name", "UP" ) ); - add_keycode_pair( KEY_DOWN, translate_marker_context( "key name", "DOWN" ) ); - add_keycode_pair( KEY_LEFT, translate_marker_context( "key name", "LEFT" ) ); - add_keycode_pair( KEY_RIGHT, translate_marker_context( "key name", "RIGHT" ) ); - add_keycode_pair( KEY_NPAGE, translate_marker_context( "key name", "NPAGE" ) ); - add_keycode_pair( KEY_PPAGE, translate_marker_context( "key name", "PPAGE" ) ); - add_keycode_pair( KEY_ESCAPE, translate_marker_context( "key name", "ESC" ) ); - add_keycode_pair( KEY_BACKSPACE, translate_marker_context( "key name", "BACKSPACE" ) ); - add_keycode_pair( KEY_HOME, translate_marker_context( "key name", "HOME" ) ); - add_keycode_pair( KEY_BREAK, translate_marker_context( "key name", "BREAK" ) ); - add_keycode_pair( KEY_END, translate_marker_context( "key name", "END" ) ); - add_keycode_pair( '\n', translate_marker_context( "key name", "RETURN" ) ); + add_keyboard_char_keycode_pair( c, name ); + add_keyboard_code_keycode_pair( c, name ); + } + + add_keyboard_char_keycode_pair( '\t', translate_marker_context( "key name", "TAB" ) ); + add_keyboard_char_keycode_pair( KEY_BTAB, translate_marker_context( "key name", "BACKTAB" ) ); + add_keyboard_char_keycode_pair( ' ', translate_marker_context( "key name", "SPACE" ) ); + add_keyboard_char_keycode_pair( KEY_UP, translate_marker_context( "key name", "UP" ) ); + add_keyboard_char_keycode_pair( KEY_DOWN, translate_marker_context( "key name", "DOWN" ) ); + add_keyboard_char_keycode_pair( KEY_LEFT, translate_marker_context( "key name", "LEFT" ) ); + add_keyboard_char_keycode_pair( KEY_RIGHT, translate_marker_context( "key name", "RIGHT" ) ); + add_keyboard_char_keycode_pair( KEY_NPAGE, translate_marker_context( "key name", "NPAGE" ) ); + add_keyboard_char_keycode_pair( KEY_PPAGE, translate_marker_context( "key name", "PPAGE" ) ); + add_keyboard_char_keycode_pair( KEY_ESCAPE, translate_marker_context( "key name", "ESC" ) ); + add_keyboard_char_keycode_pair( KEY_BACKSPACE, + translate_marker_context( "key name", "BACKSPACE" ) ); + add_keyboard_char_keycode_pair( KEY_HOME, translate_marker_context( "key name", "HOME" ) ); + add_keyboard_char_keycode_pair( KEY_BREAK, translate_marker_context( "key name", "BREAK" ) ); + add_keyboard_char_keycode_pair( KEY_END, translate_marker_context( "key name", "END" ) ); + add_keyboard_char_keycode_pair( '\n', translate_marker_context( "key name", "RETURN" ) ); // function keys, as defined by ncurses for( int i = F_KEY_NUM_BEG; i <= F_KEY_NUM_END; i++ ) { // not marked for translation here, but specially handled in get_keyname so // it gets properly translated. - add_keycode_pair( KEY_F( i ), string_format( "F%d", i ) ); + add_keyboard_char_keycode_pair( KEY_F( i ), string_format( "F%d", i ) ); + } + + static const std::vector> keyboard_code_keycode_pair = { + { keycode::backspace, translate_marker_context( "key name", "BACKSPACE" ) }, + { keycode::tab, translate_marker_context( "key name", "TAB" ) }, + { keycode::return_, translate_marker_context( "key name", "RETURN" ) }, + { keycode::escape, translate_marker_context( "key name", "ESC" ) }, + { keycode::space, translate_marker_context( "key name", "SPACE" ) }, + { keycode::f1, translate_marker_context( "key name", "F1" ) }, + { keycode::f2, translate_marker_context( "key name", "F2" ) }, + { keycode::f3, translate_marker_context( "key name", "F3" ) }, + { keycode::f4, translate_marker_context( "key name", "F4" ) }, + { keycode::f5, translate_marker_context( "key name", "F5" ) }, + { keycode::f6, translate_marker_context( "key name", "F6" ) }, + { keycode::f7, translate_marker_context( "key name", "F7" ) }, + { keycode::f8, translate_marker_context( "key name", "F8" ) }, + { keycode::f9, translate_marker_context( "key name", "F9" ) }, + { keycode::f10, translate_marker_context( "key name", "F10" ) }, + { keycode::f11, translate_marker_context( "key name", "F11" ) }, + { keycode::f12, translate_marker_context( "key name", "F12" ) }, + { keycode::ppage, translate_marker_context( "key name", "PPAGE" ) }, + { keycode::home, translate_marker_context( "key name", "HOME" ) }, + { keycode::end, translate_marker_context( "key name", "END" ) }, + { keycode::npage, translate_marker_context( "key name", "NPAGE" ) }, + { keycode::right, translate_marker_context( "key name", "RIGHT" ) }, + { keycode::left, translate_marker_context( "key name", "LEFT" ) }, + { keycode::down, translate_marker_context( "key name", "DOWN" ) }, + { keycode::up, translate_marker_context( "key name", "UP" ) }, + { keycode::kp_divide, translate_marker_context( "key name", "KEYPAD_DIVIDE" ) }, + { keycode::kp_multiply, translate_marker_context( "key name", "KEYPAD_MULTIPLY" ) }, + { keycode::kp_minus, translate_marker_context( "key name", "KEYPAD_MINUS" ) }, + { keycode::kp_plus, translate_marker_context( "key name", "KEYPAD_PLUS" ) }, + { keycode::kp_enter, translate_marker_context( "key name", "KEYPAD_ENTER" ) }, + { keycode::kp_1, translate_marker_context( "key name", "KEYPAD_1" ) }, + { keycode::kp_2, translate_marker_context( "key name", "KEYPAD_2" ) }, + { keycode::kp_3, translate_marker_context( "key name", "KEYPAD_3" ) }, + { keycode::kp_4, translate_marker_context( "key name", "KEYPAD_4" ) }, + { keycode::kp_5, translate_marker_context( "key name", "KEYPAD_5" ) }, + { keycode::kp_6, translate_marker_context( "key name", "KEYPAD_6" ) }, + { keycode::kp_7, translate_marker_context( "key name", "KEYPAD_7" ) }, + { keycode::kp_8, translate_marker_context( "key name", "KEYPAD_8" ) }, + { keycode::kp_9, translate_marker_context( "key name", "KEYPAD_9" ) }, + { keycode::kp_0, translate_marker_context( "key name", "KEYPAD_0" ) }, + { keycode::kp_period, translate_marker_context( "key name", "KEYPAD_PERIOD" ) }, + { keycode::f13, translate_marker_context( "key name", "F13" ) }, + { keycode::f14, translate_marker_context( "key name", "F14" ) }, + { keycode::f15, translate_marker_context( "key name", "F15" ) }, + { keycode::f16, translate_marker_context( "key name", "F16" ) }, + { keycode::f17, translate_marker_context( "key name", "F17" ) }, + { keycode::f18, translate_marker_context( "key name", "F18" ) }, + { keycode::f19, translate_marker_context( "key name", "F19" ) }, + { keycode::f20, translate_marker_context( "key name", "F20" ) }, + { keycode::f21, translate_marker_context( "key name", "F21" ) }, + { keycode::f22, translate_marker_context( "key name", "F22" ) }, + { keycode::f23, translate_marker_context( "key name", "F23" ) }, + { keycode::f24, translate_marker_context( "key name", "F24" ) }, + }; + for( const auto &v : keyboard_code_keycode_pair ) { + add_keyboard_code_keycode_pair( v.first, v.second ); } add_gamepad_keycode_pair( JOY_LEFT, translate_marker_context( "key name", "JOY_LEFT" ) ); @@ -387,18 +552,37 @@ void input_manager::init_keycode_mapping() add_gamepad_keycode_pair( JOY_6, translate_marker_context( "key name", "JOY_6" ) ); add_gamepad_keycode_pair( JOY_7, translate_marker_context( "key name", "JOY_7" ) ); - keyname_to_keycode["MOUSE_LEFT"] = MOUSE_BUTTON_LEFT; - keyname_to_keycode["MOUSE_RIGHT"] = MOUSE_BUTTON_RIGHT; - keyname_to_keycode["SCROLL_UP"] = SCROLLWHEEL_UP; - keyname_to_keycode["SCROLL_DOWN"] = SCROLLWHEEL_DOWN; - keyname_to_keycode["MOUSE_MOVE"] = MOUSE_MOVE; + add_mouse_keycode_pair( MOUSE_BUTTON_LEFT, translate_marker_context( "key name", "MOUSE_LEFT" ) ); + add_mouse_keycode_pair( MOUSE_BUTTON_RIGHT, translate_marker_context( "key name", "MOUSE_RIGHT" ) ); + add_mouse_keycode_pair( SCROLLWHEEL_UP, translate_marker_context( "key name", "SCROLL_UP" ) ); + add_mouse_keycode_pair( SCROLLWHEEL_DOWN, translate_marker_context( "key name", "SCROLL_DOWN" ) ); + add_mouse_keycode_pair( MOUSE_MOVE, translate_marker_context( "key name", "MOUSE_MOVE" ) ); } -int input_manager::get_keycode( const std::string &name ) const +int input_manager::get_keycode( const input_event_t inp_type, const std::string &name ) const { - const t_name_to_key_map::const_iterator a = keyname_to_keycode.find( name ); - if( a != keyname_to_keycode.end() ) { - return a->second; + const t_name_to_key_map *map = nullptr; + switch( inp_type ) { + default: + break; + case input_event_t::keyboard_char: + map = &keyboard_char_keyname_to_keycode; + break; + case input_event_t::keyboard_code: + map = &keyboard_code_keyname_to_keycode; + break; + case input_event_t::gamepad: + map = &gamepad_keyname_to_keycode; + break; + case input_event_t::mouse: + map = &mouse_keyname_to_keycode; + break; + } + if( map ) { + const auto it = map->find( name ); + if( it != map->end() ) { + return it->second; + } } // Not found in map, try to parse as int if( name.compare( 0, 8, "UNKNOWN_" ) == 0 ) { @@ -409,51 +593,58 @@ int input_manager::get_keycode( const std::string &name ) const std::string input_manager::get_keyname( int ch, input_event_t inp_type, bool portable ) const { - std::optional raw; - if( inp_type == CATA_INPUT_KEYBOARD ) { - const t_key_to_name_map::const_iterator a = keycode_to_keyname.find( ch ); - if( a != keycode_to_keyname.end() ) { - if( IS_F_KEY( ch ) ) { - // special case it since F key names are generated using loop - // and not marked individually for translation - if( portable ) { - return a->second; - } else { - return string_format( pgettext( "function key name", "F%d" ), F_KEY_NUM( ch ) ); - } - } else if( ch >= char_key_beg && ch <= char_key_end && ch != ' ' ) { - // character keys except space need no translation - return a->second; + const t_key_to_name_map *map = nullptr; + switch( inp_type ) { + default: + break; + case input_event_t::keyboard_char: + map = &keyboard_char_keycode_to_keyname; + break; + case input_event_t::keyboard_code: + map = &keyboard_code_keycode_to_keyname; + break; + case input_event_t::gamepad: + map = &gamepad_keycode_to_keyname; + break; + case input_event_t::mouse: + map = &mouse_keycode_to_keyname; + break; + } + if( map ) { + const auto it = map->find( ch ); + if( it != map->end() ) { + switch( inp_type ) { + case input_event_t::keyboard_char: + if( IS_F_KEY( ch ) ) { + // special case it since F key names are generated using loop + // and not marked individually for translation + if( portable ) { + return it->second; + } else { + return string_format( pgettext( "function key name", "F%d" ), F_KEY_NUM( ch ) ); + } + } else if( ch >= char_key_beg && ch <= char_key_end && ch != ' ' ) { + // character keys except space need no translation + return it->second; + } + break; + case input_event_t::keyboard_code: + if( ch >= char_key_beg && ch < char_key_end && ch != ' ' ) { + // character keys except space need no translation + return it->second; + } + break; + default: + break; } - raw = a->second; - } - } else if( inp_type == CATA_INPUT_MOUSE ) { - if( ch == MOUSE_BUTTON_LEFT ) { - raw = translate_marker_context( "key name", "MOUSE_LEFT" ); - } else if( ch == MOUSE_BUTTON_RIGHT ) { - raw = translate_marker_context( "key name", "MOUSE_RIGHT" ); - } else if( ch == SCROLLWHEEL_UP ) { - raw = translate_marker_context( "key name", "SCROLL_UP" ); - } else if( ch == SCROLLWHEEL_DOWN ) { - raw = translate_marker_context( "key name", "SCROLL_DOWN" ); - } else if( ch == MOUSE_MOVE ) { - raw = translate_marker_context( "key name", "MOUSE_MOVE" ); + return portable ? it->second : pgettext( "key name", it->second.c_str() ); } - } else if( inp_type == CATA_INPUT_GAMEPAD ) { - const t_key_to_name_map::const_iterator a = gamepad_keycode_to_keyname.find( ch ); - if( a != gamepad_keycode_to_keyname.end() ) { - raw = a->second; - } - } else { - raw = translate_marker_context( "key name", "UNKNOWN" ); } - if( !raw ) { - if( portable ) { - return std::string( "UNKNOWN_" ) + int_to_str( ch ); - } + if( portable ) { + return std::string( "UNKNOWN_" ) + int_to_str( ch ); + } else { return string_format( _( "unknown key %ld" ), ch ); } - return portable ? *raw : pgettext( "key name", raw->c_str() ); } const std::vector &input_manager::get_input_for_action( const std::string @@ -697,7 +888,7 @@ std::vector input_context::keys_bound_to( const std::string &action_descri for( const auto &events_event : events ) { // Ignore multi-key input and non-keyboard input // TODO: fix for Unicode. - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.sequence.size() == 1 ) { + if( events_event.type == input_event_t::keyboard_char && events_event.sequence.size() == 1 ) { if( !restrict_to_printable || ( events_event.sequence.front() < 0xFF && isprint( events_event.sequence.front() ) ) ) { result.push_back( static_cast( events_event.sequence.front() ) ); @@ -722,7 +913,7 @@ std::string input_context::get_available_single_char_hotkeys( std::string reques for( const auto &events_event : events ) { // Only consider keyboard events without modifiers - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.modifiers.empty() ) { + if( events_event.type == input_event_t::keyboard_char && events_event.modifiers.empty() ) { requested_keys.erase( std::remove_if( requested_keys.begin(), requested_keys.end(), ContainsPredicate, char>( events_event.sequence ) ), @@ -736,7 +927,7 @@ std::string input_context::get_available_single_char_hotkeys( std::string reques const input_context::input_event_filter input_context::disallow_lower_case = []( const input_event &evt ) -> bool { - return evt.type != CATA_INPUT_KEYBOARD || + return evt.type != input_event_t::keyboard_char || // std::lower from is undefined outside unsigned char range // and std::lower from may throw bad_cast for some locales evt.get_first_input() < 'a' || evt.get_first_input() > 'z'; @@ -747,6 +938,12 @@ const input_context::input_event_filter input_context::allow_all_keys = return true; }; +static const std::vector> keymod_desc = { + { keymod_t::ctrl, to_translation( "key modifier", "CTRL-" ) }, + { keymod_t::alt, to_translation( "key modifier", "ALT-" ) }, + { keymod_t::shift, to_translation( "key modifier", "SHIFT-" ) }, +}; + std::string input_context::get_desc( const std::string &action_descriptor, const unsigned int max_limit, const input_context::input_event_filter &evt_filter ) const @@ -767,10 +964,7 @@ std::string input_context::get_desc( const std::string &action_descriptor, for( auto &events_i : events ) { const input_event &event = events_i; - if( evt_filter( event ) && - // Only display gamepad buttons if a gamepad is available. - ( gamepad_available() || event.type != CATA_INPUT_GAMEPAD ) ) { - + if( is_event_type_enabled( event.type ) && evt_filter( event ) ) { inputs_to_show.push_back( event ); } @@ -785,6 +979,12 @@ std::string input_context::get_desc( const std::string &action_descriptor, std::string rval; for( size_t i = 0; i < inputs_to_show.size(); ++i ) { + // test in fixed order to generate consistent description + for( const auto &v : keymod_desc ) { + if( inputs_to_show[i].modifiers.count( v.first ) ) { + rval += v.second.translated(); + } + } for( size_t j = 0; j < inputs_to_show[i].sequence.size(); ++j ) { rval += inp_mngr.get_keyname( inputs_to_show[i].sequence[j], inputs_to_show[i].type ); } @@ -799,31 +999,34 @@ std::string input_context::get_desc( const std::string &action_descriptor, return rval; } -std::string input_context::get_desc( const std::string &action_descriptor, - const std::string &text, - const input_context::input_event_filter &evt_filter ) const +std::string input_context::get_desc( + const std::string &action_descriptor, + const std::string &text, + const input_context::input_event_filter &evt_filter, + const translation &inline_fmt, + const translation &separate_fmt ) const { if( action_descriptor == "ANY_INPUT" ) { - // \u00A0 is the non-breaking space //~ keybinding description for anykey - return string_format( pgettext( "keybinding", "[any]\u00A0%s" ), text ); + return string_format( separate_fmt, pgettext( "keybinding", "any" ), text ); } const auto &events = inp_mngr.get_input_for_action( action_descriptor, category ); bool na = true; for( const auto &evt : events ) { - if( evt_filter( evt ) && - // Only display gamepad buttons if a gamepad is available. - ( gamepad_available() || evt.type != CATA_INPUT_GAMEPAD ) ) { - + if( is_event_type_enabled( evt.type ) && evt_filter( evt ) ) { na = false; - if( evt.type == CATA_INPUT_KEYBOARD && evt.sequence.size() == 1 ) { + if( ( evt.type == input_event_t::keyboard_char || evt.type == input_event_t::keyboard_code ) && + evt.modifiers.empty() && evt.sequence.size() == 1 ) { const int ch = evt.get_first_input(); - const std::string key = utf32_to_utf8( ch ); - const auto pos = ci_find_substr( text, key ); - if( ch > ' ' && ch <= '~' && pos >= 0 ) { - return text.substr( 0, pos ) + "(" + key + ")" + text.substr( pos + key.size() ); + if( ch > ' ' && ch <= '~' ) { + const std::string key = utf32_to_utf8( ch ); + const auto pos = ci_find_substr( text, key ); + if( pos >= 0 ) { + return string_format( inline_fmt, text.substr( 0, pos ), + key, text.substr( pos + key.size() ) ); + } } } } @@ -831,14 +1034,30 @@ std::string input_context::get_desc( const std::string &action_descriptor, if( na ) { //~ keybinding description for unbound or disabled keys - return string_format( pgettext( "keybinding", "[n/a]\u00A0%s" ), text ); + return string_format( separate_fmt, pgettext( "keybinding", "n/a" ), text ); } else { - //~ keybinding description for bound keys - return string_format( pgettext( "keybinding", "[%s]\u00A0%s" ), - get_desc( action_descriptor, 1, evt_filter ), text ); + return string_format( separate_fmt, get_desc( action_descriptor, 1, evt_filter ), text ); } } +std::string input_context::get_desc( + const std::string &action_descriptor, + const std::string &text, + const input_event_filter &evt_filter ) const +{ + return get_desc( action_descriptor, text, evt_filter, + to_translation( + //~ %1$s: action description text before key, + //~ %2$s: key description, + //~ %3$s: action description text after key. + "keybinding", "%1$s(%2$s)%3$s" ), + to_translation( + // \u00A0 is the non-breaking space + //~ %1$s: key description, + //~ %2$s: action description. + "keybinding", "[%1$s]\u00A0%2$s" ) ); +} + std::string input_context::describe_key_and_name( const std::string &action_descriptor, const input_context::input_event_filter &evt_filter ) const { @@ -856,11 +1075,11 @@ const std::string &input_context::handle_input( const int timeout ) if( timeout >= 0 ) { inp_mngr.set_timeout( timeout ); } - next_action.type = CATA_INPUT_ERROR; + next_action.type = input_event_t::error; const std::string *result = &CATA_ERROR; while( true ) { - next_action = inp_mngr.get_input_event(); - if( next_action.type == CATA_INPUT_TIMEOUT ) { + next_action = inp_mngr.get_input_event( preferred_keyboard_mode ); + if( next_action.type == input_event_t::timeout ) { result = &TIMEOUT; break; } @@ -876,7 +1095,7 @@ const std::string &input_context::handle_input( const int timeout ) break; } - if( next_action.type == CATA_INPUT_MOUSE ) { + if( next_action.type == input_event_t::mouse ) { if( !handling_coordinate_input && action == CATA_ERROR ) { continue; // Ignore this mouse input. } @@ -990,12 +1209,28 @@ std::optional input_context::get_direction( const std::string &action const std::string display_help_hotkeys = "abcdefghijkpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:;'\",/<>?!@#$%^&*()_[]\\{}|`~"; +namespace +{ +enum class fallback_action { + add_local, + add_global, + remove, + execute, +}; +} // namespace +static const std::map fallback_keys = { + { fallback_action::add_local, '+' }, + { fallback_action::add_global, '=' }, + { fallback_action::remove, '-' }, + { fallback_action::execute, '.' }, +}; + action_id input_context::display_menu( const bool permit_execute_action ) { action_id action_to_execute = ACTION_NULL; - // Shamelessly stolen from help.cpp - input_context ctxt( "HELP_KEYBINDINGS" ); + input_context ctxt( "HELP_KEYBINDINGS", keyboard_mode::keychar ); + // Keybinding menu actions ctxt.register_action( "UP", to_translation( "Scroll up" ) ); ctxt.register_action( "DOWN", to_translation( "Scroll down" ) ); ctxt.register_action( "PAGE_DOWN" ); @@ -1003,8 +1238,22 @@ action_id input_context::display_menu( const bool permit_execute_action ) ctxt.register_action( "REMOVE" ); ctxt.register_action( "ADD_LOCAL" ); ctxt.register_action( "ADD_GLOBAL" ); - ctxt.register_action( "EXECUTE" ); + if( permit_execute_action ) { + ctxt.register_action( "EXECUTE" ); + } ctxt.register_action( "QUIT" ); + // String input actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); ctxt.register_action( "ANY_INPUT" ); if( category != "HELP_KEYBINDINGS" ) { @@ -1021,6 +1270,12 @@ action_id input_context::display_menu( const bool permit_execute_action ) size_t display_height = 0; size_t legwidth = 0; string_input_popup spopup; + // ignore hardcoded keys in string input popup + for( const std::pair &v : fallback_keys ) { + spopup.callbacks[v.second] = []() { + return true; + }; + } const auto recalc_size = [&]( ui_adaptor & ui ) { int maxwidth = std::max( FULL_SCREEN_WIDTH, TERMX ); width = min( 80, maxwidth ); @@ -1031,9 +1286,11 @@ action_id input_context::display_menu( const bool permit_execute_action ) point( maxwidth / 2 - width / 2, maxheight / 2 - height / 2 ) ); // height of the area usable for display of keybindings, excludes headers & borders display_height = height - LEGEND_HEIGHT - BORDER_SPACE; // -2 for the border + const point filter_pos( 4, 8 ); // width of the legend - legwidth = width - 4 - BORDER_SPACE; - spopup.window( w_help, point( 4, 8 ), legwidth ) + legwidth = width - filter_pos.x * 2 - BORDER_SPACE; + // +1 for end-of-text cursor + spopup.window( w_help, filter_pos, filter_pos.x + legwidth + 1 ) .max_length( legwidth ) .context( ctxt ); ui.position_from_window( w_help ); @@ -1066,9 +1323,15 @@ action_id input_context::display_menu( const bool permit_execute_action ) legend += colorize( _( "Unbound keys" ), unbound_key ) + "\n"; legend += colorize( _( "Keybinding active only on this screen" ), local_key ) + "\n"; legend += colorize( _( "Keybinding active globally" ), global_key ) + "\n"; - legend += _( "Press - to remove keybinding\nPress + to add local keybinding\nPress = to add global keybinding\n" ); + legend += string_format( + _( "Press %c to remove keybinding\nPress %c to add local keybinding\nPress %c to add global keybinding\n" ), + fallback_keys.at( fallback_action::remove ), + fallback_keys.at( fallback_action::add_local ), + fallback_keys.at( fallback_action::add_global ) ); if( permit_execute_action ) { - legend += _( "Press . to execute action\n" ); + legend += string_format( + _( "Press %c to execute action\n" ), + fallback_keys.at( fallback_action::execute ) ); } std::vector filtered_registered_actions = org_registered_actions; @@ -1076,7 +1339,7 @@ action_id input_context::display_menu( const bool permit_execute_action ) std::string action; int raw_input_char = 0; - const auto redraw = [&]( const ui_adaptor & ) { + const auto redraw = [&]( ui_adaptor & ui ) { werase( w_help ); draw_border( w_help, BORDER_COLOR, _( "Keybindings" ), c_light_red ); draw_scrollbar( w_help, scroll_offset, display_height, @@ -1126,11 +1389,11 @@ action_id input_context::display_menu( const bool permit_execute_action ) // spopup.query_string() will call wnoutrefresh( w_help ) spopup.text( filter_phrase ); spopup.query_string( false, true ); + // Record cursor immediately after spopup drawing + ui.record_term_cursor(); }; ui.on_redraw( redraw ); - // do not switch IME mode now, but restore previous mode on return - ime_sentry sentry( ime_sentry::keep ); while( true ) { ui_manager::redraw(); @@ -1141,28 +1404,82 @@ action_id input_context::display_menu( const bool permit_execute_action ) action = ctxt.handle_input(); } raw_input_char = ctxt.get_raw_input().get_first_input(); + for( const std::pair &v : fallback_keys ) { + if( v.second == raw_input_char ) { + action.clear(); + } + } filtered_registered_actions = filter_strings_by_phrase( org_registered_actions, filter_phrase ); if( scroll_offset > filtered_registered_actions.size() ) { scroll_offset = 0; } - if( filtered_registered_actions.empty() && action != "QUIT" ) { - continue; - } // In addition to the modifiable hotkeys, we also check for hardcoded // keys, e.g. '+', '-', '=', '.' in order to prevent the user from // entering an unrecoverable state. - if( action == "ADD_LOCAL" || raw_input_char == '+' ) { - status = s_add; - } else if( action == "ADD_GLOBAL" || raw_input_char == '=' ) { - status = s_add_global; - } else if( action == "REMOVE" || raw_input_char == '-' ) { - status = s_remove; - } else if( ( action == "EXECUTE" || raw_input_char == '.' ) && permit_execute_action ) { - status = s_execute; - } else if( action == "ANY_INPUT" ) { + if( action == "ADD_LOCAL" + || raw_input_char == fallback_keys.at( fallback_action::add_local ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_add; + } + } else if( action == "ADD_GLOBAL" + || raw_input_char == fallback_keys.at( fallback_action::add_global ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_add_global; + } + } else if( action == "REMOVE" + || raw_input_char == fallback_keys.at( fallback_action::remove ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_remove; + } + } else if( ( action == "EXECUTE" + || raw_input_char == fallback_keys.at( fallback_action::execute ) ) + && permit_execute_action ) { + if( !filtered_registered_actions.empty() ) { + status = s_execute; + } + } else if( action == "DOWN" ) { + if( !filtered_registered_actions.empty() + && filtered_registered_actions.size() > display_height + && scroll_offset < filtered_registered_actions.size() - display_height ) { + scroll_offset++; + } + } else if( action == "UP" ) { + if( !filtered_registered_actions.empty() + && scroll_offset > 0 ) { + scroll_offset--; + } + } else if( action == "PAGE_DOWN" ) { + if( filtered_registered_actions.empty() ) { + // do nothing + } else if( scroll_offset + display_height < filtered_registered_actions.size() ) { + scroll_offset += std::min( display_height, filtered_registered_actions.size() - + display_height - scroll_offset ); + } else if( filtered_registered_actions.size() > display_height ) { + scroll_offset = 0; + } + } else if( action == "PAGE_UP" ) { + if( filtered_registered_actions.empty() ) { + // do nothing + } if( scroll_offset >= display_height ) { + scroll_offset -= display_height; + } else if( scroll_offset > 0 ) { + scroll_offset = 0; + } else if( filtered_registered_actions.size() > display_height ) { + scroll_offset = filtered_registered_actions.size() - display_height; + } + } else if( action == "QUIT" ) { + if( status != s_show ) { + status = s_show; + } else { + break; + } + } else if( action == "HELP_KEYBINDINGS" ) { + // update available hotkeys in case they've changed + hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); + } else if( !filtered_registered_actions.empty() && status != s_show ) { const size_t hotkey_index = hotkeys.find_first_of( raw_input_char ); if( hotkey_index == std::string::npos ) { continue; @@ -1200,6 +1517,7 @@ action_id input_context::display_menu( const bool permit_execute_action ) popup( _( "There are already local keybindings defined for this action, please remove them first." ) ); } else if( status == s_add || status == s_add_global ) { const input_event new_event = query_popup() + .preferred_keyboard_mode( preferred_keyboard_mode ) .message( _( "New key for %s" ), name ) .allow_anykey( true ) .query() @@ -1240,39 +1558,6 @@ action_id input_context::display_menu( const bool permit_execute_action ) break; } status = s_show; - } else if( action == "DOWN" ) { - if( filtered_registered_actions.size() > display_height && - scroll_offset < filtered_registered_actions.size() - display_height ) { - scroll_offset++; - } - } else if( action == "UP" ) { - if( scroll_offset > 0 ) { - scroll_offset--; - } - } else if( action == "PAGE_DOWN" ) { - if( scroll_offset + display_height < filtered_registered_actions.size() ) { - scroll_offset += std::min( display_height, filtered_registered_actions.size() - - display_height - scroll_offset ); - } else if( filtered_registered_actions.size() > display_height ) { - scroll_offset = 0; - } - } else if( action == "PAGE_UP" ) { - if( scroll_offset >= display_height ) { - scroll_offset -= display_height; - } else if( scroll_offset > 0 ) { - scroll_offset = 0; - } else if( filtered_registered_actions.size() > display_height ) { - scroll_offset = filtered_registered_actions.size() - display_height; - } - } else if( action == "QUIT" ) { - if( status != s_show ) { - status = s_show; - } else { - break; - } - } else if( action == "HELP_KEYBINDINGS" ) { - // update available hotkeys in case they've changed - hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); } } @@ -1303,18 +1588,20 @@ int input_manager::get_previously_pressed_key() const void input_manager::wait_for_any_key() { #if defined(__ANDROID__) - input_context ctxt( "WAIT_FOR_ANY_KEY" ); + input_context ctxt( "WAIT_FOR_ANY_KEY", keyboard_mode::keycode ); #endif while( true ) { - const input_event evt = inp_mngr.get_input_event(); + const input_event evt = inp_mngr.get_input_event( keyboard_mode::keycode ); switch( evt.type ) { - case CATA_INPUT_KEYBOARD: + case input_event_t::keyboard_char: if( !evt.sequence.empty() ) { return; } break; + case input_event_t::keyboard_code: + return; // errors are accepted as well to avoid an infinite loop - case CATA_INPUT_ERROR: + case input_event_t::error: return; default: break; @@ -1402,13 +1689,21 @@ std::string input_context::press_x( const std::string &action_id, const std::str if( action_id == "COORDINATE" ) { return _( "mouse movement" ); } - const input_manager::t_input_event_list &events = inp_mngr.get_input_for_action( action_id, - category ); + input_manager::t_input_event_list events = inp_mngr.get_input_for_action( action_id, category ); + events.erase( std::remove_if( events.begin(), events.end(), [this]( const input_event & evt ) { + return !is_event_type_enabled( evt.type ); + } ), events.end() ); if( events.empty() ) { return key_unbound; } std::string keyed = key_bound_pre; for( size_t j = 0; j < events.size(); j++ ) { + // test in fixed order to generate consistent description + for( const auto &v : keymod_desc ) { + if( events[j].modifiers.count( v.first ) ) { + keyed += v.second.translated(); + } + } for( size_t k = 0; k < events[j].sequence.size(); ++k ) { keyed += inp_mngr.get_keyname( events[j].sequence[k], events[j].type ); } @@ -1458,3 +1753,22 @@ void input_context::reset_timeout() { timeout = -1; } + +bool input_context::is_event_type_enabled( const input_event_t type ) const +{ + switch( type ) { + case input_event_t::error: + return false; + case input_event_t::timeout: + return true; + case input_event_t::keyboard_char: + return preferred_keyboard_mode == keyboard_mode::keychar || !is_keycode_mode_supported(); + case input_event_t::keyboard_code: + return preferred_keyboard_mode == keyboard_mode::keycode && is_keycode_mode_supported(); + case input_event_t::gamepad: + return gamepad_available(); + case input_event_t::mouse: + return true; + } + return true; +} diff --git a/src/input.h b/src/input.h index 4b65c1e7b3b7..149f4a2eb813 100644 --- a/src/input.h +++ b/src/input.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,8 @@ namespace catacurses class window; } // namespace catacurses +// Curses key constants + static constexpr int KEY_ESCAPE = 27; static constexpr int KEY_MIN = 0x101; /* minimum extended key value */ //<---------not used @@ -64,6 +67,72 @@ static constexpr int KEY_ENTER = 0x157; /* enter */ static constexpr int KEY_BTAB = 0x161; /* back-tab = shift + tab */ static constexpr int KEY_END = 0x168; /* End */ +// Platform independent key code (though largely based on SDL key code) +// +// These and other code related to keyboard_code event should NOT be guarded with +// platform macros, since they are required to load and save keyboard_code events, +// which should always be done regardless whether the platform supports it, otherwise +// custom keybindings might be lost when someone switches e.g. from tiles to curses +// and save the keybindings, and switches back to tiles later. +namespace keycode +{ +enum : int { + backspace = 0x08, + tab = 0x09, + return_ = 0x0D, + escape = 0x1B, + space = 0x20, + f1 = 0x4000003A, + f2 = 0x4000003B, + f3 = 0x4000003C, + f4 = 0x4000003D, + f5 = 0x4000003E, + f6 = 0x4000003F, + f7 = 0x40000040, + f8 = 0x40000041, + f9 = 0x40000042, + f10 = 0x40000043, + f11 = 0x40000044, + f12 = 0x40000045, + home = 0x4000004A, + ppage = 0x4000004B, + end = 0x4000004D, + npage = 0x4000004E, + right = 0x4000004F, + left = 0x40000050, + down = 0x40000051, + up = 0x40000052, + kp_divide = 0x40000054, + kp_multiply = 0x40000055, + kp_minus = 0x40000056, + kp_plus = 0x40000057, + kp_enter = 0x40000058, + kp_1 = 0x40000059, + kp_2 = 0x4000005A, + kp_3 = 0x4000005B, + kp_4 = 0x4000005C, + kp_5 = 0x4000005D, + kp_6 = 0x4000005E, + kp_7 = 0x4000005F, + kp_8 = 0x40000060, + kp_9 = 0x40000061, + kp_0 = 0x40000062, + kp_period = 0x40000063, + f13 = 0x40000068, + f14 = 0x40000069, + f15 = 0x4000006A, + f16 = 0x4000006B, + f17 = 0x4000006C, + f18 = 0x4000006D, + f19 = 0x4000006E, + f20 = 0x4000006F, + f21 = 0x40000070, + f22 = 0x40000071, + f23 = 0x40000072, + f24 = 0x40000073, +}; +} // namespace keycode + static constexpr int LEGEND_HEIGHT = 11; static constexpr int BORDER_SPACE = 2; @@ -72,12 +141,21 @@ std::string get_input_string_from_file( const std::string &fname = "input.txt" ) enum mouse_buttons { MOUSE_BUTTON_LEFT = 1, MOUSE_BUTTON_RIGHT, SCROLLWHEEL_UP, SCROLLWHEEL_DOWN, MOUSE_MOVE }; -enum input_event_t { - CATA_INPUT_ERROR, - CATA_INPUT_TIMEOUT, - CATA_INPUT_KEYBOARD, - CATA_INPUT_GAMEPAD, - CATA_INPUT_MOUSE +enum class input_event_t : int { + error, + timeout, + // used on platforms with only character/text input, such as curses + keyboard_char, + // used on platforms with raw keycode input, such as non-android sdl + keyboard_code, + gamepad, + mouse +}; + +enum class keymod_t { + ctrl, + alt, + shift, }; /** @@ -91,7 +169,7 @@ enum input_event_t { struct input_event { input_event_t type; - std::vector modifiers; // Keys that need to be held down for + std::set modifiers; // Keys that need to be held down for // this event to be activated. std::vector sequence; // The sequence of key or mouse events that @@ -112,7 +190,7 @@ struct input_event { #endif input_event() : edit_refresh( false ) { - type = CATA_INPUT_ERROR; + type = input_event_t::error; #if defined(__ANDROID__) shortcut_last_used_action_counter = 0; #endif @@ -124,6 +202,7 @@ struct input_event { shortcut_last_used_action_counter = 0; #endif } + input_event( const std::set &mod, int s, input_event_t t ); int get_first_input() const; @@ -145,29 +224,7 @@ struct input_event { #endif bool operator==( const input_event &other ) const { - if( type != other.type ) { - return false; - } - - if( sequence.size() != other.sequence.size() ) { - return false; - } - for( size_t i = 0; i < sequence.size(); ++i ) { - if( sequence[i] != other.sequence[i] ) { - return false; - } - } - - if( modifiers.size() != other.modifiers.size() ) { - return false; - } - for( size_t i = 0; i < modifiers.size(); ++i ) { - if( modifiers[i] != other.modifiers[i] ) { - return false; - } - } - - return true; + return type == other.type && modifiers == other.modifiers && sequence == other.sequence; } }; @@ -204,6 +261,18 @@ struct action_attributes { #define JOY_LEFTUP (256 + 7) #define JOY_LEFTDOWN (256 + 8) +enum class keyboard_mode { + // Accept character input and text input. Input in this mode + // may be manipulated by the system via IMEs or dead keys. + keychar, + // Accept raw key code input. Text input is not available in this + // mode. All keyboard events are directly fed to the program + // in this mode, bypassing IMEs and dead keys. Only supported on + // some platforms, such as non-android SDL. On other platforms + // this falls back to `keychar` automatically. + keycode, +}; + /** * Manages the translation from action IDs to associated input. * @@ -253,7 +322,7 @@ class input_manager /** * Get the keycode associated with the given key name. */ - int get_keycode( const std::string &name ) const; + int get_keycode( input_event_t inp_type, const std::string &name ) const; /** * Get the key name associated with the given keyboard keycode. @@ -271,7 +340,7 @@ class input_manager * * Defined in the respective platform wrapper, e.g. sdlcurse.cpp */ - input_event get_input_event(); + input_event get_input_event( keyboard_mode preferred_keyboard_mode = keyboard_mode::keychar ); /** * Resize & refresh if necessary, process all pending window events, and ignore keypresses */ @@ -305,10 +374,15 @@ class input_manager t_action_contexts action_contexts; using t_key_to_name_map = std::map; - t_key_to_name_map keycode_to_keyname; + t_key_to_name_map keyboard_char_keycode_to_keyname; + t_key_to_name_map keyboard_code_keycode_to_keyname; t_key_to_name_map gamepad_keycode_to_keyname; + t_key_to_name_map mouse_keycode_to_keyname; using t_name_to_key_map = std::map; - t_name_to_key_map keyname_to_keycode; + t_name_to_key_map keyboard_char_keyname_to_keycode; + t_name_to_key_map keyboard_code_keyname_to_keycode; + t_name_to_key_map gamepad_keyname_to_keycode; + t_name_to_key_map mouse_keyname_to_keycode; // See @ref get_previously_pressed_key int previously_pressed_key; @@ -316,8 +390,10 @@ class input_manager // Maps the key names we see in keybindings.json and in-game to // the keycode integers. void init_keycode_mapping(); - void add_keycode_pair( int ch, const std::string &name ); + void add_keyboard_char_keycode_pair( int ch, const std::string &name ); + void add_keyboard_code_keycode_pair( int ch, const std::string &name ); void add_gamepad_keycode_pair( int ch, const std::string &name ); + void add_mouse_keycode_pair( int ch, const std::string &name ); /** * Load keybindings from a json file, override existing bindings. @@ -396,8 +472,11 @@ class input_context } // TODO: consider making the curses WINDOW an argument to the constructor, so that mouse input // outside that window can be ignored - input_context( const std::string &category ) : registered_any_input( false ), - category( category ), coordinate_input_received( false ), handling_coordinate_input( false ) { + input_context( const std::string &category, + const keyboard_mode preferred_keyboard_mode = keyboard_mode::keycode ) + : registered_any_input( false ), category( category ), + coordinate_input_received( false ), handling_coordinate_input( false ), + preferred_keyboard_mode( preferred_keyboard_mode ) { #if defined(__ANDROID__) input_context_stack.push_back( this ); allow_text_entry = false; @@ -574,7 +653,16 @@ class input_context * @param text The base text for action description * * @param evt_filter Only keys satisfying this function will be considered + * @param inline_fmt Action description format when a key is found in the + * text (for example "(a)ctive") + * @param separate_fmt Action description format when a key is not found + * in the text (for example "[X] active" or "[N/A] active") */ + std::string get_desc( const std::string &action_descriptor, + const std::string &text, + const input_event_filter &evt_filter, + const translation &inline_fmt, + const translation &separate_fmt ) const; std::string get_desc( const std::string &action_descriptor, const std::string &text, const input_event_filter &evt_filter = allow_all_keys ) const; @@ -680,7 +768,7 @@ class input_context * Sets input polling timeout as appropriate for the current interface system. * Use this method to set timeouts when using input_context, rather than calling * the old timeout() method or using input_manager::(re)set_timeout, as using - * this method will cause CATA_INPUT_TIMEOUT events to be generated correctly, + * this method will cause input_event_t::timeout events to be generated correctly, * and will reset timeout correctly when a new input context is entered. */ void set_timeout( int val ); @@ -700,6 +788,7 @@ class input_context input_event next_action; bool iso_mode = false; // should this context follow the game's isometric settings? int timeout = -1; + keyboard_mode preferred_keyboard_mode = keyboard_mode::keycode; /** * When registering for actions within an input_context, callers can @@ -709,6 +798,8 @@ class input_context */ std::map action_name_overrides; + bool is_event_type_enabled( input_event_t type ) const; + /** * Returns whether action uses the specified input */ diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index 7bbd39e53975..e3f15af6a6cb 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -6,7 +6,6 @@ #include "character.h" #include "debug.h" #include "game.h" -#include "ime.h" #include "inventory.h" #include "item.h" #include "item_category.h" @@ -1553,7 +1552,6 @@ void inventory_selector::set_filter() current_ui->mark_resize(); } - ime_sentry sentry; do { ui_manager::redraw(); @@ -1687,7 +1685,7 @@ void inventory_selector::draw_footer( const catacurses::window &w ) const inventory_selector::inventory_selector( player &u, const inventory_selector_preset &preset ) : u( u ) , preset( preset ) - , ctxt( "INVENTORY" ) + , ctxt( "INVENTORY", keyboard_mode::keychar ) , active_column_index( 0 ) , mode( navigation_mode::ITEM ) , own_inv_column( preset ) diff --git a/src/item.cpp b/src/item.cpp index 4850df785ea5..fdce9ba2634a 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1313,8 +1313,8 @@ item::sizing item::get_sizing( const Character &p ) const if( to_ignore ) { return sizing::ignore; } else { - const bool small = p.get_size() == MS_TINY; - const bool big = p.get_size() == MS_HUGE; + const bool small = p.get_size() == creature_size::tiny; + const bool big = p.get_size() == creature_size::huge; // due to the iterative nature of these features, something can fit and be undersized/oversized // but that is fine because we have separate logic to adjust encumberance per each. One day we diff --git a/src/item.h b/src/item.h index 0869959e4005..ffe99649022a 100644 --- a/src/item.h +++ b/src/item.h @@ -77,7 +77,6 @@ struct use_function; enum art_effect_passive : int; enum phase_id : int; enum body_part : int; -enum m_size : int; enum class side : int; class body_part_set; class map; diff --git a/src/item_action.cpp b/src/item_action.cpp index 859b172c2f7a..61975ab0be68 100644 --- a/src/item_action.cpp +++ b/src/item_action.cpp @@ -254,7 +254,7 @@ void game::item_action_menu() uilist kmenu; kmenu.text = _( "Execute which action?" ); kmenu.input_category = "ITEM_ACTIONS"; - input_context ctxt( "ITEM_ACTIONS" ); + input_context ctxt( "ITEM_ACTIONS", keyboard_mode::keychar ); for( const auto &id : item_actions ) { ctxt.register_action( id.first, id.second.name ); kmenu.additional_actions.emplace_back( id.first, id.second.name ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 804febc1bda0..4b4a706fbf48 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -7440,7 +7440,8 @@ int iuse::camera( player *p, item *it, bool, const tripoint & ) monster &z = *mon; // shoot past small monsters and hallucinations - if( trajectory_point != aim_point && ( z.type->size <= MS_SMALL || z.is_hallucination() || + if( trajectory_point != aim_point && ( z.type->size <= creature_size::small || + z.is_hallucination() || z.type->in_species( HALLUCINATION ) ) ) { continue; } @@ -9173,13 +9174,13 @@ int iuse::capture_monster_act( player *p, item *it, bool, const tripoint &pos ) return 0; } } else { - if( !it->has_property( "monster_size_capacity" ) ) { - debugmsg( "%s has no monster_size_capacity.", it->tname() ); + if( !it->has_property( "creature_size_capacity" ) ) { + debugmsg( "%s has no creature_size_capacity.", it->tname() ); return 0; } - const std::string capacity = it->get_property_string( "monster_size_capacity" ); + const std::string capacity = it->get_property_string( "creature_size_capacity" ); if( Creature::size_map.count( capacity ) == 0 ) { - debugmsg( "%s has invalid monster_size_capacity %s.", + debugmsg( "%s has invalid creature_size_capacity %s.", it->tname(), capacity.c_str() ); return 0; } diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 5cd44e6b4c98..84ad8ad7557a 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -3261,7 +3261,7 @@ bool repair_item_actor::can_repair_target( player &pl, const item &fix, } const bool resizing_matters = fix.get_sizing( pl ) != item::sizing::ignore; - const bool small = pl.get_size() == MS_TINY; + const bool small = pl.get_size() == creature_size::tiny; const bool can_resize = small != fix.has_flag( flag_UNDERSIZE ); if( can_be_refitted && resizing_matters && can_resize ) { return true; @@ -3344,7 +3344,7 @@ repair_item_actor::repair_type repair_item_actor::default_action( const item &fi } Character &player_character = get_player_character(); - const bool smol = player_character.get_size() == MS_TINY; + const bool smol = player_character.get_size() == creature_size::tiny; const bool is_undersized = fix.has_flag( flag_UNDERSIZE ); const bool is_oversized = fix.has_flag( flag_OVERSIZE ); diff --git a/src/loading_ui.cpp b/src/loading_ui.cpp index abcd927fa605..5e5c8a120ab6 100644 --- a/src/loading_ui.cpp +++ b/src/loading_ui.cpp @@ -49,8 +49,8 @@ void loading_ui::init() menu->reposition( ui ); } ); menu->reposition( *ui ); - ui->on_redraw( [this]( const ui_adaptor & ) { - menu->show(); + ui->on_redraw( [this]( ui_adaptor & ui ) { + menu->show( ui ); } ); } } diff --git a/src/main_menu.h b/src/main_menu.h index 12eada5537bb..31c22ec06afe 100644 --- a/src/main_menu.h +++ b/src/main_menu.h @@ -17,7 +17,7 @@ class main_menu { friend class sound_on_move_uilist_callback; public: - main_menu() : ctxt( "MAIN_MENU" ) { } + main_menu() : ctxt( "MAIN_MENU", keyboard_mode::keychar ) { } // Shows the main menu and returns whether a game was started or not bool opening_screen(); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 4d49e6d49219..254b5d0aee96 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6072,7 +6072,7 @@ void map::draw_connections( const mapgendata &dat ) } // finally, any terrain with SIDEWALKS should contribute sidewalks to neighboring diagonal roads - if( terrain_type->has_flag( has_sidewalk ) ) { + if( terrain_type->has_flag( oter_flags::has_sidewalk ) ) { for( int dir = 4; dir < 8; dir++ ) { // NE SE SW NW bool n_roads_nesw[4] = {}; int n_num_dirs = terrain_type_to_nesw_array( oter_id( dat.t_nesw[dir] ), n_roads_nesw ); diff --git a/src/mapgen_functions.cpp b/src/mapgen_functions.cpp index d3fb2eccf643..b3145931ce84 100644 --- a/src/mapgen_functions.cpp +++ b/src/mapgen_functions.cpp @@ -492,7 +492,7 @@ void mapgen_road( mapgendata &dat ) int neighbor_sidewalks = 0; // N E S W NE SE SW NW for( int dir = 0; dir < 8; dir++ ) { - sidewalks_neswx[dir] = dat.t_nesw[dir]->has_flag( has_sidewalk ); + sidewalks_neswx[dir] = dat.t_nesw[dir]->has_flag( oter_flags::has_sidewalk ); neighbor_sidewalks += sidewalks_neswx[dir]; } @@ -841,7 +841,7 @@ void mapgen_subway( mapgendata &dat ) // N E S W for( int dir = 0; dir < 4; dir++ ) { - if( dat.t_nesw[dir]->has_flag( subway_connection ) && !subway_nesw[dir] ) { + if( dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) && !subway_nesw[dir] ) { num_dirs++; subway_nesw[dir] = true; } @@ -856,7 +856,7 @@ void mapgen_subway( mapgendata &dat ) } if( dat.t_nesw[dir]->get_type_id().str() != "subway" && - !dat.t_nesw[dir]->has_flag( subway_connection ) ) { + !dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) ) { continue; } // n_* contain details about the neighbor being considered @@ -864,7 +864,7 @@ void mapgen_subway( mapgendata &dat ) // TODO: figure out how to call this function without creating a new oter_id object int n_num_dirs = terrain_type_to_nesw_array( dat.t_nesw[dir], n_subway_nesw ); for( int dir = 0; dir < 4; dir++ ) { - if( dat.t_nesw[dir]->has_flag( subway_connection ) && !n_subway_nesw[dir] ) { + if( dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) && !n_subway_nesw[dir] ) { n_num_dirs++; n_subway_nesw[dir] = true; } diff --git a/src/melee.cpp b/src/melee.cpp index 52b140360fb6..ca4829f19483 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -668,7 +668,7 @@ void Character::reach_attack( const tripoint &p ) map &here = get_map(); Creature *critter = g->critter_at( p ); // Original target size, used when there are monsters in front of our target - int target_size = critter != nullptr ? ( critter->get_size() + 1 ) : 2; + const int target_size = critter != nullptr ? static_cast( critter->get_size() + 1 ) : 2; // Reset last target pos as_player()->last_target_pos = std::nullopt; // Max out recoil diff --git a/src/messages.cpp b/src/messages.cpp index 9b3d4e21cf58..76f278eb621e 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -6,7 +6,6 @@ #include "debug.h" #include "enums.h" #include "game.h" -#include "ime.h" #include "input.h" #include "json.h" #include "output.h" @@ -464,7 +463,6 @@ class dialog bool canceled = false; bool errored = false; - std::optional filter_sentry; bool first_init = true; }; @@ -709,9 +707,6 @@ void Messages::dialog::input( const ui_adaptor &ui ) filter.query( false ); if( filter.confirmed() || filter.canceled() ) { filtering = false; - if( filter_sentry ) { - disable_ime(); - } } if( !filter.canceled() ) { const std::string &new_filter_str = filter.text(); @@ -745,13 +740,6 @@ void Messages::dialog::input( const ui_adaptor &ui ) } } else if( action == "FILTER" ) { filtering = true; - if( filter_sentry ) { - enable_ime(); - } else { - // this implies enable_ime() and ensures that the ime mode is always - // restored when closing the dialog if at least filtered once - filter_sentry.emplace(); - } } else if( action == "RESET_FILTER" ) { filter_str.clear(); filter.text( filter_str ); diff --git a/src/monattack.cpp b/src/monattack.cpp index 2a676391c30e..5eef3c264b1a 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -2281,21 +2281,21 @@ static bool blobify( monster &blob, monster &target ) } switch( target.get_size() ) { - case MS_TINY: + case creature_size::tiny: // Just consume it target.set_hp( 0 ); blob.set_speed_base( blob.get_speed_base() + 5 ); return false; - case MS_SMALL: + case creature_size::small: target.poly( mon_blob_small ); break; - case MS_MEDIUM: + case creature_size::medium: target.poly( mon_blob ); break; - case MS_LARGE: + case creature_size::large: target.poly( mon_blob_large ); break; - case MS_HUGE: + case creature_size::huge: // No polymorphing huge stuff target.add_effect( effect_slimed, rng( 2_turns, 10_turns ) ); break; diff --git a/src/mondeath.cpp b/src/mondeath.cpp index 220a4ef06338..75f4f0cd8f86 100644 --- a/src/mondeath.cpp +++ b/src/mondeath.cpp @@ -210,7 +210,7 @@ void mdeath::splatter( monster &z ) const auto area = here.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); - if( pulverized && z.type->size >= MS_MEDIUM ) { + if( pulverized && z.type->size >= creature_size::medium ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } @@ -587,19 +587,19 @@ void mdeath::explode( monster &z ) { int size = 0; switch( z.type->size ) { - case MS_TINY: + case creature_size::tiny: size = 4; break; - case MS_SMALL: + case creature_size::small: size = 8; break; - case MS_MEDIUM: + case creature_size::medium: size = 14; break; - case MS_LARGE: + case creature_size::large: size = 20; break; - case MS_HUGE: + case creature_size::huge: size = 26; break; default: diff --git a/src/monmove.cpp b/src/monmove.cpp index 647eb92e5f2a..60a5d4f2e75a 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -150,7 +150,7 @@ bool monster::will_move_to( const tripoint &p ) const return false; } - if( get_size() > MS_MEDIUM && g->m.has_flag_ter( TFLAG_SMALL_PASSAGE, p ) ) { + if( get_size() > creature_size::medium && g->m.has_flag_ter( TFLAG_SMALL_PASSAGE, p ) ) { return false; // if a large critter, can't move through tight passages } @@ -191,7 +191,7 @@ bool monster::will_move_to( const tripoint &p ) const } // Don't enter open pits ever unless tiny, can fly or climb well - if( !( type->size == MS_TINY || can_climb() ) && + if( !( type->size == creature_size::tiny || can_climb() ) && ( target == t_pit || target == t_pit_spiked || target == t_pit_glass ) ) { return false; } @@ -201,7 +201,7 @@ bool monster::will_move_to( const tripoint &p ) const if( attitude( &g->u ) != MATT_ATTACK ) { // Sharp terrain is ignored while attacking if( avoid_simple && g->m.has_flag( "SHARP", p ) && - !( type->size == MS_TINY || flies() ) ) { + !( type->size == creature_size::tiny || flies() ) ) { return false; } } @@ -1203,18 +1203,18 @@ void monster::footsteps( const tripoint &p ) volume = 10; } switch( type->size ) { - case MS_TINY: + case creature_size::tiny: volume = 0; // No sound for the tinies break; - case MS_SMALL: + case creature_size::small: volume /= 3; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: volume *= 1.5; break; - case MS_HUGE: + case creature_size::huge: volume *= 2; break; default: @@ -1689,7 +1689,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, return true; } - if( type->size != MS_TINY && on_ground ) { + if( type->size != creature_size::tiny && on_ground ) { const int sharp_damage = rng( 1, 10 ); const int rough_damage = rng( 1, 2 ); if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) && @@ -1725,19 +1725,19 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, if( digging() && g->m.has_flag( "DIGGABLE", pos() ) ) { int factor = 0; switch( type->size ) { - case MS_TINY: + case creature_size::tiny: factor = 100; break; - case MS_SMALL: + case creature_size::small: factor = 30; break; - case MS_MEDIUM: + case creature_size::medium: factor = 6; break; - case MS_LARGE: + case creature_size::large: factor = 3; break; - case MS_HUGE: + case creature_size::huge: factor = 1; break; default: @@ -1989,14 +1989,14 @@ void monster::knock_back_to( const tripoint &to ) // First, see if we hit another monster if( monster *const z = g->critter_at( to ) ) { - apply_damage( z, bodypart_id( "torso" ), z->type->size ); + apply_damage( z, bodypart_id( "torso" ), static_cast( z->type->size ) ); add_effect( effect_stunned, 1_turns ); if( type->size > 1 + z->type->size ) { z->knock_back_from( pos() ); // Chain reaction! - z->apply_damage( this, bodypart_id( "torso" ), type->size ); + z->apply_damage( this, bodypart_id( "torso" ), static_cast( type->size ) ); z->add_effect( effect_stunned, 1_turns ); } else if( type->size > z->type->size ) { - z->apply_damage( this, bodypart_id( "torso" ), type->size ); + z->apply_damage( this, bodypart_id( "torso" ), static_cast( type->size ) ); z->add_effect( effect_stunned, 1_turns ); } z->check_dead_state(); @@ -2011,7 +2011,8 @@ void monster::knock_back_to( const tripoint &to ) if( npc *const p = g->critter_at( to ) ) { apply_damage( p, bodypart_id( "torso" ), 3 ); add_effect( effect_stunned, 1_turns ); - p->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, type->size ) ); + p->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( type->size ) ) ); if( u_see ) { add_msg( _( "The %1$s bounces off %2$s!" ), name(), p->name ); } @@ -2033,7 +2034,7 @@ void monster::knock_back_to( const tripoint &to ) if( g->m.impassable( to ) ) { // It's some kind of wall. - apply_damage( nullptr, bodypart_id( "torso" ), type->size ); + apply_damage( nullptr, bodypart_id( "torso" ), static_cast( type->size ) ); add_effect( effect_stunned, 2_turns ); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s." ), name(), @@ -2131,10 +2132,10 @@ void monster::shove_vehicle( const tripoint &remote_destination, float shove_damage_min = 0.00F; float shove_damage_max = 0.00F; switch( this->get_size() ) { - case MS_TINY: - case MS_SMALL: + case creature_size::tiny: + case creature_size::small: break; - case MS_MEDIUM: + case creature_size::medium: if( veh_mass < 500_kilogram ) { shove_moves_minimal = 150; shove_veh_mass_moves_factor = 20; @@ -2143,7 +2144,7 @@ void monster::shove_vehicle( const tripoint &remote_destination, shove_damage_max = 0.01F; } break; - case MS_LARGE: + case creature_size::large: if( veh_mass < 1000_kilogram ) { shove_moves_minimal = 100; shove_veh_mass_moves_factor = 8; @@ -2152,7 +2153,7 @@ void monster::shove_vehicle( const tripoint &remote_destination, shove_damage_max = 0.03F; } break; - case MS_HUGE: + case creature_size::huge: if( veh_mass < 2000_kilogram ) { shove_moves_minimal = 50; shove_veh_mass_moves_factor = 4; diff --git a/src/monster.cpp b/src/monster.cpp index 2505be65216e..ba3dc32634aa 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -137,12 +137,12 @@ struct pathfinding_settings; // The rough formula is 2^(-x), e.g. for x = 5 it's 0.03125 (~ 3%). #define UPGRADE_MAX_ITERS 5 -static const std::map size_names { - { m_size::MS_TINY, to_translation( "size adj", "tiny" ) }, - { m_size::MS_SMALL, to_translation( "size adj", "small" ) }, - { m_size::MS_MEDIUM, to_translation( "size adj", "medium" ) }, - { m_size::MS_LARGE, to_translation( "size adj", "large" ) }, - { m_size::MS_HUGE, to_translation( "size adj", "huge" ) }, +static const std::map size_names { + { creature_size::tiny, to_translation( "size adj", "tiny" ) }, + { creature_size::small, to_translation( "size adj", "small" ) }, + { creature_size::medium, to_translation( "size adj", "medium" ) }, + { creature_size::large, to_translation( "size adj", "large" ) }, + { creature_size::huge, to_translation( "size adj", "huge" ) }, }; static const std::map> attitude_names { @@ -2229,16 +2229,16 @@ float monster::stability_roll() const { int size_bonus = 0; switch( type->size ) { - case MS_TINY: + case creature_size::tiny: size_bonus -= 7; break; - case MS_SMALL: + case creature_size::small: size_bonus -= 3; break; - case MS_LARGE: + case creature_size::large: size_bonus += 5; break; - case MS_HUGE: + case creature_size::huge: size_bonus += 10; break; default: @@ -2293,15 +2293,15 @@ float monster::fall_damage_mod() const } switch( type->size ) { - case MS_TINY: + case creature_size::tiny: return 0.2f; - case MS_SMALL: + case creature_size::small: return 0.6f; - case MS_MEDIUM: + case creature_size::medium: return 1.0f; - case MS_LARGE: + case creature_size::large: return 1.4f; - case MS_HUGE: + case creature_size::huge: return 2.0f; default: return 1.0f; @@ -2987,9 +2987,9 @@ field_type_id monster::gibType() const return type->gibType(); } -m_size monster::get_size() const +creature_size monster::get_size() const { - return m_size( type->size ); + return creature_size( type->size ); } units::mass monster::get_weight() const diff --git a/src/monster.h b/src/monster.h index e333bd365788..aa1adf5e6b69 100644 --- a/src/monster.h +++ b/src/monster.h @@ -128,7 +128,7 @@ class monster : public Creature, public location_visitable void reproduce(); void refill_udders(); void spawn( const tripoint &p ); - m_size get_size() const override; + creature_size get_size() const override; units::mass get_weight() const override; units::mass weight_capacity() const override; units::volume get_volume() const; diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 0de5b41c0231..fa1062d88478 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -265,18 +265,18 @@ static int calc_bash_skill( const mtype &t ) return ret; } -static m_size volume_to_size( const units::volume &vol ) +static creature_size volume_to_size( const units::volume &vol ) { if( vol <= 7500_ml ) { - return MS_TINY; + return creature_size::tiny; } else if( vol <= 46250_ml ) { - return MS_SMALL; + return creature_size::small; } else if( vol <= 77500_ml ) { - return MS_MEDIUM; + return creature_size::medium; } else if( vol <= 483750_ml ) { - return MS_LARGE; + return creature_size::large; } - return MS_HUGE; + return creature_size::huge; } struct monster_adjustment { diff --git a/src/mtype.cpp b/src/mtype.cpp index e0d784cb0188..270a245d9642 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -31,7 +31,7 @@ mtype::mtype() name = pl_translation( "human", "humans" ); sym = " "; color = c_white; - size = MS_MEDIUM; + size = creature_size::medium; volume = 62499_ml; weight = 81499_gram; mat = { material_id( "flesh" ) }; diff --git a/src/mtype.h b/src/mtype.h index 2896a451d1f1..48d71aaa5df1 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -27,7 +27,7 @@ struct species_type; template struct enum_traits; enum body_part : int; -enum m_size : int; +enum creature_size : int; using mon_action_death = void ( * )( monster & ); using mon_action_attack = bool ( * )( monster * ); @@ -73,7 +73,7 @@ enum m_flag : int { MF_STUMBLES, // Stumbles in its movement MF_WARM, // Warm blooded MF_NOHEAD, // Headshots not allowed! - MF_HARDTOSHOOT, // It's one size smaller for ranged attacks, no less then MS_TINY + MF_HARDTOSHOOT, // It's one size smaller for ranged attacks, no less then creature_size::tiny MF_GRABS, // Its attacks may grab us! MF_BASHES, // Bashes down doors MF_DESTROYS, // Bashes down walls and more @@ -262,7 +262,7 @@ struct mtype { mfaction_id default_faction; bodytype_id bodytype; nc_color color = c_white; - m_size size; + creature_size size; units::volume volume; units::mass weight; phase_id phase; diff --git a/src/mutation.cpp b/src/mutation.cpp index 5440b5f48c5c..1c5e18d9a83a 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -238,7 +238,7 @@ const resistances &mutation_branch::damage_resistance( body_part bp ) const void Character::recalculate_size() { - size_class = MS_MEDIUM; + size_class = creature_size::medium; // Only one size-changing mutation is expected, so it will only use the first one it finds. for( const mutation_branch *mut : cached_mutations ) { if( mut->body_size ) { diff --git a/src/mutation.h b/src/mutation.h index eb173e458c35..8bfd1207e298 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -227,7 +227,7 @@ struct mutation_branch { std::set no_cbm_on_bp; // Body size from mutations, e.g. large, small, etc. - std::optional body_size; + std::optional body_size; // amount of mana added or subtracted from max float mana_modifier = 0.0f; diff --git a/src/mutation_ui.cpp b/src/mutation_ui.cpp index 81a8c927dc71..7b717fb47896 100644 --- a/src/mutation_ui.cpp +++ b/src/mutation_ui.cpp @@ -176,6 +176,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) int second_column = 0; int scroll_position = 0; + int examine_pos = 0; int cursor = 0; int max_scroll_position = 0; int list_height = 0; @@ -222,6 +223,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) point( START.x + 1, TITLE_START_Y ) ); recalc_max_scroll_position(); + examine_pos = 0; // X-coordinate of the list of active mutations second_column = 32 + ( TERMX - FULL_SCREEN_WIDTH ) / 4; @@ -230,13 +232,15 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) } ); ui.mark_resize(); - input_context ctxt( "MUTATIONS" ); + input_context ctxt( "MUTATIONS", keyboard_mode::keychar ); ctxt.register_updown(); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "TOGGLE_EXAMINE" ); ctxt.register_action( "REASSIGN" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "SCROLL_TRAIT_INFO_UP" ); + ctxt.register_action( "SCROLL_TRAIT_INFO_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "QUIT" ); @@ -336,12 +340,23 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) draw_scrollbar( wBio, scroll_position, list_height, mutations_count, point( 0, list_start_y ), c_white, true ); - wnoutrefresh( wBio ); - show_mutations_titlebar( w_title, menu_mode, ctxt ); if( menu_mode == mutation_menu_mode::examining && examine_id.has_value() ) { werase( w_description ); - fold_and_print( w_description, point_zero, WIDTH - 2, c_light_blue, examine_id.value()->desc() ); + std::vector desc = foldstring( examine_id.value() ->desc(), WIDTH - 2 ); + const int winh = catacurses::getmaxy( w_description ); + const bool do_scroll = desc.size() > static_cast( std::abs( winh ) ); + const int fline = do_scroll ? examine_pos % ( desc.size() + 1 - winh ) : 0; + const int lline = do_scroll ? fline + winh : desc.size(); + for( int i = fline; i < lline; i++ ) { + trim_and_print( w_description, point( 0, i - fline ), WIDTH - 2, c_light_blue, desc[i] ); + } + draw_scrollbar( wBio, fline, winh, desc.size(), point( 0, catacurses::getmaxy( wBio ) - winh - 1 ), + c_white, true ); + } + wnoutrefresh( wBio ); + show_mutations_titlebar( w_title, menu_mode, ctxt ); + if( menu_mode == mutation_menu_mode::examining && examine_id.has_value() ) { wnoutrefresh( w_description ); } } ); @@ -354,7 +369,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) bool handled = false; const std::string action = ctxt.handle_input(); const input_event evt = ctxt.get_raw_input(); - if( evt.type == CATA_INPUT_KEYBOARD && !evt.sequence.empty() ) { + if( evt.type == input_event_t::keyboard_char && !evt.sequence.empty() ) { const int ch = evt.get_first_input(); if( ch == ' ' ) { //skip if space is pressed (space is used as an empty hotkey) continue; @@ -367,6 +382,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) query_popup pop; pop.message( _( "%s; enter new letter." ), mutation_branch::get_name( *mut_id ) ) + .preferred_keyboard_mode( keyboard_mode::keychar ) .context( "POPUP_WAIT" ) .allow_cancel( true ) .allow_anykey( true ); @@ -375,7 +391,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) while( !pop_exit ) { const query_popup::result ret = pop.query(); bool pop_handled = false; - if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { + if( ret.evt.type == input_event_t::keyboard_char && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { const std::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); @@ -392,7 +408,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) if( ret.action == "QUIT" ) { pop_exit = true; } else if( ret.action != "HELP_KEYBINDINGS" && - ret.evt.type == CATA_INPUT_KEYBOARD ) { + ret.evt.type == input_event_t::keyboard_char ) { popup( _( "Invalid mutation letter. Only those characters are valid:\n\n%s" ), mutation_chars.get_allowed_chars() ); } @@ -474,6 +490,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) if( scroll_position > 0 && cursor - scroll_position < half_list_view_location ) { scroll_position = std::max( cursor - half_list_view_location, 0 ); } + examine_pos = 0; // Draw the description, shabby workaround examine_id = GetTrait( active, passive, cursor, tab_mode ); @@ -502,8 +519,12 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) std::max( std::min( lim + 1 - list_height, cursor - half_list_view_location ), 0 ); } + examine_pos = 0; examine_id = GetTrait( active, passive, cursor, tab_mode ); + } else if( ( action == "SCROLL_TRAIT_INFO_UP" || action == "SCROLL_TRAIT_INFO_DOWN" ) && + menu_mode == mutation_menu_mode::examining ) { + examine_pos += action == "SCROLL_TRAIT_INFO_UP" ? -1 : 1; } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) { if( tab_mode == mutation_tab_mode::active && !passive.empty() ) { tab_mode = mutation_tab_mode::passive; @@ -514,6 +535,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) } examine_id = GetTrait( active, passive, cursor, tab_mode ); scroll_position = 0; + examine_pos = 0; cursor = 0; } else if( action == "CONFIRM" ) { trait_id mut_id; @@ -539,7 +561,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) while( !pop_exit ) { const query_popup::result ret = pop.query(); bool pop_handled = false; - if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { + if( ret.evt.type == input_event_t::keyboard_char && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { const std::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); @@ -560,7 +582,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) if( ret.action == "QUIT" ) { pop_exit = true; } else if( ret.action != "HELP_KEYBINDINGS" && - ret.evt.type == CATA_INPUT_KEYBOARD ) { + ret.evt.type == input_event_t::keyboard_char ) { popup( _( "Invalid mutation letter. Only those characters are valid:\n\n%s" ), mutation_chars.get_allowed_chars() ); } @@ -606,7 +628,12 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) } } else if( action == "REASSIGN" ) { menu_mode = mutation_menu_mode::reassigning; - examine_id = std::nullopt; + if( menu_mode == mutation_menu_mode::examining ) { + examine_id = GetTrait( active, passive, cursor, tab_mode ); + } else { + examine_id = std::nullopt; + } + examine_pos = 0; } else if( action == "TOGGLE_EXAMINE" ) { // switches between activation and examination menu_mode = menu_mode == mutation_menu_mode::activating ? diff --git a/src/ncurses_def.cpp b/src/ncurses_def.cpp index 5b7d9cfe8189..883169942473 100644 --- a/src/ncurses_def.cpp +++ b/src/ncurses_def.cpp @@ -215,6 +215,7 @@ void catacurses::init_pair( const short pair, const base_color f, const base_col OK, "init_pair" ); } +catacurses::window catacurses::newscr; catacurses::window catacurses::stdscr; void catacurses::resizeterm() @@ -237,6 +238,10 @@ void catacurses::init_interface() if( !stdscr ) { throw std::runtime_error( "initscr failed" ); } + newscr = window( std::shared_ptr( ::newscr, []( void *const ) { } ) ); + if( !newscr ) { + throw std::runtime_error( "null newscr" ); + } #if !defined(__CYGWIN__) // ncurses mouse registration mousemask( BUTTON1_CLICKED | BUTTON3_CLICKED | REPORT_MOUSE_POSITION, nullptr ); @@ -277,7 +282,9 @@ void input_manager::pump_events() previously_pressed_key = 0; } -input_event input_manager::get_input_event() +// there isn't a portable way to get raw key code on curses, +// ignoring preferred keyboard mode +input_event input_manager::get_input_event( const keyboard_mode /*preferred_keyboard_mode*/ ) { int key = ERR; input_event rval; @@ -303,9 +310,9 @@ input_event input_manager::get_input_event() rval = input_event(); if( key == ERR ) { if( input_timeout > 0 ) { - rval.type = CATA_INPUT_TIMEOUT; + rval.type = input_event_t::timeout; } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } // ncurses mouse handling } else if( key == KEY_RESIZE ) { @@ -313,7 +320,7 @@ input_event input_manager::get_input_event() } else if( key == KEY_MOUSE ) { MEVENT event; if( getmouse( &event ) == OK ) { - rval.type = CATA_INPUT_MOUSE; + rval.type = input_event_t::mouse; rval.mouse_pos = point( event.x, event.y ); if( event.bstate & BUTTON1_CLICKED ) { rval.add_input( MOUSE_BUTTON_LEFT ); @@ -326,17 +333,17 @@ input_event input_manager::get_input_event() set_timeout( input_timeout ); } } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { if( key == 127 ) { // == Unicode DELETE previously_pressed_key = KEY_BACKSPACE; - return input_event( KEY_BACKSPACE, CATA_INPUT_KEYBOARD ); + return input_event( KEY_BACKSPACE, input_event_t::keyboard_char ); } - rval.type = CATA_INPUT_KEYBOARD; + rval.type = input_event_t::keyboard_char; rval.text.append( 1, static_cast( key ) ); // Read the UTF-8 sequence (if any) if( key < 127 ) { @@ -354,7 +361,7 @@ input_event input_manager::get_input_event() // Other control character, etc. - no text at all, return an event // without the text property previously_pressed_key = key; - return input_event( key, CATA_INPUT_KEYBOARD ); + return input_event( key, input_event_t::keyboard_char ); } // Now we have loaded an UTF-8 sequence (possibly several bytes) // but we should only return *one* key, so return the code point of it. @@ -363,7 +370,7 @@ input_event input_manager::get_input_event() // Invalid UTF-8 sequence, this should never happen, what now? // Maybe return any error instead? previously_pressed_key = key; - return input_event( key, CATA_INPUT_KEYBOARD ); + return input_event( key, input_event_t::keyboard_char ); } previously_pressed_key = cp; // for compatibility only add the first byte, not the code point @@ -378,7 +385,7 @@ input_event input_manager::get_input_event() void input_manager::set_timeout( const int delay ) { timeout( delay ); - // Use this to determine when curses should return a CATA_INPUT_TIMEOUT event. + // Use this to determine when curses should return a input_event_t::timeout event. input_timeout = delay; } diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index 5382417bc0db..19f847b69ba8 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -30,7 +30,6 @@ #include "fstream_utils.h" #include "game.h" #include "game_constants.h" -#include "ime.h" #include "input.h" #include "int_id.h" #include "inventory.h" @@ -147,7 +146,7 @@ static matype_id choose_ma_style( const character_type type, const std::vector #include #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "color.h" #include "numeric_interval.h" #include "coordinates.h" +#include "enum_bitset.h" #include "int_id.h" #include "om_direction.h" #include "mapgen_parameter.h" @@ -84,7 +84,7 @@ struct overmap_static_spawns : public overmap_spawns { }; //terrain flags enum! this is for tracking the indices of each flag. -enum oter_flags { +enum class oter_flags : int { known_down = 0, known_up, no_rotate, // this tile doesn't have four rotated versions (north, east, south, west) @@ -123,6 +123,11 @@ enum oter_flags { num_oter_flags }; +template<> +struct enum_traits { + static constexpr auto last = oter_flags::num_oter_flags; +}; + struct oter_type_t { public: static const oter_type_t null_type; @@ -155,7 +160,7 @@ struct oter_type_t { } void set_flag( oter_flags flag, bool value = true ) { - flags[flag] = value; + flags.set( flag, value ); } void load( const JsonObject &jo, const std::string &src ); @@ -163,11 +168,11 @@ struct oter_type_t { void finalize(); bool is_rotatable() const { - return !has_flag( no_rotate ) && !has_flag( line_drawing ); + return !has_flag( oter_flags::no_rotate ) && !has_flag( oter_flags::line_drawing ); } bool is_linear() const { - return has_flag( line_drawing ); + return has_flag( oter_flags::line_drawing ); } bool has_connections() const { @@ -179,7 +184,7 @@ struct oter_type_t { } private: - std::bitset flags; + enum_bitset flags; std::vector directional_peers; std::string connect_group; // Group for connection when rendering overmap tiles @@ -277,7 +282,7 @@ struct oter_t { } bool is_river() const { - return type->has_flag( river_tile ); + return type->has_flag( oter_flags::river_tile ); } bool is_wooded() const { @@ -288,11 +293,11 @@ struct oter_t { } bool is_lake() const { - return type->has_flag( lake ); + return type->has_flag( oter_flags::lake ); } bool is_lake_shore() const { - return type->has_flag( lake_shore ); + return type->has_flag( oter_flags::lake_shore ); } private: diff --git a/src/output.cpp b/src/output.cpp index 48f520a6de0f..500e7fbed1f4 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -703,6 +703,7 @@ bool query_yn( const std::string &text ) : input_context::allow_all_keys; return query_popup() + .preferred_keyboard_mode( keyboard_mode::keychar ) .context( "YESNO" ) .message( force_uc ? pgettext( "query_yn", "%s (Case Sensitive)" ) : @@ -764,6 +765,7 @@ std::vector get_hotkeys( const std::string &s ) int popup( const std::string &text, PopupFlags flags ) { query_popup pop; + pop.preferred_keyboard_mode( keyboard_mode::keychar ); pop.message( "%s", text ); if( flags & PF_GET_KEY ) { pop.allow_anykey( true ); @@ -779,7 +781,7 @@ int popup( const std::string &text, PopupFlags flags ) pop.context( "POPUP_WAIT" ); const auto &res = pop.query(); - if( res.evt.type == CATA_INPUT_KEYBOARD ) { + if( res.evt.type == input_event_t::keyboard_char ) { return res.evt.get_first_input(); } else { return UNKNOWN_UNICODE; @@ -1027,7 +1029,7 @@ input_event draw_item_info( const std::function &init_wind redraw(); } ); - input_context ctxt; + input_context ctxt( "default", keyboard_mode::keychar ); if( data.handle_scrolling ) { ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "PAGE_DOWN" ); diff --git a/src/overmap.cpp b/src/overmap.cpp index de6a76f4d2c2..27a72bec0c37 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -725,8 +725,8 @@ void oter_type_t::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "connect_group", connect_group, string_reader{} ); - if( has_flag( line_drawing ) ) { - if( has_flag( no_rotate ) ) { + if( has_flag( oter_flags::line_drawing ) ) { + if( has_flag( oter_flags::no_rotate ) ) { jo.throw_error( R"(Mutually exclusive flags: "NO_ROTATE" and "LINEAR".)" ); } @@ -766,7 +766,7 @@ void oter_type_t::finalize() for( om_direction::type dir : om_direction::all ) { register_terrain( oter_t( *this, dir ), static_cast( dir ), om_direction::size ); } - } else if( has_flag( line_drawing ) ) { + } else if( has_flag( oter_flags::line_drawing ) ) { for( size_t i = 0; i < om_lines::size; ++i ) { register_terrain( oter_t( *this, i ), i, om_lines::size ); } @@ -810,7 +810,7 @@ oter_id oter_type_t::get_rotated( om_direction::type dir ) const oter_id oter_type_t::get_linear( size_t n ) const { - if( !has_flag( line_drawing ) ) { + if( !has_flag( oter_flags::line_drawing ) ) { debugmsg( "Overmap terrain \"%s \" isn't drawn with lines.", id.c_str() ); return ot_null; } @@ -848,14 +848,14 @@ oter_t::oter_t( const oter_type_t &type, size_t line ) : std::string oter_t::get_mapgen_id() const { - return type->has_flag( line_drawing ) + return type->has_flag( oter_flags::line_drawing ) ? type->id.str() + om_lines::mapgen_suffixes[om_lines::all[line].mapgen] : type->id.str(); } oter_id oter_t::get_rotated( om_direction::type dir ) const { - return type->has_flag( line_drawing ) + return type->has_flag( oter_flags::line_drawing ) ? type->get_linear( om_lines::rotate( this->line, dir ) ) : type->get_rotated( om_direction::add( this->dir, dir ) ); } diff --git a/src/overmap.h b/src/overmap.h index cf305a613408..db144b5ba2b1 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -114,41 +114,41 @@ struct map_layer { }; static const std::map oter_flags_map = { - { "KNOWN_DOWN", known_down }, - { "KNOWN_UP", known_up }, - { "RIVER", river_tile }, - { "SIDEWALK", has_sidewalk }, - { "NO_ROTATE", no_rotate }, - { "IGNORE_ROTATION_FOR_ADJACENCY", ignore_rotation_for_adjacency }, - { "LINEAR", line_drawing }, - { "SUBWAY", subway_connection }, - { "LAKE", lake }, - { "LAKE_SHORE", lake_shore }, - { "GENERIC_LOOT", generic_loot }, - { "RISK_HIGH", risk_high }, - { "RISK_LOW", risk_low }, - { "SOURCE_AMMO", source_ammo }, - { "SOURCE_ANIMALS", source_animals }, - { "SOURCE_BOOKS", source_books }, - { "SOURCE_CHEMISTRY", source_chemistry }, - { "SOURCE_CLOTHING", source_clothing }, - { "SOURCE_CONSTRUCTION", source_construction }, - { "SOURCE_COOKING", source_cooking }, - { "SOURCE_DRINK", source_drink }, - { "SOURCE_ELECTRONICS", source_electronics }, - { "SOURCE_FABRICATION", source_fabrication }, - { "SOURCE_FARMING", source_farming }, - { "SOURCE_FOOD", source_food }, - { "SOURCE_FORAGE", source_forage }, - { "SOURCE_FUEL", source_fuel }, - { "SOURCE_GUN", source_gun }, - { "SOURCE_LUXURY", source_luxury }, - { "SOURCE_MEDICINE", source_medicine }, - { "SOURCE_PEOPLE", source_people }, - { "SOURCE_SAFETY", source_safety }, - { "SOURCE_TAILORING", source_tailoring }, - { "SOURCE_VEHICLES", source_vehicles }, - { "SOURCE_WEAPON", source_weapon } + { "KNOWN_DOWN", oter_flags::known_down }, + { "KNOWN_UP", oter_flags::known_up }, + { "RIVER", oter_flags::river_tile }, + { "SIDEWALK", oter_flags::has_sidewalk }, + { "NO_ROTATE", oter_flags::no_rotate }, + { "IGNORE_ROTATION_FOR_ADJACENCY", oter_flags::ignore_rotation_for_adjacency }, + { "LINEAR", oter_flags::line_drawing }, + { "SUBWAY", oter_flags::subway_connection }, + { "LAKE", oter_flags::lake }, + { "LAKE_SHORE", oter_flags::lake_shore }, + { "GENERIC_LOOT", oter_flags::generic_loot }, + { "RISK_HIGH", oter_flags::risk_high }, + { "RISK_LOW", oter_flags::risk_low }, + { "SOURCE_AMMO", oter_flags::source_ammo }, + { "SOURCE_ANIMALS", oter_flags::source_animals }, + { "SOURCE_BOOKS", oter_flags::source_books }, + { "SOURCE_CHEMISTRY", oter_flags::source_chemistry }, + { "SOURCE_CLOTHING", oter_flags::source_clothing }, + { "SOURCE_CONSTRUCTION", oter_flags::source_construction }, + { "SOURCE_COOKING", oter_flags::source_cooking }, + { "SOURCE_DRINK", oter_flags::source_drink }, + { "SOURCE_ELECTRONICS", oter_flags::source_electronics }, + { "SOURCE_FABRICATION", oter_flags::source_fabrication }, + { "SOURCE_FARMING", oter_flags::source_farming }, + { "SOURCE_FOOD", oter_flags::source_food }, + { "SOURCE_FORAGE", oter_flags::source_forage }, + { "SOURCE_FUEL", oter_flags::source_fuel }, + { "SOURCE_GUN", oter_flags::source_gun }, + { "SOURCE_LUXURY", oter_flags::source_luxury }, + { "SOURCE_MEDICINE", oter_flags::source_medicine }, + { "SOURCE_PEOPLE", oter_flags::source_people }, + { "SOURCE_SAFETY", oter_flags::source_safety }, + { "SOURCE_TAILORING", oter_flags::source_tailoring }, + { "SOURCE_VEHICLES", oter_flags::source_vehicles }, + { "SOURCE_WEAPON", oter_flags::source_weapon } }; template diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 2319ae8384a7..41e3fafcd96c 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -39,7 +39,6 @@ #include "game_constants.h" #include "game_ui.h" #include "hash_utils.h" -#include "ime.h" #include "input.h" #include "int_id.h" #include "line.h" @@ -591,7 +590,7 @@ static tripoint_abs_omt show_notes_manager( const tripoint_abs_omt &origin ) nmenu.additional_actions.emplace_back( "CHANGE_SORT", translation() ); nmenu.additional_actions.emplace_back( "CLEAR_FILTER", translation() ); nmenu.additional_actions.emplace_back( "MARK_DANGER", translation() ); - const input_context ctxt( nmenu.input_category ); + const input_context ctxt( nmenu.input_category, keyboard_mode::keychar ); nmenu.text = string_format( _( "<%s> - center on note, <%s> - edit note, <%s> - mark as dangerous, <%s> - delete note, <%s> - close window" ), colorize( "RETURN", c_yellow ), @@ -738,7 +737,8 @@ static tripoint_abs_omt show_notes_manager( const tripoint_abs_omt &origin ) return result; } -static void draw_ascii( const catacurses::window &w, +static void draw_ascii( ui_adaptor &ui, + const catacurses::window &w, const tripoint_abs_omt ¢er, const tripoint_abs_omt &/*orig*/, bool blink, @@ -1230,8 +1230,9 @@ static void draw_ascii( const catacurses::window &w, mvwputch( w, point( om_half_width + 1, om_half_height + 1 ), c_light_gray, LINE_XOOX ); } // Done with all drawing! - wmove( w, point( om_half_width, om_half_height ) ); wnoutrefresh( w ); + // Set cursor for screen readers + ui.set_cursor( w, point( om_half_width, om_half_height ) ); } static void draw_om_sidebar( @@ -1428,6 +1429,7 @@ tiles_redraw_info redraw_info; #endif static void draw( + ui_adaptor &ui, const tripoint_abs_omt ¢er, const tripoint_abs_omt &orig, bool blink, @@ -1439,7 +1441,7 @@ static void draw( { draw_om_sidebar( g->w_omlegend, center, orig, blink, fast_scroll, inp_ctxt, data ); if( !use_tiles || !use_tiles_overmap ) { - draw_ascii( g->w_overmap, center, orig, blink, show_explored, fast_scroll, inp_ctxt, data, + draw_ascii( ui, g->w_overmap, center, orig, blink, show_explored, fast_scroll, inp_ctxt, data, grids_data ); } else { #ifdef TILES @@ -1496,9 +1498,6 @@ static void create_note( const tripoint_abs_omt &curs ) update_note_preview( new_note, map_around, preview_windows ); } ); - // this implies enable_ime() and ensures that ime mode is always restored on return - ime_sentry sentry; - bool esc_pressed = false; string_input_popup input_popup; input_popup @@ -1520,10 +1519,9 @@ static void create_note( const tripoint_abs_omt &curs ) } else if( input_popup.confirmed() ) { break; } + ui.invalidate_ui(); } while( true ); - disable_ime(); - if( !esc_pressed && new_note.empty() && !old_note.empty() ) { if( query_yn( _( "Really delete note?" ) ) ) { overmap_buffer.delete_note( curs ); @@ -1994,8 +1992,8 @@ static tripoint_abs_omt display( const tripoint_abs_omt &orig, std::chrono::time_point last_blink = std::chrono::steady_clock::now(); grids_draw_data grids_data; - ui.on_redraw( [&]( const ui_adaptor & ) { - draw( curs, orig, uistate.overmap_show_overlays, + ui.on_redraw( [&]( ui_adaptor & ui ) { + draw( ui, curs, orig, uistate.overmap_show_overlays, show_explored, fast_scroll, &ictxt, data, grids_data ); } ); diff --git a/src/pickup.cpp b/src/pickup.cpp index 0dbb34579c69..433d98fba86d 100644 --- a/src/pickup.cpp +++ b/src/pickup.cpp @@ -192,6 +192,7 @@ bool pickup::query_thief() const auto &allow_key = force_uc ? input_context::disallow_lower_case : input_context::allow_all_keys; std::string answer = query_popup() + .preferred_keyboard_mode( keyboard_mode::keychar ) .allow_cancel( false ) .context( "YES_NO_ALWAYS_NEVER" ) .message( "%s", force_uc @@ -747,7 +748,7 @@ void pickup::pick_up( const tripoint &p, int min, from_where get_items_from ) std::string action; int raw_input_char = ' '; - input_context ctxt( "PICKUP" ); + input_context ctxt( "PICKUP", keyboard_mode::keychar ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); diff --git a/src/player.cpp b/src/player.cpp index 12b14cb9fa7a..79545554fa85 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -215,7 +215,7 @@ bool character_martial_arts::pick_style( const avatar &you ) // Style selecti bio_cqb ) ? bio_cqb_styles : ma_styles; - input_context ctxt( "MELEE_STYLE_PICKER" ); + input_context ctxt( "MELEE_STYLE_PICKER", keyboard_mode::keychar ); ctxt.register_action( "SHOW_DESCRIPTION" ); uilist kmenu; diff --git a/src/popup.cpp b/src/popup.cpp index 3944ebbdc40d..197be8acd7c5 100644 --- a/src/popup.cpp +++ b/src/popup.cpp @@ -6,7 +6,6 @@ #include "cached_options.h" #include "catacharset.h" -#include "ime.h" #include "input.h" #include "output.h" #include "sdl_wrappers.h" @@ -14,7 +13,7 @@ query_popup::query_popup() : cur( 0 ), default_text_color( c_white ), anykey( false ), cancel( false ), ontop( false ), - fullscr( false ) + fullscr( false ), pref_kbd_mode( keyboard_mode::keycode ) { } @@ -83,12 +82,20 @@ query_popup &query_popup::default_color( const nc_color &d_color ) return *this; } +query_popup &query_popup::preferred_keyboard_mode( const keyboard_mode mode ) +{ + invalidate_ui(); + pref_kbd_mode = mode; + return *this; +} + std::vector> query_popup::fold_query( const std::string &category, + const keyboard_mode pref_kbd_mode, const std::vector &options, const int max_width, const int horz_padding ) { - input_context ctxt( category ); + input_context ctxt( category, pref_kbd_mode ); std::vector> folded_query; folded_query.emplace_back(); @@ -152,7 +159,8 @@ void query_popup::init() const folded_msg = foldstring( text, max_line_width ); // Fold query buttons - const auto &folded_query = fold_query( category, options, max_line_width, horz_padding ); + const auto &folded_query = fold_query( category, pref_kbd_mode, options, max_line_width, + horz_padding ); // Calculate size of message part int msg_width = 0; @@ -274,7 +282,7 @@ query_popup::result query_popup::query_once() ui_manager::redraw(); - input_context ctxt( category ); + input_context ctxt( category, pref_kbd_mode ); if( cancel || !options.empty() ) { ctxt.register_action( "HELP_KEYBINDINGS" ); } @@ -303,9 +311,9 @@ query_popup::result query_popup::query_once() res.evt = ctxt.get_raw_input(); } while( // Always ignore mouse movement - ( res.evt.type == CATA_INPUT_MOUSE && res.evt.get_first_input() == MOUSE_MOVE ) || + ( res.evt.type == input_event_t::mouse && res.evt.get_first_input() == MOUSE_MOVE ) || // Ignore window losing focus in SDL - ( res.evt.type == CATA_INPUT_KEYBOARD && res.evt.sequence.empty() ) + ( res.evt.type == input_event_t::keyboard_char && res.evt.sequence.empty() ) ); if( cancel && res.action == "QUIT" ) { @@ -347,8 +355,6 @@ query_popup::result query_popup::query_once() query_popup::result query_popup::query() { - ime_sentry sentry( ime_sentry::disable ); - std::shared_ptr ui = create_or_get_adaptor(); result res; diff --git a/src/popup.h b/src/popup.h index 8bf0d372243d..c162233fe024 100644 --- a/src/popup.h +++ b/src/popup.h @@ -174,6 +174,11 @@ class query_popup * Specify the default message color. **/ query_popup &default_color( const nc_color &d_color ); + /** + * Specify the desired keyboard mode. Used in keybindings menu to assign + * actions to input events of the approriate type of the parent UI. + */ + query_popup &preferred_keyboard_mode( keyboard_mode mode ); /** * Draw the UI. An input context should be provided using `context()` @@ -217,6 +222,7 @@ class query_popup bool cancel; bool ontop; bool fullscr; + keyboard_mode pref_kbd_mode; struct button { button( const std::string &text, point ); @@ -234,6 +240,7 @@ class query_popup static std::vector> fold_query( const std::string &category, + keyboard_mode pref_kbd_mode, const std::vector &options, int max_width, int horz_padding ); void invalidate_ui() const; diff --git a/src/ranged.cpp b/src/ranged.cpp index fbb5a6d2e6c8..94ca6fc51a11 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -514,18 +514,18 @@ target_handler::trajectory target_handler::mode_shaped( avatar &you, shape_facto return ui.run(); } -static double occupied_tile_fraction( m_size target_size ) +static double occupied_tile_fraction( creature_size target_size ) { switch( target_size ) { - case MS_TINY: + case creature_size::tiny: return 0.1; - case MS_SMALL: + case creature_size::small: return 0.25; - case MS_MEDIUM: + case creature_size::medium: return 0.5; - case MS_LARGE: + case creature_size::large: return 0.75; - case MS_HUGE: + case creature_size::huge: return 1.0; default: break; @@ -538,15 +538,15 @@ double Creature::ranged_target_size() const { if( has_flag( MF_HARDTOSHOOT ) ) { switch( get_size() ) { - case MS_TINY: - case MS_SMALL: - return occupied_tile_fraction( MS_TINY ); - case MS_MEDIUM: - return occupied_tile_fraction( MS_SMALL ); - case MS_LARGE: - return occupied_tile_fraction( MS_MEDIUM ); - case MS_HUGE: - return occupied_tile_fraction( MS_LARGE ); + case creature_size::tiny: + case creature_size::small: + return occupied_tile_fraction( creature_size::tiny ); + case creature_size::medium: + return occupied_tile_fraction( creature_size::small ); + case creature_size::large: + return occupied_tile_fraction( creature_size::medium ); + case creature_size::huge: + return occupied_tile_fraction( creature_size::large ); default: break; } @@ -970,9 +970,10 @@ int ranged::fire_gun( Character &who, const tripoint &target, int max_shots, ite // If user is currently able to fire a mounted gun freely, penalize dispersion // HEAVY_WEAPON_SUPPORT flag has highest penalty, Large mutants lower penalty, no penalty for Huge mutants. if( gun.has_flag( flag_MOUNTED_GUN ) && !can_use_heavy_weapon( shooter, here, shooter.pos() ) ) { - if( who.get_size() == MS_LARGE ) { + if( who.get_size() == creature_size::large ) { gun_recoil = gun_recoil * 2; - } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && ( who.get_size() <= MS_MEDIUM ) ) { + } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && + ( who.get_size() <= creature_size::medium ) ) { gun_recoil = gun_recoil * 3; } } @@ -1975,9 +1976,10 @@ dispersion_sources ranged::get_weapon_dispersion( const Character &who, const it // If user is currently able to fire a mounted gun freely, penalize dispersion // HEAVY_WEAPON_SUPPORT flag has highest penalty, Large mutants lower penalty, no penalty for Huge mutants. if( obj.has_flag( flag_MOUNTED_GUN ) && !can_use_heavy_weapon( who, get_map(), who.pos() ) ) { - if( who.get_size() == MS_LARGE ) { + if( who.get_size() == creature_size::large ) { dispersion.add_range( 500 ); - } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && ( who.get_size() <= MS_MEDIUM ) ) { + } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && + ( who.get_size() <= creature_size::medium ) ) { dispersion.add_range( 1000 ); } } @@ -2482,7 +2484,7 @@ void target_ui::init_window_and_input() w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); - ctxt = input_context( "TARGET" ); + ctxt = input_context( "TARGET", keyboard_mode::keychar ); ctxt.set_iso( true ); ctxt.register_directions(); ctxt.register_action( "COORDINATE" ); @@ -3683,7 +3685,7 @@ void target_ui::panel_fire_mode_aim( int &text_y ) } const double target_size = dst_critter ? dst_critter->ranged_target_size() : - occupied_tile_fraction( m_size::MS_MEDIUM ); + occupied_tile_fraction( creature_size::medium ); item *load_loc = activity->reload_loc ? &*activity->reload_loc : nullptr; text_y = print_aim( *you, w_target, text_y, ctxt, *relevant->gun_current_mode(), @@ -3808,7 +3810,8 @@ auto ranged::gunmode_checks_weapon( avatar &you, const map &m, std::vectorhas_flag( flag_MOUNTED_GUN ) ) { const Character &shooter = you; - if( !can_use_heavy_weapon( shooter, m, shooter.pos() ) && !( you.get_size() > MS_MEDIUM ) && + if( !can_use_heavy_weapon( shooter, m, shooter.pos() ) && + !( you.get_size() > creature_size::medium ) && !you.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) ) { messages.push_back( string_format( _( "You must stand near acceptable terrain or furniture to fire the %s. A table, a mound of dirt, a broken window, etc." ), diff --git a/src/savegame.cpp b/src/savegame.cpp index 006e98fe36b2..dcdd1e1e87cf 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -312,7 +312,7 @@ void game::load_shortcuts( std::istream &fin ) for( const JsonMember &member : data.get_object( "quick_shortcuts" ) ) { std::list &qslist = quick_shortcuts_map[member.name()]; for( const int i : member.get_array() ) { - qslist.push_back( input_event( i, CATA_INPUT_KEYBOARD ) ); + qslist.push_back( input_event( i, input_event_t::keyboard_char ) ); } } } diff --git a/src/sdltiles.cpp b/src/sdltiles.cpp index 561d2e767dab..ec8dafb88517 100644 --- a/src/sdltiles.cpp +++ b/src/sdltiles.cpp @@ -1633,7 +1633,7 @@ static int HandleDPad() return 0; } - last_input = input_event( lc, CATA_INPUT_GAMEPAD ); + last_input = input_event( lc, input_event_t::gamepad ); lastdpad = lc; queued_dpad = ERR; @@ -1653,7 +1653,7 @@ static int HandleDPad() // If we didn't hold it down for a while, just // fire the last registered press. if( queued_dpad != ERR ) { - last_input = input_event( queued_dpad, CATA_INPUT_GAMEPAD ); + last_input = input_event( queued_dpad, input_event_t::gamepad ); queued_dpad = ERR; return 1; } @@ -1884,6 +1884,32 @@ static int sdl_keysym_to_curses( const SDL_Keysym &keysym ) } } +static input_event sdl_keysym_to_keycode_evt( const SDL_Keysym &keysym ) +{ + switch( keysym.sym ) { + case SDLK_LCTRL: + case SDLK_LSHIFT: + case SDLK_LALT: + case SDLK_RCTRL: + case SDLK_RSHIFT: + case SDLK_RALT: + return input_event(); + } + input_event evt; + evt.type = input_event_t::keyboard_code; + if( keysym.mod & KMOD_CTRL ) { + evt.modifiers.emplace( keymod_t::ctrl ); + } + if( keysym.mod & KMOD_ALT ) { + evt.modifiers.emplace( keymod_t::alt ); + } + if( keysym.mod & KMOD_SHIFT ) { + evt.modifiers.emplace( keymod_t::shift ); + } + evt.sequence.emplace_back( keysym.sym ); + return evt; +} + bool handle_resize( int w, int h ) { if( ( w != WindowWidth ) || ( h != WindowHeight ) ) { @@ -2139,7 +2165,7 @@ int choose_best_key_for_action( const std::string &action, const std::string &ca const std::vector &events = inp_mngr.get_input_for_action( action, category ); int best_key = -1; for( const auto &events_event : events ) { - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.sequence.size() == 1 ) { + if( events_event.type == input_event_t::keyboard_char && events_event.sequence.size() == 1 ) { bool is_ascii_char = isprint( events_event.sequence.front() ) && events_event.sequence.front() < 0xFF; bool is_best_ascii_char = best_key >= 0 && isprint( best_key ) && best_key < 0xFF; @@ -2155,7 +2181,7 @@ bool add_key_to_quick_shortcuts( int key, const std::string &category, bool back { if( key > 0 ) { quick_shortcuts_t &qsl = quick_shortcuts_map[get_quick_shortcut_name( category )]; - input_event event = input_event( key, CATA_INPUT_KEYBOARD ); + input_event event = input_event( key, input_event_t::keyboard_char ); quick_shortcuts_t::iterator it = std::find( qsl.begin(), qsl.end(), event ); if( it != qsl.end() ) { // already exists ( *it ).shortcut_last_used_action_counter = @@ -2328,7 +2354,7 @@ void draw_quick_shortcuts() std::vector ®istered_manual_keys = touch_input_context.get_registered_manual_keys(); for( const auto &manual_key : registered_manual_keys ) { - input_event event( manual_key.key, CATA_INPUT_KEYBOARD ); + input_event event( manual_key.key, input_event_t::keyboard_char ); add_quick_shortcut( qsl, event, !shortcut_right, true ); } } @@ -2573,56 +2599,56 @@ void handle_finger_input( uint32_t ticks ) WindowHeight ) ) ) { if( !handle_diagonals ) { if( delta_x >= 0 && delta_y >= 0 ) { - last_input = input_event( delta_x > delta_y ? KEY_RIGHT : KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( delta_x > delta_y ? KEY_RIGHT : KEY_DOWN, input_event_t::keyboard_char ); } else if( delta_x < 0 && delta_y >= 0 ) { - last_input = input_event( -delta_x > delta_y ? KEY_LEFT : KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( -delta_x > delta_y ? KEY_LEFT : KEY_DOWN, input_event_t::keyboard_char ); } else if( delta_x >= 0 && delta_y < 0 ) { - last_input = input_event( delta_x > -delta_y ? KEY_RIGHT : KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( delta_x > -delta_y ? KEY_RIGHT : KEY_UP, input_event_t::keyboard_char ); } else if( delta_x < 0 && delta_y < 0 ) { - last_input = input_event( -delta_x > -delta_y ? KEY_LEFT : KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( -delta_x > -delta_y ? KEY_LEFT : KEY_UP, input_event_t::keyboard_char ); } } else { if( delta_x > 0 ) { if( std::abs( delta_y ) < delta_x * 0.5f ) { // swipe right - last_input = input_event( KEY_RIGHT, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_RIGHT, input_event_t::keyboard_char ); } else if( std::abs( delta_y ) < delta_x * 2.0f ) { if( delta_y < 0 ) { // swipe up-right - last_input = input_event( JOY_RIGHTUP, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_RIGHTUP, input_event_t::gamepad ); } else { // swipe down-right - last_input = input_event( JOY_RIGHTDOWN, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_RIGHTDOWN, input_event_t::gamepad ); } } else { if( delta_y < 0 ) { // swipe up - last_input = input_event( KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_UP, input_event_t::keyboard_char ); } else { // swipe down - last_input = input_event( KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_DOWN, input_event_t::keyboard_char ); } } } else { if( std::abs( delta_y ) < -delta_x * 0.5f ) { // swipe left - last_input = input_event( KEY_LEFT, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_LEFT, input_event_t::keyboard_char ); } else if( std::abs( delta_y ) < -delta_x * 2.0f ) { if( delta_y < 0 ) { // swipe up-left - last_input = input_event( JOY_LEFTUP, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_LEFTUP, input_event_t::gamepad ); } else { // swipe down-left - last_input = input_event( JOY_LEFTDOWN, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_LEFTDOWN, input_event_t::gamepad ); } } else { if( delta_y < 0 ) { // swipe up - last_input = input_event( KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_UP, input_event_t::keyboard_char ); } else { // swipe down - last_input = input_event( KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_DOWN, input_event_t::keyboard_char ); } } } @@ -2634,13 +2660,13 @@ void handle_finger_input( uint32_t ticks ) // We only allow repeats for waiting, not confirming in menus as that's a bit silly if( is_default_mode ) { last_input = input_event( get_key_event_from_string( get_option( "ANDROID_TAP_KEY" ) ), - CATA_INPUT_KEYBOARD ); + input_event_t::keyboard_char ); } } else { if( last_tap_time > 0 && ticks - last_tap_time < static_cast( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // Double tap - last_input = input_event( is_default_mode ? KEY_ESCAPE : KEY_ESCAPE, CATA_INPUT_KEYBOARD ); + last_input = input_event( is_default_mode ? KEY_ESCAPE : KEY_ESCAPE, input_event_t::keyboard_char ); last_tap_time = 0; } else { // First tap detected, waiting to decide whether it's a single or a double tap input @@ -2970,7 +2996,7 @@ static void CheckMessages() // Single tap last_tap_time = ticks; last_input = input_event( is_default_mode ? get_key_event_from_string( - get_option( "ANDROID_TAP_KEY" ) ) : '\n', CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_TAP_KEY" ) ) : '\n', input_event_t::keyboard_char ); last_tap_time = 0; return; } @@ -3057,34 +3083,45 @@ static void CheckMessages() if( get_option( "HIDE_CURSOR" ) != "show" && SDL_ShowCursor( -1 ) ) { SDL_ShowCursor( SDL_DISABLE ); } - const int lc = sdl_keysym_to_curses( ev.key.keysym ); - if( lc <= 0 ) { - // a key we don't know in curses and won't handle. - break; - } else if( add_alt_code( lc ) ) { - // key was handled - } else { - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + keyboard_mode mode = keyboard_mode::keychar; +#if !defined( __ANDROID__ ) + if( !SDL_IsTextInputActive() ) { + mode = keyboard_mode::keycode; + } +#endif + if( mode == keyboard_mode::keychar ) { + const int lc = sdl_keysym_to_curses( ev.key.keysym ); + if( lc <= 0 ) { + // a key we don't know in curses and won't handle. + break; + } else if( add_alt_code( lc ) ) { + // key was handled + } else { + last_input = input_event( lc, input_event_t::keyboard_char ); #if defined(__ANDROID__) - if( !android_is_hardware_keyboard_available() ) { - if( !is_string_input( touch_input_context ) && !touch_input_context.allow_text_entry ) { - if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { - SDL_StopTextInput(); - } + if( !android_is_hardware_keyboard_available() ) { + if( !is_string_input( touch_input_context ) && !touch_input_context.allow_text_entry ) { + if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { + SDL_StopTextInput(); + } - // add a quick shortcut - if( !last_input.text.empty() || !inp_mngr.get_keyname( lc, CATA_INPUT_KEYBOARD ).empty() ) { - qsl.remove( last_input ); - add_quick_shortcut( qsl, last_input, false, true ); - refresh_display(); - } - } else if( lc == '\n' || lc == KEY_ESCAPE ) { - if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { - SDL_StopTextInput(); + // add a quick shortcut + if( !last_input.text.empty() || + !inp_mngr.get_keyname( lc, input_event_t::keyboard_char ).empty() ) { + qsl.remove( last_input ); + add_quick_shortcut( qsl, last_input, false, true ); + refresh_display(); + } + } else if( lc == '\n' || lc == KEY_ESCAPE ) { + if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { + SDL_StopTextInput(); + } } } - } #endif + } + } else { + last_input = sdl_keysym_to_keycode_evt( ev.key.keysym ); } } break; @@ -3102,14 +3139,24 @@ static void CheckMessages() } ac_back_down_time = 0; } +#endif + keyboard_mode mode = keyboard_mode::keychar; +#if !defined( __ANDROID__ ) + if( !SDL_IsTextInputActive() ) { + mode = keyboard_mode::keycode; + } #endif is_repeat = ev.key.repeat; - if( ev.key.keysym.sym == SDLK_LALT || ev.key.keysym.sym == SDLK_RALT ) { - int code = end_alt_code(); - if( code ) { - last_input = input_event( code, CATA_INPUT_KEYBOARD ); - last_input.text = utf32_to_utf8( code ); + if( mode == keyboard_mode::keychar ) { + if( ev.key.keysym.sym == SDLK_LALT || ev.key.keysym.sym == SDLK_RALT ) { + int code = end_alt_code(); + if( code ) { + last_input = input_event( code, input_event_t::keyboard_char ); + last_input.text = utf32_to_utf8( code ); + } } + } else if( is_repeat ) { + last_input = sdl_keysym_to_keycode_evt( ev.key.keysym ); } } break; @@ -3117,7 +3164,7 @@ static void CheckMessages() if( !add_alt_code( *ev.text.text ) ) { if( strlen( ev.text.text ) > 0 ) { const unsigned lc = UTF8_getch( ev.text.text ); - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + last_input = input_event( lc, input_event_t::keyboard_char ); #if defined(__ANDROID__) if( !android_is_hardware_keyboard_available() ) { if( !is_string_input( touch_input_context ) && !touch_input_context.allow_text_entry ) { @@ -3140,7 +3187,7 @@ static void CheckMessages() } else { // no key pressed in this event last_input = input_event(); - last_input.type = CATA_INPUT_KEYBOARD; + last_input.type = input_event_t::keyboard_char; } last_input.text = ev.text.text; text_refresh = true; @@ -3149,11 +3196,11 @@ static void CheckMessages() case SDL_TEXTEDITING: { if( strlen( ev.edit.text ) > 0 ) { const unsigned lc = UTF8_getch( ev.edit.text ); - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + last_input = input_event( lc, input_event_t::keyboard_char ); } else { // no key pressed in this event last_input = input_event(); - last_input.type = CATA_INPUT_KEYBOARD; + last_input.type = input_event_t::keyboard_char; } last_input.edit = ev.edit.text; last_input.edit_refresh = true; @@ -3161,7 +3208,7 @@ static void CheckMessages() } break; case SDL_JOYBUTTONDOWN: - last_input = input_event( ev.jbutton.button, CATA_INPUT_KEYBOARD ); + last_input = input_event( ev.jbutton.button, input_event_t::keyboard_char ); break; case SDL_JOYAXISMOTION: // on gamepads, the axes are the analog sticks @@ -3175,26 +3222,26 @@ static void CheckMessages() } // Only monitor motion when cursor is visible - last_input = input_event( MOUSE_MOVE, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_MOVE, input_event_t::mouse ); } break; case SDL_MOUSEBUTTONUP: switch( ev.button.button ) { case SDL_BUTTON_LEFT: - last_input = input_event( MOUSE_BUTTON_LEFT, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_BUTTON_LEFT, input_event_t::mouse ); break; case SDL_BUTTON_RIGHT: - last_input = input_event( MOUSE_BUTTON_RIGHT, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_BUTTON_RIGHT, input_event_t::mouse ); break; } break; case SDL_MOUSEWHEEL: if( ev.wheel.y > 0 ) { - last_input = input_event( SCROLLWHEEL_UP, CATA_INPUT_MOUSE ); + last_input = input_event( SCROLLWHEEL_UP, input_event_t::mouse ); } else if( ev.wheel.y < 0 ) { - last_input = input_event( SCROLLWHEEL_DOWN, CATA_INPUT_MOUSE ); + last_input = input_event( SCROLLWHEEL_DOWN, input_event_t::mouse ); } break; @@ -3290,7 +3337,7 @@ static void CheckMessages() if( std::max( d1, d2 ) < get_option( "ANDROID_DEADZONE_RANGE" ) * longest_window_edge ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_TAP_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_TAP_KEY" ) ), input_event_t::keyboard_char ); } else { float dot = ( x1 * x2 + y1 * y2 ) / ( d1 * d2 ); // dot product of two finger vectors, -1 to +1 if( dot > 0.0f ) { // both fingers mostly heading in same direction, check for double-finger swipe gesture @@ -3303,16 +3350,16 @@ static void CheckMessages() float yavg = 0.5f * ( y1 + y2 ); if( xavg > 0 && xavg > std::abs( yavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_LEFT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_LEFT_KEY" ) ), input_event_t::keyboard_char ); } else if( xavg < 0 && -xavg > std::abs( yavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_RIGHT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_RIGHT_KEY" ) ), input_event_t::keyboard_char ); } else if( yavg > 0 && yavg > std::abs( xavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_DOWN_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_DOWN_KEY" ) ), input_event_t::keyboard_char ); } else { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_UP_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_UP_KEY" ) ), input_event_t::keyboard_char ); } } } else { @@ -3328,10 +3375,10 @@ static void CheckMessages() const float zoom_ratio = 0.9f; if( curr_dist < down_dist * zoom_ratio ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_PINCH_IN_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_PINCH_IN_KEY" ) ), input_event_t::keyboard_char ); } else if( curr_dist > down_dist / zoom_ratio ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_PINCH_OUT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_PINCH_OUT_KEY" ) ), input_event_t::keyboard_char ); } } } @@ -3642,8 +3689,19 @@ void input_manager::pump_events() // This is how we're actually going to handle input events, SDL getch // is simply a wrapper around this. -input_event input_manager::get_input_event() +input_event input_manager::get_input_event( const keyboard_mode preferred_keyboard_mode ) { +#if !defined( __ANDROID__ ) + if( preferred_keyboard_mode == keyboard_mode::keychar ) { + SDL_StartTextInput(); + } else { + SDL_StopTextInput(); + } +#else + // TODO: support keycode mode if hardware keyboard is connected? + ( void ) preferred_keyboard_mode; +#endif + previously_pressed_key = 0; // standards note: getch is sometimes required to call refresh @@ -3659,11 +3717,11 @@ input_event input_manager::get_input_event() if( inputdelay < 0 ) { do { CheckMessages(); - if( last_input.type != CATA_INPUT_ERROR ) { + if( last_input.type != input_event_t::error ) { break; } SDL_Delay( 1 ); - } while( last_input.type == CATA_INPUT_ERROR ); + } while( last_input.type == input_event_t::error ); } else if( inputdelay > 0 ) { uint32_t starttime = SDL_GetTicks(); uint32_t endtime = 0; @@ -3671,29 +3729,29 @@ input_event input_manager::get_input_event() do { CheckMessages(); endtime = SDL_GetTicks(); - if( last_input.type != CATA_INPUT_ERROR ) { + if( last_input.type != input_event_t::error ) { break; } SDL_Delay( 1 ); timedout = endtime >= starttime + inputdelay; if( timedout ) { - last_input.type = CATA_INPUT_TIMEOUT; + last_input.type = input_event_t::timeout; } } while( !timedout ); } else { CheckMessages(); } - if( last_input.type == CATA_INPUT_MOUSE ) { + if( last_input.type == input_event_t::mouse ) { SDL_GetMouseState( &last_input.mouse_pos.x, &last_input.mouse_pos.y ); - } else if( last_input.type == CATA_INPUT_KEYBOARD ) { + } else if( last_input.type == input_event_t::keyboard_char ) { previously_pressed_key = last_input.get_first_input(); #if defined(__ANDROID__) android_vibrate(); #endif } #if defined(__ANDROID__) - else if( last_input.type == CATA_INPUT_GAMEPAD ) { + else if( last_input.type == input_event_t::gamepad ) { android_vibrate(); } #endif diff --git a/src/string_editor_window.cpp b/src/string_editor_window.cpp index 4d395e432161..69fb497d5e42 100644 --- a/src/string_editor_window.cpp +++ b/src/string_editor_window.cpp @@ -11,6 +11,7 @@ #endif #include "wcwidth.h" +#include "ui_manager.h" static bool is_linebreak( const uint32_t uc ) { @@ -193,7 +194,7 @@ point string_editor_window::get_line_and_position( const int position, const boo return _folded->codepoint_coordinates( position, zero_x ); } -void string_editor_window::print_editor() +void string_editor_window::print_editor( ui_adaptor &ui ) { const point focus = _ime_preview_range ? _ime_preview_range->display_last : _cursor_display; const int ftsize = _folded->get_lines().size(); @@ -233,7 +234,9 @@ void string_editor_window::print_editor() || is_linebreak( c_cursor ) || mk_wcwidth( c_cursor ) < 1 ) { c_cursor = ' '; } - mvwprintz( _win, point( _cursor_display.x + 1, y ), h_white, "%s", utf32_to_utf8( c_cursor ) ); + const point cursor_pos( _cursor_display.x + 1, y ); + mvwprintz( _win, cursor_pos, h_white, "%s", utf32_to_utf8( c_cursor ) ); + ui.set_cursor( _win, cursor_pos ); } if( _ime_preview_range && i >= _ime_preview_range->display_first.y && i <= _ime_preview_range->display_last.y ) { @@ -247,6 +250,11 @@ void string_editor_window::print_editor() } } + if( _ime_preview_range ) { + const point cursor_pos = _ime_preview_range->display_last + point( 1, -topoflist ); + ui.set_cursor( _win, cursor_pos ); + } + if( ftsize > _max.y ) { scrollbar() .content_size( ftsize ) @@ -258,7 +266,25 @@ void string_editor_window::print_editor() void string_editor_window::create_context() { - ctxt = std::make_unique(); + ctxt = std::make_unique( "STRING_EDITOR", keyboard_mode::keychar ); + ctxt->register_action( "TEXT.QUIT" ); + ctxt->register_action( "TEXT.CONFIRM" ); + ctxt->register_action( "TEXT.LEFT" ); + ctxt->register_action( "TEXT.RIGHT" ); + ctxt->register_action( "TEXT.UP" ); + ctxt->register_action( "TEXT.DOWN" ); + ctxt->register_action( "TEXT.CLEAR" ); + ctxt->register_action( "TEXT.BACKSPACE" ); + ctxt->register_action( "TEXT.HOME" ); + ctxt->register_action( "TEXT.END" ); + ctxt->register_action( "TEXT.PAGE_UP" ); + ctxt->register_action( "TEXT.PAGE_DOWN" ); + ctxt->register_action( "TEXT.DELETE" ); +#if defined(TILES) + ctxt->register_action( "TEXT.PASTE" ); +#endif + ctxt->register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt->register_action( "HELP_KEYBINDINGS" ); ctxt->register_action( "ANY_INPUT" ); } @@ -330,7 +356,7 @@ std::pair string_editor_window::query_string() ui.position_from_window( _win ); } ); ui.mark_resize(); - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { if( refold ) { utf8_wrapper text = _utext; if( !edit.empty() ) { @@ -356,7 +382,7 @@ std::pair string_editor_window::query_string() reposition = false; } werase( _win ); - print_editor(); + print_editor( ui ); wnoutrefresh( _win ); } ); @@ -374,54 +400,50 @@ std::pair string_editor_window::query_string() const std::string action = ctxt->handle_input(); - if( action != "ANY_INPUT" ) { - continue; - } - const input_event ev = ctxt->get_raw_input(); - ch = ev.type == input_event_t::CATA_INPUT_KEYBOARD ? ev.get_first_input() : 0; + ch = ev.type == input_event_t::keyboard_char ? ev.get_first_input() : 0; - if( ch == KEY_ESCAPE ) { + if( action == "TEXT.QUIT" ) { return { false, _utext.str() }; - } else if( ch == 0x13 ) { + } else if( action == "TEXT.CONFIRM" ) { // ctrl-s: confirm return { true, _utext.str() }; - } else if( ch == KEY_UP ) { + } else if( action == "TEXT.UP" ) { if( edit.empty() ) { cursor_updown( -1 ); reposition = true; } - } else if( ch == KEY_DOWN ) { + } else if( action == "TEXT.DOWN" ) { if( edit.empty() ) { cursor_updown( 1 ); reposition = true; } - } else if( ch == KEY_RIGHT ) { + } else if( action == "TEXT.RIGHT" ) { if( edit.empty() ) { cursor_leftright( 1 ); _cursor_desired_x = -1; reposition = true; } - } else if( ch == KEY_LEFT ) { + } else if( action == "TEXT.LEFT" ) { if( edit.empty() ) { cursor_leftright( -1 ); _cursor_desired_x = -1; reposition = true; } - } else if( ch == 0x15 ) { + } else if( action == "TEXT.CLEAR" ) { // ctrl-u: delete all the things _position = 0; _cursor_desired_x = -1; _utext.erase( 0 ); refold = true; - } else if( ch == KEY_BACKSPACE ) { + } else if( action == "TEXT.BACKSPACE" ) { if( _position > 0 && _position <= static_cast( _utext.size() ) ) { _position--; _cursor_desired_x = -1; _utext.erase( _position, 1 ); refold = true; } - } else if( ch == KEY_HOME ) { + } else if( action == "TEXT.HOME" ) { if( edit.empty() && static_cast( _cursor_display.y ) < _folded->get_lines().size() ) { _position = _folded->get_lines()[_cursor_display.y].cpts_start; @@ -429,7 +451,7 @@ std::pair string_editor_window::query_string() _cursor_desired_x = 0; reposition = true; } - } else if( ch == KEY_END ) { + } else if( action == "TEXT.END" ) { if( edit.empty() && static_cast( _cursor_display.y ) < _folded->get_lines().size() ) { _position = _folded->get_lines()[_cursor_display.y].cpts_end; @@ -440,26 +462,27 @@ std::pair string_editor_window::query_string() _cursor_desired_x = -1; reposition = true; } - } else if( ch == KEY_PPAGE ) { + } else if( action == "TEXT.PAGE_UP" ) { if( edit.empty() ) { cursor_updown( -_max.y ); reposition = true; } - } else if( ch == KEY_NPAGE ) { + } else if( action == "TEXT.PAGE_DOWN" ) { if( edit.empty() ) { cursor_updown( _max.y ); reposition = true; } - } else if( ch == KEY_DC ) { + } else if( action == "TEXT.DELETE" ) { if( _position < static_cast( _utext.size() ) ) { _cursor_desired_x = -1; _utext.erase( _position, 1 ); refold = true; } - } else if( ch == 0x16 || ch == KEY_F( 2 ) || !ev.text.empty() || ch == KEY_ENTER || ch == '\n' ) { - // ctrl-v, f2, or _utext input + } else if( action == "TEXT.PASTE" || action == "TEXT.INPUT_FROM_FILE" + || !ev.text.empty() || ch == KEY_ENTER || ch == '\n' ) { + // paste, input from file, or text input std::string entered; - if( ch == 0x16 ) { + if( action == "TEXT.PASTE" ) { #if defined(TILES) if( edit.empty() ) { char *const clip = SDL_GetClipboardText(); @@ -469,7 +492,7 @@ std::pair string_editor_window::query_string() } } #endif - } else if( ch == KEY_F( 2 ) ) { + } else if( action == "TEXT.INPUT_FROM_FILE" ) { if( edit.empty() ) { entered = get_input_string_from_file(); } diff --git a/src/string_editor_window.h b/src/string_editor_window.h index 9a7b66d701d7..02baacab462a 100644 --- a/src/string_editor_window.h +++ b/src/string_editor_window.h @@ -9,12 +9,13 @@ #include "input.h" #include "output.h" #include "ui.h" -#include "ui_manager.h" class folded_text; struct ime_preview_range; +class ui_adaptor; + /// /// editor, to let the player edit text. /// @@ -59,7 +60,7 @@ class string_editor_window private: /*print the editor*/ - void print_editor(); + void print_editor( ui_adaptor &ui ); void create_context(); diff --git a/src/string_input_popup.cpp b/src/string_input_popup.cpp index 015dd96e1218..ea37e128dbf6 100644 --- a/src/string_input_popup.cpp +++ b/src/string_input_popup.cpp @@ -4,7 +4,6 @@ #include #include "catacharset.h" -#include "ime.h" #include "input.h" #include "output.h" #include "point.h" @@ -12,6 +11,11 @@ #include "ui.h" #include "ui_manager.h" #include "uistate.h" +#include "wcwidth.h" + +#if defined(TILES) +#include "sdl_wrappers.h" +#endif #if defined(__ANDROID__) #include @@ -58,17 +62,21 @@ void string_input_popup::create_window() break; } } - w_height += static_cast( title_split.size() ) - 1; + title_height = static_cast( title_split.size() ) - 1; + w_height += title_height; } - descformatted.clear(); if( !_description.empty() ) { const int twidth = std::min( utf8_width( remove_color_tags( _description ) ), w_width - 4 ); - descformatted = foldstring( _description, twidth ); - w_height += descformatted.size(); + description_height = foldstring( _description, twidth ).size(); + w_height += description_height; + if( w_height > TERMY ) { + description_height = TERMY - 2 - title_height - 1; + w_height = TERMY; + } } // length of title + border (left) + space - _startx = titlesize + 2; + _startx = titlesize + 1; if( _max_length <= 0 ) { _max_length = 1024; @@ -77,17 +85,46 @@ void string_input_popup::create_window() const int w_y = ( TERMY - w_height ) / 2; const int w_x = std::max( ( TERMX - w_width ) / 2, 0 ); - w = catacurses::newwin( w_height, w_width, point( w_x, w_y ) ); - - _starty = w_height - 2; // The ____ looks better at the bottom right when the title folds + _starty = title_height; + w_full = catacurses::newwin( w_height, w_width, point( w_x, w_y ) ); + if( !_description.empty() ) { + w_description = catacurses::newwin( description_height, w_width - 1, point( w_x, + w_y + 1 ) ); + desc_view_ptr = std::make_unique( w_description ); + desc_view_ptr->set_text( _description ); + } + w_title_and_entry = catacurses::newwin( w_height - description_height - 2, w_width - 2, + point( w_x + 1, w_y + 1 + description_height ) ); custom_window = false; } void string_input_popup::create_context() { - ctxt_ptr = std::make_unique( "STRING_INPUT" ); + ctxt_ptr = std::make_unique( "STRING_INPUT", keyboard_mode::keychar ); ctxt = ctxt_ptr.get(); + ctxt->register_action( "TEXT.QUIT" ); + ctxt->register_action( "TEXT.CONFIRM" ); + if( !_identifier.empty() ) { + ctxt->register_action( "HISTORY_UP" ); + ctxt->register_action( "HISTORY_DOWN" ); + } + ctxt->register_action( "TEXT.LEFT" ); + ctxt->register_action( "TEXT.RIGHT" ); + ctxt->register_action( "TEXT.CLEAR" ); + ctxt->register_action( "TEXT.BACKSPACE" ); + ctxt->register_action( "TEXT.HOME" ); + ctxt->register_action( "TEXT.END" ); + ctxt->register_action( "TEXT.DELETE" ); +#if defined(TILES) + ctxt->register_action( "TEXT.PASTE" ); +#endif + ctxt->register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt->register_action( "HELP_KEYBINDINGS" ); + ctxt->register_action( "PAGE_UP" ); + ctxt->register_action( "PAGE_DOWN" ); + ctxt->register_action( "SCROLL_UP" ); + ctxt->register_action( "SCROLL_DOWN" ); ctxt->register_action( "ANY_INPUT" ); } @@ -114,17 +151,17 @@ void string_input_popup::show_history( utf8_wrapper &ret ) hmenu.w_height_setup = [&]() -> int { // number of lines that make up the menu window: 2*border+entries int height = 2 + hmenu.entries.size(); - if( getbegy( w ) < height ) + if( getbegy( w_full ) < height ) { - height = std::max( getbegy( w ), 4 ); + height = std::max( getbegy( w_full ), 4 ); } return height; }; hmenu.w_x_setup = [&]( int ) -> int { - return getbegx( w ); + return getbegx( w_full ); }; hmenu.w_y_setup = [&]( const int height ) -> int { - return std::max( getbegy( w ) - height, 0 ); + return std::max( getbegy( w_full ) - height, 0 ); }; bool finished = false; @@ -202,20 +239,19 @@ void string_input_popup::update_input_history( utf8_wrapper &ret, bool up ) _position = ret.length(); } -void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit, - const int shift ) const +void string_input_popup::draw( ui_adaptor *const ui, const utf8_wrapper &ret, + const utf8_wrapper &edit ) const { if( !custom_window ) { - draw_border( w ); + werase( w_full ); + draw_border( w_full ); + wnoutrefresh( w_full ); - for( size_t i = 0; i < descformatted.size(); ++i ) { - trim_and_print( w, point( 1, 1 + i ), w_width - 2, _desc_color, descformatted[i] ); - } - int pos_y = descformatted.size() + 1; + int pos_y = 0; for( int i = 0; i < static_cast( title_split.size() ) - 1; i++ ) { - mvwprintz( w, point( i + 1, pos_y++ ), _title_color, title_split[i] ); + mvwprintz( w_title_and_entry, point( i, pos_y++ ), _title_color, title_split[i] ); } - right_print( w, pos_y, w_width - titlesize - 1, _title_color, title_split.back() ); + trim_and_print( w_title_and_entry, point( 0, pos_y ), titlesize, _title_color, title_split.back() ); } const int scrmax = _endx - _startx; @@ -223,11 +259,13 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit const utf8_wrapper ds( ret.substr_display( shift, scrmax ) ); int start_x_edit = _startx; // Clear the line - mvwprintw( w, point( _startx, _starty ), std::string( std::max( 0, scrmax ), ' ' ) ); + mvwprintw( w_title_and_entry, point( _startx, _starty ), std::string( std::max( 0, scrmax ), + ' ' ) ); // Print the whole input string in default color - mvwprintz( w, point( _startx, _starty ), _string_color, "%s", ds.c_str() ); + mvwprintz( w_title_and_entry, point( _startx, _starty ), _string_color, "%s", ds.c_str() ); size_t sx = ds.display_width(); // Print the cursor in its own color + point cursor_pos; if( _position >= 0 && static_cast( _position ) < ret.length() ) { utf8_wrapper cursor = ret.substr( _position, 1 ); size_t a = _position; @@ -238,14 +276,18 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit cursor = ret.substr( a, _position - a + 1 ); } const size_t left_over = ret.substr( 0, a ).display_width() - shift; - mvwprintz( w, point( _startx + left_over, _starty ), _cursor_color, "%s", cursor.c_str() ); + cursor_pos = point( _startx + left_over, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, "%s", cursor.c_str() ); start_x_edit += left_over; - } else if( _position == _max_length && _max_length > 0 ) { - mvwprintz( w, point( _startx + sx, _starty ), _cursor_color, " " ); + } else if( _max_length > 0 + && ret.display_width() >= static_cast( _max_length ) ) { + cursor_pos = point( _startx + sx, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, " " ); start_x_edit += sx; sx++; // don't override trailing ' ' } else { - mvwprintz( w, point( _startx + sx, _starty ), _cursor_color, "_" ); + cursor_pos = point( _startx + sx, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, "_" ); start_x_edit += sx; sx++; // don't override trailing '_' } @@ -253,24 +295,42 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit // could be scrolled out of view when the cursor is at the start of the input size_t l = scrmax - sx; if( _max_length > 0 ) { - if( static_cast( ret.length() ) >= _max_length ) { + if( ret.display_width() >= static_cast( _max_length ) ) { l = 0; // no more input possible! } else if( _position == static_cast( ret.length() ) ) { // one '_' is already printed, formatted as cursor - l = std::min( l, _max_length - ret.length() - 1 ); + l = std::min( l, _max_length - ret.display_width() - 1 ); } else { - l = std::min( l, _max_length - ret.length() ); + l = std::min( l, _max_length - ret.display_width() ); } } if( l > 0 ) { - mvwprintz( w, point( _startx + sx, _starty ), _underscore_color, std::string( l, '_' ) ); + mvwprintz( w_title_and_entry, point( _startx + sx, _starty ), _underscore_color, std::string( l, + '_' ) ); } } if( !edit.empty() ) { - mvwprintz( w, point( start_x_edit, _starty ), _cursor_color, "%s", edit.c_str() ); + mvwprintz( w_title_and_entry, point( start_x_edit, _starty ), _cursor_color, "%s", edit.c_str() ); + } + wnoutrefresh( w_title_and_entry ); + + std::unique_ptr move_cursor_and_refresh; + if( ui ) { + ui->set_cursor( w_title_and_entry, cursor_pos ); + } else { + // This ensures the cursor is set last for calling UIs to record and set + // for screen readers and IME preview + move_cursor_and_refresh = std::make_unique( [&]() { + wmove( w_title_and_entry, cursor_pos ); + wnoutrefresh( w_title_and_entry ); + } ); } - wnoutrefresh( w ); + //Draw scrolling description + if( !custom_window && desc_view_ptr ) { + desc_view_ptr->draw( _desc_color ); + wnoutrefresh( w_description ); + } } void string_input_popup::query( const bool loop, const bool draw_only ) @@ -290,7 +350,7 @@ int64_t string_input_popup::query_int64_t( const bool loop, const bool draw_only const std::string &string_input_popup::query_string( const bool loop, const bool draw_only ) { - if( !custom_window && !w ) { + if( !custom_window && !w_full ) { create_window(); _position = -1; } @@ -298,29 +358,23 @@ const std::string &string_input_popup::query_string( const bool loop, const bool create_context(); } - std::optional sentry; - if( !draw_only && loop ) { - sentry.emplace(); - } utf8_wrapper ret( _text ); utf8_wrapper edit( ctxt->get_edittext() ); if( _position == -1 ) { _position = ret.length(); } - const int scrmax = _endx - _startx; - // in output (console) cells, not characters of the string! - int shift = 0; + const int scrmax = std::max( 0, _endx - _startx ); std::unique_ptr ui; if( !draw_only && !custom_window ) { ui = std::make_unique(); - ui->position_from_window( w ); + ui->position_from_window( w_full ); ui->on_screen_resize( [this]( ui_adaptor & ui ) { create_window(); - ui.position_from_window( w ); + ui.position_from_window( w_full ); } ); - ui->on_redraw( [&]( const ui_adaptor & ) { - draw( ret, edit, shift ); + ui->on_redraw( [&]( ui_adaptor & ui ) { + draw( &ui, ret, edit ); } ); } @@ -329,39 +383,43 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _canceled = false; _confirmed = false; do { - if( _position < 0 ) { _position = 0; } - const size_t left_shift = ret.substr( 0, _position ).display_width(); - if( static_cast( left_shift ) < shift ) { + if( shift < 0 ) { shift = 0; - } else if( _position < static_cast( ret.length() ) && - static_cast( left_shift ) + 1 >= shift + scrmax ) { - // if the cursor is inside the input string, keep one cell right of - // the cursor visible, because the cursor might be on a multi-cell - // character. - shift = left_shift - scrmax + 2; - } else if( _position == static_cast( ret.length() ) && - static_cast( left_shift ) >= shift + scrmax ) { - // cursor is behind the end of the input string, keep the - // trailing '_' visible (always a single cell character) - shift = left_shift - scrmax + 1; - } else if( shift < 0 ) { + } + + const size_t width_to_cursor_start = ret.substr( 0, _position ).display_width(); + size_t width_to_cursor_end = width_to_cursor_start; + if( static_cast( _position ) < ret.length() ) { + width_to_cursor_end += ret.substr( _position, 1 ).display_width(); + } else { + width_to_cursor_end += 1; + } + // starts scrolling when the cursor is this far from the start or end + const size_t scroll_width = std::min( 10, scrmax / 5 ); + if( ret.display_width() < static_cast( scrmax ) ) { shift = 0; + } else if( width_to_cursor_start < static_cast( shift ) + scroll_width ) { + shift = std::max( width_to_cursor_start, scroll_width ) - scroll_width; + } else if( width_to_cursor_end > static_cast( shift + scrmax ) - scroll_width ) { + shift = std::min( width_to_cursor_end + scroll_width, ret.display_width() ) - scrmax; } - const size_t xleft_shift = ret.substr_display( 0, shift ).display_width(); - if( static_cast( xleft_shift ) != shift ) { + const utf8_wrapper text_before_start = ret.substr_display( 0, shift ); + const size_t width_before_start = text_before_start.display_width(); + if( width_before_start != static_cast( shift ) ) { // This prevents a multi-cell character from been split, which is not possible // instead scroll a cell further to make that character disappear completely - shift++; + const size_t width_at_start = ret.substr( text_before_start.length(), 1 ).display_width(); + shift = width_before_start + width_at_start; } if( ui ) { ui_manager::redraw(); } else { - draw( ret, edit, shift ); + draw( nullptr, ret, edit ); } if( draw_only ) { @@ -370,7 +428,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool const std::string action = ctxt->handle_input(); const input_event ev = ctxt->get_raw_input(); - ch = ev.type == CATA_INPUT_KEYBOARD ? ev.get_first_input() : 0; + ch = ev.type == input_event_t::keyboard_char ? ev.get_first_input() : 0; _handled = true; if( callbacks[ch] ) { @@ -379,12 +437,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool } } - if( _ignore_custom_actions && action != "ANY_INPUT" ) { - _handled = false; - continue; - } - - if( ch == KEY_ESCAPE ) { + if( action == "TEXT.QUIT" ) { #if defined(__ANDROID__) if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { SDL_StopTextInput(); @@ -394,7 +447,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _position = -1; _canceled = true; return _text; - } else if( ch == '\n' ) { + } else if( action == "TEXT.CONFIRM" ) { add_to_history( ret.str() ); _confirmed = true; _text = ret.str(); @@ -403,77 +456,124 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _session_str_entered.erase( 0 ); } return _text; - } else if( ch == KEY_UP ) { + } else if( action == "HISTORY_UP" ) { if( !_identifier.empty() ) { - if( _hist_use_uilist ) { - show_history( ret ); - } else { - update_input_history( ret, true ); + if( edit.empty() ) { + if( _hist_use_uilist ) { + show_history( ret ); + } else { + update_input_history( ret, true ); + } } } else { _handled = false; } - } else if( ch == KEY_DOWN ) { + } else if( action == "HISTORY_DOWN" ) { if( !_identifier.empty() ) { - if( !_hist_use_uilist ) { + if( edit.empty() && !_hist_use_uilist ) { update_input_history( ret, false ); } } else { _handled = false; } - } else if( ch == KEY_DOWN || ch == KEY_NPAGE || ch == KEY_PPAGE || ch == KEY_BTAB || ch == 9 ) { - _handled = false; - } else if( ch == KEY_RIGHT ) { - if( _position + 1 <= static_cast( ret.size() ) ) { + } else if( action == "TEXT.RIGHT" ) { + if( edit.empty() && _position + 1 <= static_cast( ret.size() ) ) { _position++; } - } else if( ch == KEY_LEFT ) { - if( _position > 0 ) { + } else if( action == "TEXT.LEFT" ) { + if( edit.empty() && _position > 0 ) { _position--; } - } else if( ch == 0x15 ) { // ctrl-u: delete all the things + } else if( action == "TEXT.CLEAR" ) { _position = 0; ret.erase( 0 ); - // Move the cursor back and re-draw it - } else if( ch == KEY_BACKSPACE ) { - // but silently drop input if we're at 0, instead of adding '^' + } else if( action == "TEXT.BACKSPACE" ) { if( _position > 0 && _position <= static_cast( ret.size() ) ) { - // TODO: it is safe now since you only input ASCII chars _position--; ret.erase( _position, 1 ); } - } else if( ch == KEY_HOME ) { - _position = 0; - } else if( ch == KEY_END ) { - _position = ret.size(); - } else if( ch == KEY_DC ) { + } else if( action == "TEXT.HOME" ) { + if( edit.empty() ) { + if( edit.empty() ) { + _position = 0; + } + } + } else if( action == "TEXT.END" ) { + if( edit.empty() ) { + _position = ret.size(); + } + } else if( action == "TEXT.DELETE" ) { if( _position < static_cast( ret.size() ) ) { ret.erase( _position, 1 ); } - } else if( ch == KEY_F( 2 ) ) { - std::string tmp = get_input_string_from_file(); - int tmplen = utf8_width( tmp ); - if( tmplen > 0 && ( tmplen + utf8_width( ret ) <= _max_length || _max_length == 0 ) ) { - ret.append( tmp ); + /*Note: SCROLL_UP/SCROLL_DOWN should by default only trigger on mousewheel, + * since up/down arrow were handled above */ + } else if( action == "SCROLL_UP" ) { + if( desc_view_ptr ) { + desc_view_ptr->scroll_up(); + } + } else if( action == "SCROLL_DOWN" ) { + if( desc_view_ptr ) { + desc_view_ptr->scroll_down(); + } + } else if( action == "PAGE_UP" ) { + if( desc_view_ptr ) { + desc_view_ptr->page_up(); + } + } else if( action == "PAGE_DOWN" ) { + if( desc_view_ptr ) { + desc_view_ptr->page_down(); + } + } else if( action == "TEXT.PASTE" || action == "TEXT.INPUT_FROM_FILE" + || ( action == "ANY_INPUT" && !ev.text.empty() ) ) { + // paste, input from file, or text input + // bail out early if already at length limit + if( _max_length <= 0 || ret.display_width() < static_cast( _max_length ) ) { + std::string entered; + if( action == "TEXT.PASTE" ) { +#if defined(TILES) + if( edit.empty() ) { + char *const clip = SDL_GetClipboardText(); + if( clip ) { + entered = clip; + SDL_free( clip ); + } + } +#endif + } else if( action == "TEXT.INPUT_FROM_FILE" ) { + if( edit.empty() ) { + entered = get_input_string_from_file(); + } + } else { + entered = ev.text; + } + if( !entered.empty() ) { + utf8_wrapper insertion; + const char *str = entered.c_str(); + int len = entered.length(); + int width = ret.display_width(); + while( len > 0 ) { + const uint32_t ch = UTF8_getch( &str, &len ); + // Use mk_wcwidth to filter out control characters + if( _only_digits ? ch == '-' || isdigit( ch ) : mk_wcwidth( ch ) >= 0 ) { + const int newwidth = mk_wcwidth( ch ); + if( _max_length <= 0 || width + newwidth <= _max_length ) { + insertion.append( utf8_wrapper( utf32_to_utf8( ch ) ) ); + width += newwidth; + } else { + break; + } + } + } + ret.insert( _position, insertion ); + _position += insertion.length(); + edit = utf8_wrapper(); + ctxt->set_edittext( std::string() ); + } } - } else if( !ev.text.empty() && _only_digits && !( isdigit( ev.text[0] ) || ev.text[0] == '-' ) ) { - // ignore non-digit (and '-' is a digit as well) - } else if( _max_length > 0 && static_cast( ret.length() ) >= _max_length ) { - // no further input possible, ignore key - } else if( !ev.text.empty() ) { - const utf8_wrapper t( ev.text ); - ret.insert( _position, t ); - _position += t.length(); - edit.erase( 0 ); - ctxt->set_edittext( edit.c_str() ); } else if( ev.edit_refresh ) { - const utf8_wrapper t( ev.edit ); - edit.erase( 0 ); - edit.insert( 0, t ); - ctxt->set_edittext( edit.c_str() ); - } else if( ev.edit.empty() ) { - edit.erase( 0 ); - ctxt->set_edittext( edit.c_str() ); + edit = utf8_wrapper( ev.edit ); + ctxt->set_edittext( ev.edit ); } else { _handled = false; } @@ -485,11 +585,11 @@ const std::string &string_input_popup::query_string( const bool loop, const bool string_input_popup &string_input_popup::window( const catacurses::window &w, point start, int endx ) { - if( !custom_window && this->w ) { + if( !custom_window && this->w_full ) { // default window already created return *this; } - this->w = w; + this->w_title_and_entry = w; _startx = start.x; _starty = start.y; _endx = endx; diff --git a/src/string_input_popup.h b/src/string_input_popup.h index d3e153766ba0..ce6a22f71c7c 100644 --- a/src/string_input_popup.h +++ b/src/string_input_popup.h @@ -15,6 +15,8 @@ #include "cursesdef.h" class input_context; +class scrolling_text_view; +class ui_adaptor; class utf8_wrapper; struct point; @@ -61,11 +63,12 @@ class string_input_popup // NOLINT(cata-xy) int _max_length = -1; bool _only_digits = false; bool _hist_use_uilist = true; - bool _ignore_custom_actions = true; int _startx = 0; int _starty = 0; int _endx = 0; int _position = -1; + // in output (console) cells, not characters of the string! + int shift = 0; int _hist_str_ind = 0; //Counts only when @_hist_use_uilist is false const size_t _hist_max_size = 100; @@ -73,12 +76,16 @@ class string_input_popup // NOLINT(cata-xy) // Cache when using the default window int w_width = 0; int w_height = 0; - std::vector descformatted; + int title_height = 0; + int description_height = 0; std::vector title_split; int titlesize = 0; bool custom_window = false; - catacurses::window w; + catacurses::window w_full; + catacurses::window w_description; + catacurses::window w_title_and_entry; + std::unique_ptr desc_view_ptr; std::unique_ptr ctxt_ptr; input_context *ctxt = nullptr; @@ -93,7 +100,7 @@ class string_input_popup // NOLINT(cata-xy) void show_history( utf8_wrapper &ret ); void add_to_history( const std::string &value ) const; void update_input_history( utf8_wrapper &ret, bool up ); - void draw( const utf8_wrapper &ret, const utf8_wrapper &edit, int shift ) const; + void draw( ui_adaptor *ui, const utf8_wrapper &ret, const utf8_wrapper &edit ) const; public: string_input_popup(); @@ -168,16 +175,6 @@ class string_input_popup // NOLINT(cata-xy) _hist_use_uilist = value; return *this; } - /** - * If true and the custom input context returns an input action, the - * action is not handled at all and left to be handled by the caller. - * Otherwise the action is always handled as an input event to the popup. - * The caller can use @ref handled to check whether the last input is handled. - */ - string_input_popup &ignore_custom_actions( const bool value ) { - _ignore_custom_actions = value; - return *this; - } /** * Set the window area where to display the input text. If this is set, * the class will not create a separate window and *only* the editable diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index 2f6729f9550a..6a3b47d6838d 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -85,7 +85,7 @@ bool trapfunc::none( const tripoint &, Creature *, item * ) bool trapfunc::bubble( const tripoint &p, Creature *c, item * ) { // tiny animals don't trigger bubble wrap - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } if( c != nullptr ) { @@ -104,7 +104,7 @@ bool trapfunc::glass( const tripoint &p, Creature *c, item * ) { if( c != nullptr ) { // tiny animals and hallucinations don't trigger glass trap - if( c->get_size() == MS_TINY || c->is_hallucination() ) { + if( c->get_size() == creature_size::tiny || c->is_hallucination() ) { return false; } c->add_msg_player_or_npc( m_warning, _( "You step on some glass!" ), @@ -143,7 +143,7 @@ bool trapfunc::cot( const tripoint &p, Creature *c, item * ) bool trapfunc::beartrap( const tripoint &p, Creature *c, item * ) { // tiny animals don't trigger bear traps - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } sounds::sound( p, 8, sounds::sound_t::combat, _( "SNAP!" ), false, "trap", "bear_trap" ); @@ -178,7 +178,7 @@ bool trapfunc::board( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger spiked boards, they can squeeze between the nails - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a spiked board!" ), @@ -208,7 +208,7 @@ bool trapfunc::caltrops( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger caltrops, they can squeeze between them - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a sharp metal caltrop!" ), @@ -238,7 +238,7 @@ bool trapfunc::caltrops_glass( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger caltrops, they can squeeze between them - if( c->get_size() == MS_TINY || c->is_hallucination() ) { + if( c->get_size() == creature_size::tiny || c->is_hallucination() ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a sharp glass caltrop!" ), @@ -268,7 +268,7 @@ bool trapfunc::tripwire( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger tripwires, they just squeeze under it - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You trip over a tripwire!" ), @@ -378,19 +378,19 @@ bool trapfunc::crossbow( const tripoint &p, Creature *c, item * ) int chance = 0; // chance of getting hit depends on size switch( z->type->size ) { - case MS_TINY: + case creature_size::tiny: chance = 50; break; - case MS_SMALL: + case creature_size::small: chance = 8; break; - case MS_MEDIUM: + case creature_size::medium: chance = 6; break; - case MS_LARGE: + case creature_size::large: chance = 4; break; - case MS_HUGE: + case creature_size::huge: chance = 1; break; default: @@ -528,7 +528,7 @@ bool trapfunc::snare_light( const tripoint &p, Creature *c, item * ) // Actual effects c->add_effect( effect_lightsnare, 1_turns, hit->token ); monster *z = dynamic_cast( c ); - if( z != nullptr && z->type->size == MS_TINY ) { + if( z != nullptr && z->type->size == creature_size::tiny ) { z->deal_damage( nullptr, hit, damage_instance( DT_BASH, 10 ) ); } c->check_dead_state(); @@ -563,11 +563,11 @@ bool trapfunc::snare_heavy( const tripoint &p, Creature *c, item * ) } else if( z != nullptr ) { int damage; switch( z->type->size ) { - case MS_TINY: - case MS_SMALL: + case creature_size::tiny: + case creature_size::small: damage = 20; break; - case MS_MEDIUM: + case creature_size::medium: damage = 10; break; default: @@ -598,7 +598,7 @@ static explosion_data get_basic_explosion_data() bool trapfunc::landmine( const tripoint &p, Creature *c, item * ) { // tiny animals are too light to trigger land mines - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } if( c != nullptr ) { @@ -748,7 +748,7 @@ bool trapfunc::pit( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } const float eff = pit_effectiveness( p ); @@ -799,7 +799,7 @@ bool trapfunc::pit_spikes( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into spiked pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You fall in a spiked pit!" ), @@ -880,7 +880,7 @@ bool trapfunc::pit_glass( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into glass pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You fall in a pit filled with glass shards!" ), @@ -1068,7 +1068,7 @@ static bool sinkhole_safety_roll( player *p, const itype_id &itemname, const int bool trapfunc::sinkhole( const tripoint &p, Creature *c, item *i ) { // tiny creatures don't trigger the sinkhole to collapse - if( c == nullptr || c->get_size() == MS_TINY ) { + if( c == nullptr || c->get_size() == creature_size::tiny ) { return false; } monster *z = dynamic_cast( c ); diff --git a/src/ui.cpp b/src/ui.cpp index 0e18873bc737..98b6f27115ad 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -13,7 +13,6 @@ #include "catacharset.h" #include "debug.h" #include "game.h" -#include "ime.h" #include "input.h" #include "output.h" #include "player.h" @@ -199,7 +198,7 @@ void uilist::init() callback = nullptr; // * uilist_callback filter.clear(); // filter string. If "", show everything fentries.clear(); // fentries is the actual display after filtering, and maps displayed entry number to actual entry number - fselected = 0; // fentries[selected] + fselected = 0; // selected = fentries[fselected] filtering = true; // enable list display filtering via '/' or '.' filtering_igncase = true; // ignore case when filtering max_entry_len = 0; @@ -211,6 +210,60 @@ void uilist::init() additional_actions.clear(); } +input_context uilist::create_main_input_context() const +{ + input_context ctxt( input_category, keyboard_mode::keycode ); + ctxt.register_updown(); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "HOME", to_translation( "Go to first entry" ) ); + ctxt.register_action( "END", to_translation( "Go to last entry" ) ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); + if( allow_cancel ) { + ctxt.register_action( "QUIT" ); + } + ctxt.register_action( "SELECT" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "FILTER" ); + ctxt.register_action( "ANY_INPUT" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + for( const auto &additional_action : additional_actions ) { + ctxt.register_action( additional_action.first, additional_action.second ); + } + return ctxt; +} + +input_context uilist::create_filter_input_context() const +{ + input_context ctxt( input_category, keyboard_mode::keychar ); + // string input popup actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.QUIT" ); + ctxt.register_action( "TEXT.CONFIRM" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "ANY_INPUT" ); + // uilist actions + ctxt.register_updown(); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "HOME", to_translation( "Go to first entry" ) ); + ctxt.register_action( "END", to_translation( "Go to last entry" ) ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); + return ctxt; +} + void uilist::filterlist() { bool filtering = ( this->filtering && !filter.empty() ); @@ -288,19 +341,11 @@ void uilist::set_filter( const std::string &fstr ) void uilist::inputfilter() { - input_context ctxt( input_category ); - ctxt.register_updown(); - ctxt.register_action( "PAGE_UP" ); - ctxt.register_action( "PAGE_DOWN" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - ctxt.register_action( "ANY_INPUT" ); + input_context ctxt = create_filter_input_context(); filter_popup = std::make_unique(); filter_popup->context( ctxt ).text( filter ) - .ignore_custom_actions( false ) .max_length( 256 ) .window( window, point( 4, w_height - 1 ), w_width - 4 ); - ime_sentry sentry; do { ui_manager::redraw(); filter = filter_popup->query_string( false ); @@ -607,7 +652,7 @@ void uilist::apply_scrollbar() /** * Generate and refresh output */ -void uilist::show() +void uilist::show( ui_adaptor &ui ) { if( !started ) { setup(); @@ -643,6 +688,10 @@ void uilist::show() const int pad_size = std::max( 0, w_width - 2 - pad_left - pad_right ); const std::string padspaces = std::string( pad_size, ' ' ); + for( uilist_entry &entry : entries ) { + entry.drawn_rect = std::nullopt; + } + for( int fei = vshift, si = 0; si < vmax; fei++, si++ ) { if( fei < static_cast( fentries.size() ) ) { int ei = fentries [ fei ]; @@ -725,6 +774,8 @@ void uilist::show() mvwprintz( window, point( 2, w_height - 1 ), border_color, "< " ); mvwprintz( window, point( w_width - 3, w_height - 1 ), border_color, " >" ); filter_popup->query( /*loop=*/false, /*draw_only=*/true ); + // Record cursor immediately after filter_popup drawing + ui.record_term_cursor(); } else { if( !filter.empty() ) { mvwprintz( window, point( 2, w_height - 1 ), border_color, "< %s >", filter ); @@ -820,8 +871,8 @@ shared_ptr_fast uilist::create_or_get_ui_adaptor() shared_ptr_fast current_ui = ui.lock(); if( !current_ui ) { ui = current_ui = make_shared_fast(); - current_ui->on_redraw( [this]( const ui_adaptor & ) { - show(); + current_ui->on_redraw( [this]( ui_adaptor & ui ) { + show( ui ); } ); current_ui->on_screen_resize( [this]( ui_adaptor & ui ) { reposition( ui ); @@ -849,22 +900,8 @@ void uilist::query( bool loop, int timeout ) } ret = UILIST_WAIT_INPUT; - input_context ctxt( input_category ); - ctxt.register_updown(); - ctxt.register_action( "PAGE_UP" ); - ctxt.register_action( "PAGE_DOWN" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - if( allow_cancel ) { - ctxt.register_action( "QUIT" ); - } - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "FILTER" ); - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - for( const auto &additional_action : additional_actions ) { - ctxt.register_action( additional_action.first, additional_action.second ); - } + input_context ctxt = create_main_input_context(); + hotkeys = ctxt.get_available_single_char_hotkeys( hotkeys ); shared_ptr_fast ui = create_or_get_ui_adaptor(); @@ -889,15 +926,25 @@ void uilist::query( bool loop, int timeout ) /* nothing */ } else if( filtering && ret_act == "FILTER" ) { inputfilter(); - } else if( iter != keymap.end() ) { - selected = iter->second; - if( entries[ selected ].enabled ) { - ret = entries[ selected ].retval; // valid - } else if( allow_disabled ) { - ret = entries[selected].retval; // disabled - } - if( callback != nullptr ) { - callback->select( this ); + } else if( ret_act == "ANY_INPUT" && iter != keymap.end() ) { + // only handle "ANY_INPUT" since "HELP_KEYBINDINGS" is already + // handled by the input context and the caller might want to handle + // its custom actions + const auto it = std::find( fentries.begin(), fentries.end(), iter->second ); + if( it != fentries.end() ) { + const bool enabled = entries[*it].enabled; + if( enabled || allow_disabled || hilight_disabled ) { + // Change the selection to display correctly when this function + // is called again. + fselected = std::distance( fentries.begin(), it ); + selected = *it; + if( enabled || allow_disabled ) { + ret = entries[selected].retval; + } + if( callback != nullptr ) { + callback->select( this ); + } + } } } else if( !fentries.empty() && ret_act == "CONFIRM" ) { if( entries[ selected ].enabled ) { diff --git a/src/ui.h b/src/ui.h index 7658ca69cd96..7abee2417407 100644 --- a/src/ui.h +++ b/src/ui.h @@ -10,6 +10,7 @@ #include #include "color.h" +#include "cuboid_rectangle.h" #include "cursesdef.h" #include "memory_fast.h" #include "pimpl.h" @@ -123,6 +124,8 @@ struct uilist_entry { text_color = c; return *this; } + + std::optional> drawn_rect; }; /** @@ -236,7 +239,7 @@ class uilist // NOLINT(cata-xy) void setup(); // initialize the window or reposition it after screen size change. void reposition( ui_adaptor &ui ); - void show(); + void show( ui_adaptor &ui ); bool scrollby( int scrollby ); void query( bool loop = true, int timeout = -1 ); @@ -368,6 +371,8 @@ class uilist // NOLINT(cata-xy) private: std::string hotkeys; report_color_error _color_error = report_color_error::yes; + input_context create_main_input_context() const; + input_context create_filter_input_context() const; public: // Iternal states @@ -411,7 +416,6 @@ class uilist // NOLINT(cata-xy) std::string ret_act; int ret; int keypress; - int selected; }; diff --git a/src/ui_manager.cpp b/src/ui_manager.cpp index 20590600cd2a..40feb3f1c774 100644 --- a/src/ui_manager.cpp +++ b/src/ui_manager.cpp @@ -1,6 +1,7 @@ #include "ui_manager.h" #include +#include #include #include #include @@ -135,6 +136,67 @@ void ui_adaptor::on_screen_resize( const screen_resize_callback_t &fun ) screen_resized_cb = fun; } +void ui_adaptor::set_cursor( const catacurses::window &w, const point &pos ) +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getbegx( w ), getbegy( w ) ) + pos; +#else + // Unimplemented + cursor_type = cursor::disabled; + static_cast( w ); + static_cast( pos ); +#endif +} + +void ui_adaptor::record_cursor( const catacurses::window &w ) +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getbegx( w ) + getcurx( w ), getbegy( w ) + getcury( w ) ); +#else + // Unimplemented + cursor_type = cursor::disabled; + static_cast( w ); +#endif +} + +void ui_adaptor::record_term_cursor() +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getcurx( catacurses::newscr ), getcury( catacurses::newscr ) ); +#else + // Unimplemented + cursor_type = cursor::disabled; +#endif +} + +void ui_adaptor::default_cursor() +{ +#if !defined( TILES ) + cursor_type = cursor::last; +#else + // Unimplemented + cursor_type = cursor::disabled; +#endif +} + +void ui_adaptor::disable_cursor() +{ + cursor_type = cursor::disabled; +} + +static void restore_cursor( const point &p ) +{ +#if !defined( TILES ) + wmove( catacurses::newscr, p ); +#else + static_cast( p ); +#endif +} + + void ui_adaptor::mark_resize() const { deferred_resize = true; @@ -343,17 +405,29 @@ void ui_adaptor::redraw_invalidated() first_enabled = ui_stack_copy->begin() + ( first_enabled - ui_stack_orig->begin() ); ui_stack_orig = &*ui_stack_copy; } + std::optional cursor_pos; for( auto it = first_enabled; !restart_redrawing && it != ui_stack_orig->end(); ++it ) { - const ui_adaptor &ui = *it; + ui_adaptor &ui = *it; if( ui.invalidated ) { if( ui.redraw_cb ) { + ui.default_cursor(); ui.redraw_cb( ui ); + if( ui.cursor_type == cursor::last ) { + ui.record_term_cursor(); + assert( ui.cursor_type != cursor::last ); + } + if( ui.cursor_type == cursor::custom ) { + cursor_pos = ui.cursor_pos; + } } if( !restart_redrawing ) { ui.invalidated = false; } } } + if( !restart_redrawing && cursor_pos.has_value() ) { + restore_cursor( cursor_pos.value() ); + } } } while( restart_redrawing ); } diff --git a/src/ui_manager.h b/src/ui_manager.h index dcaa0978ba4c..44d0465bcb1e 100644 --- a/src/ui_manager.h +++ b/src/ui_manager.h @@ -35,11 +35,14 @@ class window; * // Mark the resize callback to be called on the first redraw * ui.mark_resize(); * // Things to do when redrawing the UI - * ui.on_redraw( [&]( const ui_adaptor & ) { + * ui.on_redraw( [&]( ui_adaptor & ui ) { * // Clear UI area * werase( win ); * // Print things * mvwprintw( win, point_zero, "Hello World!" ); + * // Record the cursor position for screen readers and IME preview to + * // correctly function on curses + * ui.record_cursor( win ); * // Write to frame buffer * wnoutrefresh( win ); * } ); @@ -64,7 +67,7 @@ class window; class ui_adaptor { public: - using redraw_callback_t = std::function; + using redraw_callback_t = std::function; using screen_resize_callback_t = std::function; struct disable_uis_below { @@ -133,7 +136,8 @@ class ui_adaptor * - Construct new `ui_adaptor` instances * - Deconstruct old `ui_adaptor` instances * - Call `redraw` or `screen_resized` - * - (Redraw callback) call `position_from_window` + * - (Redraw callback) call `position_from_window`, `position`, or + * `invalidate_ui` * - Call any function that does these things, except for `debugmsg` * * Otherwise, display glitches or even crashes might happen. @@ -142,6 +146,46 @@ class ui_adaptor /* See `on_redraw`. */ void on_screen_resize( const screen_resize_callback_t &fun ); + /** + * Automatically set the termianl cursor to a window position after + * drawing is done. The cursor position of the last drawn UI takes + * effect. This ensures the cursor is always set to the desired position + * even if some subsequent drawing code moves the cursor, so screen + * readers (and in the case of text input, the IME preview) can + * correctly function. This is only supposed to be called from the + * `on_redraw` callback on the `ui_adaptor` argument of the callback. + * Currently only supports curses (on tiles, the windows may have + * different cell sizes, so the current implementation of recording the + * on-screen position does not work, and screen readers do not work with + * the current tiles implementation anyway). + */ + void set_cursor( const catacurses::window &win, const point &pos ); + /** + * Record the current window cursor position and restore it when drawing + * is done. The most recent cursor position is recorded regardless of + * whether the terminal cursor is updated by refreshing the window. See + * also `set_cursor`. + */ + void record_cursor( const catacurses::window &win ); + /** + * Record the current terminal cursor position (where the cursor was + * placed when refreshing the last window) and restore it when drawing + * is done. See also `set_cursor`. + */ + void record_term_cursor(); + /** + * Use the terminal cursor position when the redraw callback returns. + * This is the default. See also `set_cursor`. + */ + void default_cursor(); + /** + * Do not set cursor for this `ui_adaptor`. If any higher or lower UI + * sets the cursor, that cursor position is used instead. If no UI sets + * the cursor, the final cursor position when drawing is done is used. + * See also `set_cursor`. + */ + void disable_cursor(); + /** * Mark this `ui_adaptor` for resizing the next time it is redrawn. * This is normally called alongside `on_screen_resize` to initialize @@ -178,6 +222,16 @@ class ui_adaptor redraw_callback_t redraw_cb; screen_resize_callback_t screen_resized_cb; + // For optimization purposes, these value may or may not be reset or + // modified before, during, or after drawing, so they do not necessarily + // indicate what the current cursor position would be, and therefore + // should not be made public or have a public getter. + enum class cursor { + last, custom, disabled + }; + cursor cursor_type = cursor::last; + point cursor_pos; + bool disabling_uis_below; bool is_debug_message_ui; diff --git a/src/veh_interact.cpp b/src/veh_interact.cpp index e1172f2484e9..db0e7080ef5f 100644 --- a/src/veh_interact.cpp +++ b/src/veh_interact.cpp @@ -214,7 +214,7 @@ vehicle_part &veh_interact::select_part( const vehicle &veh, const part_selector * Creates a blank veh_interact window. */ veh_interact::veh_interact( vehicle &veh, point p ) - : dd( p ), veh( &veh ), main_context( "VEH_INTERACT" ) + : dd( p ), veh( &veh ), main_context( "VEH_INTERACT", keyboard_mode::keychar ) { // Only build the shapes map and the wheel list once for( const auto &e : vpart_info::all() ) { @@ -2656,6 +2656,26 @@ void veh_interact::display_name() wnoutrefresh( w_name ); } +static std::string veh_act_desc( const input_context &ctxt, const std::string &id, + const std::string &desc, const task_reason reason ) +{ + static const translation inline_fmt_enabled = to_translation( + "keybinding", "%1$s%2$s%3$s" ); + static const translation inline_fmt_disabled = to_translation( + "keybinding", "%1$s%2$s%3$s" ); + static const translation separate_fmt_enabled = to_translation( + "keybinding", "%1$s-%2$s" ); + static const translation separate_fmt_disabled = to_translation( + "keybinding", "%1$s-%2$s" ); + if( reason == task_reason::CAN_DO ) { + return ctxt.get_desc( id, desc, input_context::allow_all_keys, + inline_fmt_enabled, separate_fmt_enabled ); + } else { + return ctxt.get_desc( id, desc, input_context::allow_all_keys, + inline_fmt_disabled, separate_fmt_disabled ); + } +} + /** * Prints the list of usable commands, and highlights the hotkeys used to activate them. */ @@ -2668,65 +2688,59 @@ void veh_interact::display_mode() // NOLINTNEXTLINE(cata-use-named-point-constants) print_colored_text( w_mode, point( 1, 0 ), title_col, title_col, title.value() ); } else { - size_t esc_pos = display_esc( w_mode ); - - // broken indentation preserved to avoid breaking git history for large number of lines - const std::array actions = { { - { _( "nstall" ) }, - { _( "epair" ) }, - { _( "end" ) }, - { _( "reill" ) }, - { _( "remve" ) }, - { _( "iphon" ) }, - { _( "unloa" ) }, - { _( "cre" ) }, - { _( "sha

e" ) }, - { _( "rname" ) }, - { _( "lbel" ) }, + constexpr size_t action_cnt = 11; + const std::array actions = { { + veh_act_desc( main_context, "INSTALL", + pgettext( "veh_interact", "install" ), + cant_do( 'i' ) ), + veh_act_desc( main_context, "REPAIR", + pgettext( "veh_interact", "repair" ), + cant_do( 'r' ) ), + veh_act_desc( main_context, "MEND", + pgettext( "veh_interact", "mend" ), + cant_do( 'm' ) ), + veh_act_desc( main_context, "REFILL", + pgettext( "veh_interact", "refill" ), + cant_do( 'f' ) ), + veh_act_desc( main_context, "REMOVE", + pgettext( "veh_interact", "remove" ), + cant_do( 'o' ) ), + veh_act_desc( main_context, "SIPHON", + pgettext( "veh_interact", "siphon" ), + cant_do( 's' ) ), + veh_act_desc( main_context, "UNLOAD", + pgettext( "veh_interact", "unload" ), + cant_do( 'd' ) ), + veh_act_desc( main_context, "ASSIGN_CREW", + pgettext( "veh_interact", "crew" ), + cant_do( 'w' ) ), + veh_act_desc( main_context, "RENAME", + pgettext( "veh_interact", "rename" ), + task_reason::CAN_DO ), + veh_act_desc( main_context, "RELABEL", + pgettext( "veh_interact", "label" ), + cant_do( 'a' ) ), + veh_act_desc( main_context, "QUIT", + pgettext( "veh_interact", "back" ), + task_reason::CAN_DO ), } }; - const std::array::value> enabled = { { - !cant_do( 'i' ), - !cant_do( 'r' ), - !cant_do( 'm' ), - !cant_do( 'f' ), - !cant_do( 'o' ), - !cant_do( 's' ), - !cant_do( 'd' ), - !cant_do( 'w' ), - !cant_do( 'p' ), - true, // 'rename' is always available - !cant_do( 'a' ), - } - }; - - int pos[std::tuple_size::value + 1]; - pos[0] = 1; - for( size_t i = 0; i < actions.size(); i++ ) { - pos[i + 1] = pos[i] + utf8_width( actions[i] ) - 2; + std::array < int, action_cnt + 1 > pos; + pos[0] = 0; + for( size_t i = 0; i < action_cnt; i++ ) { + pos[i + 1] = pos[i] + utf8_width( actions[i], true ); } - int spacing = static_cast( ( esc_pos - 1 - pos[actions.size()] ) / actions.size() ); - int shift = static_cast( ( esc_pos - pos[actions.size()] - spacing * - ( actions.size() - 1 ) ) / 2 ) - 1; - for( size_t i = 0; i < actions.size(); i++ ) { - shortcut_print( w_mode, point( pos[i] + spacing * i + shift, 0 ), - enabled[i] ? c_light_gray : c_dark_gray, enabled[i] ? c_light_green : c_green, - actions[i] ); + const int space = std::max( getmaxx( w_mode ) - pos.back(), action_cnt + 1 ); + for( size_t i = 0; i < action_cnt; i++ ) { + nc_color dummy = c_white; + print_colored_text( w_mode, point( pos[i] + space * ( i + 1 ) / ( action_cnt + 1 ), 0 ), + dummy, c_white, actions[i] ); } } wnoutrefresh( w_mode ); } -size_t veh_interact::display_esc( const catacurses::window &win ) -{ - std::string backstr = _( "-back" ); - // right text align - size_t pos = getmaxx( win ) - utf8_width( backstr ) + 2; - shortcut_print( win, point( pos, 0 ), c_light_gray, c_light_green, backstr ); - return pos; -} - /** * Draws the list of parts that can be mounted in the selected square. Used * when installing new parts. diff --git a/src/veh_interact.h b/src/veh_interact.h index 6bab27f1c97e..d7fc58a9a6af 100644 --- a/src/veh_interact.h +++ b/src/veh_interact.h @@ -163,7 +163,6 @@ class veh_interact void display_mode(); void display_list( size_t pos, const std::vector &list, int header = 0 ); void display_details( const vpart_info *part ); - size_t display_esc( const catacurses::window &win ); struct part_option { part_option( const std::string &key, vehicle_part *part, char hotkey, diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index bcd071a73efd..b0d641a04aae 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -102,7 +102,7 @@ enum change_types : int { char keybind( const std::string &opt, const std::string &context ) { - const auto keys = input_context( context ).keys_bound_to( opt ); + const auto keys = input_context( context, keyboard_mode::keychar ).keys_bound_to( opt ); return keys.empty() ? ' ' : keys.front(); } diff --git a/src/wincurse.cpp b/src/wincurse.cpp index 2bb6140cc642..fcc754ad2461 100644 --- a/src/wincurse.cpp +++ b/src/wincurse.cpp @@ -676,7 +676,9 @@ void input_manager::pump_events() previously_pressed_key = 0; } -input_event input_manager::get_input_event() +// we can probably add support for keycode mode, but wincurse is deprecated +// so we just ignore the mode argument. +input_event input_manager::get_input_event( const keyboard_mode /*preferred_keyboard_mode*/ ) { // standards note: getch is sometimes required to call refresh // see, e.g., http://linux.die.net/man/3/getch @@ -712,17 +714,17 @@ input_event input_manager::get_input_event() input_event rval; if( lastchar == ERR ) { if( input_timeout > 0 ) { - rval.type = CATA_INPUT_TIMEOUT; + rval.type = input_event_t::timeout; } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { // == Unicode DELETE if( lastchar == 127 ) { previously_pressed_key = KEY_BACKSPACE; - return input_event( KEY_BACKSPACE, CATA_INPUT_KEYBOARD ); + return input_event( KEY_BACKSPACE, input_event_t::keyboard_char ); } - rval.type = CATA_INPUT_KEYBOARD; + rval.type = input_event_t::keyboard_char; rval.text = utf32_to_utf8( lastchar ); previously_pressed_key = lastchar; // for compatibility only add the first byte, not the code point diff --git a/src/wish.cpp b/src/wish.cpp index 16bb8bfbb1c2..cb62ec30eefd 100644 --- a/src/wish.cpp +++ b/src/wish.cpp @@ -222,7 +222,7 @@ class wish_mutate_callback: public uilist_callback mvwprintz( menu->window, point( startx, menu->w_height - 3 ), c_green, msg ); msg.clear(); - input_context ctxt( menu->input_category ); + input_context ctxt( menu->input_category, keyboard_mode::keychar ); mvwprintw( menu->window, point( startx, menu->w_height - 2 ), _( "[%s] find, [%s] quit, [t] toggle base trait" ), ctxt.get_desc( "FILTER" ), ctxt.get_desc( "QUIT" ) ); @@ -505,7 +505,7 @@ class wish_monster_callback: public uilist_callback mvwprintz( w_info, point( 0, getmaxy( w_info ) - 3 ), c_green, msg ); msg.clear(); - input_context ctxt( menu->input_category ); + input_context ctxt( menu->input_category, keyboard_mode::keychar ); mvwprintw( w_info, point( 0, getmaxy( w_info ) - 2 ), _( "[%s] find, [f]riendly, [h]allucination, [i]ncrease group, [d]ecrease group, [%s] quit" ), ctxt.get_desc( "FILTER" ), ctxt.get_desc( "QUIT" ) ); @@ -561,7 +561,7 @@ void debug_menu::wishmonster( const std::optional &p ) } ++num_spawned; } - input_context ctxt( wmenu.input_category ); + input_context ctxt( wmenu.input_category, keyboard_mode::keychar ); cb.msg = string_format( _( "Spawned %d monsters, choose another or [%s] to quit." ), num_spawned, ctxt.get_desc( "QUIT" ) ); if( num_spawned == 0 ) { @@ -645,7 +645,7 @@ class wish_item_callback: public uilist_callback } mvwprintz( menu->window, point( startx, menu->w_height - 3 ), c_green, msg ); msg.erase(); - input_context ctxt( menu->input_category ); + input_context ctxt( menu->input_category, keyboard_mode::keychar ); mvwprintw( menu->window, point( startx, menu->w_height - 2 ), _( "[%s] find, [f] container, [F] flag, [E] everything, [%s] quit" ), ctxt.get_desc( "FILTER" ), ctxt.get_desc( "QUIT" ) ); @@ -765,7 +765,7 @@ void debug_menu::wishitem( player *p, const tripoint &pos ) wmenu.ret = -1; } if( amount > 0 ) { - input_context ctxt( wmenu.input_category ); + input_context ctxt( wmenu.input_category, keyboard_mode::keychar ); cb.msg = string_format( _( "Wish granted. Wish for more or hit [%s] to quit." ), ctxt.get_desc( "QUIT" ) ); } diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 830f8d13765e..0e71e0bb1244 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -22,7 +22,6 @@ #include "filesystem.h" #include "fstream_utils.h" #include "game.h" -#include "ime.h" #include "input.h" #include "json.h" #include "mod_manager.h" @@ -42,6 +41,12 @@ using namespace std::placeholders; // single instance of world generator std::unique_ptr world_generator; +/** + * Max utf-8 character worldname length. + * 0 index is inclusive. + */ +static const int max_worldname_len = 32; + save_t::save_t( const std::string &name ): name( name ) {} std::string save_t::decoded_name() const @@ -1197,8 +1202,6 @@ int worldfactory::show_modselection_window( const catacurses::window &win, const std::string old_filter = current_filter; fpopup->text( current_filter ); - ime_sentry sentry; - // On next redraw, call resize callback which will configure how popup is rendered ui.mark_resize(); @@ -1359,6 +1362,32 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL ui_adaptor ui; + string_input_popup spopup; + spopup.max_length( max_worldname_len ); + + const point namebar_pos( 3 + utf8_width( _( "World Name:" ) ), 1 ); + + input_context ctxt( "WORLDGEN_CONFIRM_DIALOG", keyboard_mode::keychar ); + // dialog actions + ctxt.register_action( "QUIT" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "PICK_RANDOM_WORLDNAME" ); + // string input popup actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "ANY_INPUT" ); + const auto init_windows = [&]( ui_adaptor & ui ) { const int iTooltipHeight = 1; const int iContentHeight = TERMY - 3 - iTooltipHeight; @@ -1367,33 +1396,25 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL w_confirmation = catacurses::newwin( iContentHeight, iMinScreenWidth - 2, point( 1 + iOffsetX, iTooltipHeight + 2 ) ); + // +1 for end-of-text cursor + spopup.window( w_confirmation, namebar_pos, namebar_pos.x + max_worldname_len + 1 ) + .context( ctxt ); ui.position_from_window( win ); }; init_windows( ui ); ui.on_screen_resize( init_windows ); - int namebar_y = 1; - int namebar_x = 3 + utf8_width( _( "World Name:" ) ); - bool noname = false; - input_context ctxt( "WORLDGEN_CONFIRM_DIALOG" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_action( "NEXT_TAB" ); - ctxt.register_action( "PREV_TAB" ); - ctxt.register_action( "PICK_RANDOM_WORLDNAME" ); std::string worldname = world->world_name; - // do not switch IME mode now, but restore previous mode on return - ime_sentry sentry( ime_sentry::keep ); - ui.on_redraw( [&]( const ui_adaptor & ) { draw_worldgen_tabs( win, 2 ); + wnoutrefresh( win ); - mvwprintz( w_confirmation, point( 2, namebar_y ), c_white, _( "World Name:" ) ); + werase( w_confirmation ); + mvwprintz( w_confirmation, point( 2, namebar_pos.y ), c_white, _( "World Name:" ) ); fold_and_print( w_confirmation, point( 2, 3 ), getmaxx( w_confirmation ) - 2, c_light_gray, _( "Press [%s] to pick a random name for your world." ), ctxt.get_desc( "PICK_RANDOM_WORLDNAME" ) ); @@ -1403,25 +1424,22 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL "to continue, or [%s] to go back and review your world." ), ctxt.get_desc( "NEXT_TAB" ), ctxt.get_desc( "PREV_TAB" ) ); if( noname ) { - mvwprintz( w_confirmation, point( namebar_x, namebar_y ), h_light_gray, + mvwprintz( w_confirmation, namebar_pos, h_light_gray, _( "________NO NAME ENTERED!________" ) ); + wnoutrefresh( w_confirmation ); } else { - mvwprintz( w_confirmation, point( namebar_x, namebar_y ), c_light_gray, worldname ); - wprintz( w_confirmation, h_light_gray, "_" ); - for( int underscores = 31 - utf8_width( worldname ); - underscores > 0; --underscores ) { - wprintz( w_confirmation, c_light_gray, "_" ); - } + // spopup.query_string() will call wnoutrefresh( w_confirmation ), and should + // be called last to position the cursor at the correct place in the curses build. + spopup.text( worldname ); + spopup.query_string( false, true ); } - - wnoutrefresh( win ); - wnoutrefresh( w_confirmation ); } ); do { ui_manager::redraw(); - const std::string action = ctxt.handle_input(); + worldname = spopup.query_string( false ); + const std::string action = ctxt.input_to_action( ctxt.get_raw_input() ); if( action == "NEXT_TAB" ) { if( worldname.empty() ) { noname = true; @@ -1437,13 +1455,9 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL } return 1; } - } else if( query_yn( _( "Are you SURE you're finished?" ) ) ) { - if( valid_worldname( worldname ) ) { - world->world_name = worldname; - return 1; - } else { - continue; - } + } else if( valid_worldname( worldname ) && query_yn( _( "Are you SURE you're finished?" ) ) ) { + world->world_name = worldname; + return 1; } else { continue; } @@ -1455,27 +1469,6 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL } else if( action == "QUIT" && ( !on_quit || on_quit() ) ) { world->world_name = worldname; return -999; - } else if( action == "ANY_INPUT" ) { - const input_event ev = ctxt.get_raw_input(); - const int ch = ev.get_first_input(); - utf8_wrapper wrap( worldname ); - utf8_wrapper newtext( ev.text ); - if( ch == KEY_BACKSPACE ) { - if( !wrap.empty() ) { - wrap.erase( wrap.length() - 1, 1 ); - worldname = wrap.str(); - } - } else if( ch == KEY_F( 2 ) ) { - std::string tmp = get_input_string_from_file(); - int tmplen = utf8_width( tmp ); - if( tmplen > 0 && tmplen + utf8_width( worldname ) < 30 ) { - worldname.append( tmp ); - } - } else if( !newtext.empty() && is_char_allowed( newtext.at( 0 ) ) ) { - // No empty string, no slash, no backslash, no control sequence - wrap.append( newtext ); - worldname = wrap.str(); - } } } while( true ); @@ -1583,12 +1576,29 @@ bool worldfactory::valid_worldname( const std::string &name, bool automated ) { std::string msg; - if( name == "save" || name == "TUTORIAL" || name == "DEFENSE" ) { + if( name.empty() ) { + msg = _( "World name cannot be empty!" ); + } else if( name == "save" || name == "TUTORIAL" || name == "DEFENSE" ) { msg = string_format( _( "%s is a reserved name!" ), name ); - } else if( !has_world( name ) ) { - return true; - } else { + } else if( has_world( name ) ) { msg = string_format( _( "A world named %s already exists!" ), name ); + } else { + // just check the raw bytes because unicode characters are always acceptable + bool allowed = true; + for( const char ch : name ) { + if( !is_char_allowed( ch ) ) { + if( std::isprint( ch ) ) { + msg = string_format( _( "World name contains invalid character: '%c'" ), ch ); + } else { + msg = string_format( _( "World name contains invalid character: 0x%x" ), ch ); + } + allowed = false; + break; + } + } + if( allowed ) { + return true; + } } if( !automated ) { popup( msg, PF_GET_KEY ); diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index b702f2ac00fb..38c45e7bd0e4 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -58,7 +58,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut REQUIRE( dummy.base_height() == init_height ); WHEN( "they are normal-sized (MEDIUM)" ) { - REQUIRE( dummy.get_size() == MS_MEDIUM ); + REQUIRE( dummy.get_size() == creature_size::medium ); THEN( "height is initial height" ) { CHECK( dummy.height() == init_height ); @@ -67,7 +67,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become SMALL" ) { set_single_trait( dummy, "SMALL" ); - REQUIRE( dummy.get_size() == MS_SMALL ); + REQUIRE( dummy.get_size() == creature_size::small ); THEN( "they are 50cm shorter" ) { CHECK( dummy.height() == init_height - 50 ); @@ -76,7 +76,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become LARGE" ) { set_single_trait( dummy, "LARGE" ); - REQUIRE( dummy.get_size() == MS_LARGE ); + REQUIRE( dummy.get_size() == creature_size::large ); THEN( "they are 50cm taller" ) { CHECK( dummy.height() == init_height + 50 ); @@ -85,7 +85,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become HUGE" ) { set_single_trait( dummy, "HUGE" ); - REQUIRE( dummy.get_size() == MS_HUGE ); + REQUIRE( dummy.get_size() == creature_size::huge ); THEN( "they are 100cm taler" ) { CHECK( dummy.height() == init_height + 100 ); diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index 43bc936046a8..22c2d7357678 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -742,7 +742,7 @@ TEST_CASE( "tool selection ui", "[crafting][ui]" ) REQUIRE( map_inv.size() == 1 ); CAPTURE( map_inv.find_item( 0 ).display_name() ); CHECK( result.comp.type == tool_component.type ); - CHECK( result.use_from == use_from_map ); + CHECK( result.use_from == usage_from::map ); } } @@ -758,7 +758,7 @@ TEST_CASE( "tool selection ui", "[crafting][ui]" ) DEFAULT_HOTKEYS, cost_adjustment::none ); THEN( "Use from is set to none" ) { - CHECK( result.use_from == use_from_none ); + CHECK( result.use_from == usage_from::none ); } } diff --git a/tests/creature_test.cpp b/tests/creature_test.cpp index 4700b7600003..9487f1387bb3 100644 --- a/tests/creature_test.cpp +++ b/tests/creature_test.cpp @@ -58,8 +58,8 @@ const auto expected_larger = Expected }; -void calculate_bodypart_distribution( const enum m_size attacker_size, - const enum m_size defender_size, +void calculate_bodypart_distribution( const enum creature_size attacker_size, + const enum creature_size defender_size, const int hit_roll, const Weights &expected ) { INFO( "hit roll = " << hit_roll ); @@ -105,25 +105,34 @@ TEST_CASE( "Check distribution of attacks to body parts for same sized opponents { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 0, expected_same.base ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 1, expected_same.base ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 100, expected_same.max ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 0, + expected_same.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 1, + expected_same.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 100, + expected_same.max ); } TEST_CASE( "Check distribution of attacks to body parts for smaller attacker." ) { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 0, expected_smaller.base ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 1, expected_smaller.base ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 100, expected_smaller.max ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 0, + expected_smaller.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 1, + expected_smaller.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 100, + expected_smaller.max ); } TEST_CASE( "Check distribution of attacks to body parts for larger attacker." ) { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 0, expected_larger.base ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 1, expected_larger.base ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 100, expected_larger.max ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 0, + expected_larger.base ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 1, + expected_larger.base ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 100, + expected_larger.max ); }