UNPKG

scrawl-canvas

Version:

Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun

506 lines (385 loc) 18.8 kB
// # Display shape mixin // This mixin defines additional attributes and functions for Stack and Canvas artefacts, in particular adding hooks for functions that will be automatically invoked when the artefact's dimensions update. // #### Imports import { isa_fn, isa_number, mergeOver, pushUnique, λnull, Ωempty } from '../helper/utilities.js'; // Shared constants import { _entries, RECTANGLE, ZERO_STR } from '../helper/shared-vars.js'; // Local constants const BANNER = 'banner', LANDSCAPE = 'landscape', LARGER = 'larger', LARGEST = 'largest', PORTRAIT = 'portrait', REGULAR = 'regular', SKYSCRAPER = 'skyscraper', SMALLER = 'smaller', SMALLEST = 'smallest'; // #### Export function export default function (P = Ωempty) { // #### Shared attributes const defaultAttributes = { // Scrawl-canvas recognises five shapes, separated by four breakpoints: // + `banner` // + `landscape` // + `rectangle` // + `portrait` // + `skyscraper` // // The values assigned to the breakpoints are Float numbers for the displayed Canvas element's width/height ratio - the value `3` represents the case where the width value is three times __more__ than the height value, while `0.35` represents a width (roughly) 3 times __less__ than the height. // // We can set a Canvas artefact's breakpoints in one go using the dedicated `setDisplayShapeBreakpoints()` function, as below. Alternatively we can use the regular `set()` function, supplying the attributes `breakToBanner`, `breakToLandscape`, `breakToPortrait` and `breakToSkyscraper` as required. The values given here are the default values for Canvas artefacts. breakToBanner: 3, breakToLandscape: 1.5, breakToPortrait: 0.65, breakToSkyscraper: 0.35, actionBannerShape: null, actionLandscapeShape: null, actionRectangleShape: null, actionPortraitShape: null, actionSkyscraperShape: null, // Scrawl-canvas also recognises five areas, again separated by four breakpoints: // + `smallest` // + `smaller` // + `regular` // + `larger` // + `largest` // // The values assigned to the breakpoints are Float numbers for the displayed Canvas element's pixel area (`width * height`). // + Useful for dynamic Cells - for example Canvas base Cells, where the canvas `baseMatchesCanvasDimensions` flag has been set to `true`; in these situations we can use the area breakpoints to adjust entity dimensions and scales, and text font sizes, to better match the changed environment. // + The other option - to set the base Cell's dimensions to known, static values and set the canvas's `fit` attribute - suffers from image degredation when the canvas and its base cell's dimensions are excessively different. breakToSmallest: 20000, breakToSmaller: 80000, breakToLarger: 180000, breakToLargest: 320000, actionSmallestArea: null, actionSmallerArea: null, actionRegularArea: null, actionLargerArea: null, actionLargestArea: null, }; P.defs = mergeOver(P.defs, defaultAttributes); // #### Packet management P.packetFunctions = pushUnique(P.packetFunctions, ['actionBannerShape', 'actionLandscapeShape', 'actionRectangleShape', 'actionPortraitShape', 'actionSkyscraperShape', 'actionSmallestArea', 'actionSmallerArea', 'actionRegularArea', 'actionLargerArea', 'actionLargestArea']); // #### Clone management // No additional clone functionality defined here // #### Kill management // No additional kill functionality defined here // #### Get, Set, deltaSet const G = P.getters, S = P.setters; // Get __displayShape__ - returns the current display shape for the Canvas or Stack artefact. Returns a string whose value can be one of `banner`, `landscape`, `rectangle`, `portrait`, or `skyscraper`. G.displayShape = function () { return this.currentDisplayShape; }; // Get __displayShapeBreakpoints__ - returns an object with the current Number values for each of the breakpoint attributes. G.displayShapeBreakpoints = function () { return { breakToBanner: this.breakToBanner, breakToLandscape: this.breakToLandscape, breakToPortrait: this.breakToPortrait, breakToSkyscraper: this.breakToSkyscraper, breakToSmallest: this.breakToSmallest, breakToSmaller: this.breakToSmaller, breakToLarger: this.breakToLarger, breakToLargest: this.breakToLargest, }; }; // Set __displayShapeBreakpoints__ - breakpoints can be set individually, or alternatively they can be supplied in an object keyed to this attribute S.displayShapeBreakpoints = function (items = Ωempty) { for (const [key, val] of _entries(items)) { if (isa_number(val)) { switch (key) { case 'breakToBanner' : this.breakToBanner = val; break; case 'breakToLandscape' : this.breakToLandscape = val; break; case 'breakToPortrait' : this.breakToPortrait = val; break; case 'breakToSkyscraper' : this.breakToSkyscraper = val; break; case 'breakToSmallest' : this.breakToSmallest = val; break; case 'breakToSmaller' : this.breakToSmaller = val; break; case 'breakToLarger' : this.breakToLarger = val; break; case 'breakToLargest' : this.breakToLargest = val; break; } } } this.dirtyDisplayShape = true; this.dirtyDisplayArea = true; }; // `setDisplayShapeBreakpoints` - an alternative mechanism to set breakpoints beyond the normal `set` function P.setDisplayShapeBreakpoints = S.displayShapeBreakpoints; // Set __breakToBanner__ - the breakpoint between `landscape` and `banner` display shapes; value will generally be a number greater than `1` S.breakToBanner = function (item) { if (isa_number(item)) this.breakToBanner = item; this.dirtyDisplayShape = true; }; // Set __breakToLandscape__ - the breakpoint between `landscape` and `rectangle` display shapes; value will generally be a number greater than `1` S.breakToLandscape = function (item) { if (isa_number(item)) this.breakToLandscape = item; this.dirtyDisplayShape = true; }; // Set __breakToPortrait__ - the breakpoint between `portrait` and `rectangle` display shapes; value will generally be a number greater than `0` and less than `1` S.breakToPortrait = function (item) { if (isa_number(item)) this.breakToPortrait = item; this.dirtyDisplayShape = true; }; // Set __breakToSkyscraper__ - the breakpoint between `portrait` and `skyscraper` display shapes; value will generally be a number greater than `0` and less than `1` S.breakToSkyscraper = function (item) { if (isa_number(item)) this.breakToSkyscraper = item; this.dirtyDisplayShape = true; }; // Set __breakToSmallest__ - the breakpoint between `smaller` and `smallest` display shapes S.breakToSmallest = function (item) { if (isa_number(item)) this.breakToSmallest = item; this.dirtyDisplayArea = true; }; // Set __breakToSmaller__ - the breakpoint between `regular` and `smaller` display shapes S.breakToSmaller = function (item) { if (isa_number(item)) this.breakToSmaller = item; this.dirtyDisplayArea = true; }; // Set __breakToLarger__ - the breakpoint between `regular` and `larger` display shapes S.breakToLarger = function (item) { if (isa_number(item)) this.breakToLarger = item; this.dirtyDisplayArea = true; }; // Set __breakToLargest__ - the breakpoint between `larger` and `largest` display shapes S.breakToLargest = function (item) { if (isa_number(item)) this.breakToLargest = item; this.dirtyDisplayArea = true; }; // Each display shape has an associated hook function (by default a function that does nothing) which Scrawl-canvas will run each time it detects that the Canvas display shape has changed to that shape. We can replace these null-functions with our own; this allows us to configure the scene/animation to accommodate different display shapes, thus making the code reusable in a range of different web page environments. // // We can set/update these functions at any time using the normal `set()` function. We can also set/update the functions using dedicated `setAction???Shape()` functions: // Set __actionBannerShape__ - must be a Function S.actionBannerShape = function (item) { if (isa_fn(item)) this.actionBannerShape = item; this.dirtyDisplayShape = true; }; // `setActionBannerShape` - an alternative mechanism to set the __actionBannerShape__ function, beyond the normal `set` functionality P.setActionBannerShape = S.actionBannerShape; // Set __actionLandscapeShape__ - must be a Function S.actionLandscapeShape = function (item) { if (isa_fn(item)) this.actionLandscapeShape = item; this.dirtyDisplayShape = true; }; // `setActionLandscapeShape` - an alternative mechanism to set the __actionLandscapeShape__ function, beyond the normal `set` functionality P.setActionLandscapeShape = S.actionLandscapeShape; // Set __actionRectangleShape__ - must be a Function S.actionRectangleShape = function (item) { if (isa_fn(item)) this.actionRectangleShape = item; this.dirtyDisplayShape = true; }; // `setActionRectangleShape` - an alternative mechanism to set the __actionRectangleShape__ function, beyond the normal `set` functionality P.setActionRectangleShape = S.actionRectangleShape; // Set __actionPortraitShape__ - must be a Function S.actionPortraitShape = function (item) { if (isa_fn(item)) this.actionPortraitShape = item; this.dirtyDisplayShape = true; }; // `setActionPortraitShape` - an alternative mechanism to set the __actionPortraitShape__ function, beyond the normal `set` functionality P.setActionPortraitShape = S.actionPortraitShape; // Set __actionSkyscraperShape__ - must be a Function S.actionSkyscraperShape = function (item) { if (isa_fn(item)) this.actionSkyscraperShape = item; this.dirtyDisplayShape = true; }; // `setActionSkyscraperShape` - an alternative mechanism to set the __actionSkyscraperShape__ function, beyond the normal `set` functionality P.setActionSkyscraperShape = S.actionSkyscraperShape; // Set __actionSmallestArea__ - must be a Function S.actionSmallestArea = function (item) { if (isa_fn(item)) this.actionSmallestArea = item; this.dirtyDisplayArea = true; }; // `setActionSmallestArea` - an alternative mechanism to set the __actionSmallestArea__ function, beyond the normal `set` functionality P.setActionSmallestArea = S.actionSmallestArea; // Set __actionSmallerArea__ - must be a Function S.actionSmallerArea = function (item) { if (isa_fn(item)) this.actionSmallerArea = item; this.dirtyDisplayArea = true; }; // `setActionSmallerArea` - an alternative mechanism to set the __actionSmallerArea__ function, beyond the normal `set` functionality P.setActionSmallerArea = S.actionSmallerArea; // Set __actionRegularArea__ - must be a Function S.actionRegularArea = function (item) { if (isa_fn(item)) this.actionRegularArea = item; this.dirtyDisplayArea = true; }; // `setActionRegularArea` - an alternative mechanism to set the __actionRegularArea__ function, beyond the normal `set` functionality P.setActionRegularArea = S.actionRegularArea; // Set __actionLargerArea__ - must be a Function S.actionLargerArea = function (item) { if (isa_fn(item)) this.actionLargerArea = item; this.dirtyDisplayArea = true; }; // `setActionLargerArea` - an alternative mechanism to set the __actionLargerArea__ function, beyond the normal `set` functionality P.setActionLargerArea = S.actionLargerArea; // Set __actionLargestArea__ - must be a Function S.actionLargestArea = function (item) { if (isa_fn(item)) this.actionLargestArea = item; this.dirtyDisplayArea = true; }; // `setActionLargestArea` - an alternative mechanism to set the __actionLargestArea__ function, beyond the normal `set` functionality P.setActionLargestArea = S.actionLargestArea; // #### Prototype functions // `initializeDisplayShapeActions` - internal function; called by the Canvas and Stack artefact constructors P.initializeDisplayShapeActions = function () { this.actionBannerShape = λnull; this.actionLandscapeShape = λnull; this.actionRectangleShape = λnull; this.actionPortraitShape = λnull; this.actionSkyscraperShape = λnull; this.currentDisplayShape = ZERO_STR; this.dirtyDisplayShape = true; this.actionSmallestArea = λnull; this.actionSmallerArea = λnull; this.actionRegularArea = λnull; this.actionLargerArea = λnull; this.actionLargestArea = λnull; this.currentDisplayArea = ZERO_STR; this.dirtyDisplayArea = true; }; // `cleanDisplayShape` - internal function; replaces the function defined in the dom.js mixin, invoked when required as part of the DOM artefact `prestamp` functionality P.cleanDisplayShape = function () { this.dirtyDisplayShape = false; const [width, height] = this.currentDimensions; if (width > 0 && height > 0) { const ratio = width / height, current = this.currentDisplayShape, banner = this.breakToBanner, landscape = this.breakToLandscape, portrait = this.breakToPortrait, skyscraper = this.breakToSkyscraper; if (ratio > banner) { if (current !== BANNER) { this.currentDisplayShape = BANNER; this.actionBannerShape(); return true; } return false; } else if (ratio > landscape) { if (current !== LANDSCAPE) { this.currentDisplayShape = LANDSCAPE; this.actionLandscapeShape(); return true; } return false; } else if (ratio < skyscraper) { if (current !== SKYSCRAPER) { this.currentDisplayShape = SKYSCRAPER; this.actionSkyscraperShape(); return true; } return false; } else if (ratio < portrait) { if (current !== PORTRAIT) { this.currentDisplayShape = PORTRAIT; this.actionPortraitShape(); return true; } return false; } else { if (current !== RECTANGLE) { this.currentDisplayShape = RECTANGLE; this.actionRectangleShape(); return true; } return false; } } else { this.dirtyDisplayShape = true; return false; } }; // `cleanDisplayArea` - internal function; replaces the function defined in the dom.js mixin, invoked when required as part of the DOM artefact `prestamp` functionality // + Note that `cleanDisplayArea` fires before `cleanDisplayShape`! P.cleanDisplayArea = function () { this.dirtyDisplayArea = false; const [width, height] = this.currentDimensions; if (width > 0 && height > 0) { const area = width * height, current = this.currentDisplayArea, largest = this.breakToLargest, larger = this.breakToLarger, smaller = this.breakToSmaller, smallest = this.breakToSmallest; if (area > largest) { if (current !== LARGEST) { this.currentDisplayArea = LARGEST; this.actionLargestArea(); return true; } return false; } else if (area > larger) { if (current !== LARGER) { this.currentDisplayArea = LARGER; this.actionLargerArea(); return true; } return false; } else if (area < smallest) { if (current !== SMALLEST) { this.currentDisplayArea = SMALLEST; this.actionSmallestArea(); return true; } return false; } else if (area < smaller) { if (current !== SMALLER) { this.currentDisplayArea = SMALLER; this.actionSmallerArea(); return true; } return false; } else { if (current !== REGULAR) { this.currentDisplayArea = REGULAR; this.actionRegularArea(); return true; } return false; } } else { this.dirtyDisplayArea = true; return false; } }; // `updateDisplayShape` - use this function to force the Canvas or Stack artefact to re-evaluate its current display shape, and invoke the action hook function associated with that shape. P.updateDisplayShape = function () { this.currentDisplayShape = ZERO_STR; this.dirtyDisplayShape = true; }; // `updateDisplayArea` - use this function to force the Canvas or Stack artefact to re-evaluate its current display area, and invoke the action hook function associated with that area. P.updateDisplayArea = function () { this.currentDisplayArea = ZERO_STR; this.dirtyDisplayArea = true; }; // `updateDisplay` - perform update for both display shape and area. P.updateDisplay = function () { this.updateDisplayShape(); this.updateDisplayArea(); }; }