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 }