1 module rpui.shortcuts;
2
3 import std.stdio;
4 import std..string;
5 import std.conv;
6 import std.path;
7
8 import rpdl;
9 import rpdl.exception;
10 import rpui.input;
11 import rpui.widget;
12 import rpui.paths;
13
14 struct Shortcut {
15 bool shift;
16 bool ctrl;
17 bool alt;
18 KeyCode key;
19
20 private this(bool shift, bool ctrl, bool alt, KeyCode key) {
21 this.shift = shift;
22 this.ctrl = ctrl;
23 this.alt = alt;
24 this.key = key;
25 }
26
27 /**
28 * Fill `Shortcut` fields from string.
29 *
30 * Example:
31 * ---
32 * readKey("Ctrl"); // will assign `true` to `ctrl`
33 * readKey("A"); // will assign `input.KeyCode.A` to `key`
34 * ---
35 */
36 void readKey(in string key) {
37 switch (key) {
38 case "Ctrl":
39 this.ctrl = true;
40 break;
41
42 case "Shift":
43 this.shift = true;
44 break;
45
46 case "Alt":
47 this.alt = true;
48 break;
49
50 default:
51 this.key = to!KeyCode(key);
52 }
53 }
54
55 /**
56 * Parsing shortcut from string - all keys will split by '+' symbol.
57 *
58 * Example:
59 * ---
60 * Shortcut("Ctrl+C"); // ctrl = true; key = KeyCode.C;
61 * Shortcut("Ctrl+Shift+S"); // ctrl = true; shift = true; key = KeyCode.C;
62 * ---
63 */
64 this(in string shortcut) {
65 foreach (string key; shortcut.split("+")) {
66 readKey(key);
67 }
68 }
69 }
70
71 final class Shortcuts {
72 private ShortcutAction[string] shortcuts; /// Available shortcuts.
73 private RpdlTree shortcutsData; /// Shortcuts declared in `rpdl` file.
74
75 /// Action to be invoked when shortcut is pressed.
76 struct ShortcutAction {
77 /// Not yet used.
78 enum Type {
79 simpleWidgetListener,
80 simpleFunction
81 }
82
83 Shortcut[] shortcuts; /// Composition of shortcuts e.g. Ctrl+X Ctrl+S.
84
85 /**
86 * Create action from `shortcutString` - constructor will fill `shortcuts`
87 * by parsing string - all shortcuts will split by space symbol.
88 *
89 * Example:
90 * ---
91 * // Corresponds to: shortcuts ~= Shortcut("Ctrl+X")
92 * ShortcutAction("Ctrl+X")
93 *
94 * // Corresponds to: shortcuts ~= Shortcut("Ctrl+X") ~ Shortcut("Alt+Shift+C")
95 * ShortcutAction("Ctrl+X Alt+C")
96 * ---
97 */
98 this(in string shortcutString, void delegate() action = null) {
99 foreach (shortcut; shortcutString.split(" ")) {
100 shortcuts ~= Shortcut(shortcut);
101 }
102
103 this.action = action;
104 }
105
106 void delegate() action;
107 }
108
109 /// Load shortcuts from `rpdl` file by `fileName`; `fileName` is absolute.
110 this(in string fileName) {
111 shortcutsData = new RpdlTree(dirName(fileName));
112 shortcutsData.load(baseName(fileName), RpdlTree.FileType.text);
113 }
114
115 /// Load shortcuts from `rpdl` file relative to $(I resources/ui/shortcuts).
116 static createFromFile(in string fileName) {
117 const paths = createPathes();
118 const string path = buildPath(paths.resources, "ui", "shortcuts", fileName);
119 return new Shortcuts(path);
120 }
121
122 /// Handle on key released and invoke action if shortcut is pressed.
123 void onKeyReleased(in KeyCode key) {
124 foreach (shortcut; shortcuts) {
125 if (doShortcut(shortcut))
126 return;
127 }
128 }
129
130 /// Attach `action` to particular shortcut placed by `path` (in `rpdl` file)
131 void attachByPath(in string path, void delegate() action) {
132 try {
133 const shortcut = shortcutsData.data.getString(path ~ ".0");
134 auto shortcutAction = Shortcuts.ShortcutAction(shortcut, action);
135
136 shortcuts[path] = shortcutAction;
137 } catch (NotFoundException) {
138 debug assert(false, "Not found shortcut with path " ~ path);
139 }
140 }
141
142 /// Attach `action` to particular shortcut
143 void attach(in string shortcut, void delegate() action) {
144 auto shortcutAction = Shortcuts.ShortcutAction(shortcut, action);
145 shortcuts[shortcut] = shortcutAction;
146 }
147
148 string getShourtcutString(in string path) {
149 return shortcutsData.data.getString(path ~ ".0");
150 }
151
152 ///
153 void merge(Shortcuts shortcuts) {
154 shortcutsData.merge(shortcuts.shortcutsData);
155 }
156
157 /// Invoke shortcut action if all keys from shortcut is pressed.
158 private bool doShortcut(in ShortcutAction shortcutAction) {
159 const Shortcut shortcut = shortcutAction.shortcuts[0];
160
161 if (isKeyPressed(shortcut.key) &&
162 testKeyState(KeyCode.Shift, shortcut.shift) &&
163 testKeyState(KeyCode.Ctrl, shortcut.ctrl) &&
164 testKeyState(KeyCode.Alt, shortcut.alt))
165 {
166 shortcutAction.action();
167 return true;
168 }
169
170 return false;
171 }
172 }