UNPKG

scriptable-testlab

Version:

A lightweight, efficient tool designed to manage and update scripts for Scriptable.

434 lines (389 loc) 9.96 kB
import {AbsDrawContext} from 'scriptable-abstract'; import {MockImage} from '../media/image'; import {MockSize} from './size'; interface DrawOperation { type: 'image' | 'path' | 'rect' | 'ellipse' | 'text'; data: any; } interface DrawContextState { size: Size; respectScreenScale: boolean; opaque: boolean; currentPath?: Path; fillColor?: Color; strokeColor?: Color; lineWidth: number; canvas: MockImage; font?: Font; textColor?: Color; textAlignment: 'left' | 'center' | 'right'; operations: DrawOperation[]; } const DEFAULT_STATE: DrawContextState = { size: new MockSize(0, 0) as unknown as Size, respectScreenScale: true, opaque: false, lineWidth: 1, canvas: new MockImage() as unknown as MockImage, textAlignment: 'left', operations: [], }; /** * Mock implementation of Scriptable's DrawContext. * Provides a context for drawing shapes, text and images. * @implements DrawContext */ export class MockDrawContext extends AbsDrawContext<DrawContextState> { constructor() { super(DEFAULT_STATE); } /** * Creates a new drawing context with the specified size. */ static create(width: number, height: number): DrawContext { const context = new MockDrawContext(); context.size = new MockSize(width, height) as unknown as Size; return context; } get size(): Size { return this.state.size; } set size(value: Size) { this.setState({size: value}); } get respectScreenScale(): boolean { return this.state.respectScreenScale; } set respectScreenScale(value: boolean) { this.setState({respectScreenScale: value}); } get opaque(): boolean { return this.state.opaque; } set opaque(value: boolean) { this.setState({opaque: value}); } /** * Gets the image that was drawn in the context. */ getImage(): Image { return this.state.canvas; } /** * Draws an image in the specified rect. */ drawImageInRect(image: Image, rect: Rect): void { this.state.operations.push({ type: 'image', data: {image, rect, mode: 'rect'}, }); this.setState({ canvas: image as MockImage, operations: this.state.operations, }); } /** * Draws an image at the specified point. */ drawImageAtPoint(image: Image, point: Point): void { this.state.operations.push({ type: 'image', data: {image, point, mode: 'point'}, }); this.setState({ canvas: image as MockImage, operations: this.state.operations, }); } /** * Sets the fill color used when filling paths and shapes. */ setFillColor(color: Color): void { this.setState({fillColor: color}); } /** * Sets the stroke color used when stroking paths and shapes. */ setStrokeColor(color: Color): void { this.setState({strokeColor: color}); } /** * Sets the width of lines when stroking paths and shapes. */ setLineWidth(width: number): void { if (width < 0) { throw new Error('Line width must be non-negative'); } this.setState({lineWidth: width}); } /** * Begins a new path. */ beginPath(): void { // In mock implementation, we don't create actual paths this.setState({currentPath: undefined}); } /** * Moves to a point in the current path. */ moveToPoint(point: Point): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: {type: 'moveTo', point}, }); this.setState({operations: this.state.operations}); } /** * Adds a line to a point in the current path. */ addLineToPoint(point: Point): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: {type: 'lineTo', point}, }); this.setState({operations: this.state.operations}); } /** * Adds a curve to the current path. */ addCurveToPoint(point: Point, control1: Point, control2: Point): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: {type: 'curveTo', point, control1, control2}, }); this.setState({operations: this.state.operations}); } /** * Adds a quadratic curve to the current path. */ addQuadCurveToPoint(point: Point, control: Point): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: {type: 'quadCurveTo', point, control}, }); this.setState({operations: this.state.operations}); } /** * Closes the current path. */ closePath(): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: {type: 'close'}, }); this.setState({operations: this.state.operations}); } /** * Strokes the current path. */ strokePath(): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: { type: 'stroke', color: this.state.strokeColor, lineWidth: this.state.lineWidth, }, }); this.setState({operations: this.state.operations}); } /** * Fills the current path. */ fillPath(): void { // Store operation for tracking this.state.operations.push({ type: 'path', data: { type: 'fill', color: this.state.fillColor, }, }); this.setState({operations: this.state.operations}); } /** * Fills and strokes the current path. */ fillAndStrokePath(): void { this.fillPath(); this.strokePath(); } /** * Adds a rectangle to the current path. */ addRect(rect: Rect): void { const {x, y, width, height} = rect; this.moveToPoint({x, y}); this.addLineToPoint({x: x + width, y}); this.addLineToPoint({x: x + width, y: y + height}); this.addLineToPoint({x, y: y + height}); this.closePath(); } /** * Adds an ellipse to the current path. */ addEllipseInRect(rect: Rect): void { // Approximate ellipse with Bezier curves const {x, y, width, height} = rect; const kappa = 0.5522848; const ox = (width / 2) * kappa; const oy = (height / 2) * kappa; const xe = x + width; const ye = y + height; const xm = x + width / 2; const ym = y + height / 2; this.moveToPoint({x, y: ym}); this.addCurveToPoint({x: xm, y}, {x, y: y + oy}, {x: xm - ox, y}); this.addCurveToPoint({x: xe, y: ym}, {x: xm + ox, y}, {x: xe, y: ym - oy}); this.addCurveToPoint({x: xm, y: ye}, {x: xe, y: ym + oy}, {x: xm + ox, y: ye}); this.addCurveToPoint({x, y: ym}, {x: xm - ox, y: ye}, {x, y: ym + oy}); } /** * Fills a rectangle. */ fill(rect: Rect): void { this.fillRect(rect); } /** * Fills a rectangle. */ fillRect(rect: Rect): void { this.state.operations.push({ type: 'rect', data: {rect, mode: 'fill', color: this.state.fillColor}, }); this.setState({operations: this.state.operations}); } /** * Fills an ellipse. */ fillEllipse(rect: Rect): void { this.state.operations.push({ type: 'ellipse', data: {rect, mode: 'fill', color: this.state.fillColor}, }); this.setState({operations: this.state.operations}); } /** * Strokes a rectangle. */ stroke(rect: Rect): void { this.strokeRect(rect); } /** * Strokes a rectangle. */ strokeRect(rect: Rect): void { this.state.operations.push({ type: 'rect', data: { rect, mode: 'stroke', color: this.state.strokeColor, lineWidth: this.state.lineWidth, }, }); this.setState({operations: this.state.operations}); } /** * Strokes an ellipse. */ strokeEllipse(rect: Rect): void { this.state.operations.push({ type: 'ellipse', data: { rect, mode: 'stroke', color: this.state.strokeColor, lineWidth: this.state.lineWidth, }, }); this.setState({operations: this.state.operations}); } /** * Adds a path to the context. */ addPath(path: Path): void { this.setState({currentPath: path}); } /** * Draws text at a position. */ drawText(text: string, pos: Point): void { if (!this.state.textColor) { throw new Error('Text color must be set before drawing text'); } if (!this.state.font) { throw new Error('Font must be set before drawing text'); } this.state.operations.push({ type: 'text', data: { text, pos, color: this.state.textColor, font: this.state.font, alignment: this.state.textAlignment, }, }); this.setState({operations: this.state.operations}); } /** * Draws text in a rectangle. */ drawTextInRect(text: string, rect: Rect): void { if (!this.state.textColor) { throw new Error('Text color must be set before drawing text'); } if (!this.state.font) { throw new Error('Font must be set before drawing text'); } this.state.operations.push({ type: 'text', data: { text, rect, color: this.state.textColor, font: this.state.font, alignment: this.state.textAlignment, }, }); this.setState({operations: this.state.operations}); } /** * Sets the font to use when drawing text. */ setFont(font: Font): void { this.setState({font}); } /** * Sets the text color used when drawing text. */ setTextColor(color: Color): void { this.setState({textColor: color}); } /** * Specifies that texts should be left aligned. */ setTextAlignedLeft(): void { this.setState({textAlignment: 'left'}); } /** * Specifies that texts should be center aligned. */ setTextAlignedCenter(): void { this.setState({textAlignment: 'center'}); } /** * Specifies that texts should be right aligned. */ setTextAlignedRight(): void { this.setState({textAlignment: 'right'}); } }