UNPKG

dis-gui-lifetoys

Version:

An extensible, styleable, & React-based controller library inspired by the venerable dat-gui.

316 lines (291 loc) 9.05 kB
'use strict'; import PropTypes from 'prop-types'; import React, { useState, useContext, createRef, useEffect } from 'react'; import { StyleContext } from '../styleContext.js'; import update from 'immutability-helper'; import Row from '../core/row'; import Label from '../core/label'; import Control from '../core/control'; import ColorRange from '../core/colorRange'; import ColorStop from '../core/colorStop.js'; export default function GradientWidget({expanded, stops, label, onChange, onFinishChange}) { const [expandedState, setExpanded] = useState(expanded); const [stopsState, setStops] = useState(stops); const [selectedStopState, setSelectedStop] = useState(0); const styleContext = useContext(StyleContext); const canvasRef = createRef(); const stopfieldRef = createRef(); useEffect(() => { updateCanvas(); }); const handleStopFieldMouseDown = (event) => { const el = event.target.closest('div'); if (el !== stopfieldRef.current) { return; } let updatedStops = stops.slice(); let rect = event.target.getBoundingClientRect(); let newStop = (event.pageX - rect.left)/rect.width; let c = getGradientValue(getCleanStops(), newStop); updatedStops.push({ stop: newStop, red: c.red, green: c.green, blue: c.blue }); /*let newState = update(this.state, { $set: { stops: stops, selectedStopState: stops.length - 1 } })*/ setStops(updatedStops); setSelectedStop(updatedStops.length - 1); handleChange(); handleFinishChange() }; const handleRemoveStop = () => { let updatedStops = stops.slice(); updatedStops.splice(selectedStopState, 1); /*let newState = update(this.state, { $set: { stops: stops, selectedStopState: 0 } })*/ setStops(updatedStops); setSelectedStop(0); handleChange(); handleFinishChange() }; const handleChangeRed = (value) => { let updatedStops = stops.slice(); updatedStops[selectedStopState].red = parseInt(value); /*let newState = update(this.state, { $set: { stops: stops } })*/ setStops(updatedStops); handleChange(); }; const handleChangeGreen = (value) => { let updatedStops = stops.slice(); updatedStops[selectedStopState].green = parseInt(value); /*let newState = update(this.state, { $set: { stops: stops } })*/ setStops(updatedStops); handleChange(); }; const handleChangeBlue = (value) => { let updatedStops = stops.slice(); updatedStops[selectedStopState].blue = parseInt(value); /*let newState = update(this.state, { $set: { stops: stops } })*/ setStops(updatedStops); handleChange(); }; const handleChange = () => { if (onChange) { onChange(getCleanStops()); } }; const handleFinishChange = () => { if (onFinishChange) { onFinishChange(getCleanStops()); } }; const handleCanvasClick = () => { setExpanded(!expandedState); }; const handleStopChange = (event) => { let updatedStops = stops.slice(); updatedStops[event.index].stop = event.stop; /*let newState = update(this.state, { $set: { stops: stops, } });*/ setStops(updatedStops); handleChange(); }; const handleStopClick = (event) => { setSelectedStop(event.index); }; const getCleanStops = () => { if (!stops) { return []; } // Returns stops, bounded to [0..1] and sorted. let cleanStops = stops.slice(); cleanStops.sort(function(a, b) { return a.stop - b.stop; }); if (cleanStops[0].stop > 0) { cleanStops.unshift({ stop: 0, red: cleanStops[0].red, green: cleanStops[0].green, blue: cleanStops[0].blue }); } let lastStop = cleanStops[cleanStops.length - 1]; if (lastStop.stop < 1) { cleanStops.push({ stop: 1, red: lastStop.red, green: lastStop.green, blue: lastStop.blue }); } return cleanStops; } const getGradientValue = (cleanStops, frac) => { for (let i = 0; i < cleanStops.length - 1; i++) { if (frac >= cleanStops[i].stop && frac <= cleanStops[i + 1].stop) { let left = cleanStops[i]; let right = cleanStops[i + 1]; let ifrac = (frac - left.stop)/(right.stop - left.stop); return { red: Math.round(left.red + ifrac * (right.red - left.red)), green: Math.round(left.green + ifrac * (right.green - left.green)), blue: Math.round(left.blue + ifrac * (right.blue - left.blue)), } } } throw ('Error calculating gradient value.'); } const updateCanvas = () => { let stops = getCleanStops() let canvas = canvasRef.current; canvas.width = 512; canvas.height = 1; let ctx = canvas.getContext('2d'); for (var x = 0; x < canvas.width; x++) { let frac = x/(canvas.width - 1); let c = getGradientValue(stops, frac); ctx.fillStyle = `rgb(${c.red}, ${c.green}, ${c.blue})`; ctx.fillRect(x, 0, 1, 1); } } return ( <div style={{ }}> <Row> <Label>{label}</Label> <Control> <canvas ref={ canvasRef } onClick={handleCanvasClick.bind(this)} style={{ width: `${styleContext.controlWidth}px`, position: 'relative', height: `${styleContext.computed.itemHeight}px`, WebkitUserSelect: 'none', MozUserSelect: 'none', msUserSelect: 'none', userSelect: 'none', cursor: 'pointer', }} ></canvas> {expandedState && 0 === 1 && <div> <div ref={ stopfieldRef } onMouseDown={ handleStopFieldMouseDown.bind(this) } style={{ width: `${styleContext.controlWidth}px`, height: `${styleContext.computed.fontHeight*1.875}px`, boxSizing: 'border-box', position: 'relative', overflow: 'hidden', cursor: 'pointer', }} > {stopsState.map((stop, index) => { return ( <ColorStop key={'stop' + index} index={index} stop={stop.stop} red={stop.red} green={stop.green} blue={stop.blue} selected={index === selectedStopState} onClick={handleStopClick.bind(this)} onChange={handleStopChange.bind(this)} onFinishChange={handleFinishChange.bind(this)} /> ) })} </div> <ColorRange label='Red' value={stops[selectedStopState].red} onChange={handleChangeRed.bind(this)} onFinishChange={handleFinishChange.bind(this)} /> <ColorRange label='Green' value={stops[selectedStopState].green} onChange={handleChangeGreen.bind(this)} onFinishChange={handleFinishChange.bind(this)} /> <ColorRange label='Blue' value={stops[selectedStopState].blue} onChange={handleChangeBlue.bind(this)} onFinishChange={handleFinishChange.bind(this)} /> { stopsState.length > 1 && <div onClick={handleRemoveStop} style={{ backgroundColor: styleContext.lowlight, color: styleContext.highlight, font: styleContext.font, padding: `${styleContext.paddingY}px 0px`, marginTop: `${styleContext.paddingY}px`, textAlign: 'center', cursor: 'pointer', width: '100%', WebkitUserSelect: 'none', MozUserSelect: 'none', msUserSelect: 'none', userSelect: 'none', }} > Remove Stop </div> } </div> } </Control> </Row> </div> ); } GradientWidget.propTypes = { expanded: PropTypes.bool, stops: PropTypes.array, label: PropTypes.string, onChange: PropTypes.func, onFinishChange: PropTypes.func, }; GradientWidget.defaultProps = { expanded: false, stops: [ {red: 255, green: 0, blue: 0, stop: 0.125}, {red: 255, green: 255, blue: 0, stop: 0.5}, {red: 255, green: 255, blue: 255, stop: 0.875}, ], }; /*GradientWidget.contextTypes = { styleContext: PropTypes.object, };*/