1 module nodes.popup_frame; 2 3 import fluid; 4 import fluid.future.pipe; 5 6 @safe: 7 8 @("PopupFrames can be spawned") 9 unittest { 10 11 auto overlay = overlayChain( 12 .layout!(1, "fill") 13 ); 14 auto root = sizeLock!testSpace( 15 .nullTheme, 16 .sizeLimit(600, 600), 17 overlay 18 ); 19 auto popup = popupFrame( 20 label("This is my popup"), 21 ); 22 23 overlay.addPopup(popup, Rectangle(40, 40, 5, 5)); 24 root.drawAndAssert( 25 popup.isDrawn().at(45, 45), 26 overlay.doesNotDrawChildren(), 27 ); 28 root.drawAndAssert( 29 overlay.drawsChild(popup), 30 overlay.doesNotDrawChildren(), 31 ); 32 33 } 34 35 @("Popups disappear when clicked outside") 36 unittest { 37 38 auto overlay = overlayChain(); 39 auto hover = hoverChain(); 40 auto focus = focusChain(); 41 auto root = testSpace( 42 .nullTheme, 43 chain( 44 focus, 45 hover, 46 overlay 47 ), 48 ); 49 auto popup = sizeLock!popupFrame( 50 .layout!"start", 51 .sizeLimit(100, 100), 52 ); 53 overlay.addPopup(popup, Rectangle(50, 50, 0, 0)); 54 55 root.drawAndAssert( 56 overlay.drawsChild(popup), 57 overlay.doesNotDrawChildren(), 58 ); 59 root.drawAndAssert( 60 popup.isDrawn().at(50, 50, 100, 100), 61 overlay.doesNotDrawChildren(), 62 ); 63 64 hover.point(25, 25) 65 .then((a) { 66 a.click; 67 return a.stayIdle; 68 }) 69 .runWhileDrawing(root, 2); 70 71 root.drawAndAssert( 72 overlay.doesNotDrawChildren(), 73 ); 74 root.drawAndAssertFailure( 75 popup.isDrawn(), 76 ); 77 78 } 79 80 @("PopupFrame stays focused and visible as long as a child node is focused") 81 unittest { 82 83 auto overlay = overlayChain(); 84 auto focus = focusChain(); 85 auto root = testSpace( 86 .nullTheme, 87 chain( 88 focus, 89 overlay 90 ), 91 ); 92 93 Button innerButton, outerButton; 94 auto popup = popupFrame( 95 hspace( 96 innerButton = button("Foo", delegate { }), 97 ), 98 outerButton = button("Bar", delegate { }), 99 ); 100 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 101 102 root.drawAndAssert( 103 popup.isDrawn(), 104 innerButton.isDrawn(), 105 outerButton.isDrawn(), 106 ); 107 // The first item in the popup should be chosen for focus 108 assert(focus.isFocused(popup)); 109 assert(popup.FocusIO.isFocused(innerButton)); 110 assert( popup.isFocused); 111 assert( innerButton.isFocused); 112 assert(!outerButton.isFocused); 113 114 outerButton.focus(); 115 root.drawAndAssert( 116 popup.isDrawn(), 117 ); 118 assert(focus.isFocused(popup)); 119 assert(popup.FocusIO.isFocused(outerButton)); 120 assert( popup.isFocused); 121 assert(!innerButton.isFocused); 122 assert( outerButton.isFocused); 123 124 // Focus cleared, close the popup 125 focus.clearFocus(); 126 assert(!focus.isFocused(popup)); 127 assert(!popup.FocusIO.isFocused(outerButton)); 128 assert(!popup.isFocused); 129 assert(!innerButton.isFocused); 130 assert(!outerButton.isFocused); 131 root.drawAndAssertFailure( 132 popup.isDrawn(), 133 ); 134 135 } 136 137 @("PopupFrames can be exited with a cancel action") 138 unittest { 139 140 auto overlay = overlayChain(); 141 auto focus = focusChain(); 142 auto root = testSpace( 143 .nullTheme, 144 chain( 145 focus, 146 overlay 147 ), 148 ); 149 150 Button btn; 151 auto popup = popupFrame( 152 btn = button("Bar", delegate { }), 153 ); 154 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 155 156 root.drawAndAssert(popup.isDrawn); 157 focus.runInputAction!(FluidInputAction.cancel); 158 root.drawAndAssertFailure(popup.isDrawn); 159 160 } 161 162 @("PopupFrames can be exited with an escape key") 163 unittest { 164 165 auto overlay = overlayChain(); 166 auto focus = focusChain(); 167 auto root = testSpace( 168 .nullTheme, 169 chain( 170 inputMapChain(), 171 focus, 172 overlay 173 ), 174 ); 175 176 Button btn; 177 auto popup = popupFrame( 178 btn = button("Bar", delegate { }), 179 ); 180 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 181 182 root.drawAndAssert(popup.isDrawn); 183 assert(focus.isFocused(popup)); 184 focus.emitEvent(KeyboardIO.press.escape); 185 root.draw(); 186 assert(!focus.isFocused(popup)); 187 root.drawAndAssertFailure(popup.isDrawn); 188 189 } 190 191 @("PopupFrame children can accept focus and input") 192 unittest { 193 194 auto overlay = overlayChain(); 195 auto focus = focusChain(); 196 auto root = testSpace( 197 .nullTheme, 198 chain( 199 inputMapChain(), 200 focus, 201 overlay 202 ), 203 ); 204 205 Button button1, button2; 206 int pressed1, pressed2; 207 auto popup = popupFrame( 208 button1 = button("Foo", delegate { 209 pressed1++; 210 }), 211 button2 = button("Bar", delegate { 212 pressed2++; 213 }), 214 ); 215 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 216 217 root.draw(); 218 assert( button1.isFocused); 219 assert(!button2.isFocused); 220 assert( popup.isFocused); 221 button2.focus(); 222 assert(!button1.isFocused); 223 assert( button2.isFocused); 224 assert( popup.isFocused); 225 226 assert(pressed1 == 0); 227 assert(pressed2 == 0); 228 focus.runInputAction!(FluidInputAction.press); 229 assert(pressed1 == 0); 230 assert(pressed2 == 1); 231 232 } 233 234 @("PopupFrame triggers focusImpl") 235 unittest { 236 237 import nodes.focus_chain : focusTracker; 238 239 auto overlay = overlayChain(); 240 auto focus = focusChain(); 241 auto root = testSpace( 242 .nullTheme, 243 chain( 244 inputMapChain(), 245 focus, 246 overlay 247 ), 248 ); 249 250 auto tracker = focusTracker(); 251 auto popup = popupFrame(tracker); 252 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 253 254 // TODO This focus call shouldn't be necessary; 255 // focusRecurse currently requires FluidFocusable 256 root.draw(); 257 tracker.focus(); 258 259 root.draw(); 260 assert(popup.isFocused); 261 assert(tracker.isFocused); 262 assert(tracker.focusImplCalls == 1); 263 root.draw(); 264 assert(tracker.focusImplCalls == 2); 265 focus.runInputAction!(FluidInputAction.press); 266 root.draw(); 267 assert(tracker.pressCalls == 1); 268 assert(tracker.focusImplCalls == 2); 269 270 } 271 272 @("PopupFrame implements tabbing and tab wrapping") 273 unittest { 274 275 Button button1, button2, button3; 276 277 auto overlay = overlayChain(); 278 auto focus = focusChain(overlay); 279 auto root = testSpace(.nullTheme, focus); 280 auto popup = popupFrame( 281 button1 = button("One", delegate { }), 282 button2 = button("Two", delegate { }), 283 button3 = button("Three", delegate { }), 284 ); 285 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 286 287 root.draw(); 288 289 // Forwards 290 focus.runInputAction!(FluidInputAction.focusNext); 291 root.draw(); 292 assert(popup.FocusIO.isFocused(button2)); 293 294 focus.runInputAction!(FluidInputAction.focusNext); 295 root.draw(); 296 assert(popup.FocusIO.isFocused(button3)); 297 298 focus.runInputAction!(FluidInputAction.focusNext); 299 root.draw(); 300 assert(popup.FocusIO.isFocused(button1)); 301 302 // Backwards 303 focus.runInputAction!(FluidInputAction.focusPrevious); 304 root.draw(); 305 assert(popup.FocusIO.isFocused(button3)); 306 307 focus.runInputAction!(FluidInputAction.focusPrevious); 308 root.draw(); 309 assert(popup.FocusIO.isFocused(button2)); 310 311 focus.runInputAction!(FluidInputAction.focusPrevious); 312 root.draw(); 313 assert(popup.FocusIO.isFocused(button1)); 314 315 316 } 317 318 @("PopupFrame implements positional focus") 319 unittest { 320 321 Button button1, button2, button3; 322 323 auto overlay = overlayChain(); 324 auto focus = focusChain(overlay); 325 auto root = testSpace(.nullTheme, focus); 326 auto popup = popupFrame( 327 hspace( 328 button1 = button("One", delegate { }), 329 button2 = button("Two", delegate { }), 330 ), 331 button3 = button("Three", delegate { }), 332 ); 333 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 334 335 root.draw(); 336 337 // Horizontal 338 focus.runInputAction!(FluidInputAction.focusRight); 339 root.draw(); 340 assert(popup.FocusIO.isFocused(button2)); 341 root.draw(); 342 343 focus.runInputAction!(FluidInputAction.focusLeft); 344 root.draw(); 345 assert(popup.FocusIO.isFocused(button1)); 346 root.draw(); 347 348 focus.runInputAction!(FluidInputAction.focusLeft); 349 root.draw(); 350 assert(popup.FocusIO.isFocused(button1)); 351 root.draw(); 352 353 // Vertical 354 focus.runInputAction!(FluidInputAction.focusDown); 355 root.draw(); 356 assert(popup.FocusIO.isFocused(button3)); 357 root.draw(); 358 359 focus.runInputAction!(FluidInputAction.focusDown); 360 root.draw(); 361 assert(popup.FocusIO.isFocused(button3)); 362 root.draw(); 363 364 focus.runInputAction!(FluidInputAction.focusUp); 365 root.draw(); 366 assert(popup.FocusIO.isFocused(button1)); 367 root.draw(); 368 369 } 370 371 @("PopupFrame can open child popups") 372 unittest { 373 374 auto overlay = overlayChain(); 375 auto focus = focusChain(overlay); 376 auto root = testSpace(.nullTheme, focus); 377 378 auto popup1 = popupFrame(); 379 overlay.addPopup(popup1, Rectangle(0, 0, 0, 0)); 380 381 auto popup2 = popupFrame(); 382 overlay.addChildPopup(popup1, popup2, Rectangle(0, 0, 0, 0)); 383 384 root.draw(); 385 assert( focus.isFocused(popup2)); 386 assert(!focus.isFocused(popup1)); 387 assert(popup2.isFocused); 388 assert(popup1.isFocused); 389 390 root.drawAndAssert( 391 popup1.isDrawn, 392 popup2.isDrawn, 393 ); 394 root.drawAndAssert( 395 popup1.isDrawn, 396 popup2.isDrawn, 397 ); 398 399 popup1.focus(); 400 root.drawAndAssert( 401 popup1.isDrawn, 402 ); 403 404 } 405 406 @("Using `cancel` in a child PopupFrame returns to the main popup") 407 unittest { 408 409 auto overlay = overlayChain(); 410 auto focus = focusChain(overlay); 411 auto root = testSpace(.nullTheme, focus); 412 413 auto popup1 = popupFrame(); 414 overlay.addPopup(popup1, Rectangle(0, 0, 0, 0)); 415 auto popup2 = popupFrame(); 416 overlay.addChildPopup(popup1, popup2, Rectangle(0, 0, 0, 0)); 417 418 root.drawAndAssert( 419 popup1.isDrawn, 420 popup2.isDrawn, 421 ); 422 assert(popup1.previousFocusable is null); 423 assert(popup2.previousFocusable.opEquals(popup1)); 424 assert(focus.isFocused(popup2)); 425 426 focus.runInputAction!(FluidInputAction.cancel); 427 root.drawAndAssert( 428 popup1.isDrawn, 429 ); 430 root.drawAndAssertFailure( 431 popup2.isDrawn, 432 ); 433 assert(focus.isFocused(popup1)); 434 435 } 436 437 @("PopupFrame restores focus to what was focused when it is opened") 438 unittest { 439 440 auto btn = button("One", delegate { }); 441 auto overlay = overlayChain(btn); 442 auto focus = focusChain(overlay); 443 auto root = testSpace(.nullTheme, focus); 444 445 focus.currentFocus = btn; 446 root.drawAndAssert( 447 btn.isDrawn, 448 ); 449 assert(btn.isFocused); 450 451 auto popup = popupFrame(); 452 overlay.addPopup(popup, Rectangle(0, 0, 0, 0)); 453 454 root.drawAndAssert( 455 btn.isDrawn, 456 popup.isDrawn, 457 ); 458 assert(!btn.isFocused); 459 assert( popup.isFocused); 460 461 popup.restorePreviousFocus(); 462 assert( btn.isFocused); 463 assert(!popup.isFocused); 464 root.drawAndAssert(btn.isDrawn); 465 root.drawAndAssertFailure(popup.isDrawn); 466 467 }