1 ///
2 module fluid.popup_button;
3 
4 import fluid.node;
5 import fluid.utils;
6 import fluid.label;
7 import fluid.button;
8 import fluid.popup_frame;
9 import fluid.style_macros;
10 
11 @safe:
12 
13 /// A button made to open popups.
14 alias popupButton = simpleConstructor!(PopupButton);
15 
16 /// ditto
17 class PopupButton : Button!Label {
18 
19     mixin defineStyles;
20     mixin enableInputActions;
21 
22     public {
23 
24         /// Popup enabled by this button.
25         PopupFrame popup;
26 
27         /// Popup this button belongs to, if any. Set automatically if the popup is spawned with `spawnPopup`.
28         PopupFrame parentPopup;
29 
30     }
31 
32     /// Create a new button.
33     /// Params:
34     ///     params        = Generic node parameters for the button.
35     ///     text          = Text for the button.
36     ///     popupChildren = Children to appear within the button.
37     this(NodeParams params, string text, Node[] popupChildren...) {
38 
39         // Craft the popup
40         popup = popupFrame(popupChildren);
41 
42         super(params, text, delegate {
43 
44             // Parent popup active
45             if (parentPopup && parentPopup.isFocused)
46                 parentPopup.spawnChildPopup(popup);
47 
48             // No parent
49             else
50                 tree.spawnPopup(popup);
51 
52         });
53 
54     }
55 
56     override string toString() const {
57 
58         import std.format;
59         return format!"popupButton(%s)"(text);
60 
61     }
62 
63 }
64 
65 ///
66 unittest {
67 
68     auto myButton = popupButton("Options",
69         button("Edit", delegate { }),
70         button("Copy", delegate { }),
71         popupButton("Share",
72             button("SMS", delegate { }),
73             button("Via e-mail", delegate { }),
74             button("Send to device", delegate { }),
75         ),
76     );
77 
78 }
79 
80 unittest {
81 
82     import fluid.backend;
83 
84     string lastAction;
85 
86     void action(string text)() {
87         lastAction = text;
88     }
89 
90     Button!()[6] buttons;
91 
92     auto io = new HeadlessBackend;
93     auto root = popupButton("Options",
94         buttons[0] = button("Edit", &action!"edit"),
95         buttons[1] = button("Copy", &action!"copy"),
96         buttons[2] = popupButton("Share",
97             buttons[3] = button("SMS", &action!"sms"),
98             buttons[4] = button("Via e-mail", &action!"email"),
99             buttons[5] = button("Send to device", &action!"device"),
100         ),
101     );
102 
103     auto sharePopupButton = cast(PopupButton) buttons[2];
104     auto sharePopup = sharePopupButton.popup;
105 
106     root.io = io;
107     root.draw();
108 
109     import std.stdio;
110 
111     // Focus the button
112     {
113         io.nextFrame;
114         io.press(KeyboardKey.down);
115 
116         root.draw();
117 
118         assert(root.isFocused);
119     }
120 
121     // Press it
122     {
123         io.nextFrame;
124         io.release(KeyboardKey.down);
125         io.press(KeyboardKey.enter);
126 
127         root.draw();
128     }
129 
130     // Popup opens
131     {
132         io.nextFrame;
133         io.release(KeyboardKey.enter);
134 
135         root.draw();
136 
137         assert(buttons[0].isFocused, "The first button inside should be focused");
138     }
139 
140     // Go to the previous button, expecting wrap
141     {
142         io.nextFrame;
143         io.press(KeyboardKey.leftShift);
144         io.press(KeyboardKey.tab);
145 
146         root.draw();
147 
148         assert(buttons[2].isFocused, "The last button inside the first menu should be focused");
149     }
150 
151     // Press it
152     {
153         io.nextFrame;
154         io.release(KeyboardKey.leftShift);
155         io.release(KeyboardKey.tab);
156         io.press(KeyboardKey.enter);
157 
158         root.draw();
159     }
160 
161     // Wait for the popup to appear
162     {
163         io.nextFrame;
164         io.release(KeyboardKey.enter);
165 
166         root.draw();
167         assert(buttons[3].isFocused, "The first button of the second menu should be focused");
168     }
169 
170     // Press the up arrow, it should do nothing
171     {
172         io.nextFrame;
173         io.press(KeyboardKey.up);
174 
175         root.draw();
176         assert(buttons[3].isFocused);
177     }
178 
179     // Press the down arrow
180     {
181         io.nextFrame;
182         io.release(KeyboardKey.up);
183         io.press(KeyboardKey.down);
184 
185         root.draw();
186         assert(buttons[4].isFocused);
187     }
188 
189     // Press the button
190     {
191         io.nextFrame;
192         io.release(KeyboardKey.down);
193         io.press(KeyboardKey.enter);
194 
195         root.draw();
196         assert(buttons[4].isFocused);
197         assert(lastAction == "email");
198         assert(!sharePopup.isHidden);
199 
200     }
201 
202     // Close the popup by pressing escape
203     {
204         io.nextFrame;
205         io.release(KeyboardKey.enter);
206         io.press(KeyboardKey.escape);
207 
208         root.draw();
209 
210         // Need another frame for the tree action
211         io.nextFrame;
212         root.draw();
213 
214         assert(sharePopup.isHidden);
215     }
216 
217 }