UNPKG

g2o-gradient

Version:

g2o LinearGradient and RadialGradient

393 lines (385 loc) 12.9 kB
/** * g2o-gradient 1.0.0-alpha.0 * (c) David Geo Holmes david.geo.holmes@gmail.com * Released under the MIT License. */ 'use strict'; var g2o = require('g2o'); var g2oReactive = require('g2o-reactive'); let next_unique_id = 0; const Constants = { Identifier: 'g2o-gradient-', /** * Default amount of vertices to be used for interpreting Arcs and ArcSegments. */ Resolution: 12, uniqueId: function () { return next_unique_id++; } }; class Stop extends g2o.ElementBase { #offset = g2oReactive.state(0); #opacity = g2oReactive.state(1); #color = g2oReactive.state('#fff'); #change = g2o.variable(this); change$ = this.#change.asObservable(); /** * @param offset The offset percentage of the stop represented as a zero-to-one value. Default value flip flops from zero-to-one as new stops are created. * @param color The color of the stop. Default value flip flops from white to black as new stops are created. * @param opacity The opacity value. Default value is 1, cannot be lower than 0. */ constructor(offset, color, opacity) { super(Constants.Identifier + Constants.uniqueId()); this.offset = typeof offset === 'number' ? offset : Stop.Index <= 0 ? 0 : 1; this.opacity = typeof opacity === 'number' ? opacity : 1; this.color = (typeof color === 'string') ? color : Stop.Index <= 0 ? '#fff' : '#000'; Stop.Index = (Stop.Index + 1) % 2; } /** * The current index being referenced for calculating a stop's default offset value. */ static Index = 0; get color() { return this.#color.get(); } set color(color) { this.#color.set(color); if (this.parent && this.parent instanceof Gradient) { this.parent._flagStops = true; } this.#change.set(this); } get offset() { return this.#offset.get(); } set offset(offset) { this.#offset.set(offset); if (this.parent && this.parent instanceof Gradient) { this.parent._flagStops = true; } this.#change.set(this); } get opacity() { return this.#opacity.get(); } set opacity(opacity) { this.#opacity.set(opacity); if (this.parent && this.parent instanceof Gradient) { this.parent._flagStops = true; } this.#change.set(this); } } /** * */ class Gradient extends g2o.ElementBase { #refCount = 0; _flagStops = false; #spreadMethod = g2oReactive.state('pad'); #units = g2oReactive.state('userSpaceOnUse'); #stops = g2oReactive.state([]); _change = g2o.variable(this); change$ = this._change.asObservable(); constructor(stops = [], options = {}) { super(ensure_identifier(options)); if (typeof options.spreadMethod === 'string') { this.spreadMethod = options.spreadMethod; } if (typeof options.units === 'string') { this.units = options.units; } this.#stops = g2oReactive.state(map_to_stops(stops)); } dispose() { super.dispose(); } serialize() { return `url(#${this.id})`; } // eslint-disable-next-line @typescript-eslint/no-unused-vars render(viewDOM, defs) { this.zzz.disposables.push(g2oReactive.effect(() => { while (viewDOM.getLastChild(this.zzz.viewee)) { viewDOM.removeChild(this.zzz.viewee, viewDOM.getLastChild(this.zzz.viewee)); } const stops = this.stops; const N = stops.length; for (let i = 0; i < N; i++) { const stop = stops[i]; { const attrs = { id: stop.id }; const stopElement = viewDOM.createSVGElement('stop', attrs); stop.zzz.viewee = stopElement; viewDOM.appendChild(this.zzz.viewee, stopElement); stop.zzz.disposables.push(g2oReactive.effect(() => { viewDOM.setAttribute(stopElement, 'offset', 100 * stop.offset + '%'); })); stop.zzz.disposables.push(g2oReactive.effect(() => { viewDOM.setAttribute(stopElement, 'stop-color', stop.color); })); stop.zzz.disposables.push(g2oReactive.effect(() => { viewDOM.setAttribute(stopElement, 'stop-opacity', `${stop.opacity}`); })); } } })); } incrementUse(viewDOM, defs) { this.#refCount++; if (this.#refCount === 1) { this.render(viewDOM, defs); } } decrementUse(viewDOM, defs) { this.#refCount--; if (this.#refCount === 0) { viewDOM.removeChild(defs, viewDOM.downcast(this.zzz.viewee)); this.zzz.viewee = null; } } update() { if (this._flagStops) { this._change.set(this); } return this; } flagReset(dirtyFlag = false) { this._flagStops = dirtyFlag; return this; } /** * Indicates what happens if the gradient starts or ends inside the bounds of the target rectangle. * @see {@link https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute} for more information */ get spreadMethod() { return this.#spreadMethod.get(); } set spreadMethod(spread) { this.#spreadMethod.set(spread); } get stops() { return this.#stops.get(); } set stops(stops) { this.#stops.set(stops); } /** * Indicates how coordinate values are interpreted by the renderer. * @see {@link https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementGradientUnitsAttribute} for more information */ get units() { return this.#units.get(); } set units(units) { this.#units.set(units); } } function ensure_identifier(attributes) { if (typeof attributes.id === 'string') { return attributes.id; } else { return `${Constants.Identifier}${Constants.uniqueId()}`; } } function map_to_stops(stops) { const retval = []; const N = stops.length; for (let i = 0; i < N; i++) { const candidate = stops[i]; if (candidate instanceof Stop) { retval.push(candidate); } else if (Array.isArray(candidate)) { retval.push(new Stop(candidate[0], candidate[1], candidate[2])); } else { throw new Error(); } } return retval; } class LinearGradient extends Gradient { #point1; #point2; /** * @param point1 The position of the first end point of the linear gradient. * @param point2 The position of the second end point of the linear gradient. * @param stops A list of {@link Stop}s that contain the gradient fill pattern for the gradient. * The linear gradient lives within the space of the parent object's matrix space. */ constructor(point1, point2, stops, options = {}) { super(stops, options); this.#point1 = g2o.vector_from_like(point1); this.#point2 = g2o.vector_from_like(point2); } render(viewDOM, defs) { // If there is no attached DOM element yet, // create it with all necessary attributes. if (this.zzz.viewee) ; else { { const changed = {}; changed.id = this.id; const viewee = viewDOM.createSVGElement('linearGradient', changed); this.zzz.viewee = viewee; if (viewDOM.getParentNode(viewee) === null) { viewDOM.appendChild(defs, viewee); } this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.x1 = `${this.point1.x}`; change.y1 = `${this.point1.y}`; viewDOM.setAttributes(viewee, change); })); this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.x2 = `${this.point2.x}`; change.y2 = `${this.point2.y}`; viewDOM.setAttributes(viewee, change); })); this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.gradientUnits = this.units; viewDOM.setAttributes(viewee, change); })); this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.spreadMethod = this.spreadMethod; viewDOM.setAttributes(viewee, change); })); super.render(viewDOM, defs); } } return this.flagReset(); } update() { if (this._flagStops) { this._change.set(this); } return this; } flagReset(dirtyFlag = false) { super.flagReset(dirtyFlag); return this; } get point1() { return this.#point1; } set point1(point1) { if (point1 instanceof g2o.G20) { this.#point1.copyVector(point1); } } get point2() { return this.#point2; } set point2(point2) { if (point2 instanceof g2o.G20) { this.#point2.copyVector(point2); } } } class RadialGradient extends Gradient { #center; #radius = g2oReactive.state(1); #focal; /** * * @param center The position of the origin of the radial gradient. * @param stops A list of {@link Stop}s that contain the gradient fill pattern for the gradient. * @param options */ constructor(center, stops = [], options = {}) { super(stops, options); this.#center = g2o.vector_from_like(center); if (typeof options.radius === 'number') { this.#radius.set(options.radius); } this.#focal = this.center.clone(); if (typeof options.fx === 'number') { this.focal.x = options.fx; } if (typeof options.fy === 'number') { this.focal.y = options.fy; } } render(viewDOM, defs) { if (this.zzz.viewee) ; else { const changed = {}; changed.id = this.id; const viewee = viewDOM.createSVGElement('radialGradient', changed); this.zzz.viewee = viewee; viewDOM.appendChild(defs, viewee); super.render(viewDOM, defs); // center this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.cx = `${this.center.x}`; change.cy = `${this.center.y}`; viewDOM.setAttributes(viewee, change); })); // focal this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.fx = `${this.focal.x}`; change.fy = `${this.focal.y}`; viewDOM.setAttributes(viewee, change); })); // gradientUnits this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.gradientUnits = this.units; viewDOM.setAttributes(viewee, change); })); // radius this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.r = `${this.radius}`; viewDOM.setAttributes(viewee, change); })); // spreadMethod this.zzz.disposables.push(g2oReactive.effect(() => { const change = {}; change.spreadMethod = this.spreadMethod; viewDOM.setAttributes(viewee, change); })); } return this.flagReset(); } update() { if (this._flagStops) { this._change.set(this); } return this; } flagReset(dirtyFlag = false) { super.flagReset(dirtyFlag); return this; } get center() { return this.#center; } set center(center) { this.#center.copyVector(center); } get focal() { return this.#focal; } set focal(focal) { this.#focal.copyVector(focal); } get radius() { return this.#radius.get(); } set radius(radius) { this.#radius.set(radius); } } exports.Gradient = Gradient; exports.LinearGradient = LinearGradient; exports.RadialGradient = RadialGradient; exports.Stop = Stop; //# sourceMappingURL=index.js.map