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 }