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