1 module nanogui.experimental.utils; 2 3 import nanogui.common : NanoContext; 4 import nanogui.common : Vector2f, Vector2i; 5 6 public import auxil.sizeindex; 7 public import auxil.model; 8 public import auxil.traits; 9 public import auxil.treepath : TreePath; 10 public import auxil.default_visitor : MeasuringVisitor, TreePathVisitorImpl; 11 12 private string enumToString(E)(E e) @nogc @safe nothrow 13 if (is(E == enum)) 14 { 15 import std.traits : Unqual; 16 17 // using `if` instead of `final switch` is simple 18 // workaround of duplicated enum members 19 static foreach(v; __traits(allMembers, E)) 20 mixin("if (e == E." ~ v ~ ") return `" ~ v ~ "`;"); 21 return "Unrepresentable by " ~ Unqual!E.stringof ~ " value"; 22 } 23 24 void indent(ref NanoContext ctx) 25 { 26 ctx.position.x += 20; 27 ctx.size.x -= 20; 28 } 29 30 void unindent(ref NanoContext ctx) 31 { 32 ctx.position.x -= 20; 33 ctx.size.x += 20; 34 } 35 36 bool isPointInRect(Vector2f topleft, Vector2f size, Vector2f point) 37 { 38 return point.x >= topleft.x && point.x < topleft.x+size.x && 39 point.y >= topleft.y && point.y < topleft.y+size.y; 40 } 41 42 bool isPointInRect(Vector2i topleft, Vector2i size, Vector2i point) 43 { 44 return point.x >= topleft.x && point.x < topleft.x+size.x && 45 point.y >= topleft.y && point.y < topleft.y+size.y; 46 } 47 48 auto drawItem(T)(ref NanoContext ctx, float height, T item) 49 { 50 import std.traits : isSomeString; 51 52 static if (isSomeString!T) 53 return drawString(ctx, height, item); 54 else 55 return drawPodType(ctx, height, item); 56 } 57 58 private auto drawString(Char)(ref NanoContext ctx, float height, const(Char)[] str) 59 { 60 import nanogui.common : textAlign, text, roundedRect, fill, beginPath, linearGradient, fillPaint, fillColor; 61 62 // it is possible if the item is placed out of visible area 63 if (ctx.size[ctx.orientation] <= 0) 64 return false; 65 66 bool inside; 67 if (isPointInRect(ctx.position, ctx.size, ctx.mouse)) 68 { 69 const gradTop = ctx.theme.mButtonGradientTopFocused; 70 const gradBot = ctx.theme.mButtonGradientBotFocused; 71 72 ctx.beginPath; 73 ctx.roundedRect( 74 ctx.position.x, ctx.position.y, 75 ctx.size.x, ctx.size.y, 76 ctx.theme.mButtonCornerRadius - 1 77 ); 78 79 const bg = ctx.linearGradient( 80 ctx.position.x, ctx.position.y, 81 ctx.position.x, ctx.position.y + height, 82 gradTop, gradBot 83 ); 84 85 ctx.fillPaint(bg); 86 ctx.fill; 87 ctx.fillColor(ctx.theme.mTextColor); 88 inside = true; 89 } 90 ctx.textAlign(ctx.algn); 91 ctx.text(ctx.position.x, ctx.position.y, str); 92 93 return inside; 94 } 95 96 private auto drawPodType(T)(ref NanoContext ctx, float height, T item) 97 { 98 import std.format : sformat; 99 import std.traits : isIntegral, isFloatingPoint, isBoolean, isSomeString, 100 isPointer, isSomeChar; 101 102 enum textBufferSize = 512; 103 char[textBufferSize] buffer; 104 size_t l; 105 106 // format specifier depends on type 107 static if (is(T == enum)) 108 { 109 const s = item.enumToString; 110 l += sformat(buffer[l..$], "%s", s).length; 111 } 112 else static if (isIntegral!T) 113 l += sformat(buffer[l..$], "%d", item).length; 114 else static if (isFloatingPoint!T) 115 l += sformat(buffer[l..$], "%f", item).length; 116 else static if (isBoolean!T) 117 l += sformat(buffer[l..$], item ? "true" : "false").length; 118 else static if (isSomeString!T || isPointer!T) 119 l += sformat(buffer[l..$], "%s", item).length; 120 else static if (isSomeChar!T) 121 l += sformat(buffer[l..$], "%s", item).length; 122 else 123 static assert(0, T.stringof); 124 125 buffer[l < $ ? l : $-1] = '\0'; 126 127 return ctx.drawString(height, buffer[0..l]); 128 } 129 130 mixin template DependencyProperty(T, alias string name) 131 { 132 import std.string : capitalize; 133 134 protected 135 { 136 mixin("T m" ~ name.capitalize ~ ";"); 137 } 138 public 139 { 140 import std.format : format; 141 mixin (format(q{ 142 final T %1$s() const { return m%2$s; } 143 final void %1$s(T value) 144 { 145 if (value == m%2$s) return; 146 m%2$s = value; 147 invalidate(); 148 } 149 }, name, name.capitalize)); 150 } 151 }