From 3f29f1a82dcd54f8230ff51a1cc83917538c14bf Mon Sep 17 00:00:00 2001 From: deadlink Date: Sat, 24 Feb 2024 19:21:14 +0400 Subject: [PATCH 01/10] WIP: rounded rectangle --- src/core/layers/rectangle.layer.ts | 16 ++++++- src/draw/draw-context.ts | 76 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index de4884d3..f34173a9 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -7,6 +7,7 @@ type TRectangleState = TLayerState & { p: number[]; // position [x, y] s: number[]; // size [w, h] f: boolean; // fill + r: number; // radius }; export class RectangleLayer extends AbstractLayer { @@ -36,6 +37,7 @@ export class RectangleLayer extends AbstractLayer { public position: Point = new Point(); public size: Point = new Point(); public fill: boolean = false; + public radius: number = 0; modifiers: TLayerModifiers = { x: { @@ -79,6 +81,16 @@ export class RectangleLayer extends AbstractLayer { }, type: TModifierType.number }, + radius: { + getValue: () => this.radius, + setValue: (v: number) => { + this.radius = v; + this.updateBounds(); + this.saveState(); + this.draw(); + }, + type: TModifierType.number + }, fill: { getValue: () => this.fill, setValue: (v: boolean) => { @@ -221,13 +233,14 @@ export class RectangleLayer extends AbstractLayer { dc.clear(); dc.ctx.fillStyle = this.color; dc.ctx.strokeStyle = this.color; - dc.rect(position, size, this.fill); + dc.pixelateRoundedRect(position, size, this.radius, this.fill); } saveState() { const state: TRectangleState = { p: this.position.xy, s: this.size.xy, + r: this.radius, n: this.name, i: this.index, g: this.group, @@ -243,6 +256,7 @@ export class RectangleLayer extends AbstractLayer { loadState(state: TRectangleState) { this.position = new Point(state.p); this.size = new Point(state.s); + this.radius = state.r ?? 0; this.name = state.n; this.index = state.i; this.group = state.g; diff --git a/src/draw/draw-context.ts b/src/draw/draw-context.ts index 795ba242..9fc9e4a3 100644 --- a/src/draw/draw-context.ts +++ b/src/draw/draw-context.ts @@ -134,6 +134,82 @@ export class DrawContext { return this; } + pixelateCircleSection( + center: Point, + radius: number, + startAngle: number, + endAngle: number, + fill: boolean + ): DrawContext { + // arc pixelated + const xStart = center.x + radius * Math.cos(startAngle); + const yStart = center.y + radius * Math.sin(startAngle); + const xEnd = center.x + radius * Math.cos(endAngle); + const yEnd = center.y + radius * Math.sin(endAngle); + const xCenter = center.x; + const yCenter = center.y; + // this.pixelateLine(center, new Point(xStart, yStart), 1); + // this.pixelateLine(center, new Point(xEnd, yEnd), 1); + this.ctx.beginPath(); + this.ctx.arc(xCenter, yCenter, radius, startAngle, endAngle); + if (fill) { + this.ctx.fill(); + } + this.ctx.stroke(); + this.ctx.closePath(); + return this; + } + + pixelateRoundedRect(position: Point, size: Point, radius: number, fill: boolean): DrawContext { + // using pixelateCircleSection + const topLeft = position.clone(); + const topRight = position.clone().add(new Point(size.x, 0)); + const bottomRight = position.clone().add(size); + const bottomLeft = position.clone().add(new Point(0, size.y)); + this.pixelateCircleSection( + topLeft.clone().add(new Point(radius, radius)), + radius, + Math.PI, + Math.PI * 1.5, + fill + ); + this.pixelateCircleSection( + topRight.clone().add(new Point(-radius, radius)), + radius, + Math.PI * 1.5, + Math.PI * 2, + fill + ); + this.pixelateCircleSection( + bottomRight.clone().add(new Point(-radius, -radius)), + radius, + 0, + Math.PI * 0.5, + fill + ); + this.pixelateCircleSection( + bottomLeft.clone().add(new Point(radius, -radius)), + radius, + Math.PI * 0.5, + Math.PI, + fill + ); + this.pixelateLine(topLeft.clone().add(new Point(radius, 0)), topRight.clone().add(new Point(-radius, 0)), 1); + this.pixelateLine( + topRight.clone().add(new Point(-1, radius)), + bottomRight.clone().add(new Point(-1, -radius)), + 1 + ); + this.pixelateLine( + bottomRight.clone().add(new Point(-radius, -1)), + bottomLeft.clone().add(new Point(radius, -1)), + 1 + ); + this.pixelateLine(bottomLeft.clone().add(new Point(0, -radius)), topLeft.clone().add(new Point(0, radius)), 1); + + return this; + } + pixelateEllipse(center: Point, radiusX: number, radiusY: number, fill: boolean): DrawContext { this.ctx.beginPath(); for (let n = 0; n < radiusX; n++) { From 2e8316d635f5ec44c124728ec905d76551f822c8 Mon Sep 17 00:00:00 2001 From: deadlink Date: Tue, 2 Apr 2024 15:15:14 +0400 Subject: [PATCH 02/10] Drawing method for rounded rect --- src/core/layers/rectangle.layer.ts | 2 +- src/draw/draw-context.ts | 149 ++++++++++++++--------------- 2 files changed, 74 insertions(+), 77 deletions(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index f34173a9..0521b660 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -84,7 +84,7 @@ export class RectangleLayer extends AbstractLayer { radius: { getValue: () => this.radius, setValue: (v: number) => { - this.radius = v; + this.radius = Math.min(v, Math.round(this.size.x / 2), Math.round(this.size.y / 2)); this.updateBounds(); this.saveState(); this.draw(); diff --git a/src/draw/draw-context.ts b/src/draw/draw-context.ts index 9fc9e4a3..5169cb8c 100644 --- a/src/draw/draw-context.ts +++ b/src/draw/draw-context.ts @@ -122,91 +122,88 @@ export class DrawContext { } } this.ctx.fill(); - if (!fill) { - this.ctx.save(); - this.ctx.fillStyle = 'rgba(0,0,0,0)'; - this.ctx.beginPath(); - this.ctx.arc(center.x + 0.5, center.y + 0.5, radius + 0.5, 0, 2 * Math.PI); - this.ctx.fill(); - this.ctx.restore(); - } this.ctx.closePath(); return this; } - pixelateCircleSection( - center: Point, - radius: number, - startAngle: number, - endAngle: number, - fill: boolean - ): DrawContext { - // arc pixelated - const xStart = center.x + radius * Math.cos(startAngle); - const yStart = center.y + radius * Math.sin(startAngle); - const xEnd = center.x + radius * Math.cos(endAngle); - const yEnd = center.y + radius * Math.sin(endAngle); - const xCenter = center.x; - const yCenter = center.y; - // this.pixelateLine(center, new Point(xStart, yStart), 1); - // this.pixelateLine(center, new Point(xEnd, yEnd), 1); - this.ctx.beginPath(); - this.ctx.arc(xCenter, yCenter, radius, startAngle, endAngle); - if (fill) { - this.ctx.fill(); + // draw pixelated corner with corner point, radius, and fill in quadrant + pixelateRectCorner(point: Point, radius: number, quadrant: number, fill: boolean): DrawContext { + const radiusPoint = new Point(radius); + switch (quadrant) { + case 1: + radiusPoint.multiply(-1, 1); + break; + case 2: + radiusPoint.multiply(-1, -1); + break; + case 3: + radiusPoint.multiply(1, -1); + break; + } + const center = point.clone().add(radiusPoint); + for (let n = 0; n < radius; n++) { + const x = n; + let y = Math.ceil(Math.sqrt(radius * radius - x * x)); + const p1 = center.clone(); + const p2 = center.clone(); + const r = new Point(radius); + switch (quadrant) { + case 0: + p1.add(-x, -y); + p2.add(-y, -x); + r.subtract(p2.x - point.x - 1, radius); + break; + case 1: + p1.add(x, -y); + p2.add(y, -x); + r.subtract(point.x - p2.x - 1, radius); + break; + case 2: + p1.add(x, y); + p2.add(y, x); + r.subtract(point.x - p2.x, radius).multiply(-1); + break; + case 3: + p1.add(-x, y); + p2.add(-y, x); + r.subtract(p2.x - point.x, radius).multiply(-1); + break; + } + this.ctx.rect(p1.x, p1.y, 1, 1); + this.ctx.rect(p2.x, p2.y, 1, 1); + if (fill) { + this.ctx.rect(p1.x, p1.y, 1, r.x); + this.ctx.rect(p2.x, p2.y, r.y, 1); + } } - this.ctx.stroke(); - this.ctx.closePath(); return this; } pixelateRoundedRect(position: Point, size: Point, radius: number, fill: boolean): DrawContext { - // using pixelateCircleSection - const topLeft = position.clone(); - const topRight = position.clone().add(new Point(size.x, 0)); - const bottomRight = position.clone().add(size); - const bottomLeft = position.clone().add(new Point(0, size.y)); - this.pixelateCircleSection( - topLeft.clone().add(new Point(radius, radius)), - radius, - Math.PI, - Math.PI * 1.5, - fill - ); - this.pixelateCircleSection( - topRight.clone().add(new Point(-radius, radius)), - radius, - Math.PI * 1.5, - Math.PI * 2, - fill - ); - this.pixelateCircleSection( - bottomRight.clone().add(new Point(-radius, -radius)), - radius, - 0, - Math.PI * 0.5, - fill - ); - this.pixelateCircleSection( - bottomLeft.clone().add(new Point(radius, -radius)), - radius, - Math.PI * 0.5, - Math.PI, - fill - ); - this.pixelateLine(topLeft.clone().add(new Point(radius, 0)), topRight.clone().add(new Point(-radius, 0)), 1); - this.pixelateLine( - topRight.clone().add(new Point(-1, radius)), - bottomRight.clone().add(new Point(-1, -radius)), - 1 - ); - this.pixelateLine( - bottomRight.clone().add(new Point(-radius, -1)), - bottomLeft.clone().add(new Point(radius, -1)), - 1 - ); - this.pixelateLine(bottomLeft.clone().add(new Point(0, -radius)), topLeft.clone().add(new Point(0, radius)), 1); - + this.ctx.beginPath(); + if (fill) { + this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, size.y); + this.ctx.rect(position.x, position.y + radius, size.x, size.y - 2 * radius); + } else { + this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, 1); + this.ctx.rect(position.x + radius, position.y + size.y - 1, size.x - 2 * radius, 1); + this.ctx.rect(position.x, position.y + radius, 1, size.y - 2 * radius); + this.ctx.rect(position.x + size.x - 1, position.y + radius, 1, size.y - 2 * radius); + } + this.ctx.fill(); + this.ctx.closePath(); + this.ctx.beginPath(); + this.pixelateRectCorner(position, radius, 0, fill); + this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y), radius, 1, fill); + this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y + size.y - 1), radius, 2, fill); + this.pixelateRectCorner(new Point(position.x, position.y + size.y - 1), radius, 3, fill); + this.ctx.fill(); + this.ctx.save(); + this.ctx.fillStyle = 'rgba(0,0,0,0)'; + this.ctx.beginPath(); + this.ctx.rect(position.x, position.y, size.x, size.y); + this.ctx.fill(); + this.ctx.restore(); return this; } From a5b5dbbd3120790e35c961c45b3295f452f213e8 Mon Sep 17 00:00:00 2001 From: deadlink Date: Fri, 5 Apr 2024 00:23:13 +0400 Subject: [PATCH 03/10] Fix runded rect draw --- src/core/layers/rectangle.layer.ts | 2 +- src/core/point.ts | 9 ++++ src/draw/draw-context.ts | 84 ++++++++---------------------- 3 files changed, 32 insertions(+), 63 deletions(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index 694ec895..2776017f 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -62,7 +62,7 @@ export class RectangleLayer extends AbstractLayer { radius: { getValue: () => this.radius, setValue: (v: number) => { - this.radius = Math.min(v, Math.round(this.size.x / 2), Math.round(this.size.y / 2)); + this.radius = Math.max(0, Math.min(v, Math.round(this.size.x / 2), Math.round(this.size.y / 2))); this.draw(); }, type: TModifierType.number diff --git a/src/core/point.ts b/src/core/point.ts index 02280ae8..4c25cb08 100644 --- a/src/core/point.ts +++ b/src/core/point.ts @@ -176,6 +176,15 @@ export class Point { this[y] = Math.ceil(this[y]); return this; } + swap(): Point { + const t = this[x]; + this[x] = this[y]; + this[y] = t; + return this; + } + sign(): [number, number] { + return [Math.sign(this[x]), Math.sign(this[y])]; + } distanceTo(p: Point): number { return Math.hypot(this.x - p.x, this.y - p.y); } diff --git a/src/draw/draw-context.ts b/src/draw/draw-context.ts index 5169cb8c..0ae953c6 100644 --- a/src/draw/draw-context.ts +++ b/src/draw/draw-context.ts @@ -126,78 +126,38 @@ export class DrawContext { return this; } - // draw pixelated corner with corner point, radius, and fill in quadrant pixelateRectCorner(point: Point, radius: number, quadrant: number, fill: boolean): DrawContext { - const radiusPoint = new Point(radius); - switch (quadrant) { - case 1: - radiusPoint.multiply(-1, 1); - break; - case 2: - radiusPoint.multiply(-1, -1); - break; - case 3: - radiusPoint.multiply(1, -1); - break; - } - const center = point.clone().add(radiusPoint); - for (let n = 0; n < radius; n++) { - const x = n; - let y = Math.ceil(Math.sqrt(radius * radius - x * x)); - const p1 = center.clone(); - const p2 = center.clone(); - const r = new Point(radius); - switch (quadrant) { - case 0: - p1.add(-x, -y); - p2.add(-y, -x); - r.subtract(p2.x - point.x - 1, radius); - break; - case 1: - p1.add(x, -y); - p2.add(y, -x); - r.subtract(point.x - p2.x - 1, radius); - break; - case 2: - p1.add(x, y); - p2.add(y, x); - r.subtract(point.x - p2.x, radius).multiply(-1); - break; - case 3: - p1.add(-x, y); - p2.add(-y, x); - r.subtract(p2.x - point.x, radius).multiply(-1); - break; - } - this.ctx.rect(p1.x, p1.y, 1, 1); - this.ctx.rect(p2.x, p2.y, 1, 1); - if (fill) { - this.ctx.rect(p1.x, p1.y, 1, r.x); - this.ctx.rect(p2.x, p2.y, r.y, 1); - } + const signs = new Point(quadrant == 0 || quadrant == 3 ? 1 : -1, quadrant == 0 || quadrant == 1 ? 1 : -1); + const center = point.clone().add(new Point(radius).multiply(signs)); + this.ctx.beginPath(); + for (let x = 0; x < radius; x++) { + let y = Math.sqrt(radius * radius - x * x); + let p = center.clone().subtract(new Point(x, y).multiply(signs)).round(); + this.ctx.rect(p.x, p.y, 1, 1); + fill && this.ctx.fillRect(p.x, p.y, 1, signs.y * (radius - Math.abs(p.y - point.y))); + p = center.clone().subtract(new Point(y, x).multiply(signs)).round(); + this.ctx.rect(p.x, p.y, 1, 1); } + this.ctx.fill(); return this; } pixelateRoundedRect(position: Point, size: Point, radius: number, fill: boolean): DrawContext { this.ctx.beginPath(); + + this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, 1); + this.ctx.rect(position.x + radius, position.y + size.y - 1, size.x - 2 * radius, 1); + this.ctx.rect(position.x, position.y + radius, 1, size.y - 2 * radius); + this.ctx.rect(position.x + size.x - 1, position.y + radius, 1, size.y - 2 * radius); if (fill) { - this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, size.y); - this.ctx.rect(position.x, position.y + radius, size.x, size.y - 2 * radius); - } else { - this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, 1); - this.ctx.rect(position.x + radius, position.y + size.y - 1, size.x - 2 * radius, 1); - this.ctx.rect(position.x, position.y + radius, 1, size.y - 2 * radius); - this.ctx.rect(position.x + size.x - 1, position.y + radius, 1, size.y - 2 * radius); + this.ctx.fillRect(position.x + radius, position.y, size.x - 2 * radius, size.y); + this.ctx.fillRect(position.x, position.y + radius, size.x, size.y - 2 * radius); } this.ctx.fill(); - this.ctx.closePath(); - this.ctx.beginPath(); - this.pixelateRectCorner(position, radius, 0, fill); - this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y), radius, 1, fill); - this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y + size.y - 1), radius, 2, fill); - this.pixelateRectCorner(new Point(position.x, position.y + size.y - 1), radius, 3, fill); - this.ctx.fill(); + this.pixelateRectCorner(position, radius + 1, 0, fill); + this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y), radius + 1, 1, fill); + this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y + size.y - 1), radius + 1, 2, fill); + this.pixelateRectCorner(new Point(position.x, position.y + size.y - 1), radius + 1, 3, fill); this.ctx.save(); this.ctx.fillStyle = 'rgba(0,0,0,0)'; this.ctx.beginPath(); From 0c0e82e1e10a399fab97f72b68b32296cb7d79d9 Mon Sep 17 00:00:00 2001 From: deadlink Date: Fri, 5 Apr 2024 13:08:23 +0400 Subject: [PATCH 04/10] code generation for rounded rect --- src/platforms/templates/adafruit/default.pug | 4 ++-- src/platforms/templates/adafruit/inkplate.pug | 4 ++-- src/platforms/templates/flipper/default.pug | 4 ++-- src/platforms/templates/u8g2/c_esp_idf.pug | 4 ++-- src/platforms/templates/u8g2/default.pug | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platforms/templates/adafruit/default.pug b/src/platforms/templates/adafruit/default.pug index ae9ce604..efafd4f3 100644 --- a/src/platforms/templates/adafruit/default.pug +++ b/src/platforms/templates/adafruit/default.pug @@ -19,8 +19,8 @@ each layer in layers when 'line' | @!{layer.uid};!{pad}display.drawLine(!{layer.p1[0]}, !{layer.p1[1]}, !{layer.p2[0]}, !{layer.p2[1]}, !{packColor(layer.color)}); when 'rect' - - var func = layer.fill ? 'fillRect' : 'drawRect' - | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}, !{packColor(layer.color)}); + - var func = (layer.fill ? 'fill' : 'draw') + (layer.radius? 'Round' : '') + 'Rect' + | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}!{layer.radius? `, ${layer.radius}`: ''}, !{packColor(layer.color)}); when 'circle' - var func = layer.fill ? 'fillCircle' : 'drawCircle' | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0] + layer.radius}, !{layer.position[1] + layer.radius}, !{layer.radius}, !{packColor(layer.color)}); diff --git a/src/platforms/templates/adafruit/inkplate.pug b/src/platforms/templates/adafruit/inkplate.pug index ae9ce604..efafd4f3 100644 --- a/src/platforms/templates/adafruit/inkplate.pug +++ b/src/platforms/templates/adafruit/inkplate.pug @@ -19,8 +19,8 @@ each layer in layers when 'line' | @!{layer.uid};!{pad}display.drawLine(!{layer.p1[0]}, !{layer.p1[1]}, !{layer.p2[0]}, !{layer.p2[1]}, !{packColor(layer.color)}); when 'rect' - - var func = layer.fill ? 'fillRect' : 'drawRect' - | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}, !{packColor(layer.color)}); + - var func = (layer.fill ? 'fill' : 'draw') + (layer.radius? 'Round' : '') + 'Rect' + | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}!{layer.radius? `, ${layer.radius}`: ''}, !{packColor(layer.color)}); when 'circle' - var func = layer.fill ? 'fillCircle' : 'drawCircle' | @!{layer.uid};!{pad}display.!{func}(!{layer.position[0] + layer.radius}, !{layer.position[1] + layer.radius}, !{layer.radius}, !{packColor(layer.color)}); diff --git a/src/platforms/templates/flipper/default.pug b/src/platforms/templates/flipper/default.pug index 2b1a970d..6cd49279 100644 --- a/src/platforms/templates/flipper/default.pug +++ b/src/platforms/templates/flipper/default.pug @@ -17,8 +17,8 @@ each layer in layers when 'line' | @!{layer.uid};canvas_draw_line(canvas, !{layer.p1[0]}, !{layer.p1[1]}, !{layer.p2[0]}, !{layer.p2[1]}); when 'rect' - - var func = layer.fill ? 'canvas_draw_box' : 'canvas_draw_frame' - | @!{layer.uid};!{func}(canvas, !{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}); + - var func = `canvas_draw_${layer.radius? 'r_': ''}${layer.fill ? 'box' : 'frame'}`; + | @!{layer.uid};!{func}(canvas, !{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}!{layer.radius? `, ${layer.radius}`: ''}); when 'circle' - var func = layer.fill ? 'canvas_draw_disc' : 'canvas_draw_circle' | @!{layer.uid};!{func}(canvas, !{layer.position[0] + layer.radius}, !{layer.position[1] + layer.radius}, !{layer.radius}); diff --git a/src/platforms/templates/u8g2/c_esp_idf.pug b/src/platforms/templates/u8g2/c_esp_idf.pug index 65b163fd..508a7d79 100644 --- a/src/platforms/templates/u8g2/c_esp_idf.pug +++ b/src/platforms/templates/u8g2/c_esp_idf.pug @@ -32,8 +32,8 @@ each layer in layers when 'line' | @!{layer.uid};!{pad}u8g2_DrawLine(&u8g2, !{layer.p1[0]}, !{layer.p1[1]}, !{layer.p2[0]}, !{layer.p2[1]}); when 'rect' - - var func = layer.fill ? 'DrawBox' : 'DrawFrame' - | @!{layer.uid};!{pad}u8g2_!{func}(&u8g2, !{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}); + - var func = 'Draw' + (layer.radius? 'R': '') + (layer.fill ? 'Box' : 'Frame') + | @!{layer.uid};!{pad}u8g2_!{func}(&u8g2, !{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}!{layer.radius? `, ${layer.radius}`: ''}); when 'circle' - var func = layer.fill ? 'DrawDisc' : 'DrawCircle' | @!{layer.uid};!{pad}u8g2_!{func}(&u8g2, !{layer.position[0] + layer.radius}, !{layer.position[1] + layer.radius}, !{layer.radius}); diff --git a/src/platforms/templates/u8g2/default.pug b/src/platforms/templates/u8g2/default.pug index f6f302dd..ac00b8bb 100644 --- a/src/platforms/templates/u8g2/default.pug +++ b/src/platforms/templates/u8g2/default.pug @@ -32,8 +32,8 @@ each layer in layers when 'line' | @!{layer.uid};!{pad}u8g2.drawLine(@x1:!{layer.p1[0]}, @y1:!{layer.p1[1]}, @x2:!{layer.p2[0]}, @y2:!{layer.p2[1]}); when 'rect' - - var func = layer.fill ? 'drawBox' : 'drawFrame' - | @!{layer.uid};!{pad}u8g2.!{func}(@x:!{layer.position[0]}, @y:!{layer.position[1]}, @w:!{layer.size[0]}, @h:!{layer.size[1]}); + - var func = 'draw' + (layer.radius? 'R': '') + (layer.fill ? 'Box' : 'Frame') + | @!{layer.uid};!{pad}u8g2.!{func}(@x:!{layer.position[0]}, @y:!{layer.position[1]}, @w:!{layer.size[0]}, @h:!{layer.size[1]}!{layer.radius? `, @r:${layer.radius}`: ''}); when 'circle' - var func = layer.fill ? 'drawDisc' : 'drawCircle' | @!{layer.uid};!{pad}u8g2.!{func}(@x:!{layer.position[0] + layer.radius}, @y:!{layer.position[1] + layer.radius}, @r:!{layer.radius}); From a33daa3b847806d533b200db2e64b667ebdb5c3c Mon Sep 17 00:00:00 2001 From: deadlink Date: Fri, 5 Apr 2024 13:11:26 +0400 Subject: [PATCH 05/10] Tests --- .../__snapshots__/adafruit.test.ts.snap | 2 ++ .../__snapshots__/adafruit_mono.test.ts.snap | 2 ++ .../__snapshots__/flipper.test.ts.snap | 2 ++ .../__snapshots__/inkplate.test.ts.snap | 2 ++ src/platforms/__snapshots__/u8g2.test.ts.snap | 6 +++++ src/platforms/layers.mock.ts | 22 +++++++++++++++++++ 6 files changed, 36 insertions(+) diff --git a/src/platforms/__snapshots__/adafruit.test.ts.snap b/src/platforms/__snapshots__/adafruit.test.ts.snap index 3a5f2a46..070a9768 100644 --- a/src/platforms/__snapshots__/adafruit.test.ts.snap +++ b/src/platforms/__snapshots__/adafruit.test.ts.snap @@ -24,5 +24,7 @@ display.setCursor(3, 10); // Unknown layer type ellipse @rrsjcz4oz2ln6jx4y4;display.drawBitmap(0, 0, image_paint_0_bits, 128, 64, 0xF800); +@njsoidnfijoinisn;display.fillRoundRect(10, 4, 12, 19, 3, 0x7E0); +@jnijisniijaojosd;display.drawRoundRect(45, 20, 14, 12, 5, 0x7E0); " `; diff --git a/src/platforms/__snapshots__/adafruit_mono.test.ts.snap b/src/platforms/__snapshots__/adafruit_mono.test.ts.snap index b9a3aac0..867fabd3 100644 --- a/src/platforms/__snapshots__/adafruit_mono.test.ts.snap +++ b/src/platforms/__snapshots__/adafruit_mono.test.ts.snap @@ -24,5 +24,7 @@ display.setCursor(3, 10); // Unknown layer type ellipse @rrsjcz4oz2ln6jx4y4;display.drawBitmap(0, 0, image_paint_0_bits, 128, 64, 1); +@njsoidnfijoinisn;display.fillRoundRect(10, 4, 12, 19, 3, 1); +@jnijisniijaojosd;display.drawRoundRect(45, 20, 14, 12, 5, 1); " `; diff --git a/src/platforms/__snapshots__/flipper.test.ts.snap b/src/platforms/__snapshots__/flipper.test.ts.snap index 775b101e..8f1113ce 100644 --- a/src/platforms/__snapshots__/flipper.test.ts.snap +++ b/src/platforms/__snapshots__/flipper.test.ts.snap @@ -22,5 +22,7 @@ canvas_set_font(canvas, profont22); // Unknown layer type ellipse @rrsjcz4oz2ln6jx4y4;canvas_draw_xbm(canvas, 0, 0, 128, 64, image_1_bits); +@njsoidnfijoinisn;canvas_draw_r_box(canvas, 10, 4, 12, 19, 3); +@jnijisniijaojosd;canvas_draw_r_frame(canvas, 45, 20, 14, 12, 5); " `; diff --git a/src/platforms/__snapshots__/inkplate.test.ts.snap b/src/platforms/__snapshots__/inkplate.test.ts.snap index 5d6f5b6c..ea3b13cc 100644 --- a/src/platforms/__snapshots__/inkplate.test.ts.snap +++ b/src/platforms/__snapshots__/inkplate.test.ts.snap @@ -24,5 +24,7 @@ display.setCursor(3, 10); // Unknown layer type ellipse @rrsjcz4oz2ln6jx4y4;display.drawBitmap(0, 0, image_paint_0_bits, 128, 64, 0); +@njsoidnfijoinisn;display.fillRoundRect(10, 4, 12, 19, 3, 0); +@jnijisniijaojosd;display.drawRoundRect(45, 20, 14, 12, 5, 0); " `; diff --git a/src/platforms/__snapshots__/u8g2.test.ts.snap b/src/platforms/__snapshots__/u8g2.test.ts.snap index ae9300b2..84b7b99f 100644 --- a/src/platforms/__snapshots__/u8g2.test.ts.snap +++ b/src/platforms/__snapshots__/u8g2.test.ts.snap @@ -20,6 +20,8 @@ u8g2.setFont(u8g2_font_profont22_tr); @g7pk1wfbqqkln6jwxby;u8g2.drawFrame(@x:44, @y:29, @w:44, @h:28); @u9uidj9d90fu9sj9jj9;u8g2.drawEllipse(@x:15, @y:36, @rx:7, @ry:7); @rrsjcz4oz2ln6jx4y4;u8g2.drawXBM(@x:0, @y:0, @w:128, @h:64, @image:image_paint_0_bits); +@njsoidnfijoinisn;u8g2.drawRBox(@x:10, @y:4, @w:12, @h:19, @r:3); +@jnijisniijaojosd;u8g2.drawRFrame(@x:45, @y:20, @w:14, @h:12, @r:5); u8g2.sendBuffer();" `; @@ -43,6 +45,8 @@ u8g2_SetFont(&u8g2, u8g2_font_profont22_tr); @g7pk1wfbqqkln6jwxby;u8g2_DrawFrame(&u8g2, 44, 29, 44, 28); @u9uidj9d90fu9sj9jj9;u8g2_DrawEllipse(&u8g2, 15, 36, 7, 7); @rrsjcz4oz2ln6jx4y4;u8g2_DrawXBM(&u8g2, 0, 0, 128, 64, image_paint_0_bits); +@njsoidnfijoinisn;u8g2_DrawRBox(&u8g2, 10, 4, 12, 19, 3); +@jnijisniijaojosd;u8g2_DrawRFrame(&u8g2, 45, 20, 14, 12, 5); u8g2_SendBuffer(&u8g2); " `; @@ -66,5 +70,7 @@ u8g2.setFont(u8g2_font_profont22_tr); @g7pk1wfbqqkln6jwxby;u8g2.drawFrame(@x:44, @y:29, @w:44, @h:28); @u9uidj9d90fu9sj9jj9;u8g2.drawEllipse(@x:15, @y:36, @rx:7, @ry:7); @rrsjcz4oz2ln6jx4y4;u8g2.drawXBM(@x:0, @y:0, @w:128, @h:64, @image:image_paint_0_bits); +@njsoidnfijoinisn;u8g2.drawRBox(@x:10, @y:4, @w:12, @h:19, @r:3); +@jnijisniijaojosd;u8g2.drawRFrame(@x:45, @y:20, @w:14, @h:12, @r:5); u8g2.sendBuffer();" `; diff --git a/src/platforms/layers.mock.ts b/src/platforms/layers.mock.ts index d9fa954f..29ca9628 100644 --- a/src/platforms/layers.mock.ts +++ b/src/platforms/layers.mock.ts @@ -158,6 +158,28 @@ export const layersMock: AbstractLayer[] = [ rx: 7, ry: 7, f: false + }, + { + n: 'box_njsoidnfijoinisn', + t: 'rect', + c: '#00FF00', + r: 3, + f: true, + i: 15, + p: [10, 4], + u: 'njsoidnfijoinisn', + s: [12, 19] + }, + { + n: 'box_jnijisniijaojosd', + t: 'rect', + c: '#00FF00', + r: 5, + f: false, + i: 16, + p: [45, 20], + u: 'jnijisniijaojosd', + s: [14, 12] } ].map((l) => { const type: ELayerType = l.t as any; From 2cf5634323bed0da52faded54f5f4e7d2213f821 Mon Sep 17 00:00:00 2001 From: deadlink Date: Thu, 18 Apr 2024 20:45:31 +0400 Subject: [PATCH 06/10] Fixed rectangle resizing --- src/core/layers/rectangle.layer.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index 2776017f..f83e52b0 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -26,7 +26,6 @@ export class RectangleLayer extends AbstractLayer { getValue: () => this.position.x, setValue: (v: number) => { this.position.x = v; - this.updateBounds(); this.draw(); }, @@ -44,7 +43,7 @@ export class RectangleLayer extends AbstractLayer { w: { getValue: () => this.size.x, setValue: (v: number) => { - this.size.x = v; + this.size.x = Math.max(v, 1); this.updateBounds(); this.draw(); }, @@ -53,7 +52,7 @@ export class RectangleLayer extends AbstractLayer { h: { getValue: () => this.size.y, setValue: (v: number) => { - this.size.y = v; + this.size.y = Math.max(v, 1); this.updateBounds(); this.draw(); }, @@ -106,7 +105,9 @@ export class RectangleLayer extends AbstractLayer { ), move: (offset: Point): void => { this.position = this.editState.position.clone().subtract(0, offset.y); - this.size = new Point(this.editState.size.x - offset.x, this.editState.size.y + offset.y); + this.size = new Point(this.editState.size.x - offset.x, this.editState.size.y + offset.y).max( + new Point(1) + ); } }, { @@ -117,7 +118,10 @@ export class RectangleLayer extends AbstractLayer { new Point(3) ).subtract(1.5, 1.5, 0, 0), move: (offset: Point): void => { - this.size = this.editState.size.clone().subtract(offset); + this.size = this.editState.size + .clone() + .subtract(offset) + .max(new Point(this.radius * 2)); } }, { @@ -131,7 +135,10 @@ export class RectangleLayer extends AbstractLayer { ), move: (offset: Point): void => { this.position = this.editState.position.clone().subtract(offset.x, 0); - this.size = this.editState.size.clone().add(offset.x, -offset.y); + this.size = this.editState.size + .clone() + .add(offset.x, -offset.y) + .max(new Point(this.radius * 2)); } }, { @@ -140,7 +147,10 @@ export class RectangleLayer extends AbstractLayer { new Rect(new Point(this.bounds.x, this.bounds.y), new Point(3)).subtract(1.5, 1.5, 0, 0), move: (offset: Point): void => { this.position = this.editState.position.clone().subtract(offset); - this.size = this.editState.size.clone().add(offset); + this.size = this.editState.size + .clone() + .add(offset) + .max(new Point(this.radius * 2)); } } ]; From 7c970065811a3dfc7591d228df80e9aeb5ef31f4 Mon Sep 17 00:00:00 2001 From: deadlink Date: Fri, 19 Apr 2024 03:34:59 +0400 Subject: [PATCH 07/10] Fix radius limit --- src/core/layers/rectangle.layer.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index f83e52b0..0bb8e127 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -43,7 +43,7 @@ export class RectangleLayer extends AbstractLayer { w: { getValue: () => this.size.x, setValue: (v: number) => { - this.size.x = Math.max(v, 1); + this.size.x = Math.max(v, this.radius * 2 + 2); this.updateBounds(); this.draw(); }, @@ -52,7 +52,7 @@ export class RectangleLayer extends AbstractLayer { h: { getValue: () => this.size.y, setValue: (v: number) => { - this.size.y = Math.max(v, 1); + this.size.y = Math.max(v, this.radius * 2 + 2); this.updateBounds(); this.draw(); }, @@ -61,7 +61,10 @@ export class RectangleLayer extends AbstractLayer { radius: { getValue: () => this.radius, setValue: (v: number) => { - this.radius = Math.max(0, Math.min(v, Math.round(this.size.x / 2), Math.round(this.size.y / 2))); + this.radius = Math.max( + 0, + Math.min(v, Math.round(this.size.x / 2 - 1), Math.round(this.size.y / 2 - 1)) + ); this.draw(); }, type: TModifierType.number @@ -121,7 +124,7 @@ export class RectangleLayer extends AbstractLayer { this.size = this.editState.size .clone() .subtract(offset) - .max(new Point(this.radius * 2)); + .max(new Point(this.radius * 2 + 2)); } }, { @@ -138,7 +141,7 @@ export class RectangleLayer extends AbstractLayer { this.size = this.editState.size .clone() .add(offset.x, -offset.y) - .max(new Point(this.radius * 2)); + .max(new Point(this.radius * 2 + 2)); } }, { @@ -150,7 +153,7 @@ export class RectangleLayer extends AbstractLayer { this.size = this.editState.size .clone() .add(offset) - .max(new Point(this.radius * 2)); + .max(new Point(this.radius * 2 + 2)); } } ]; From 2e9e1cd45b80ae25d0b87ac9cdbd62ebf3b1b18a Mon Sep 17 00:00:00 2001 From: deadlink Date: Mon, 6 May 2024 06:53:06 +0900 Subject: [PATCH 08/10] Fixed edititng rounded rect --- src/core/layers/rectangle.layer.ts | 57 +++++++++++++++++++----------- src/draw/draw-context.ts | 19 +++++----- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/core/layers/rectangle.layer.ts b/src/core/layers/rectangle.layer.ts index 0bb8e127..22d8e334 100644 --- a/src/core/layers/rectangle.layer.ts +++ b/src/core/layers/rectangle.layer.ts @@ -21,6 +21,10 @@ export class RectangleLayer extends AbstractLayer { @mapping('f') public fill: boolean = false; + get minLen(): number { + return this.radius * 2 + 2; + } + modifiers: TLayerModifiers = { x: { getValue: () => this.position.x, @@ -43,7 +47,7 @@ export class RectangleLayer extends AbstractLayer { w: { getValue: () => this.size.x, setValue: (v: number) => { - this.size.x = Math.max(v, this.radius * 2 + 2); + this.size.x = Math.max(v, this.minLen); this.updateBounds(); this.draw(); }, @@ -52,7 +56,7 @@ export class RectangleLayer extends AbstractLayer { h: { getValue: () => this.size.y, setValue: (v: number) => { - this.size.y = Math.max(v, this.radius * 2 + 2); + this.size.y = Math.max(v, this.minLen); this.updateBounds(); this.draw(); }, @@ -107,10 +111,16 @@ export class RectangleLayer extends AbstractLayer { 0 ), move: (offset: Point): void => { - this.position = this.editState.position.clone().subtract(0, offset.y); - this.size = new Point(this.editState.size.x - offset.x, this.editState.size.y + offset.y).max( - new Point(1) - ); + const size = new Point(this.editState.size.x - offset.x, this.editState.size.y + offset.y); + const position = this.editState.position.clone().subtract(0, offset.y); + if (size.x != this.size.x && size.x >= this.minLen) { + this.position.x = position.x; + this.size.x = size.x; + } + if (size.y != this.size.y && size.y >= this.minLen) { + this.position.y = position.y; + this.size.y = size.y; + } } }, { @@ -121,10 +131,7 @@ export class RectangleLayer extends AbstractLayer { new Point(3) ).subtract(1.5, 1.5, 0, 0), move: (offset: Point): void => { - this.size = this.editState.size - .clone() - .subtract(offset) - .max(new Point(this.radius * 2 + 2)); + this.size = this.editState.size.clone().subtract(offset).max(new Point(this.minLen)); } }, { @@ -137,11 +144,16 @@ export class RectangleLayer extends AbstractLayer { 0 ), move: (offset: Point): void => { - this.position = this.editState.position.clone().subtract(offset.x, 0); - this.size = this.editState.size - .clone() - .add(offset.x, -offset.y) - .max(new Point(this.radius * 2 + 2)); + const position = this.editState.position.clone().subtract(offset.x, 0); + const size = this.editState.size.clone().add(offset.x, -offset.y); + if (size.x != this.size.x && size.x >= this.minLen) { + this.position.x = position.x; + this.size.x = size.x; + } + if (size.y != this.size.y && size.y >= this.minLen) { + this.position.y = position.y; + this.size.y = size.y; + } } }, { @@ -149,11 +161,16 @@ export class RectangleLayer extends AbstractLayer { getRect: (): Rect => new Rect(new Point(this.bounds.x, this.bounds.y), new Point(3)).subtract(1.5, 1.5, 0, 0), move: (offset: Point): void => { - this.position = this.editState.position.clone().subtract(offset); - this.size = this.editState.size - .clone() - .add(offset) - .max(new Point(this.radius * 2 + 2)); + const position = this.editState.position.clone().subtract(offset); + const size = this.editState.size.clone().add(offset); + if (size.x != this.size.x && size.x >= this.minLen) { + this.position.x = position.x; + this.size.x = size.x; + } + if (size.y != this.size.y && size.y >= this.minLen) { + this.size.y = size.y; + this.position.y = position.y; + } } } ]; diff --git a/src/draw/draw-context.ts b/src/draw/draw-context.ts index 0ae953c6..33a07169 100644 --- a/src/draw/draw-context.ts +++ b/src/draw/draw-context.ts @@ -129,7 +129,6 @@ export class DrawContext { pixelateRectCorner(point: Point, radius: number, quadrant: number, fill: boolean): DrawContext { const signs = new Point(quadrant == 0 || quadrant == 3 ? 1 : -1, quadrant == 0 || quadrant == 1 ? 1 : -1); const center = point.clone().add(new Point(radius).multiply(signs)); - this.ctx.beginPath(); for (let x = 0; x < radius; x++) { let y = Math.sqrt(radius * radius - x * x); let p = center.clone().subtract(new Point(x, y).multiply(signs)).round(); @@ -138,13 +137,11 @@ export class DrawContext { p = center.clone().subtract(new Point(y, x).multiply(signs)).round(); this.ctx.rect(p.x, p.y, 1, 1); } - this.ctx.fill(); return this; } pixelateRoundedRect(position: Point, size: Point, radius: number, fill: boolean): DrawContext { this.ctx.beginPath(); - this.ctx.rect(position.x + radius, position.y, size.x - 2 * radius, 1); this.ctx.rect(position.x + radius, position.y + size.y - 1, size.x - 2 * radius, 1); this.ctx.rect(position.x, position.y + radius, 1, size.y - 2 * radius); @@ -153,17 +150,21 @@ export class DrawContext { this.ctx.fillRect(position.x + radius, position.y, size.x - 2 * radius, size.y); this.ctx.fillRect(position.x, position.y + radius, size.x, size.y - 2 * radius); } - this.ctx.fill(); this.pixelateRectCorner(position, radius + 1, 0, fill); this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y), radius + 1, 1, fill); this.pixelateRectCorner(new Point(position.x + size.x - 1, position.y + size.y - 1), radius + 1, 2, fill); this.pixelateRectCorner(new Point(position.x, position.y + size.y - 1), radius + 1, 3, fill); - this.ctx.save(); - this.ctx.fillStyle = 'rgba(0,0,0,0)'; - this.ctx.beginPath(); - this.ctx.rect(position.x, position.y, size.x, size.y); this.ctx.fill(); - this.ctx.restore(); + if (fill) { + this.ctx.save(); + this.ctx.fillStyle = 'rgba(0,0,0,0)'; + this.ctx.beginPath(); + this.ctx.arc(position.x + radius, position.y + radius, radius, Math.PI, 1.5 * Math.PI); + this.ctx.arc(position.x + size.x - radius, position.y + radius, radius, 1.5 * Math.PI, 2 * Math.PI); + this.ctx.arc(position.x + size.x - radius, position.y + size.y - radius, radius, 0, 0.5 * Math.PI); + this.ctx.arc(position.x + radius, position.y + size.y - radius, radius, 0.5 * Math.PI, Math.PI); + this.ctx.fill(); + } return this; } From aa84695e25342f26da9967e6d75d34a38b4ac166 Mon Sep 17 00:00:00 2001 From: deadlink Date: Mon, 6 May 2024 07:14:45 +0900 Subject: [PATCH 09/10] TFT espi support --- src/platforms/templates/tft-espi/default.pug | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/templates/tft-espi/default.pug b/src/platforms/templates/tft-espi/default.pug index 3c70e17d..dec81709 100644 --- a/src/platforms/templates/tft-espi/default.pug +++ b/src/platforms/templates/tft-espi/default.pug @@ -19,8 +19,8 @@ each layer in layers when 'line' | @!{layer.uid};!{pad}tft.drawLine(!{layer.p1[0]}, !{layer.p1[1]}, !{layer.p2[0]}, !{layer.p2[1]}, !{packColor(layer.color)}); when 'rect' - - var func = layer.fill ? 'fillRect' : 'drawRect' - | @!{layer.uid};!{pad}tft.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}, !{packColor(layer.color)}); + - var func = (layer.fill ? 'fill' : 'draw') + (layer.radius? 'Round' : '') + 'Rect' + | @!{layer.uid};!{pad}tft.!{func}(!{layer.position[0]}, !{layer.position[1]}, !{layer.size[0]}, !{layer.size[1]}!{layer.radius? `, ${layer.radius}`: ''}, !{packColor(layer.color)}); when 'circle' - var func = layer.fill ? 'fillCircle' : 'drawCircle' | @!{layer.uid};!{pad}tft.!{func}(!{layer.position[0] + layer.radius}, !{layer.position[1] + layer.radius}, !{layer.radius}, !{packColor(layer.color)}); From 407854e25aeaed2176c65869d5fd2fee4ea8f90f Mon Sep 17 00:00:00 2001 From: deadlink Date: Mon, 6 May 2024 07:15:26 +0900 Subject: [PATCH 10/10] Snapshot update --- src/platforms/__snapshots__/tft-espi.test.ts.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platforms/__snapshots__/tft-espi.test.ts.snap b/src/platforms/__snapshots__/tft-espi.test.ts.snap index eadc8bee..4f4e8130 100644 --- a/src/platforms/__snapshots__/tft-espi.test.ts.snap +++ b/src/platforms/__snapshots__/tft-espi.test.ts.snap @@ -17,5 +17,7 @@ tft.setTextColor(0x0); @g7pk1wfbqqkln6jwxby;tft.drawRect(44, 29, 44, 28, 0x7E0); @u9uidj9d90fu9sj9jj9;tft.drawEllipse(@x:15, @y:36, @rx:7, @ry:7, 0x1F); @rrsjcz4oz2ln6jx4y4;tft.drawBitmap(0, 0, image_paint_0_bits, 128, 64, 0xF800); +@njsoidnfijoinisn;tft.fillRoundRect(10, 4, 12, 19, 3, 0x7E0); +@jnijisniijaojosd;tft.drawRoundRect(45, 20, 14, 12, 5, 0x7E0); " `;