1 /// 2 module glui.structs; 3 4 import raylib; 5 import std.conv; 6 import glui.node; 7 8 9 @safe: 10 11 12 /// Create a new layout 13 /// Params: 14 /// expand = Numerator of the fraction of space this node should occupy in the parent. 15 /// align_ = Align of the node (horizontal and vertical). 16 /// alignX = Horizontal align of the node. 17 /// alignY = Vertical align of the node. 18 Layout layout(uint expand, NodeAlign alignX, NodeAlign alignY) { 19 20 return Layout(expand, [alignX, alignY]); 21 22 } 23 24 /// Ditto 25 Layout layout(uint expand, NodeAlign align_) { 26 27 return Layout(expand, align_); 28 29 } 30 31 /// Ditto 32 Layout layout(NodeAlign alignX, NodeAlign alignY) { 33 34 return Layout(0, [alignX, alignY]); 35 36 } 37 38 /// Ditto 39 Layout layout(NodeAlign align_) { 40 41 return Layout(0, align_); 42 43 } 44 45 /// Ditto 46 Layout layout(uint expand) { 47 48 return Layout(expand); 49 50 } 51 52 /// CTFE version of the layout constructor, allows using strings instead of enum members, to avoid boilerplate. 53 Layout layout(uint expand, string alignX, string alignY)() { 54 55 enum valueX = alignX.to!NodeAlign; 56 enum valueY = alignY.to!NodeAlign; 57 58 return Layout(expand, [valueX, valueY]); 59 60 } 61 62 /// Ditto 63 Layout layout(uint expand, string align_)() { 64 65 enum valueXY = align_.to!NodeAlign; 66 67 return Layout(expand, valueXY); 68 69 } 70 71 /// Ditto 72 Layout layout(string alignX, string alignY)() { 73 74 enum valueX = alignX.to!NodeAlign; 75 enum valueY = alignY.to!NodeAlign; 76 77 return Layout(0, [valueX, valueY]); 78 79 } 80 81 /// Ditto 82 Layout layout(string align_)() { 83 84 enum valueXY = align_.to!NodeAlign; 85 86 return Layout(0, valueXY); 87 88 } 89 90 /// Ditto 91 Layout layout(uint expand)() { 92 93 return Layout(expand); 94 95 } 96 97 unittest { 98 99 assert(layout!1 == layout(1)); 100 assert(layout!("fill") == layout(NodeAlign.fill, NodeAlign.fill)); 101 assert(layout!("fill", "fill") == layout(NodeAlign.fill)); 102 103 assert(!__traits(compiles, layout!"expand")); 104 assert(!__traits(compiles, layout!("expand", "noexpand"))); 105 assert(!__traits(compiles, layout!(1, "whatever"))); 106 assert(!__traits(compiles, layout!(2, "foo", "bar"))); 107 108 } 109 110 /// Represents a node's layout 111 struct Layout { 112 113 /// Fraction of available space this node should occupy in the node direction. 114 /// 115 /// If set to `0`, the node doesn't have a strict size limit and has size based on content. 116 uint expand; 117 118 /// Align the content box to a side of the occupied space. 119 NodeAlign[2] nodeAlign; 120 121 string toString() const { 122 123 import std.format; 124 125 const equalAlign = nodeAlign[0] == nodeAlign[1]; 126 const startAlign = equalAlign && nodeAlign[0] == NodeAlign.start; 127 128 if (expand) { 129 130 if (startAlign) return format!".layout!%s"(expand); 131 else if (equalAlign) return format!".layout!(%s, %s)"(expand, nodeAlign[0]); 132 else return format!".layout!(%s, %s, %s)"(expand, nodeAlign[0], nodeAlign[1]); 133 134 } 135 136 else { 137 138 if (startAlign) return format!"Layout()"; 139 else if (equalAlign) return format!".layout!%s"(nodeAlign[0]); 140 else return format!".layout!(%s, %s)"(nodeAlign[0], nodeAlign[1]); 141 142 } 143 144 } 145 146 } 147 148 enum NodeAlign { 149 150 start, center, end, fill 151 152 } 153 154 package interface GluiFocusable { 155 156 void focus(); 157 bool isFocused() const; 158 void mouseImpl(); 159 bool keyboardImpl(); 160 ref inout(bool) isDisabled() inout; 161 162 } 163 164 /// Global data for the layout tree. 165 struct LayoutTree { 166 167 /// Root node of the tree. 168 GluiNode root; 169 170 /// Top-most hovered node in the tree. 171 GluiNode hover; 172 173 /// Currently focused node. 174 GluiFocusable focus; 175 176 /// Check if keyboard input was handled after rendering is has completed. 177 bool keyboardHandled; 178 179 /// Current depth of "disabled" nodes, incremented for any node descended into, while any of the ancestors is 180 /// disabled. 181 uint disabledDepth; 182 183 /// Scissors stack. 184 package Rectangle[] scissors; 185 186 debug (Glui_DisableScissors) { 187 188 Rectangle intersectScissors(Rectangle rect) { return rect; } 189 void pushScissors(Rectangle) { } 190 void popScissors() { } 191 192 } 193 194 else { 195 196 /// Intersect the given rectangle against current scissor area. 197 Rectangle intersectScissors(Rectangle rect) { 198 199 import std.algorithm : min, max; 200 201 // No limit applied 202 if (!scissors.length) return rect; 203 204 const b = scissors[$-1]; 205 206 Rectangle result; 207 208 // Intersect 209 result.x = max(rect.x, b.x); 210 result.y = max(rect.y, b.y); 211 result.w = min(rect.x + rect.w, b.x + b.w) - result.x; 212 result.h = min(rect.y + rect.h, b.y + b.h) - result.y; 213 214 return result; 215 216 } 217 218 /// Start scissors mode. 219 void pushScissors(Rectangle rect) { 220 221 auto result = rect; 222 223 // There's already something on the stack 224 if (scissors.length) { 225 226 // Intersect 227 result = intersectScissors(rect); 228 229 } 230 231 // Push to the stack 232 scissors ~= result; 233 234 // Start the mode 235 applyScissors(result); 236 237 } 238 239 void popScissors() @trusted { 240 241 // Pop the stack 242 scissors = scissors[0 .. $-1]; 243 244 // Pop the mode 245 EndScissorMode(); 246 247 // There's still something left 248 if (scissors.length) { 249 250 // Start again 251 applyScissors(scissors[$-1]); 252 253 } 254 255 } 256 257 private void applyScissors(Rectangle rect) @trusted { 258 259 import glui.utils; 260 261 // End the current mode, if any 262 if (scissors.length) EndScissorMode(); 263 264 auto scale = hidpiScale; 265 266 // Start this one 267 BeginScissorMode( 268 to!int(rect.x * scale.x), 269 to!int(rect.y * scale.y), 270 to!int(rect.w * scale.x), 271 to!int(rect.h * scale.y), 272 ); 273 274 } 275 276 } 277 278 }