1 module nodes.space; 2 3 import std.range; 4 5 import fluid; 6 7 @safe: 8 9 @("Space.minSize includes gaps") 10 unittest { 11 12 import std.math; 13 14 auto theme = nullTheme.derive( 15 rule!Space( 16 Rule.gap = 12, 17 ), 18 ); 19 20 auto root = vspace( 21 theme, 22 sizeLock!vframe( 23 sizeLimitY = 200 24 ), 25 sizeLock!vframe( 26 sizeLimitY = 200 27 ), 28 sizeLock!vframe( 29 sizeLimitY = 200 30 ), 31 ); 32 root.draw(); 33 34 assert(isClose(root.getMinSize.y, 200 * 3 + 12 * 2)); 35 36 } 37 38 @("vspace aligns nodes vertically, and hspace does it horizontally") 39 unittest { 40 41 class Square : Node { 42 43 CanvasIO canvasIO; 44 Color color; 45 46 this(Color color) { 47 this.color = color; 48 } 49 50 override void resizeImpl(Vector2) { 51 use(canvasIO); 52 minSize = Vector2(50, 50); 53 } 54 55 override void drawImpl(Rectangle, Rectangle inner) { 56 canvasIO.drawRectangle(inner, this.color); 57 } 58 59 } 60 61 Square[6] squares; 62 63 auto root = testSpace( 64 squares[0] = new Square(color!"000"), 65 squares[1] = new Square(color!"001"), 66 squares[2] = new Square(color!"002"), 67 hspace( 68 squares[3] = new Square(color!"010"), 69 squares[4] = new Square(color!"011"), 70 squares[5] = new Square(color!"012"), 71 ), 72 ); 73 74 root.theme = nullTheme; 75 root.drawAndAssert( 76 77 // vspace 78 squares[0].drawsRectangle(0, 0, 50, 50).ofColor("#000"), 79 squares[1].drawsRectangle(0, 50, 50, 50).ofColor("#001"), 80 squares[2].drawsRectangle(0, 100, 50, 50).ofColor("#002"), 81 82 // hspace 83 squares[3].drawsRectangle( 0, 150, 50, 50).ofColor("#010"), 84 squares[4].drawsRectangle( 50, 150, 50, 50).ofColor("#011"), 85 squares[5].drawsRectangle(100, 150, 50, 50).ofColor("#012"), 86 87 ); 88 89 } 90 91 @("Layout.expand splits space into columns") 92 unittest { 93 94 import fluid.theme; 95 96 Frame[3] frames; 97 98 auto root = htestSpace( 99 layout!"fill", 100 frames[0] = vframe(layout!1), 101 frames[1] = vframe(layout!2), 102 frames[2] = vframe(layout!1), 103 ); 104 root.theme = nullTheme.derive( 105 rule!Frame(backgroundColor = color!"7d9"), 106 ); 107 108 root.drawAndAssert( 109 frames[0].drawsRectangle(0, 0, 0, 0).ofColor("#7d9"), 110 frames[1].drawsRectangle(200, 0, 0, 0).ofColor("#7d9"), 111 frames[2].drawsRectangle(600, 0, 0, 0).ofColor("#7d9"), 112 ); 113 114 // Fill all nodes 115 foreach (child; root.children) { 116 child.layout.nodeAlign = NodeAlign.fill; 117 } 118 root.updateSize(); 119 120 root.drawAndAssert( 121 frames[0].drawsRectangle(0, 0, 200, 600).ofColor("#7d9"), 122 frames[1].drawsRectangle(200, 0, 400, 600).ofColor("#7d9"), 123 frames[2].drawsRectangle(600, 0, 200, 600).ofColor("#7d9"), 124 ); 125 126 const alignments = [NodeAlign.start, NodeAlign.center, NodeAlign.end]; 127 128 // Make Y alignment different across all three 129 foreach (pair; root.children.zip(alignments)) { 130 pair[0].layout.nodeAlign = pair[1]; 131 } 132 root.updateSize(); 133 134 root.drawAndAssert( 135 frames[0].drawsRectangle( 0, 0, 0, 0).ofColor("#7d9"), 136 frames[1].drawsRectangle(400, 300, 0, 0).ofColor("#7d9"), 137 frames[2].drawsRectangle(800, 600, 0, 0).ofColor("#7d9"), 138 ); 139 140 } 141 142 @("Space/Frame works with multiple levels of recursion") 143 unittest { 144 145 import fluid.theme; 146 147 Frame[5] frames; 148 149 auto root = sizeLock!vtestSpace( 150 .sizeLimit(270, 270), 151 frames[0] = hframe( 152 .layout!(1, "fill"), 153 vspace(.layout!2), 154 frames[1] = vframe( 155 .layout!(1, "fill"), 156 hspace(.layout!2), 157 frames[2] = hframe( 158 .layout!(1, "fill"), 159 frames[3] = vframe( 160 .layout!(1, "fill"), 161 frames[4] = hframe( 162 .layout!(1, "fill") 163 ), 164 hspace(.layout!2), 165 ), 166 vspace(.layout!2), 167 ) 168 ), 169 ), 170 ); 171 root.theme = nullTheme.derive( 172 rule!Frame(backgroundColor = color!"0004"), 173 ); 174 175 root.drawAndAssert( 176 frames[0].drawsRectangle( 0, 0, 270, 270).ofColor("#0004"), 177 frames[1].drawsRectangle(180, 0, 90, 270).ofColor("#0004"), 178 frames[2].drawsRectangle(180, 180, 90, 90).ofColor("#0004"), 179 frames[3].drawsRectangle(180, 180, 30, 90).ofColor("#0004"), 180 frames[4].drawsRectangle(180, 180, 30, 30).ofColor("#0004"), 181 ); 182 183 } 184 185 @("Rounding errors when placing nodes https://git.samerion.com/Samerion/Fluid/issues/58") 186 unittest { 187 188 import fluid.frame; 189 import fluid.label; 190 import fluid.structs; 191 192 auto fill = layout!(1, "fill"); 193 auto myTheme = nullTheme.derive( 194 rule!Label(Rule.backgroundColor = color!"#e65bb8"), 195 ); 196 auto root = htestSpace( 197 fill, 198 myTheme, 199 label(fill, "1"), 200 label(fill, "2"), 201 label(fill, "3"), 202 label(fill, "4"), 203 label(fill, "5"), 204 label(fill, "6"), 205 label(fill, "7"), 206 label(fill, "8"), 207 label(fill, "9"), 208 label(fill, "10"), 209 label(fill, "11"), 210 label(fill, "12"), 211 ); 212 213 root.drawAndAssert( 214 root.children[ 0].drawsRectangle( 0*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 215 root.children[ 1].drawsRectangle( 1*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 216 root.children[ 2].drawsRectangle( 2*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 217 root.children[ 3].drawsRectangle( 3*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 218 root.children[ 4].drawsRectangle( 4*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 219 root.children[ 5].drawsRectangle( 5*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 220 root.children[ 6].drawsRectangle( 6*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 221 root.children[ 7].drawsRectangle( 7*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 222 root.children[ 8].drawsRectangle( 8*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 223 root.children[ 9].drawsRectangle( 9*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 224 root.children[10].drawsRectangle(10*800/12f, 0, 66.66, 600).ofColor("#e65bb8"), 225 root.children[11].drawsRectangle(11*800/12f, 0, 66.66, 600).ofColor("#e65bb8")); 226 227 } 228 229 @("Space respects gap") 230 unittest { 231 232 import fluid.frame; 233 import fluid.theme; 234 import fluid.structs : layout; 235 236 auto theme = nullTheme.derive( 237 rule!Space(gap = 4), 238 rule!Frame(backgroundColor = color("#f00")), 239 ); 240 auto root = vtestSpace( 241 layout!"fill", 242 theme, 243 vframe(layout!(1, "fill")), 244 vframe(layout!(1, "fill")), 245 vframe(layout!(1, "fill")), 246 vframe(layout!(1, "fill")), 247 ); 248 249 root.drawAndAssert( 250 root.children[0].drawsRectangle(0, 0, 800, 147).ofColor("#f00"), 251 root.children[1].drawsRectangle(0, 151, 800, 147).ofColor("#f00"), 252 root.children[2].drawsRectangle(0, 302, 800, 147).ofColor("#f00"), 253 root.children[3].drawsRectangle(0, 453, 800, 147).ofColor("#f00")); 254 255 } 256 257 @("Gaps do not apply to invisible children") 258 unittest { 259 260 import fluid.theme; 261 262 auto theme = nullTheme.derive( 263 rule!Space(gap = 4), 264 ); 265 266 auto spy = new class Space { 267 268 Vector2 position; 269 270 override void drawImpl(Rectangle outer, Rectangle inner) { 271 position = outer.start; 272 } 273 274 }; 275 276 auto root = vspace( 277 theme, 278 hspace(), 279 hspace(), 280 hspace(), 281 spy, 282 ); 283 284 root.draw(); 285 286 assert(spy.position == Vector2(0, 12)); 287 288 // Hide one child 289 root.children[0].hide(); 290 root.draw(); 291 292 assert(spy.position == Vector2(0, 8)); 293 294 } 295 296 @("Applied style.gap depends on axis") 297 unittest { 298 299 auto theme = nullTheme.derive( 300 rule!Space( 301 Rule.gap = [2, 4], 302 ), 303 ); 304 305 class Warden : Space { 306 307 Rectangle outer; 308 309 override void drawImpl(Rectangle outer, Rectangle inner) { 310 super.drawImpl(this.outer = outer, inner); 311 } 312 313 } 314 315 Warden[4] wardens; 316 317 auto root = vspace( 318 theme, 319 hspace( 320 wardens[0] = new Warden, 321 wardens[1] = new Warden, 322 ), 323 vspace( 324 wardens[2] = new Warden, 325 wardens[3] = new Warden, 326 ), 327 ); 328 329 root.draw(); 330 331 assert(wardens[0].outer.start == Vector2(0, 0)); 332 assert(wardens[1].outer.start == Vector2(2, 0)); 333 assert(wardens[2].outer.start == Vector2(0, 4)); 334 assert(wardens[3].outer.start == Vector2(0, 8)); 335 336 }