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