Documentation/Source example
Example source

samples/d2d/StrokeStyle.js

"use strict";

window.DrawMode = 1;

include(`${fb.ComponentPath}\\docs\\Flags.js`);

const BG = 0xFFF5F6F7;
const PANEL = 0xFFFFFFFF;
const BORDER = 0xFFD7DADF;
const TEXT = 0xFF202327;
const MUTED = 0xFF737980;
const ACCENT = 0xFF3169C6;
const GUIDE = 0xFFB8BDC4;

const fontTitle = gdi.Font("Segoe UI", 22, 1);
const fontSection = gdi.Font("Segoe UI", 15, 1);
const fontLabel = gdi.Font("Segoe UI", 12, 0);
const fontSmall = gdi.Font("Segoe UI", 10, 0);

// StrokeStyle objects are device-independent resources. Create them once and

// reuse them from on_paint instead of rebuilding a style every frame.

const joinStyles = [
    ["Miter", d2d.StrokeStyle({ lineJoin: LineJoin.Miter })],
    ["Bevel", d2d.StrokeStyle({ lineJoin: LineJoin.Bevel })],
    ["Round", d2d.StrokeStyle({ lineJoin: LineJoin.Round })],
    ["Miter or bevel", d2d.StrokeStyle({ lineJoin: LineJoin.MiterOrBevel, miterLimit: 2 })]
];

const capStyles = [
    ["Flat", d2d.StrokeStyle({ startCap: CapStyle.Flat, endCap: CapStyle.Flat })],
    ["Square", d2d.StrokeStyle({ startCap: CapStyle.Square, endCap: CapStyle.Square })],
    ["Round", d2d.StrokeStyle({ startCap: CapStyle.Round, endCap: CapStyle.Round })],
    ["Triangle", d2d.StrokeStyle({ startCap: CapStyle.Triangle, endCap: CapStyle.Triangle })]
];

const dashStyles = [
    ["Solid", d2d.StrokeStyle({ dashStyle: DashStyle.Solid })],
    ["Dash", d2d.StrokeStyle({ dashStyle: DashStyle.Dash })],
    ["Dot", d2d.StrokeStyle({ dashStyle: DashStyle.Dot })],
    ["Dash dot", d2d.StrokeStyle({ dashStyle: DashStyle.DashDot })],
    ["Dash dot dot", d2d.StrokeStyle({ dashStyle: DashStyle.DashDotDot })],
    ["Custom 4,2,1,2", d2d.StrokeStyle({
        dashes: [4, 2, 1, 2],
        dashCap: CapStyle.Round
    })]
];

const shapeStyles = {
    rect: d2d.StrokeStyle({
        dashStyle: DashStyle.Dash,
        dashCap: CapStyle.Round
    }),
    roundRect: d2d.StrokeStyle({
        dashStyle: DashStyle.DashDot,
        dashCap: CapStyle.Round,
        lineJoin: LineJoin.Round
    }),
    ellipse: d2d.StrokeStyle({
        dashStyle: DashStyle.Dot,
        dashCap: CapStyle.Round
    }),
    polygon: d2d.StrokeStyle({
        lineJoin: LineJoin.Round,
        startCap: CapStyle.Round,
        endCap: CapStyle.Round
    })
};

let ww = 0;
let wh = 0;

function on_size(width, height) {
    ww = width;
    wh = height;
}

function card(gr, x, y, w, h) {
    gr.FillSolidRect(x, y, w, h, PANEL);
    gr.DrawRect(x, y, w, h, 1, BORDER);
}

function label(gr, text, x, y, w, h, small = false) {
    gr.DrawText(text, small ? fontSmall : fontLabel, TEXT, x, y, w, h,
        DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

function sectionTitle(gr, text, x, y, w) {
    gr.DrawText(text, fontSection, TEXT, x, y, w, 28, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
}

function drawJoinSection(gr, x, y, w, h) {
    sectionTitle(gr, "Line joins", x, y, w);
    y += 32;
    h -= 32;

    const gap = 10;
    const cw = (w - gap * (joinStyles.length - 1)) / joinStyles.length;

    for (let i = 0; i < joinStyles.length; ++i) {
        const cx = x + i * (cw + gap);
        card(gr, cx, y, cw, h);
        label(gr, joinStyles[i][0], cx, y + 5, cw, 22);

        const top = y + 37;
        const bottom = y + h - 18;
        const mid = cx + cw / 2;
        const inset = Math.max(18, cw * 0.18);
        gr.DrawLines(ACCENT, 12, [
            cx + inset, bottom,
            mid, top,
            cx + cw - inset, bottom
        ], joinStyles[i][1]);
    }
}

function drawCapSection(gr, x, y, w, h) {
    sectionTitle(gr, "Line caps", x, y, w);
    y += 32;
    h -= 32;

    const gap = 10;
    const cw = (w - gap * (capStyles.length - 1)) / capStyles.length;

    for (let i = 0; i < capStyles.length; ++i) {
        const cx = x + i * (cw + gap);
        card(gr, cx, y, cw, h);
        label(gr, capStyles[i][0], cx, y + 5, cw, 22);

        const cy = y + h * 0.63;
        const x1 = cx + Math.max(30, cw * 0.24);
        const x2 = cx + cw - Math.max(30, cw * 0.24);

        // Guides make the amount by which each cap extends past the endpoints visible.

        gr.DrawLine(x1, cy - 22, x1, cy + 22, 1, GUIDE);
        gr.DrawLine(x2, cy - 22, x2, cy + 22, 1, GUIDE);
        gr.DrawLine(x1, cy, x2, cy, 14, ACCENT, capStyles[i][1]);
    }
}

function drawDashSection(gr, x, y, w, h) {
    sectionTitle(gr, "Dash styles", x, y, w);
    y += 32;
    h -= 32;

    const cols = 3;
    const rows = 2;
    const gapX = 10;
    const gapY = 8;
    const cw = (w - gapX * (cols - 1)) / cols;
    const ch = (h - gapY * (rows - 1)) / rows;

    for (let i = 0; i < dashStyles.length; ++i) {
        const col = i % cols;
        const row = Math.floor(i / cols);
        const cx = x + col * (cw + gapX);
        const cy = y + row * (ch + gapY);
        card(gr, cx, cy, cw, ch);
        label(gr, dashStyles[i][0], cx, cy + 2, cw, 20, true);
        gr.DrawLine(cx + 16, cy + ch * 0.65, cx + cw - 16, cy + ch * 0.65,
            6, ACCENT, dashStyles[i][1]);
    }
}

function drawShapeSection(gr, x, y, w, h) {
    sectionTitle(gr, "Shapes using StrokeStyle", x, y, w);
    y += 32;
    h -= 32;

    const gap = 10;
    const cw = (w - gap * 3) / 4;

    for (let i = 0; i < 4; ++i) {
        card(gr, x + i * (cw + gap), y, cw, h);
    }

    const inset = 22;
    const sy = y + 36;
    const sh = h - 54;

    let cx = x;
    label(gr, "DrawRect", cx, y + 4, cw, 22);
    gr.DrawRect(cx + inset, sy, cw - inset * 2, sh, 5, ACCENT, shapeStyles.rect);

    cx = x + (cw + gap);
    label(gr, "DrawRoundRect", cx, y + 4, cw, 22);
    gr.DrawRoundRect(cx + inset, sy, cw - inset * 2, sh, 16, 16, 5, ACCENT, shapeStyles.roundRect);

    cx = x + 2 * (cw + gap);
    label(gr, "DrawEllipse", cx, y + 4, cw, 22);
    gr.DrawEllipse(cx + inset, sy, cw - inset * 2, sh, 5, ACCENT, shapeStyles.ellipse);

    cx = x + 3 * (cw + gap);
    label(gr, "DrawPolygon", cx, y + 4, cw, 22);
    gr.DrawPolygon(ACCENT, 7, [
        cx + cw * 0.50, sy,
        cx + cw - inset, sy + sh * 0.38,
        cx + cw * 0.72, sy + sh,
        cx + cw * 0.28, sy + sh,
        cx + inset, sy + sh * 0.38
    ], shapeStyles.polygon);
}

function on_paint(gr) {
    gr.SetSmoothingMode(2);
    gr.FillSolidRect(0, 0, ww, wh, BG);

    const margin = 18;

    // Keep the sample readable at small panel sizes: the layout may grow with the

    // panel, but it never shrinks below this canvas. If the panel is smaller, the

    // normal window clipping simply hides the part that is outside the viewport.

    const contentW = Math.max(900, ww - margin * 2);

    gr.DrawText("Direct2D StrokeStyle", fontTitle, TEXT,
        margin, 10, contentW, 34, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
    gr.DrawText("Reusable caps, joins and dash patterns for DrawLine / DrawLines / DrawPolygon and shape outlines",
        fontSmall, MUTED, margin, 43, contentW, 22, DT_LEFT | DT_VCENTER | DT_SINGLELINE);

    const top = 75;
    const bottom = 18;
    const availableH = Math.max(590, wh - top - bottom);
    const rowGap = 14;
    const row1 = Math.max(170, availableH * 0.29);
    const row2 = Math.max(170, availableH * 0.27);
    const row3 = Math.max(220, availableH - row1 - row2 - rowGap * 2);

    drawJoinSection(gr, margin, top, contentW, row1);

    const halfGap = 14;
    const halfW = (contentW - halfGap) / 2;
    const secondY = top + row1 + rowGap;
    drawCapSection(gr, margin, secondY, halfW, row2);
    drawDashSection(gr, margin + halfW + halfGap, secondY, halfW, row2);

    const thirdY = secondY + row2 + rowGap;
    drawShapeSection(gr, margin, thirdY, contentW, row3);
}