1 /// 2 module glui.space; 3 4 import raylib; 5 6 import std.math; 7 import std.range; 8 import std.string; 9 import std.traits; 10 import std.algorithm; 11 12 import glui.node; 13 import glui.style; 14 import glui.utils; 15 import glui.children; 16 17 18 @safe: 19 20 21 /// Make a new vertical space. 22 alias vspace = simpleConstructor!GluiSpace; 23 24 /// Make a new horizontal space. 25 alias hspace = simpleConstructor!(GluiSpace, (a) { 26 27 a.directionHorizontal = true; 28 29 }); 30 31 /// This is a space, a basic container for other nodes. 32 /// 33 /// Space only acts as a container and doesn't implement styles and doesn't take focus. It can be very useful to build 34 /// overlaying nodes, eg. with `GluiOnionFrame`. 35 class GluiSpace : GluiNode { 36 37 mixin DefineStyles; 38 39 /// Children of this frame. 40 Children children; 41 42 /// Defines in what directions children of this frame should be placed. 43 /// 44 /// If true, children are placed horizontally, if false, vertically. 45 bool horizontal; 46 47 alias directionHorizontal = horizontal; 48 49 private { 50 51 /// Denominator for content sizing. 52 uint denominator; 53 54 /// Space reserved for shrinking elements. 55 uint reservedSpace; 56 57 } 58 59 // Generate constructors 60 static foreach (index; 0 .. BasicNodeParamLength) { 61 62 this(BasicNodeParam!index params, GluiNode[] nodes...) { 63 64 super(params); 65 this.children ~= nodes; 66 67 } 68 69 } 70 71 /// Add children. 72 pragma(inline, true) 73 void opOpAssign(string operator : "~", T)(T nodes) { 74 75 children ~= nodes; 76 77 } 78 79 protected override void resizeImpl(Vector2 available) { 80 81 import std.algorithm : max, map, fold; 82 83 // Reset size 84 minSize = Vector2(0, 0); 85 reservedSpace = 0; 86 denominator = 0; 87 88 // Ignore the rest if there's no children 89 if (!children.length) return; 90 91 Vector2 maxExpandSize; 92 93 // Collect expanding children in a separate array 94 GluiNode[] expandChildren; 95 foreach (child; children) { 96 97 // This node expands and isn't hidden 98 if (child.layout.expand && !child.isHidden) { 99 100 // Make it happen later 101 expandChildren ~= child; 102 103 // Add to the denominator 104 denominator += child.layout.expand; 105 106 } 107 108 // Check non-expand nodes now 109 else { 110 111 child.resize(tree, theme, childSpace(child, available)); 112 minSize = childPosition(child.minSize, minSize); 113 114 // Reserve space for this node 115 reservedSpace += directionHorizontal 116 ? cast(uint) child.minSize.x 117 : cast(uint) child.minSize.y; 118 119 } 120 121 } 122 123 // Calculate the size of expanding children last 124 foreach (child; expandChildren) { 125 126 // Resize the child 127 child.resize(tree, theme, childSpace(child, available)); 128 129 const childSize = child.minSize; 130 const childExpand = child.layout.expand; 131 132 const segmentSize = horizontal 133 ? Vector2(childSize.x / childExpand, childSize.y) 134 : Vector2(childSize.x, childSize.y / childExpand); 135 136 // Reserve expand space 137 maxExpandSize.x = max(maxExpandSize.x, segmentSize.x); 138 maxExpandSize.y = max(maxExpandSize.y, segmentSize.y); 139 140 } 141 142 const expandSize = horizontal 143 ? Vector2(maxExpandSize.x * denominator, maxExpandSize.y) 144 : Vector2(maxExpandSize.x, maxExpandSize.y * denominator); 145 146 // Add the expand space 147 minSize = childPosition(expandSize, minSize); 148 149 } 150 151 protected override void drawImpl(Rectangle, Rectangle area) { 152 153 auto position = Vector2(area.x, area.y); 154 155 drawChildren((child) { 156 157 // Get params 158 const size = childSpace(child, Vector2(area.width, area.height)); 159 const rect = Rectangle( 160 position.x, position.y, 161 size.x, size.y 162 ); 163 164 // Draw the child 165 child.draw(rect); 166 167 // Offset position 168 if (directionHorizontal) position.x += cast(int) size.x; 169 else position.y += cast(int) size.y; 170 171 }); 172 173 } 174 175 /// Iterate over every child and perform the painting function. Will automatically remove nodes queued for removal. 176 protected void drawChildren(void delegate(GluiNode) @safe painter) { 177 178 GluiNode[] leftovers; 179 180 children.lock(); 181 scope (exit) children.unlock(); 182 183 // Draw each child and get rid of removed children 184 auto range = children[] 185 186 // Check if the node is queued for removal 187 .filter!((node) { 188 const status = node.toRemove; 189 node.toRemove = false; 190 return !status; 191 }) 192 193 // Draw the node 194 .tee!((node) => painter(node)); 195 196 // Do what we ought to do 197 () @trusted { 198 199 // Process the children and move them back to the original array 200 auto leftovers = range.moveAll(children.forceMutable); 201 202 // Adjust the array size 203 children.forceMutable.length -= leftovers.length; 204 205 }(); 206 207 } 208 209 protected override bool hoveredImpl(Rectangle, Vector2) const { 210 211 return false; 212 213 } 214 215 protected override const(Style) pickStyle() const { 216 217 return null; 218 219 } 220 221 /// Params: 222 /// child = Child size to add. 223 /// previous = Previous position. 224 private Vector2 childPosition(Vector2 child, Vector2 previous) const { 225 226 import std.algorithm : max; 227 228 // Horizontal 229 if (directionHorizontal) { 230 231 return Vector2( 232 previous.x + child.x, 233 max(minSize.y, child.y), 234 ); 235 236 } 237 238 // Vertical 239 else return Vector2( 240 max(minSize.x, child.x), 241 previous.y + child.y, 242 ); 243 244 } 245 246 /// Get space for a child. 247 /// Params: 248 /// child = Child to place 249 /// available = Available space 250 private Vector2 childSpace(const GluiNode child, Vector2 available) const 251 in( 252 child.isHidden || child.layout.expand <= denominator, 253 format!"Nodes %s/%s sizes are out of date, call updateSize after updating the tree or layout (%s/%s)"( 254 typeid(this), typeid(child), child.layout.expand, denominator, 255 ) 256 ) 257 out( 258 r; [r.tupleof].all!isFinite, 259 format!"space: child %s given invalid size %s. available = %s, expand = %s, denominator = %s, reserved = %s"( 260 typeid(child), r, available, child.layout.expand, denominator, reservedSpace 261 ) 262 ) 263 do { 264 265 // Hidden, give it no space 266 if (child.isHidden) return Vector2(); 267 268 // Horizontal 269 if (directionHorizontal) { 270 271 const avail = (available.x - reservedSpace); 272 273 return Vector2( 274 child.layout.expand 275 ? avail * child.layout.expand / denominator 276 : child.minSize.x, 277 available.y, 278 ); 279 280 } 281 282 // Vertical 283 else { 284 285 const avail = (available.y - reservedSpace); 286 287 return Vector2( 288 available.x, 289 child.layout.expand 290 ? avail * child.layout.expand / denominator 291 : child.minSize.y, 292 ); 293 294 } 295 296 } 297 298 }