From c44b840e8d4ff8a24646e7414708ac799083c3df Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Mon, 27 May 2024 11:48:53 -0700 Subject: [PATCH] Add proper JCanvasLayer type --- src/jcanvas-handles.ts | 34 +++++------ src/jcanvas.d.ts | 50 ++++++++++----- src/jcanvas.ts | 134 ++++++++++++++++++++++------------------- 3 files changed, 122 insertions(+), 96 deletions(-) diff --git a/src/jcanvas-handles.ts b/src/jcanvas-handles.ts index c57493a..a0772f2 100644 --- a/src/jcanvas-handles.ts +++ b/src/jcanvas-handles.ts @@ -19,7 +19,7 @@ $.extend($.jCanvas.defaults, { }); // Determines if the given layer is a rectangular layer -function isRectLayer(layer: JCanvasObject) { +function isRectLayer(layer: JCanvasLayer) { const method = layer._method; return ( method === $.fn.drawRect || @@ -29,7 +29,7 @@ function isRectLayer(layer: JCanvasObject) { } // Determines if the given layer is a rectangular layer -function isPathLayer(layer: JCanvasObject) { +function isPathLayer(layer: JCanvasLayer) { const method = layer._method; return ( method === $.fn.drawLine || @@ -41,7 +41,7 @@ function isPathLayer(layer: JCanvasObject) { // Add a single handle to line path function addPathHandle( $canvas: JQuery, - parent: JCanvasObject, + parent: JCanvasLayer, xProp: string, yProp: string ) { @@ -81,7 +81,7 @@ function addPathHandle( dragcancel: function (layer) { $(this).triggerLayerEvent(layer._parent, "handlecancel"); }, - } as Partial + } as Partial ); $canvas.draw(handle); // Add handle to parent layer's list of handles @@ -91,7 +91,7 @@ function addPathHandle( // Add a single handle to rectangle function addRectHandle( $canvas: JQuery, - parent: JCanvasObject, + parent: JCanvasLayer, px: number, py: number ) { @@ -215,7 +215,7 @@ function addRectHandle( const parent = layer._parent; $(this).triggerLayerEvent(parent, "handlecancel"); }, - } as Partial + } as Partial ); $canvas.draw(handle); // Add handle to parent layer's list of handles @@ -225,7 +225,7 @@ function addRectHandle( // Add all handles to rectangle function addRectHandles( $canvas: JQuery, - parent: JCanvasObject + parent: JCanvasLayer ) { const handlePlacement = parent.handlePlacement; const nonNullWidth = parent.width || 0; @@ -255,7 +255,7 @@ function addRectHandles( } // Update handle guides for rectangular layer -function updateRectGuides(parent: JCanvasObject) { +function updateRectGuides(parent: JCanvasLayer) { const guide = parent._guide; if (guide) { guide.x = parent.x; @@ -269,7 +269,7 @@ function updateRectGuides(parent: JCanvasObject) { // Add handle guides to rectangular layer function addRectGuides( $canvas: JQuery, - parent: JCanvasObject + parent: JCanvasLayer ) { const guideProps = $.extend({}, parent.guide, { layer: true, @@ -287,7 +287,7 @@ function addRectGuides( // Add handles to line path function addPathHandles( $canvas: JQuery, - parent: JCanvasObject + parent: JCanvasLayer ) { for (const key in parent) { if (Object.prototype.hasOwnProperty.call(parent, key)) { @@ -308,7 +308,7 @@ function addPathHandles( } // Update handle guides for line path -function updatePathGuides(parent: JCanvasObject) { +function updatePathGuides(parent: JCanvasLayer) { let handles = parent._handles; const guides = parent._guides; @@ -343,7 +343,7 @@ function updatePathGuides(parent: JCanvasObject) { // Add guides to path layer function addPathGuides( $canvas: JQuery, - parent: JCanvasObject + parent: JCanvasLayer ) { const handles = parent._handles; const guideProps = $.extend({}, parent.guide, { @@ -394,7 +394,7 @@ function addPathGuides( // Update position of handles according to // size and dimensions of rectangular layer -function updateRectHandles(parent: JCanvasObject) { +function updateRectHandles(parent: JCanvasLayer) { const nonNullWidth = parent.width || 0; const nonNullHeight = parent.height || 0; if (parent._handles) { @@ -416,7 +416,7 @@ function updateRectHandles(parent: JCanvasObject) { // Update position of handles according to // coordinates and dimensions of path layer -function updatePathHandles(parent: JCanvasObject) { +function updatePathHandles(parent: JCanvasLayer) { const handles = parent._handles; if (handles) { // Move handles when dragging @@ -430,7 +430,7 @@ function updatePathHandles(parent: JCanvasObject) { } // Add drag handles to all four corners of rectangle layer -function addHandles(parent: JCanvasObject) { +function addHandles(parent: JCanvasLayer) { const $canvas = $(parent.canvas); // If parent's list of handles doesn't exist @@ -449,7 +449,7 @@ function addHandles(parent: JCanvasObject) { } // Remove handles if handle property was removed -function removeHandles(layer: JCanvasObject) { +function removeHandles(layer: JCanvasLayer) { const $canvas = $(layer.canvas); if (layer._handles) { // Remove handles from layer @@ -493,7 +493,7 @@ $.extend($.jCanvas.eventHooks, { } }, // Update handle positions when changing parent layer's dimensions - change: function (layer, props: Partial) { + change: function (layer, props: Partial) { if (props.handle || objectContainsPathCoords(props)) { // Add handles if handle property was added removeHandles(layer); diff --git a/src/jcanvas.d.ts b/src/jcanvas.d.ts index a2733c3..52f9ea2 100644 --- a/src/jcanvas.d.ts +++ b/src/jcanvas.d.ts @@ -39,7 +39,7 @@ interface JCanvasPx { } type JCanvasLayerCallbackWithProps = ( - layer: JCanvasObject, + layer: JCanvasLayer, props?: Partial ) => void; @@ -75,12 +75,12 @@ interface JQueryEventWithFix extends JQuery.EventExtensions { fix: (event: Event) => Event; } -type JCanvasLayerId = JCanvasObject | string | number | RegExp | undefined; -type jCanvasLayerGroupId = JCanvasObject[] | string | RegExp; -type JCanvasLayerCallback = (layer: JCanvasObject) => void; -type JCanvasGetLayersCallback = (layer: JCanvasObject) => any; +type JCanvasLayerId = JCanvasLayer | string | number | RegExp | undefined; +type jCanvasLayerGroupId = JCanvasLayer[] | string | RegExp; +type JCanvasLayerCallback = (layer: JCanvasLayer) => void; +type JCanvasGetLayersCallback = (layer: JCanvasLayer) => any; type JCanvasStyleFunction = ( - layer: JCanvasObject + layer: JCanvasLayer ) => string | CanvasGradient | CanvasPattern; type JCanvasObjectFunction = { @@ -88,6 +88,19 @@ type JCanvasObjectFunction = { (this: JCanvasObject, args?: Partial): JCanvasObject; }; +type JCanvasLayerFunction = { + new ( + this: JCanvasLayer, + canvas: HTMLCanvasElement, + params: JCanvasObject + ): JCanvasLayer; + ( + this: JCanvasLayer, + canvas: HTMLCanvasElement, + params: JCanvasObject + ): JCanvasLayer; +}; + interface JQueryStatic { jCanvas: JCanvas; jCanvasObject: JCanvasObjectFunction; @@ -96,18 +109,18 @@ interface JQueryStatic { interface JQuery { getEventHooks(): JCanvasEventHooks; setEventHooks(eventHooks: JCanvasEventHooks): JQuery; - getLayers(callback?: JCanvasGetLayersCallback): JCanvasObject[]; - getLayer(layerId: JCanvasLayerId): JCanvasObject | undefined; - getLayerGroup(groupId: jCanvasLayerGroupId): JCanvasObject[] | undefined; + getLayers(callback?: JCanvasGetLayersCallback): JCanvasLayer[]; + getLayer(layerId: JCanvasLayerId): JCanvasLayer | undefined; + getLayerGroup(groupId: jCanvasLayerGroupId): JCanvasLayer[] | undefined; getLayerIndex(layerId: JCanvasLayerId): number; setLayer(layerId: JCanvasLayerId, props: Partial): JQuery; setLayers( - props: Partial, + props: Partial, callback: JCanvasGetLayersCallback ): JQuery; setLayerGroup( groupId: jCanvasLayerGroupId, - props: Partial + props: Partial ): JQuery; moveLayer(layerId: JCanvasLayerId, index: number): JQuery; removeLayer(layerId: JCanvasLayerId): JQuery; @@ -277,8 +290,8 @@ interface JCanvasDefaults { dragstop?: JCanvasLayerCallback; drag?: JCanvasLayerCallback; dragcancel?: JCanvasLayerCallback; - updateDragX?: (layer: JCanvasObject, newX: number) => number; - updateDragY?: (layer: JCanvasObject, newY: number) => number; + updateDragX?: (layer: JCanvasLayer, newX: number) => number; + updateDragY?: (layer: JCanvasLayer, newY: number) => number; pointerdown?: JCanvasLayerCallback; pointerup?: JCanvasLayerCallback; pointermove?: JCanvasLayerCallback; @@ -288,7 +301,7 @@ interface JCanvasDefaults { change?: JCanvasLayerCallbackWithProps; move?: JCanvasLayerCallback; animatestart?: JCanvasLayerCallback; - animate?: (layer: JCanvasObject, fx: JQuery.Tween) => void; + animate?: (layer: JCanvasLayer, fx: JQuery.Tween) => void; animateend?: JCanvasLayerCallback; stop?: JCanvasLayerCallback; delay?: JCanvasLayerCallback; @@ -305,14 +318,19 @@ interface JCanvasDefaults { interface JCanvasObject extends JCanvasDefaults {} +interface JCanvasLayer extends JCanvasObject { + canvas: NonNullable; + _layer?: true; +} + interface JCanvasPropHooks { [key: string]: JQuery.PropHook; } -type NumberProperties = { +type JCanvasNumberParams = { [K in keyof JCanvasObject]: JCanvasObject[K] extends number ? K : never; }; type JCanvasAnimatableProps = { - [K in keyof NumberProperties]: NumberProperties[K] | number | string; + [K in keyof JCanvasNumberParams]: NumberProperties[K] | number | string; }; diff --git a/src/jcanvas.ts b/src/jcanvas.ts index 952677f..756b676 100644 --- a/src/jcanvas.ts +++ b/src/jcanvas.ts @@ -210,14 +210,22 @@ class jCanvasDefaults implements JCanvasDefaults { const defaults = new jCanvasDefaults(); // Constructor for creating objects that inherit from jCanvas preferences and defaults -const jCanvasObject: JCanvasObjectFunction = function jCanvasObject( - this: JCanvasObject, - args?: Partial -): JCanvasObject { +const jCanvasObject: JCanvasObjectFunction = function jCanvasObject(args) { return extendObject(this, args); } as JCanvasObjectFunction; jCanvasObject.prototype = defaults; +// Constructor for creating a fully-qualified jCanvas layer +const jCanvasLayer: JCanvasLayerFunction = function jCanvasLayer( + canvas, + params +) { + Object.assign(this, params, { + canvas, + _layer: true, + }); +} as JCanvasLayerFunction; + /* Internal helper methods */ // Determines if the given operand is a string @@ -568,24 +576,24 @@ class JCanvasInternalData { // The associated canvas element canvas: HTMLCanvasElement; // Layers array - layers: JCanvasObject[] = []; + layers: JCanvasLayer[] = []; // Layer maps layer: { - names: Record; - groups: Record; + names: Record; + groups: Record; } = { names: {}, groups: {}, }; eventHooks = {}; // All layers that intersect with the event coordinates (regardless of visibility) - intersecting: JCanvasObject[] = []; + intersecting: JCanvasLayer[] = []; // The topmost layer whose area contains the event coordinates - lastIntersected: JCanvasObject | null = null; + lastIntersected: JCanvasLayer | null = null; cursor: string; // Properties for the current drag event drag = { - layer: null as JCanvasObject | null, + layer: null as JCanvasLayer | null, dragging: false, }; // Data for the current event @@ -607,7 +615,7 @@ class JCanvasInternalData { // Whether a layer is being animated or not animating = false; // The layer currently being animated - animated: JCanvasObject | null = null; + animated: JCanvasLayer | null = null; // The device pixel ratio pixelRatio = 1; // Whether pixel ratio transformations have been applied @@ -651,7 +659,7 @@ function _getCanvasData(canvas: HTMLCanvasElement) { function _addLayerEvents( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject + layer: JCanvasLayer ) { // Determine which jCanvas events need to be bound to this layer for (const eventName in jCanvas.events) { @@ -699,7 +707,7 @@ function _addLayerEvents( function _addLayerEvent( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject, + layer: JCanvasLayer, eventName: string ) { // Use touch events if appropriate @@ -715,7 +723,7 @@ function _addLayerEvent( function _addExplicitLayerEvent( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject, + layer: JCanvasLayer, eventName: string ) { _addLayerEvent($canvas, data, layer, eventName); @@ -732,7 +740,7 @@ function _addExplicitLayerEvent( function _enableDrag( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject + layer: JCanvasLayer ) { // Only make layer draggable if necessary if (layer.draggable || layer.cursors) { @@ -754,7 +762,7 @@ function _enableDrag( // Update a layer property map if property is changed function _updateLayerName( data: JCanvasInternalData, - layer: JCanvasObject, + layer: JCanvasLayer, props?: Partial ) { const nameMap = data.layer.names; @@ -782,7 +790,7 @@ function _updateLayerName( // Create or update the data map for the given layer and group type function _updateLayerGroups( data: JCanvasInternalData, - layer: JCanvasObject, + layer: JCanvasLayer, props?: Partial ) { const groupMap = data.layer.groups; @@ -870,7 +878,7 @@ $.fn.setEventHooks = function setEventHooks(eventHooks) { // Get jCanvas layers array $.fn.getLayers = function getLayers(callback) { const $canvases = this; - let matching: JCanvasObject[] = []; + let matching: JCanvasLayer[] = []; if ($canvases.length !== 0) { const canvas = $canvases[0]; @@ -1284,7 +1292,7 @@ $.fn.removeLayerFromGroup = function removeLayerFromGroup(layerId, groupName) { // Get topmost layer that intersects with event coordinates function _getIntersectingLayer(data: JCanvasInternalData) { - let layer: JCanvasObject | null, mask: JCanvasObject, m; + let layer: JCanvasLayer | null, mask: JCanvasObject, m; // Store the topmost layer layer = null; @@ -1330,7 +1338,7 @@ function _getIntersectingLayer(data: JCanvasInternalData) { function _drawLayer( $canvas: JQuery, ctx: CanvasRenderingContext2D, - layer: JCanvasObject, + layer: JCanvasLayer, nextLayerIndex?: number ) { if (layer && layer.visible && layer._method) { @@ -1353,7 +1361,7 @@ function _handleLayerDrag( eventType: string ) { const drag = data.drag; - const layer = drag.layer as JCanvasObject; + const layer = drag.layer as JCanvasLayer; const dragGroups = (layer && layer.dragGroups) || []; const layers = data.layers; @@ -1454,7 +1462,7 @@ function _handleLayerDrag( // Set cursor on canvas function _setCursor( $canvas: JQuery, - layer: JCanvasObject, + layer: JCanvasLayer, eventType: string ) { let cursor; @@ -1484,9 +1492,9 @@ function _resetCursor( // Run the given event callback with the given arguments function _runEventCallback( $canvas: JQuery, - layer: JCanvasObject, + layer: JCanvasLayer, eventType: string, - callbacks: Record void>, + callbacks: Record void>, arg: any ) { // Prevent callback from firing recursively @@ -1501,7 +1509,7 @@ function _runEventCallback( } // Determine if the given layer can "legally" fire the given event -function _layerCanFireEvent(layer: JCanvasObject, eventType: string) { +function _layerCanFireEvent(layer: JCanvasLayer, eventType: string) { // If events are disable and if // layer is tangible or event is not tangible return ( @@ -1514,7 +1522,7 @@ function _layerCanFireEvent(layer: JCanvasObject, eventType: string) { function _triggerLayerEvent( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject, + layer: JCanvasLayer, eventType: string, arg?: any ) { @@ -1754,8 +1762,10 @@ function _addLayer( params: JCanvasObject, args?: Partial, method?: (args: JCanvasObject) => JQuery -) { - let layer = params._layer ? (args as JCanvasObject) : params; +): JCanvasLayer | null { + const layer: JCanvasObject | JCanvasLayer = params._layer + ? (args as JCanvasLayer) + : params; // Store arguments object for later use params._args = args; @@ -1795,66 +1805,67 @@ function _addLayer( _coerceNumericProps(params); // Ensure layers are unique across canvases by cloning them - layer = new jCanvasObject(params); - layer.canvas = canvas; + const newLayer = new jCanvasLayer(canvas, params); + newLayer.canvas = canvas; // Indicate that this is a layer for future checks - layer.layer = true; - layer._layer = true; - layer._running = {}; + newLayer.layer = true; + newLayer._layer = true; + newLayer._running = {}; // If layer stores user-defined data - if (layer.data !== null) { + if (newLayer.data !== null) { // Clone object - layer.data = extendObject({}, layer.data); + newLayer.data = extendObject({}, newLayer.data); } else { // Otherwise, create data object - layer.data = {}; + newLayer.data = {}; } // If layer stores a list of associated groups - if (layer.groups) { + if (newLayer.groups) { // Clone list - layer.groups = layer.groups.slice(0); + newLayer.groups = newLayer.groups.slice(0); } else { // Otherwise, create empty list - layer.groups = []; + newLayer.groups = []; } // Update layer group maps - _updateLayerName(data, layer); - _updateLayerGroups(data, layer); + _updateLayerName(data, newLayer); + _updateLayerGroups(data, newLayer); // Check for any associated jCanvas events and enable them - _addLayerEvents($canvas, data, layer); + _addLayerEvents($canvas, data, newLayer); // Optionally enable drag-and-drop support and cursor support - _enableDrag($canvas, data, layer); + _enableDrag($canvas, data, newLayer); // Copy _event property to parameters object - params._event = layer._event; + params._event = newLayer._event; // Calculate width/height for text layers - if (layer._method === $.fn.drawText) { - $canvas.measureText(layer); + if (newLayer._method === $.fn.drawText) { + $canvas.measureText(newLayer); } // Add layer to end of array if no index is specified - if (layer.index === null) { - layer.index = layers.length; + if (newLayer.index === null) { + newLayer.index = layers.length; } // Add layer to layers array at specified index - layers.splice(layer.index, 0, layer); + layers.splice(newLayer.index, 0, newLayer); // Store layer on parameters object - params._args = layer; + params._args = newLayer; // Trigger an 'add' event - _triggerLayerEvent($canvas, data, layer, "add"); + _triggerLayerEvent($canvas, data, newLayer, "add"); + return newLayer; } } else if (!params.layer) { _coerceNumericProps(params); } - return layer; + return null; } // Add a jCanvas layer @@ -1903,7 +1914,7 @@ function _hideProps(obj: Partial, reset?: boolean) { // Evaluate property values that are functions function _parseEndValues( canvas: HTMLCanvasElement, - layer: JCanvasObject, + layer: JCanvasLayer, endValues: Record ) { // Loop through all properties in map of end values @@ -1939,7 +1950,7 @@ function _parseEndValues( } // Remove sub-property aliases from layer object -function _removeSubPropAliases(layer: JCanvasObject) { +function _removeSubPropAliases(layer: JCanvasLayer) { for (const propName in layer) { if (Object.prototype.hasOwnProperty.call(layer, propName)) { if (propName.indexOf(".") !== -1) { @@ -2049,7 +2060,7 @@ $.fn.animateLayer = function animateLayer(...args) { function complete( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject + layer: JCanvasLayer ) { return function () { _showProps(layer); @@ -2080,7 +2091,7 @@ $.fn.animateLayer = function animateLayer(...args) { function step( $canvas: JQuery, data: JCanvasInternalData, - layer: JCanvasObject + layer: JCanvasLayer ) { return function (now: any, fx: any) { let parts, @@ -4007,11 +4018,8 @@ $.fn.measureText = function measureText(args) { const $canvases = this; // Attempt to retrieve layer - let params = $canvases.getLayer(args); - // If layer does not exist or if returned object is not a jCanvas layer - if (!params || (params && !params._layer)) { - params = new jCanvasObject(args as JCanvasObject); - } + const params = + $canvases.getLayer(args) || new jCanvasObject(args as JCanvasObject); const canvas = $canvases[0]; if (!_isCanvas(canvas)) { @@ -4050,7 +4058,7 @@ $.fn.drawImage = function drawImage(args) { ctx: CanvasRenderingContext2D, data: JCanvasInternalData, params: JCanvasObject, - layer: JCanvasObject | undefined + layer: JCanvasLayer | null ) { if (!img) { return; @@ -4166,7 +4174,7 @@ $.fn.drawImage = function drawImage(args) { ctx: CanvasRenderingContext2D, data: JCanvasInternalData, params: JCanvasObject, - layer: JCanvasObject | undefined + layer: JCanvasLayer | null ) { return function () { const $canvas = $(canvas);