mirror of https://github.com/buggins/dlangui.git
230 lines
8.0 KiB
D
230 lines
8.0 KiB
D
module app;
|
|
|
|
static assert(ENABLE_OPENGL, "All bezier samples in this module using
|
|
floating point drawing functions which is not supported in minimal config");
|
|
|
|
import dlangui;
|
|
|
|
import std.algorithm.comparison;
|
|
|
|
mixin APP_ENTRY_POINT;
|
|
|
|
// helper for scaling relative to average 96dpi FullHD, IDK but maybe a bad idea after all
|
|
T scaledByDPI(T)(T val) {
|
|
return val *= (SCREEN_DPI()/cast(T)96);
|
|
}
|
|
|
|
/// Entry point for dlangui based application
|
|
extern (C) int UIAppMain(string[] args) {
|
|
// portrait "mode" window
|
|
Window window = Platform.instance.createWindow("Bezier curves", null, WindowFlag.Resizable, 480.scaledByDPI, 600.scaledByDPI);
|
|
window.mainWidget = new BezierSamples();
|
|
window.show();
|
|
return Platform.instance.enterMessageLoop();
|
|
}
|
|
|
|
class BezierSamples : VerticalLayout {
|
|
|
|
this() {
|
|
this(null);
|
|
}
|
|
this(string id) {
|
|
super(id);
|
|
addChild(new CubicTraceSample);
|
|
addChild(new FlattenCubicSample);
|
|
addChild(new ColoredCubicTraceSample);
|
|
addChild(new FlattenCubicGuidesSample);
|
|
addChild(new FlattenQuadraticSample);
|
|
}
|
|
|
|
override bool animating() { return true; }
|
|
}
|
|
|
|
|
|
abstract class SampleCanvas : CanvasWidget {
|
|
|
|
dstring _sampleName = "Bezier sample";
|
|
static immutable vec2[] _controlPointsDefaultRatios = [vec2(0,0.2), vec2(0.2,0.2), vec2(0.8,0.8), vec2(1,0.8)];
|
|
static immutable vec2[] _controlPointsQuadratic = [vec2(0.2,0.8), vec2(0.7,0.4), vec2(0.3,0.2)];
|
|
|
|
this() {
|
|
fillHorizontal();
|
|
auto p = 5.scaledByDPI;
|
|
margins(Rect(p, p, p, p));
|
|
p = 15.scaledByDPI;
|
|
padding(Rect(p, p, p, p));
|
|
minHeight = 250.scaledByDPI;
|
|
}
|
|
|
|
dstring sampleName() { return _sampleName; }
|
|
|
|
override protected void measuredContent(int parentWidth, int parentHeight, int contentWidth, int contentHeight) {
|
|
_measuredWidth = max(minHeight, contentWidth);
|
|
_measuredHeight = minHeight;
|
|
}
|
|
|
|
protected void drawText(DrawBuf buf, Rect rc, dstring text) {
|
|
FontRef font = font();
|
|
Point sz = font.textSize(text);
|
|
applyAlign(rc, sz, Align.HCenter, Align.Bottom );
|
|
font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags);
|
|
}
|
|
|
|
protected void calcRectSize(const vec2[] controlPoints, vec2[] result) {
|
|
assert(result.length >= controlPoints.length);
|
|
auto r = _pos;
|
|
applyMargins(r);
|
|
applyPadding(r);
|
|
vec2 pos = vec2(r.left, r.top);
|
|
vec2 size = vec2(r.width, r.height);
|
|
result[] = controlPoints[]; // copy points
|
|
result[0] = result[0].mul(size) + pos;
|
|
result[1] = result[1].mul(size) + pos;
|
|
result[2] = result[2].mul(size) + pos;
|
|
if(controlPoints.length > 3)
|
|
result[3] = result[3].mul(size) + pos;
|
|
}
|
|
|
|
// override to draw
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
}
|
|
}
|
|
|
|
class CubicTraceSample : SampleCanvas {
|
|
this() {
|
|
_sampleName = "Cubic bezier curve drawn with ellipses (slow, high overdraw)";
|
|
}
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
vec2[4] points;
|
|
calcRectSize(_controlPointsDefaultRatios, points);
|
|
auto len = (points[0]-points[3]).magnitude;
|
|
auto interval = 1f/len;
|
|
auto step = interval;
|
|
// evaluate normal bezier curve and trace with circles
|
|
foreach ( i ; 0..len ) {
|
|
auto b = bezierCubic(points, interval);
|
|
interval+=step;
|
|
buf.drawEllipseF(b.x, b.y, 3, 3, 0, Color.black, Color.black);
|
|
}
|
|
drawText(buf,rc, sampleName());
|
|
}
|
|
}
|
|
|
|
class FlattenCubicSample : SampleCanvas {
|
|
this() {
|
|
_sampleName = "Flattened cubic bezier curve drawn with lines (fast)";
|
|
}
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
vec2[4] points;
|
|
calcRectSize(_controlPointsDefaultRatios, points);
|
|
enum segments = 10;
|
|
auto lines = flattenBezierCubic(points, segments);
|
|
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
|
drawText(buf,rc, sampleName());
|
|
}
|
|
}
|
|
|
|
class ColoredCubicTraceSample : SampleCanvas {
|
|
this() {
|
|
_sampleName = "Simple colored cubic bezier curve drawn with ellipsises";
|
|
}
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
vec2[4] points;
|
|
calcRectSize(_controlPointsDefaultRatios, points);
|
|
auto len = (points[0]-points[3]).magnitude;
|
|
auto interval = 1/len;
|
|
auto step = interval;
|
|
// evaluate normal bezier curve and trace with circles
|
|
foreach ( i ; 0..len ) {
|
|
auto b = bezierCubic(points, interval);
|
|
interval+=step;
|
|
buf.drawEllipseF(b.x, b.y, 3, 3, 0, COLOR_TRANSPARENT, lerpColor01(Color.blue, Color.red, interval));
|
|
}
|
|
drawText(buf,rc, sampleName());
|
|
}
|
|
|
|
// clamp to [0,1] and lerp color
|
|
static uint lerpColor01(uint a, uint b, float ratio) {
|
|
ratio = clamp(ratio, 0, 1);
|
|
return blendARGB(b, a, cast(uint)(ratio * 255));
|
|
}
|
|
}
|
|
|
|
class FlattenCubicGuidesSample : SampleCanvas {
|
|
this() {
|
|
_sampleName = "Flattened cubic bezier curve with direction and normal vectors";
|
|
}
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
vec2[4] points;
|
|
calcRectSize(_controlPointsDefaultRatios, points);
|
|
enum segmentsCount = 10;
|
|
auto lines = flattenBezierCubic(points, segmentsCount);
|
|
drawCubicControlsGuides(buf, points);
|
|
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
|
drawCubicSegmentsNormDir(buf, points, segmentsCount, lines, true, true);
|
|
drawText(buf,rc, sampleName());
|
|
}
|
|
}
|
|
|
|
class FlattenQuadraticSample : SampleCanvas {
|
|
this() {
|
|
_sampleName = "Flattened quadratic bezier curve";
|
|
}
|
|
override void doDraw(DrawBuf buf, Rect rc) {
|
|
vec2[3] points;
|
|
calcRectSize(_controlPointsQuadratic, points);
|
|
|
|
// guide lines
|
|
buf.drawLineF(points[0], points[1], 1, Color.dark_gray);
|
|
buf.drawLineF(points[1], points[2], 1, Color.dark_gray);
|
|
|
|
|
|
enum segmentCount = 10;
|
|
auto lines = flattenBezierQuadratic(points, segmentCount);
|
|
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
|
|
|
// end points
|
|
buf.drawEllipseF(points[0].x, points[0].y, 5,5, 1, Color.antique_white, Color.cyan);
|
|
buf.drawEllipseF(points[2].x, points[2].y, 5,5, 1, Color.antique_white, Color.cyan);
|
|
buf.drawEllipseF(points[1].x, points[1].y, 3,3, 0, Color.antique_white, Color.cyan);
|
|
|
|
// draw the direction & normal vectors
|
|
auto segStep = 1f/segmentCount;
|
|
foreach(i; 0..segmentCount) {
|
|
auto dir = bezierQuadraticDirection(points, i*segStep);
|
|
auto norm = dir.rotated90ccw;
|
|
auto point = lines[i];
|
|
buf.drawLineF(point, point + (dir * 15f), 3.scaledByDPI, Color.yellow );
|
|
buf.drawLineF(point, point + (norm * 15f), 3.scaledByDPI, Color.red );
|
|
}
|
|
|
|
drawText(buf,rc, sampleName());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void drawCubicControlsGuides(DrawBuf buf, vec2[] controls) {
|
|
buf.drawLineF(controls[0], controls[1], 1, Color.black);
|
|
buf.drawLineF(controls[2], controls[3], 1, Color.black);
|
|
buf.drawEllipseF(controls[0].x, controls[0].y, 5,5, 1, Color.antique_white, Color.cyan);
|
|
buf.drawEllipseF(controls[3].x, controls[3].y, 5,5, 1, Color.antique_white, Color.cyan);
|
|
buf.drawEllipseF(controls[1].x, controls[1].y, 3,3, 0, Color.antique_white, Color.cyan);
|
|
buf.drawEllipseF(controls[2].x, controls[2].y, 3,3, 0, Color.antique_white, Color.cyan);
|
|
}
|
|
|
|
void drawCubicSegmentsNormDir(DrawBuf buf, vec2[] controls, int segmentCount, vec2[] segments, bool direction, bool normals) {
|
|
if ( !direction && !normals && segments.length < 2)
|
|
return;
|
|
// draw the direction & normal vectors
|
|
auto segStep = 1f/segmentCount;
|
|
foreach(i; 0..segmentCount) {
|
|
auto dir = bezierCubicDirection(controls, i*segStep);
|
|
auto norm = dir.rotated90ccw;
|
|
auto point = segments[i];
|
|
if ( direction )
|
|
buf.drawLineF(point, point + (dir * 15f), 3.scaledByDPI, Color.yellow );
|
|
if ( normals )
|
|
buf.drawLineF(point, point + (norm * 15f), 3.scaledByDPI, Color.red );
|
|
}
|
|
} |