1 module nanogui.experimental.utils; 2 3 import nanogui.common : NanoContext; 4 import nanogui.common : Vector2i; 5 6 void indent(ref NanoContext ctx) 7 { 8 ctx.position.x += 20; 9 } 10 11 void unindent(ref NanoContext ctx) 12 { 13 ctx.position.x -= 20; 14 } 15 16 bool isPointInRect(Vector2i topleft, Vector2i size, Vector2i point) 17 { 18 return point.x >= topleft.x && point.x < topleft.x+size.x && 19 point.y >= topleft.y && point.y < topleft.y+size.y; 20 } 21 22 auto drawItem(T)(ref NanoContext ctx, float height, T item) 23 { 24 import std.traits : isSomeString; 25 26 static if (isSomeString!T) 27 return drawString(ctx, height, item); 28 else 29 return drawPodType(ctx, height, item); 30 } 31 32 private auto drawString(Char)(ref NanoContext ctx, float height, const(Char)[] str) 33 { 34 import nanogui.common : textAlign, text, roundedRect, fill, beginPath, linearGradient, fillPaint, fillColor; 35 const rect_size = Vector2i(cast(int)ctx.current_size, cast(int)height); 36 bool inside; 37 if (isPointInRect(ctx.position, rect_size, ctx.mouse)) 38 { 39 const gradTop = ctx.theme.mButtonGradientTopFocused; 40 const gradBot = ctx.theme.mButtonGradientBotFocused; 41 42 ctx.beginPath; 43 ctx.roundedRect( 44 ctx.position.x, ctx.position.y, 45 ctx.current_size, height, 46 ctx.theme.mButtonCornerRadius - 1 47 ); 48 49 const bg = ctx.linearGradient( 50 ctx.position.x, ctx.position.y, 51 ctx.position.x, ctx.position.y + height, 52 gradTop, gradBot 53 ); 54 55 ctx.fillPaint(bg); 56 ctx.fill; 57 ctx.fillColor(ctx.theme.mTextColor); 58 inside = true; 59 } 60 ctx.textAlign(ctx.algn); 61 ctx.text(ctx.position.x, ctx.position.y, str); 62 ctx.position.y += cast(int) height; 63 64 return inside; 65 } 66 67 private auto drawPodType(T)(ref NanoContext ctx, float height, T item) 68 { 69 import std.format : sformat; 70 import std.traits : isIntegral, isFloatingPoint, isBoolean; 71 72 enum textBufferSize = 512; 73 char[textBufferSize] buffer; 74 size_t l; 75 76 // format specifier depends on type 77 static if (is(T == enum)) 78 { 79 const s = item.enumToString; 80 l += sformat(buffer[l..$], min(buffer.length-l, s.length), "%s", s).length; 81 } 82 else static if (isIntegral!T) 83 l += sformat(buffer[l..$], "%d", item).length; 84 else static if (isFloatingPoint!T) 85 l += sformat(buffer[l..$], "%f", item).length; 86 else static if (isBoolean!T) 87 l += sformat(buffer[l..$], item ? "true\0" : "false\0").length; 88 else static if (isSomeString!T) 89 l += sformat(buffer[l..$], "%s", item).length; 90 else 91 static assert(0, T.stringof); 92 93 return ctx.drawItem(height, buffer[0..l]); 94 } 95 96 struct DataItem(T) 97 { 98 import std.traits : isAggregateType, isPointer, isArray, isSomeString, isAssociativeArray; 99 import nanogui.common : Vector2i; 100 101 enum textBufferSize = 1024; 102 103 T content; 104 private Vector2i _size; 105 106 this(string c, Vector2i s) 107 { 108 content = c; 109 _size = s; 110 } 111 112 auto draw(Context)(ref Context ctx, const(char)[] header, float height) const 113 if (!isAggregateType!T && 114 !isPointer!T && 115 (!isArray!T || isSomeString!T) && 116 !isAssociativeArray!T) 117 { 118 // import core.stdc.stdio : snprintf; 119 import std.format : sformat; 120 import std.traits : isIntegral, isFloatingPoint, isBoolean; 121 122 char[textBufferSize] buffer; 123 size_t l; 124 if (header.length) 125 l = sformat(buffer, "%s: ", header).length; 126 127 // format specifier depends on type 128 static if (is(T == enum)) 129 { 130 const s = content.enumToString; 131 l += sformat(buffer[l..$], min(buffer.length-l, s.length), "%s", s).length; 132 } 133 else static if (isIntegral!T) 134 l += sformat(buffer[l..$], "%d", content).length; 135 else static if (isFloatingPoint!T) 136 l += sformat(buffer[l..$], "%f", content).length; 137 else static if (isBoolean!T) 138 l += sformat(buffer[l..$], content ? "true\0" : "false\0").length; 139 else static if (isSomeString!T) 140 l += sformat(buffer[l..$], "%s", content).length; 141 else 142 static assert(0, T.stringof); 143 144 return ctx.drawItem(height, buffer[0..l]); 145 } 146 147 // auto draw(Context)(Context ctx, const(char)[] header) 148 // if (isAggregateType!T)// && !isInstanceOf!(TaggedAlgebraic, T) && !isNullable!T) 149 // { 150 // // static if (DrawnAsAvailable) 151 // // { 152 // // nk_layout_row_dynamic(ctx, itemHeight, 1); 153 // // static if (Cached) 154 // // state_drawn_as.draw(ctx, header, cached); 155 // // else 156 // // state_drawn_as.draw(ctx, "", t.drawnAs); 157 // // } 158 // // else 159 // static if (DrawableMembers!t.length == 1) 160 // { 161 // static foreach(member; DrawableMembers!t) 162 // mixin("state_" ~ member ~ ".draw(ctx, \"" ~ member ~"\", t." ~ member ~ ");"); 163 // } 164 // else 165 // { 166 // import core.stdc.stdio : sprintf; 167 168 // char[textBufferSize] buffer; 169 // snprintf(buffer.ptr, buffer.length, "%s", header.ptr); 170 171 // if (nk_tree_state_push(ctx, NK_TREE_NODE, buffer.ptr, &collapsed)) 172 // { 173 // scope(exit) 174 // nk_tree_pop(ctx); 175 176 // static foreach(member; DrawableMembers!t) 177 // { 178 // mixin("height += state_" ~ member ~ ".draw(ctx, \"" ~ member ~"\", t." ~ member ~ ");"); 179 // } 180 // } 181 // } 182 // } 183 184 auto visible() const nothrow @safe pure @nogc { return true; } 185 auto performLayout(NVG)(NVG ctx) { }; 186 187 auto size() const nothrow @safe pure @nogc { return _size; } 188 auto size(Vector2i v) nothrow @safe pure @nogc { _size = v; } 189 } 190 191 mixin template DependencyProperty(T, alias string name) 192 { 193 import std.string : capitalize; 194 195 protected 196 { 197 mixin("T m" ~ name.capitalize ~ ";"); 198 } 199 public 200 { 201 import std.format : format; 202 mixin (format(q{ 203 final T %1$s() const { return m%2$s; } 204 final void %1$s(T value) 205 { 206 if (value == m%2$s) return; 207 m%2$s = value; 208 invalidate(); 209 } 210 }, name, name.capitalize)); 211 } 212 } 213 214 // helpers 215 216 private string enumToString(E)(E e) @nogc @safe nothrow 217 if (is(E == enum)) 218 { 219 import std.traits : Unqual; 220 221 // using `if` instead of `final switch` is simple 222 // workaround of duplicated enum members 223 static foreach(v; __traits(allMembers, E)) 224 mixin("if (e == E." ~ v ~ ") return `" ~ v ~ "`;"); 225 return "Unrepresentable by " ~ Unqual!E.stringof ~ " value"; 226 } 227 228 import std.traits : isTypeTuple; 229 230 private template isNullable(T) 231 { 232 import std.traits : hasMember; 233 234 static if ( 235 hasMember!(T, "isNull") && 236 is(typeof(__traits(getMember, T, "isNull")) == bool) && 237 ( 238 (hasMember!(T, "get") && !is(typeof(__traits(getMember, T, "get")) == void)) || 239 (hasMember!(T, "value") && !is(typeof(__traits(getMember, T, "value")) == void)) 240 ) && 241 hasMember!(T, "nullify") && 242 is(typeof(__traits(getMember, T, "nullify")) == void) 243 ) 244 { 245 enum isNullable = true; 246 } 247 else 248 { 249 enum isNullable = false; 250 } 251 } 252 253 private bool privateOrPackage()(string protection) 254 { 255 return protection == "private" || protection == "package"; 256 } 257 258 // check if the member is readable/writeble? 259 private enum isReadableAndWritable(alias aggregate, string member) = __traits(compiles, __traits(getMember, aggregate, member) = __traits(getMember, aggregate, member)); 260 private enum isPublic(alias aggregate, string member) = !__traits(getProtection, __traits(getMember, aggregate, member)).privateOrPackage; 261 262 // check if the member is property with const qualifier 263 private template isConstProperty(alias aggregate, string member) 264 { 265 import std.traits : isSomeFunction, hasFunctionAttributes; 266 267 static if(isSomeFunction!(__traits(getMember, aggregate, member))) 268 enum isConstProperty = hasFunctionAttributes!(__traits(getMember, aggregate, member), "const", "@property"); 269 else 270 enum isConstProperty = false; 271 } 272 273 // check if the member is readable 274 private enum isReadable(alias aggregate, string member) = __traits(compiles, { auto _val = __traits(getMember, aggregate, member); }); 275 276 private template isItSequence(T...) 277 { 278 static if (T.length < 2) 279 enum isItSequence = false; 280 else 281 enum isItSequence = true; 282 } 283 284 private template hasProtection(alias aggregate, string member) 285 { 286 enum hasProtection = __traits(compiles, { enum pl = __traits(getProtection, __traits(getMember, aggregate, member)); }); 287 } 288 289 // This trait defines what members should be drawn - 290 // public members that are either readable and writable or getter properties 291 private template Drawable(alias value, string member) 292 { 293 import std.algorithm : among; 294 import std.traits : isTypeTuple, isSomeFunction; 295 296 static if (isItSequence!value) 297 enum Drawable = false; 298 else 299 static if (hasProtection!(value, member) && !isPublic!(value, member)) 300 enum Drawable = false; 301 else 302 static if (isItSequence!(__traits(getMember, value, member))) 303 enum Drawable = false; 304 else 305 static if(member.among("__ctor", "__dtor")) 306 enum Drawable = false; 307 else 308 static if (isReadableAndWritable!(value, member) && !isSomeFunction!(__traits(getMember, value, member))) 309 enum Drawable = true; 310 else 311 static if (isReadable!(value, member)) 312 enum Drawable = isConstProperty!(value, member); // a readable property is getter 313 else 314 enum Drawable = false; 315 } 316 317 /// returns alias sequence, members of which are members of value 318 /// that should be drawn 319 private template DrawableMembers(alias A) 320 { 321 import std.meta : ApplyLeft, Filter, AliasSeq; 322 import std.traits : isType, Unqual; 323 324 static if (isType!A) 325 { 326 alias Type = Unqual!A; 327 } 328 else 329 { 330 alias Type = Unqual!(typeof(A)); 331 } 332 333 Type symbol; 334 335 alias AllMembers = AliasSeq!(__traits(allMembers, Type)); 336 alias isProper = ApplyLeft!(Drawable, symbol); 337 alias DrawableMembers = Filter!(isProper, AllMembers); 338 339 }