dlangui/examples/bezier/source/app.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 );
}
}