1 module nodes.overlay_chain; 2 3 import fluid; 4 5 @safe: 6 7 alias sampleOverlay = nodeBuilder!SampleOverlay; 8 alias relativeOverlay = nodeBuilder!(SampleOverlay, (a) { 9 a.isRelative = true; 10 }); 11 12 class SampleOverlay : Node, Overlayable { 13 14 CanvasIO canvasIO; 15 Rectangle _anchor; 16 Vector2 size; 17 bool isRelative; 18 19 this(Vector2 size, Rectangle anchor) { 20 this.layout = .layout!"fill"; 21 this.size = size; 22 this._anchor = anchor; 23 } 24 25 override Rectangle getAnchor(Rectangle viewport) const nothrow { 26 if (isRelative) { 27 return Rectangle( 28 (viewport.start + _anchor.start).tupleof, 29 _anchor.size.tupleof, 30 ); 31 } 32 else { 33 return _anchor; 34 } 35 } 36 37 override void resizeImpl(Vector2) { 38 require(canvasIO); 39 minSize = size; 40 } 41 42 override void drawImpl(Rectangle, Rectangle inner) { 43 canvasIO.drawRectangle(inner, color("#000")); 44 } 45 46 alias opEquals = typeof(super).opEquals; 47 48 override bool opEquals(const Object object) const { 49 return super.opEquals(object); 50 } 51 52 } 53 54 @("OverlayChain can draw a number of different overlays") 55 unittest { 56 57 enum overlaySize = Vector2(7, 7); 58 59 auto label = label("Under overlay"); 60 auto chain = overlayChain(label); 61 auto root = sizeLock!testSpace( 62 .sizeLimit(100, 100), 63 .nullTheme, 64 chain 65 ); 66 67 root.drawAndAssert( 68 chain.drawsChild(label), 69 chain.doesNotDrawChildren(), 70 ); 71 72 auto firstOverlay = sampleOverlay( 73 .layout!"start", 74 overlaySize, 75 Rectangle(50, 50, 10, 10) 76 ); 77 78 chain.addOverlay(firstOverlay); 79 root.drawAndAssert( 80 label.isDrawn(), 81 firstOverlay.isDrawn(), 82 firstOverlay.drawsRectangle(60, 60, overlaySize.tupleof), 83 ); 84 85 auto secondOverlay = sampleOverlay( 86 .layout!"start", 87 overlaySize, 88 Rectangle(100, 100, 0, 0) 89 ); 90 chain.addOverlay(secondOverlay); 91 root.drawAndAssert( 92 label.isDrawn(), 93 firstOverlay.isDrawn(), 94 firstOverlay.drawsRectangle(60, 60, overlaySize.tupleof), 95 secondOverlay.drawsRectangle(100, 100, overlaySize.tupleof), 96 ); 97 98 } 99 100 @("Overlays in OverlayChain can be aligned with NodeAlign") 101 unittest { 102 103 enum overlaySize = Vector2(7, 7); 104 105 auto chain = overlayChain(); 106 auto root = testSpace(chain); 107 108 const centerTarget = Vector2(55, 55) - overlaySize/2; 109 auto centerOverlay = sampleOverlay( 110 .layout!"center", 111 overlaySize, 112 Rectangle(50, 50, 10, 10), 113 ); 114 chain.addOverlay(centerOverlay); 115 116 const endTarget = Vector2(50, 50) - overlaySize; 117 auto endOverlay = sampleOverlay( 118 .layout!"end", 119 overlaySize, 120 Rectangle(50, 50, 10, 10), 121 ); 122 chain.addOverlay(endOverlay); 123 124 root.drawAndAssert( 125 centerOverlay.drawsRectangle(centerTarget.tupleof, overlaySize.tupleof), 126 endOverlay.drawsRectangle(endTarget.tupleof, overlaySize.tupleof), 127 ); 128 129 } 130 131 @("In OverlayChain, NodeAlign.fill chooses alignment based on available space") 132 unittest { 133 134 auto chain = overlayChain( 135 .layout!(1, "fill") 136 ); 137 auto root = sizeLock!testSpace( 138 .sizeLimit(100, 100), 139 chain 140 ); 141 142 auto smallOverlay = sampleOverlay( 143 Vector2(25, 25), 144 Rectangle(40, 40, 20, 20), 145 ); 146 chain.addOverlay(smallOverlay); 147 148 auto cornerOverlay = sampleOverlay( 149 Vector2(32, 32), 150 Rectangle(40, 40, 40, 40), 151 ); 152 chain.addOverlay(cornerOverlay); 153 154 auto edgeOverlay = sampleOverlay( 155 Vector2(32, 32), 156 Rectangle(60, 50, 20, 0), 157 ); 158 chain.addOverlay(edgeOverlay); 159 160 auto bigOverlay = sampleOverlay( 161 Vector2(80, 80), 162 Rectangle(20, 20, 20, 20), 163 ); 164 chain.addOverlay(bigOverlay); 165 166 root.drawAndAssert( 167 smallOverlay.drawsRectangle(60, 60, 25, 25), 168 cornerOverlay.drawsRectangle(8, 8, 32, 32), 169 edgeOverlay.drawsRectangle(28, 50, 32, 32), 170 bigOverlay.drawsRectangle(-10, -10, 80, 80), 171 ); 172 173 } 174 175 @("OverlayChain allows using different alignment per axis") 176 unittest { 177 178 auto chain = overlayChain( 179 .layout!(1, "fill") 180 ); 181 auto root = testSpace(chain); 182 183 SampleOverlay[] overlays; 184 185 foreach (x; [NodeAlign.start, NodeAlign.center, NodeAlign.end]) { 186 foreach (y; [NodeAlign.start, NodeAlign.center, NodeAlign.end]) { 187 188 overlays ~= sampleOverlay( 189 .layout(x, y), 190 Vector2(32, 16), 191 Rectangle(0, 0, 10, 20), 192 ); 193 chain.addOverlay(overlays[$-1]); 194 195 } 196 } 197 198 root.drawAndAssert( 199 // (start, _) 200 overlays[0].drawsRectangle( 10, 20, 32, 16), // start 201 overlays[1].drawsRectangle( 10, 2, 32, 16), // center 202 overlays[2].drawsRectangle( 10, -16, 32, 16), // end 203 204 // (center, _) 205 overlays[3].drawsRectangle(-11, 20, 32, 16), // start 206 overlays[4].drawsRectangle(-11, 2, 32, 16), // center 207 overlays[5].drawsRectangle(-11, -16, 32, 16), // end 208 209 // (end, _) 210 overlays[6].drawsRectangle(-32, 20, 32, 16), // start 211 overlays[7].drawsRectangle(-32, 2, 32, 16), // center 212 overlays[8].drawsRectangle(-32, -16, 32, 16), // end 213 ); 214 215 } 216 217 @("Overlays in OverlayChain can move without a resize") 218 unittest { 219 220 auto chain = overlayChain( 221 .layout!(1, "fill") 222 ); 223 auto root = testSpace(chain); 224 auto overlay = sampleOverlay( 225 .layout!"start", 226 Vector2(32, 32), 227 Rectangle(0, 0, 0, 0), 228 ); 229 chain.addOverlay(overlay); 230 231 root.drawAndAssert( 232 overlay.drawsRectangle(0, 0, 32, 32), 233 ); 234 235 overlay._anchor = Rectangle(-16, -16, 0, 0), 236 root.drawAndAssert( 237 overlay.drawsRectangle(-16, -16, 32, 32), 238 ); 239 240 overlay._anchor = Rectangle(400, 400, 0, 0), 241 root.drawAndAssert( 242 overlay.drawsRectangle(400, 400, 32, 32), 243 ); 244 245 } 246 247 @("OverlayChain overlays can be positioned relative to screen, or to the chain") 248 unittest { 249 250 auto chain = overlayChain( 251 .layout!(1, "fill") 252 ); 253 auto root = sizeLock!vtestSpace( 254 .sizeLimit(300, 300), 255 vspace(.layout!1), 256 chain, 257 vspace(.layout!1), 258 ); 259 auto absolute = sampleOverlay( 260 .layout!"start", 261 Vector2(32, 32), 262 Rectangle(0, 0, 0, 0), 263 ); 264 auto relative = relativeOverlay( 265 .layout!"start", 266 Vector2(32, 32), 267 Rectangle(0, 0, 0, 0), 268 ); 269 270 chain.addOverlay(absolute); 271 chain.addOverlay(relative); 272 273 root.drawAndAssert( 274 absolute.drawsRectangle(0, 0, 32, 32), 275 relative.drawsRectangle(0, 100, 32, 32), 276 ); 277 278 } 279 280 @("OverlayChain supports removing nodes with .remove()") 281 unittest { 282 283 const size = Vector2(32, 32); 284 const anchor = Rectangle(0, 0, 0, 0); 285 286 auto chain = overlayChain(); 287 auto root = testSpace(chain); 288 289 auto one = sampleOverlay(.layout!1, size, anchor); 290 auto two = sampleOverlay(.layout!2, size, anchor); 291 auto three = sampleOverlay(.layout!3, size, anchor); 292 293 chain.addOverlay(one); 294 chain.addOverlay(two); 295 chain.addOverlay(three); 296 297 root.drawAndAssert( 298 one.isDrawn, 299 two.isDrawn, 300 three.isDrawn, 301 chain.doesNotDrawChildren, 302 ); 303 304 two.remove(); 305 root.drawAndAssertFailure( 306 one.isDrawn, 307 two.isDrawn, 308 three.isDrawn, 309 chain.doesNotDrawChildren, 310 ); 311 root.drawAndAssert( 312 one.isDrawn, 313 three.isDrawn, 314 chain.doesNotDrawChildren, 315 ); 316 317 chain.addOverlay(two); 318 assert(two.toRemove == false); 319 root.drawAndAssert( 320 one.isDrawn, 321 three.isDrawn, 322 two.isDrawn, 323 chain.doesNotDrawChildren, 324 ); 325 326 }