Scrawl-canvas events and signals
SC does not include a bespoke implementation of an event-driven system. SC artefact, entity and animation objects do not generate any custom events as part of their functionality.
Instead SC relies on the normal event system provided by the browser to handle user interactions with an SC Canvas or Stack display:
- SC uses a number of system-generated event listeners to handle changes in the browser environment, for instance:
– A single event listener to track mouse movement across the viewport.
– Similarly, event listeners to react to user-initiated page scrolling and browser window resize events.
– Intersection observers to report on<canvas>and stack DOM element positioning within the browser viewport.
– Event listeners assigned to various CSS matchMedia() queries to monitor, in particular, user-set media preferences. - SC also supplies the dev-user with a set of convenience functions to capture various user interactions with a Canvas or Stack display, including:
– Mouse, touch and pointer events.
– Drag-and-drop functionality.
– Keyboard navigation and trigger events.
– Interactions with form element controls.
– Responsive image management.
– Media stream, and screen capture, events.
For the most part, these events do not trigger immediate changes in the SC system or its data. Rather they will update values held in the currentCorePosition object (which acts as SC's Single Source of Truth for this data) and set relevant dirty flags which the system can then address during the next Display cycle.
This signals system pattern is also used by other SC objects – in particular artefact and entity objects – to communicate changes in their data to other objects that rely on the changed data for their own calculations: when a change occurs dirty flags will be set, but actioning those changes and alerting subscribed objects of those changes is deferred until the next Display cycle.
System events
[write up]
The core listeners tracker function
[write up]
Resize action
[write up]
Scroll action
[write up]
Move action
[write up]
Tracking the browser environment
[write up]
Accessibility preferences
[write up]
Preferences:
- prefers-contrast
- prefers-reduced-motion
- prefers-color-scheme
- prefers-reduced-transparency
- prefers-reduced-data
- inverted-colors
- forced-colors
System and device screen capabilities
[write up]
Capabilities:
- Display-P3 support
- Device pixel ratio
Font management
[write up]
Dynamic asset management
[write up]
Responsive images
[write up]
Responsive video and video streams
[write up]
Third-party canvas sources
[write up]
Pointer (mouse and touch) interaction
[write up]
SC event listener convenience functions
[write up]
The addListener() function
[write up]
The addNativeListener() function
[write up]
Cascading events
[write up]
Group hover event functionality
[write up]
Drag-and-drop functionality
[write up]
Non-pointer interaction
[write up]
Scrawl-canvas Anchor and Button objects
[write up]
Keyboard navigation
[write up]
User interaction with form controls
[write up]
Keyboard shortcut management
[write up]
The Scrawl-canvas signalling system
SC uses a system of dirty flags and clean functions, alongside subscriber Arrays and updateSubscribers functions, to manage communication across the SC objects estate. The system is diffuse rather than centralised, relying on objects to subscribe themselves to other objects to receive notifications (via dirty flags) of when the other object state changes.
tl;dr Repo-devs need to be aware that the signalling system is the most brittle part of the code base. If issues arise, the problem may well be because of a failure to signal under a given circumstance, or a failure to clean in the correct order. In particular the order in which cleaning operations take place is important!
Marking objects as dirty
The signalling system rests on the ability of objects to set dirty flags when something changes. This is the reason why SC is so aggressively opinionated about the need for all object updates to be routed through the object.set() and object.setDelta() functions.
The basal set and setDelta functions get defined in the mixin/base.js file. This is also where the object prototype getters, setters and deltaSetters objects get created, alongside the defs object used to store an object's default attribute values. Each of these objects store data keyed to attribute Strings:
The example code demonstrates the basic set operation:
prototype.defs = {
attributeOne: 0,
attributeTwo: 0,
};
prototype.setters = {
attributeTwo: function (item) {
this.attributeTwo = item * 2;
this.dirtyAttributeTwo = true;
this.dirtyOtherThing = true;
},
};
prototype.set = function (items = Ωempty) {
let i, key, val, fn;
const keys = _keys(items),
keysLen = keys.length;
if (keysLen) {
const setters = this.setters,
defs = this.defs;
for (i = 0; i < keysLen; i++) {
key = keys[i];
val = items[key];
// The object.name attribute can only be set during object construction
if (key && key !== NAME && val != null) {
// Accept the update if there is a dedicated setter function for the attribute
fn = setters[key];
if (fn) fn.call(this, val);
// Accept the update if the attribute exists in the defs object
else if (typeof defs[key] !== UNDEF) this[key] = val;
}
}
}
return this;
};
// Later, something updates the object using a set invocation
object.set({
attributeOne: 10,
attributeTwo: 10,
attributeThree: 10,
});
// The results of the set invocation:
// - attributeOne is defined in the defs object, the update is accepted
// - attributeTwo has a setter function, the update is accepted and processed
// - this includes setting some associated dirty flags
// - attributeThree is neither defined in the defs object, nor the setters object
// - the update is ignored
The code may seem over-complicated … but it works! Repo-devs have chosen to use this approach because:
- It keeps the factory file code understandable:
– Object attributes can be defined and explained in thedefsobject.
– Attribute updates requiringdirtyflags to be set can be managed more easily. - It prevents dev-users accidentally damaging object shapes, which can lead to code efficiency degredation
- It couples tightly with the object
packet(serialization) system, and thus with the accompanying objectclonesystem. - Mixin and factory files can easily add attributes to the
defsobject to meet their particular requirements. - Mixin and factory files can easily overwrite the
get,setandsetDeltafunctions, and individualgetter,setteranddeltaSetterattribute functions, to meet their particular requirements.
The downside to this approach is that the SC signals system has not been centralised as it needs to adapt to the individual requirements of each mixin and factory file code. For instance, the code base includes a total of 75 dirty flag attributes which may be defined in one file but actioned in various ways by various downstream files. The current list of dirty flag attributes includes: dirtyAnchor, dirtyAria, dirtyAsset, dirtyAssetSubscribers, dirtyButton, dirtyCache, dirtyCells, dirtyClasses, dirtyContent, dirtyControl, dirtyControlLock, dirtyCopyDimensions, dirtyCopyStart, dirtyCss, dirtyData, dirtyDimensions, dirtyDimensionsOverride, dirtyDisplayArea, dirtyDisplayShape, dirtyDomDimensions, dirtyEnd, dirtyEndControl, dirtyEndControlLock, dirtyEndLock, dirtyFilterIdentifier, dirtyFilters, dirtyFiltersCache, dirtyFont, dirtyHandle, dirtyHeight, dirtyHost, dirtyImage, dirtyImageSubscribers, dirtyInput, dirtyLayout, dirtyLock, dirtyMimicDimensions, dirtyMimicHandle, dirtyMimicOffset, dirtyMimicRotation, dirtyMimicScale, dirtyNavigationTabOrder, dirtyNoise, dirtyOffset, dirtyOffsetZ, dirtyOutput, dirtyPalette, dirtyPaletteData, dirtyParticles, dirtyPaste, dirtyPathData, dirtyPathObject, dirtyPerspective, dirtyPins, dirtyPivotRotation, dirtyPosition, dirtyPositionSubscribers, dirtyRotation, dirtyScale, dirtyScene, dirtySpecies, dirtyStampHandlePositions, dirtyStampOrder, dirtyStampPositions, dirtyStart, dirtyStartControl, dirtyStartControlLock, dirtyStyle, dirtyTargetImage, dirtyText, dirtyTextLayout, dirtyTextTabOrder, dirtyTransform, dirtyTransformOrigin, dirtyVisibility, dirtyZIndex.
Because of the need to set dirty flags to notify objects that they will need to do work to update their state, repo-devs should always use the set functions within the code base. For instance Tween animation objects use target.set() to communicate time-based updates to target objects. Similarly the observe-update object uses target.set() to inform an SC object of end-user interactions with DOM form elements.
tl;dr: Dev-users should never update an SC object using
object.attribute = newValueorobject['attribute'] = newValueapproaches. Such code will break SC functionality!Instead all object updates should be performed using the
object.set({ key: value, ...})orobject.setDelta({ key: value, ...})functions.
Cleaning dirty objects
[write up]
Files:
- core/events.js
- core/user-interaction.js
- factory/anchor.js
- factory/button.js
- factory/canvas.js
- factory/cell.js
- factory/group.js
- factory/render-animation.js
- mixin/anchor.js
- mixin/button.js
- mixin/cell-key-functions.js
- mixin/entity.js
- untracked-factory/drag-zone.js
- untracked-factory/keyboard-zone.js
- untracked-factory/observe-update.js