1 module glui.map_space; 2 3 import raylib; 4 5 import std.conv; 6 import std.algorithm; 7 8 import glui.node; 9 import glui.space; 10 import glui.style; 11 import glui.utils; 12 13 14 @safe: 15 16 17 alias mapSpace = simpleConstructor!GluiMapSpace; 18 19 /// Defines the direction the node is "dropped from", that is, which corner of the object will be the anchor. 20 /// Defaults to `start, start`, therefore, the supplied coordinate refers to the top-left of the object. 21 /// 22 /// Automatic may be set to make it present common dropdown behavior — top-left by default, but will change if there 23 /// is overflow. 24 enum MapDropDirection { 25 26 start, center, end, automatic 27 28 } 29 30 struct MapDropVector { 31 32 MapDropDirection x, y; 33 34 } 35 36 struct MapPosition { 37 38 Vector2 coords; 39 MapDropVector drop; 40 41 alias coords this; 42 43 } 44 45 MapDropVector dropVector()() { 46 47 return MapDropVector.init; 48 49 } 50 51 MapDropVector dropVector(string dropXY)() { 52 53 return dropVector!(dropXY, dropXY); 54 55 } 56 57 MapDropVector dropVector(string dropX, string dropY)() { 58 59 enum val(string dropV) = dropV == "auto" 60 ? MapDropDirection.automatic 61 : dropV.to!MapDropDirection; 62 63 return MapDropVector(val!dropX, val!dropY); 64 65 } 66 67 class GluiMapSpace : GluiSpace { 68 69 mixin DefineStyles; 70 71 alias DropDirection = MapDropDirection; 72 alias DropVector = MapDropVector; 73 alias Position = MapPosition; 74 75 /// Mapping of nodes to their positions. 76 Position[GluiNode] positions; 77 78 /// If true, the node will prevent its children from leaving the screen space. 79 bool preventOverlap; 80 81 static foreach (index; 0..BasicNodeParamLength) { 82 83 /// Construct the space. Arguments are either nodes, or positions/vectors affecting the next node added through 84 /// the constructor. 85 this(T...)(BasicNodeParam!index params, T children) 86 if (!T.length || is(T[0] == Vector2) || is(T[0] == DropVector) || is(T[0] == Position) || is(T[0] : GluiNode)) { 87 88 super(params); 89 90 Position position; 91 92 static foreach (child; children) { 93 94 // Update position 95 static if (is(typeof(child) == Position)) { 96 97 position = child; 98 99 } 100 101 else static if (is(typeof(child) == MapDropVector)) { 102 103 position.drop = child; 104 105 } 106 107 else static if (is(typeof(child) == Vector2)) { 108 109 position.coords = child; 110 111 } 112 113 // Add child 114 else { 115 116 addChild(child, position); 117 position = Position.init; 118 119 } 120 121 } 122 123 } 124 125 } 126 127 /// Add a new child to the space and assign it some position. 128 void addChild(GluiNode node, Position position) { 129 130 children ~= node; 131 positions[node] = position; 132 updateSize(); 133 134 } 135 136 void moveChild(GluiNode node, Position position) { 137 138 positions[node] = position; 139 140 } 141 142 void moveChild(GluiNode node, Vector2 vector) { 143 144 positions[node].coords = vector; 145 146 } 147 148 void moveChild(GluiNode node, DropVector vector) { 149 150 positions[node].drop = vector; 151 152 } 153 154 protected override void resizeImpl(Vector2 space) { 155 156 minSize = Vector2(0, 0); 157 158 // TODO get rid of position entries for removed elements 159 160 foreach (child; children) { 161 162 const position = positions[child]; 163 164 child.resize(tree, theme, space); 165 166 // Get the child's end corner 167 const endCorner = getEndCorner(space, child, position); 168 169 minSize.x = max(minSize.x, endCorner.x); 170 minSize.y = max(minSize.y, endCorner.y); 171 172 } 173 174 } 175 176 protected override void drawImpl(Rectangle outer, Rectangle inner) { 177 178 drawChildren((child) { 179 180 const position = positions.require(child, Position.init); 181 const space = Vector2(inner.w, inner.h); 182 const startCorner = getStartCorner(space, child, position); 183 184 auto x = inner.x + startCorner.x; 185 auto y = inner.y + startCorner.y; 186 187 if (preventOverlap) { 188 189 x = x.clamp(inner.x, inner.x + max(0, inner.w - child.minSize.x)); 190 y = y.clamp(inner.y, inner.y + max(0, inner.h - child.minSize.y)); 191 192 } 193 194 const childRect = Rectangle( 195 x, y, 196 child.minSize.x, child.minSize.y 197 ); 198 199 // Draw the child 200 child.draw(childRect); 201 202 }); 203 204 } 205 206 private alias getStartCorner = getCorner!false; 207 private alias getEndCorner = getCorner!true; 208 209 private Vector2 getCorner(bool end)(Vector2 space, GluiNode child, Position position) { 210 211 Vector2 result; 212 213 // Get the children's corners 214 static foreach (direction; ['x', 'y']) {{ 215 216 const pos = mixin("position.coords." ~ direction); 217 const dropDirection = mixin("position.drop." ~ direction); 218 const childSize = mixin("child.minSize." ~ direction); 219 220 const overflow = pos + childSize > mixin("space." ~ direction); 221 222 static if (end) 223 mixin("result." ~ direction) = dropDirection.predSwitch( 224 DropDirection.start, pos + childSize, 225 DropDirection.center, pos + childSize/2, 226 DropDirection.end, pos, 227 DropDirection.automatic, overflow ? pos : pos + childSize, 228 ); 229 230 else 231 mixin("result." ~ direction) = dropDirection.predSwitch( 232 DropDirection.start, pos, 233 DropDirection.center, pos - childSize/2, 234 DropDirection.end, pos - childSize, 235 DropDirection.automatic, overflow ? pos - childSize : pos, 236 ); 237 238 }} 239 240 return result; 241 242 } 243 244 }