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