From 0d9b79c5f924df789ee686cd75a2b1cfaf216c76 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 2 Aug 2024 11:22:51 +0300 Subject: [PATCH 1/7] *WIP* implement spectrum color picker --- ColorPickerPOC/ColorPickerPOC.js | 3 +- ColorPickerPOC/ColorPickerSpectrum.js | 128 ++++++++++++++++++ .../ColorPickerSpectrum.module.less | 18 +++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 ColorPickerPOC/ColorPickerSpectrum.js create mode 100644 ColorPickerPOC/ColorPickerSpectrum.module.less diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index 43ddff184f..c1d1659eb4 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -11,6 +11,7 @@ import Skinnable from '../Skinnable'; import TabLayout, {Tab} from '../TabLayout'; import ColorPickerGrid from './ColorPickerGrid'; +import ColorPickerSpectrum from './ColorPickerSpectrum'; import componentsCss from './ColorPickerPOC.module.less'; @@ -106,7 +107,7 @@ const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...re
- Spectrum +
diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js new file mode 100644 index 0000000000..2c2d3316c9 --- /dev/null +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -0,0 +1,128 @@ +import ri from '@enact/ui/resolution'; +import Spottable from '@enact/spotlight/Spottable'; +import {useCallback, useEffect, useRef, useState} from 'react'; + +import css from './ColorPickerSpectrum.module.less'; + +const CircleIndicatorBase = ({bgColor, x, y}) => { + return ( +
+ ); +}; + +const SpottableCircleIndicator = Spottable(CircleIndicatorBase); + +const SpectrumColorPicker = (props) => { + const {selectedColorHandler} = props; + const canvasRef = useRef(null); + const [indicatorX, setIndicatorX] = useState(0); + const [indicatorY, setIndicatorY] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [indicatorBgColor, setIndicatorBgColor] = useState('transparent'); + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + + const createColorGradient = (canvas, ctx) => { + for(let i = 0; i < canvas.width; i+= 0.2) { + const luminosity = 1 - (i / canvas.width); // Adjust the luminosity calculation + const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); + gradient.addColorStop(0, `hsl(0, 100%, ${luminosity * 100}%)`); // Red + gradient.addColorStop(1/6, `hsl(30, 100%, ${luminosity * 100}%)`); // Orange + gradient.addColorStop(2/6, `hsl(60, 100%, ${luminosity * 100}%)`); // Yellow + gradient.addColorStop(3/6, `hsl(120, 100%, ${luminosity * 100}%)`); // Green + gradient.addColorStop(4/6, `hsl(180, 100%, ${luminosity * 100}%)`); // Blue + gradient.addColorStop(5/6, `hsl(240, 100%, ${luminosity * 100}%)`); // Indigo + gradient.addColorStop(1, `hsl(269, 100%, ${luminosity * 100}%)`); // Violet + ctx.fillStyle = gradient; + ctx.fillRect(i, 0, 1, canvas.height); + } + }; + + createColorGradient(canvas, ctx); + }, []); + + const rgbToHex = useCallback((r, g, b) => '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''), []); + + const handleCanvasPointerDown = useCallback((e) => { + const canvas = canvasRef.current; + const rect = canvas.getBoundingClientRect(); + let x = e.clientX - rect.left; + let y = e.clientY - rect.top; + + setIndicatorX(x); + setIndicatorY(y); + setIsDragging(true); + }, [canvasRef, setIndicatorX, setIndicatorY, setIsDragging]); + + const handleCanvasPointerLeave = useCallback((e) => { + setIsDragging(false); + + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); + const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + selectedColorHandler(hexColor); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, rgbToHex]); + + const handleCanvasPointerMove = useCallback((e) => { + if (isDragging) { + const canvas = canvasRef.current; + const rect = canvas.getBoundingClientRect(); + let x = e.clientX - rect.left; + let y = e.clientY - rect.top; + + setIndicatorX(x); + setIndicatorY(y); + + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); + const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + setIndicatorBgColor(hexColor); + // console.log('here'); + // selectedColorHandler(hexColor); + } + }, [canvasRef, indicatorX, indicatorY, isDragging, selectedColorHandler, rgbToHex]); + + const handleCanvasPointerUp = useCallback(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); + console.log('x', indicatorX, 'y', indicatorY); + const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + console.log(hexColor) + selectedColorHandler(hexColor); + setIsDragging(false); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, rgbToHex]); + + return ( +
+ + +
+ ); +}; + +export default SpectrumColorPicker; diff --git a/ColorPickerPOC/ColorPickerSpectrum.module.less b/ColorPickerPOC/ColorPickerSpectrum.module.less new file mode 100644 index 0000000000..3abf7acb7c --- /dev/null +++ b/ColorPickerPOC/ColorPickerSpectrum.module.less @@ -0,0 +1,18 @@ +// ColorPickerSpectrum.module.less +// +@import '../styles/colors.less'; +@import '../styles/mixins.less'; +@import '../styles/variables.less'; + +.colorPicker { + position: relative; + //height: 100%; + //width: 100%; +} + +.circleIndicator { + .focus({ + background-color: red; + transform: scale(1.5); + }) +} From 88086d365ae0519b834e615ae06a8a003ecd2374 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 2 Aug 2024 14:05:51 +0300 Subject: [PATCH 2/7] added positionPointer, wrapped ColorPicker in Spottable, code cleanup --- ColorPickerPOC/ColorPickerPOC.js | 2 +- ColorPickerPOC/ColorPickerSpectrum.js | 69 +++++++++++++++++++-------- ColorPickerPOC/utils.js | 8 +++- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index c1d1659eb4..b353b3b833 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -107,7 +107,7 @@ const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...re
- +
diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index 2c2d3316c9..7b6271d7d4 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -1,7 +1,9 @@ -import ri from '@enact/ui/resolution'; import Spottable from '@enact/spotlight/Spottable'; +import compose from 'ramda/src/compose'; import {useCallback, useEffect, useRef, useState} from 'react'; +import {spectrumRgbToHex} from './utils'; + import css from './ColorPickerSpectrum.module.less'; const CircleIndicatorBase = ({bgColor, x, y}) => { @@ -16,7 +18,7 @@ const CircleIndicatorBase = ({bgColor, x, y}) => { borderRadius: 99, border: '2px solid #808080', backgroundColor: bgColor, - pointerEvents: 'none', + pointerEvents: 'none' }} /> ); @@ -24,8 +26,8 @@ const CircleIndicatorBase = ({bgColor, x, y}) => { const SpottableCircleIndicator = Spottable(CircleIndicatorBase); -const SpectrumColorPicker = (props) => { - const {selectedColorHandler} = props; +const SpectrumColorPickerBase = (props) => { + const {selectedColor, selectedColorHandler} = props; const canvasRef = useRef(null); const [indicatorX, setIndicatorX] = useState(0); const [indicatorY, setIndicatorY] = useState(0); @@ -37,8 +39,8 @@ const SpectrumColorPicker = (props) => { const ctx = canvas.getContext('2d'); const createColorGradient = (canvas, ctx) => { - for(let i = 0; i < canvas.width; i+= 0.2) { - const luminosity = 1 - (i / canvas.width); // Adjust the luminosity calculation + for(let i = 0; i < canvas.width; i++) { + const luminosity = 1 - (i / canvas.width); // Max luminosity on the left, min luminosity on the right const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, `hsl(0, 100%, ${luminosity * 100}%)`); // Red gradient.addColorStop(1/6, `hsl(30, 100%, ${luminosity * 100}%)`); // Orange @@ -51,11 +53,29 @@ const SpectrumColorPicker = (props) => { ctx.fillRect(i, 0, 1, canvas.height); } }; - createColorGradient(canvas, ctx); - }, []); - const rgbToHex = useCallback((r, g, b) => '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''), []); + // Position the indicator on the canvas based on the selected color + const positionIndicator = () => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + for (let x = 0; x < canvas.width; x++) { + for (let y = 0; y < canvas.height; y++) { + const pixelIndex = (y * canvas.width * 4) + (x * 4); + const pixelColor = `#${imageData.data[pixelIndex].toString(16).padStart(2, '0')}${imageData.data[pixelIndex + 1].toString(16).padStart(2, '0')}${imageData.data[pixelIndex + 2].toString(16).padStart(2, '0')}`; + if (pixelColor === selectedColor) { + setIndicatorX(x); + setIndicatorY(y); + return; + } else { + // if the color is not found, position the indicator at the origin(0, 0) of the canvas + setIndicatorX(0); + setIndicatorY(0); + } + } + } + }; + positionIndicator(); + }, []); const handleCanvasPointerDown = useCallback((e) => { const canvas = canvasRef.current; @@ -66,7 +86,12 @@ const SpectrumColorPicker = (props) => { setIndicatorX(x); setIndicatorY(y); setIsDragging(true); - }, [canvasRef, setIndicatorX, setIndicatorY, setIsDragging]); + + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(x, y, 1, 1); + const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + setIndicatorBgColor(hexColor); + }, [canvasRef, setIndicatorX, setIndicatorY, setIsDragging, spectrumRgbToHex]); const handleCanvasPointerLeave = useCallback((e) => { setIsDragging(false); @@ -74,9 +99,10 @@ const SpectrumColorPicker = (props) => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); selectedColorHandler(hexColor); - }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, rgbToHex]); + setIndicatorBgColor(hexColor); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, spectrumRgbToHex]); const handleCanvasPointerMove = useCallback((e) => { if (isDragging) { @@ -90,23 +116,20 @@ const SpectrumColorPicker = (props) => { const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); setIndicatorBgColor(hexColor); - // console.log('here'); - // selectedColorHandler(hexColor); } - }, [canvasRef, indicatorX, indicatorY, isDragging, selectedColorHandler, rgbToHex]); + }, [canvasRef, indicatorX, indicatorY, isDragging, selectedColorHandler, spectrumRgbToHex]); const handleCanvasPointerUp = useCallback(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - console.log('x', indicatorX, 'y', indicatorY); - const hexColor = rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); - console.log(hexColor) + const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); selectedColorHandler(hexColor); + setIndicatorBgColor(hexColor); setIsDragging(false); - }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, rgbToHex]); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, spectrumRgbToHex]); return (
@@ -125,4 +148,10 @@ const SpectrumColorPicker = (props) => { ); }; +const SpectrumColorPickerDecorator = compose( + Spottable +); + +const SpectrumColorPicker = SpectrumColorPickerDecorator(SpectrumColorPickerBase); + export default SpectrumColorPicker; diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index d7d843a4ea..ff7ff4b468 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -12,6 +12,12 @@ function rgbStringToHex (rgbString) { return ("#" + a[0] + a[1] + a[2]).toUpperCase(); } +// Utilities functions for spectrum color picker +function spectrumRgbToHex (r, g, b) { + return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); +} + export { - rgbStringToHex + rgbStringToHex, + spectrumRgbToHex }; From fbc2f72912b0efdcf4acd2abf2d2dcbd62817be4 Mon Sep 17 00:00:00 2001 From: Daniel Stoian Date: Mon, 5 Aug 2024 10:39:49 +0300 Subject: [PATCH 3/7] fixed spottable div --- ColorPickerPOC/ColorPickerSpectrum.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index 7b6271d7d4..d396550c73 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -6,9 +6,11 @@ import {spectrumRgbToHex} from './utils'; import css from './ColorPickerSpectrum.module.less'; +const SpottableDiv = Spottable('div'); + const CircleIndicatorBase = ({bgColor, x, y}) => { return ( -
{ ); }; -const SpottableCircleIndicator = Spottable(CircleIndicatorBase); + const SpectrumColorPickerBase = (props) => { const {selectedColor, selectedColorHandler} = props; @@ -143,7 +145,7 @@ const SpectrumColorPickerBase = (props) => { style={{touchAction: 'none'}} width={400} /> - +
); }; From bedde742af85610157c8b8d3dd617d752b001313 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Mon, 5 Aug 2024 18:00:52 +0300 Subject: [PATCH 4/7] *WIP* added 5-way navigation to indicator --- ColorPickerPOC/ColorPickerSpectrum.js | 50 +++++------ .../ColorPickerSpectrum.module.less | 11 ++- ColorPickerPOC/SpectrumIndicator.js | 87 +++++++++++++++++++ 3 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 ColorPickerPOC/SpectrumIndicator.js diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index d396550c73..48f439f6e4 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -1,33 +1,12 @@ -import Spottable from '@enact/spotlight/Spottable'; +// import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import compose from 'ramda/src/compose'; import {useCallback, useEffect, useRef, useState} from 'react'; import {spectrumRgbToHex} from './utils'; +import SpectrumIndicator from './SpectrumIndicator'; import css from './ColorPickerSpectrum.module.less'; -const SpottableDiv = Spottable('div'); - -const CircleIndicatorBase = ({bgColor, x, y}) => { - return ( - - ); -}; - - - const SpectrumColorPickerBase = (props) => { const {selectedColor, selectedColorHandler} = props; const canvasRef = useRef(null); @@ -35,6 +14,7 @@ const SpectrumColorPickerBase = (props) => { const [indicatorY, setIndicatorY] = useState(0); const [isDragging, setIsDragging] = useState(false); const [indicatorBgColor, setIndicatorBgColor] = useState('transparent'); + const [isIndicatorActive, setIsIndicatorActive] = useState(false); useEffect(() => { const canvas = canvasRef.current; @@ -93,6 +73,7 @@ const SpectrumColorPickerBase = (props) => { const imageData = ctx.getImageData(x, y, 1, 1); const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); setIndicatorBgColor(hexColor); + setIsIndicatorActive(false); }, [canvasRef, setIndicatorX, setIndicatorY, setIsDragging, spectrumRgbToHex]); const handleCanvasPointerLeave = useCallback((e) => { @@ -145,15 +126,24 @@ const SpectrumColorPickerBase = (props) => { style={{touchAction: 'none'}} width={400} /> - +
); }; -const SpectrumColorPickerDecorator = compose( - Spottable -); - -const SpectrumColorPicker = SpectrumColorPickerDecorator(SpectrumColorPickerBase); +// const SpectrumColorPickerDecorator = compose( +// // SpotlightContainerDecorator +// ); +// +// const SpectrumColorPicker = SpectrumColorPickerDecorator(SpectrumColorPickerBase); -export default SpectrumColorPicker; +export default SpectrumColorPickerBase; diff --git a/ColorPickerPOC/ColorPickerSpectrum.module.less b/ColorPickerPOC/ColorPickerSpectrum.module.less index 3abf7acb7c..57ddfdcb0c 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.module.less +++ b/ColorPickerPOC/ColorPickerSpectrum.module.less @@ -11,8 +11,15 @@ } .circleIndicator { + border: 2px solid #808080; + height: 48px; + border-radius: 99px; + pointer-events: none; + position: absolute; + width: 48px; + .focus({ - background-color: red; - transform: scale(1.5); + border: 4px solid #ff0000; + //transform: scale(1.5); }) } diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js new file mode 100644 index 0000000000..7c3c0b4627 --- /dev/null +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -0,0 +1,87 @@ +import {is} from '@enact/core/keymap'; +import Spottable from '@enact/spotlight/Spottable'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; +import spotlight from '@enact/spotlight'; +import compose from 'ramda/src/compose'; +import {useEffect, useState} from 'react'; + +import css from './ColorPickerSpectrum.module.less'; + +const SpottableDiv = Spottable('div'); + +const CircleIndicatorBase = ({bgColor, canvasRef, isIndicatorActive, setIsIndicatorActive, setIndicatorX, setIndicatorY, x, y}) => { + const [xIndicator, setXIndicator] = useState(x); + const [yIndicator, setYIndicator] = useState(y); + + useEffect(() => { + setXIndicator(x); + setYIndicator(y); + }, [x, y]) + + // resume spotlight when indicator is not active + useEffect(() => { + if (!isIndicatorActive) { + spotlight.resume(); + } + }, [isIndicatorActive]); + + // set indicator in active state and pause spotlight so no other containers get focus when selecting a color with 5-way + const handleOnKeyDown = ({keyCode}) => { + if (is('enter', keyCode)) { + setIsIndicatorActive(!isIndicatorActive); + spotlight.pause(); + } + }; + + const handleSpotlightDown = () => { + if (isIndicatorActive && yIndicator < canvasRef.current.clientHeight) { + setYIndicator(y++); + setIndicatorY(y++); + } + }; + + const handleSpotlightLeft = () => { + if (isIndicatorActive && xIndicator >= 0) { + setXIndicator(x--); + setIndicatorX(x--); + } + }; + + const handleSpotlightRight = () => { + if (isIndicatorActive && xIndicator < canvasRef.current.clientWidth) { + setXIndicator(x++); + setIndicatorX(x++); + } + }; + + const handleSpotlightUp = () => { + if (isIndicatorActive && yIndicator >= 0) { + setYIndicator(y--); + setIndicatorY(y--); + } + }; + + return ( + + ); +}; + +const CircleIndicatorDecorator = compose( + SpotlightContainerDecorator +); + +const CircleIndicator = CircleIndicatorDecorator(CircleIndicatorBase); + +export default CircleIndicator; From 9c7ee2693684125f5e0e455bfedcca92cbc41303 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Tue, 6 Aug 2024 15:40:56 +0300 Subject: [PATCH 5/7] color selection via 5-way, code cleanup, fix lint --- ColorPickerPOC/ColorPickerSpectrum.js | 77 ++++++------ .../ColorPickerSpectrum.module.less | 3 - ColorPickerPOC/SpectrumIndicator.js | 113 +++++++++++------- ColorPickerPOC/utils.js | 13 +- 4 files changed, 113 insertions(+), 93 deletions(-) diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index 48f439f6e4..54d20eb35d 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -1,13 +1,12 @@ -// import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; -import compose from 'ramda/src/compose'; +import PropTypes from 'prop-types'; import {useCallback, useEffect, useRef, useState} from 'react'; -import {spectrumRgbToHex} from './utils'; +import {getHexColorFromGradient} from './utils'; import SpectrumIndicator from './SpectrumIndicator'; import css from './ColorPickerSpectrum.module.less'; -const SpectrumColorPickerBase = (props) => { +const SpectrumColorPicker = (props) => { const {selectedColor, selectedColorHandler} = props; const canvasRef = useRef(null); const [indicatorX, setIndicatorX] = useState(0); @@ -20,19 +19,19 @@ const SpectrumColorPickerBase = (props) => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); - const createColorGradient = (canvas, ctx) => { - for(let i = 0; i < canvas.width; i++) { - const luminosity = 1 - (i / canvas.width); // Max luminosity on the left, min luminosity on the right - const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); + const createColorGradient = (canvasElement, context) => { + for (let i = 0; i < canvasElement.width; i++) { + const luminosity = 1 - (i / canvasElement.width); // Max luminosity on the left, min luminosity on the right + const gradient = context.createLinearGradient(0, 0, 0, canvasElement.height); gradient.addColorStop(0, `hsl(0, 100%, ${luminosity * 100}%)`); // Red - gradient.addColorStop(1/6, `hsl(30, 100%, ${luminosity * 100}%)`); // Orange - gradient.addColorStop(2/6, `hsl(60, 100%, ${luminosity * 100}%)`); // Yellow - gradient.addColorStop(3/6, `hsl(120, 100%, ${luminosity * 100}%)`); // Green - gradient.addColorStop(4/6, `hsl(180, 100%, ${luminosity * 100}%)`); // Blue - gradient.addColorStop(5/6, `hsl(240, 100%, ${luminosity * 100}%)`); // Indigo + gradient.addColorStop(1 / 6, `hsl(30, 100%, ${luminosity * 100}%)`); // Orange + gradient.addColorStop(2 / 6, `hsl(60, 100%, ${luminosity * 100}%)`); // Yellow + gradient.addColorStop(3 / 6, `hsl(120, 100%, ${luminosity * 100}%)`); // Green + gradient.addColorStop(4 / 6, `hsl(180, 100%, ${luminosity * 100}%)`); // Blue + gradient.addColorStop(5 / 6, `hsl(240, 100%, ${luminosity * 100}%)`); // Indigo gradient.addColorStop(1, `hsl(269, 100%, ${luminosity * 100}%)`); // Violet - ctx.fillStyle = gradient; - ctx.fillRect(i, 0, 1, canvas.height); + context.fillStyle = gradient; + context.fillRect(i, 0, 1, canvasElement.height); } }; createColorGradient(canvas, ctx); @@ -57,7 +56,7 @@ const SpectrumColorPickerBase = (props) => { } }; positionIndicator(); - }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps const handleCanvasPointerDown = useCallback((e) => { const canvas = canvasRef.current; @@ -69,23 +68,18 @@ const SpectrumColorPickerBase = (props) => { setIndicatorY(y); setIsDragging(true); - const ctx = canvas.getContext('2d'); - const imageData = ctx.getImageData(x, y, 1, 1); - const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); setIndicatorBgColor(hexColor); setIsIndicatorActive(false); - }, [canvasRef, setIndicatorX, setIndicatorY, setIsDragging, spectrumRgbToHex]); + }, [canvasRef, indicatorX, indicatorY, setIndicatorX, setIndicatorY, setIsDragging]); - const handleCanvasPointerLeave = useCallback((e) => { + const handleCanvasPointerLeave = useCallback(() => { setIsDragging(false); - const canvas = canvasRef.current; - const ctx = canvas.getContext('2d'); - const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); selectedColorHandler(hexColor); setIndicatorBgColor(hexColor); - }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, spectrumRgbToHex]); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler]); const handleCanvasPointerMove = useCallback((e) => { if (isDragging) { @@ -97,22 +91,17 @@ const SpectrumColorPickerBase = (props) => { setIndicatorX(x); setIndicatorY(y); - const ctx = canvas.getContext('2d'); - const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); setIndicatorBgColor(hexColor); } - }, [canvasRef, indicatorX, indicatorY, isDragging, selectedColorHandler, spectrumRgbToHex]); + }, [canvasRef, indicatorX, indicatorY, isDragging]); const handleCanvasPointerUp = useCallback(() => { - const canvas = canvasRef.current; - const ctx = canvas.getContext('2d'); - const imageData = ctx.getImageData(indicatorX, indicatorY, 1, 1); - const hexColor = spectrumRgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); selectedColorHandler(hexColor); setIndicatorBgColor(hexColor); setIsDragging(false); - }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler, spectrumRgbToHex]); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler]); return (
@@ -130,9 +119,11 @@ const SpectrumColorPickerBase = (props) => { bgColor={indicatorBgColor} canvasRef={canvasRef} isIndicatorActive={isIndicatorActive} + selectedColorHandler={selectedColorHandler} setIsIndicatorActive={setIsIndicatorActive} - setIndicatorX={setIndicatorX} - setIndicatorY={setIndicatorY} + setIndicatorBgColor={setIndicatorBgColor} + setX={setIndicatorX} + setY={setIndicatorY} x={indicatorX} y={indicatorY} /> @@ -140,10 +131,10 @@ const SpectrumColorPickerBase = (props) => { ); }; -// const SpectrumColorPickerDecorator = compose( -// // SpotlightContainerDecorator -// ); -// -// const SpectrumColorPicker = SpectrumColorPickerDecorator(SpectrumColorPickerBase); +SpectrumColorPicker.displayName = 'SpectrumColorPicker'; +SpectrumColorPicker.propTypes = { + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func +}; -export default SpectrumColorPickerBase; +export default SpectrumColorPicker; diff --git a/ColorPickerPOC/ColorPickerSpectrum.module.less b/ColorPickerPOC/ColorPickerSpectrum.module.less index 57ddfdcb0c..d7cf395e50 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.module.less +++ b/ColorPickerPOC/ColorPickerSpectrum.module.less @@ -6,8 +6,6 @@ .colorPicker { position: relative; - //height: 100%; - //width: 100%; } .circleIndicator { @@ -20,6 +18,5 @@ .focus({ border: 4px solid #ff0000; - //transform: scale(1.5); }) } diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js index 7c3c0b4627..df4fd91484 100644 --- a/ColorPickerPOC/SpectrumIndicator.js +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -1,22 +1,16 @@ import {is} from '@enact/core/keymap'; import Spottable from '@enact/spotlight/Spottable'; -import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import spotlight from '@enact/spotlight'; -import compose from 'ramda/src/compose'; -import {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import {useCallback, useEffect} from 'react'; + +import {getHexColorFromGradient} from './utils'; import css from './ColorPickerSpectrum.module.less'; const SpottableDiv = Spottable('div'); -const CircleIndicatorBase = ({bgColor, canvasRef, isIndicatorActive, setIsIndicatorActive, setIndicatorX, setIndicatorY, x, y}) => { - const [xIndicator, setXIndicator] = useState(x); - const [yIndicator, setYIndicator] = useState(y); - - useEffect(() => { - setXIndicator(x); - setYIndicator(y); - }, [x, y]) +const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHandler, setIsIndicatorActive, setIndicatorBgColor, setX, setY, x, y}) => { // resume spotlight when indicator is not active useEffect(() => { @@ -25,52 +19,77 @@ const CircleIndicatorBase = ({bgColor, canvasRef, isIndicatorActive, setIsIndica } }, [isIndicatorActive]); - // set indicator in active state and pause spotlight so no other containers get focus when selecting a color with 5-way - const handleOnKeyDown = ({keyCode}) => { - if (is('enter', keyCode)) { + const handleOnKeyDown = useCallback(({keyCode}) => { + if (is('enter', keyCode)) { // set indicator in active state and pause spotlight so no other containers get focus when selecting a color with 5-way setIsIndicatorActive(!isIndicatorActive); spotlight.pause(); + } else if (is('down', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('up', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('left', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('right', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); } - }; + }, [canvasRef, x, y, isIndicatorActive, setIsIndicatorActive, setIndicatorBgColor]); - const handleSpotlightDown = () => { - if (isIndicatorActive && yIndicator < canvasRef.current.clientHeight) { - setYIndicator(y++); - setIndicatorY(y++); + const handleOnKeyUp = useCallback(({keyCode}) => { + if (is('down', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('up', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('left', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('right', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); } - }; + }, [canvasRef, selectedColorHandler, x, y]); - const handleSpotlightLeft = () => { - if (isIndicatorActive && xIndicator >= 0) { - setXIndicator(x--); - setIndicatorX(x--); + const handleSpotlightDown = useCallback(() => { + if (isIndicatorActive && y <= canvasRef.current.clientHeight - 1) { + setY(y++); } - }; + }, [canvasRef, isIndicatorActive, setY, y]); - const handleSpotlightRight = () => { - if (isIndicatorActive && xIndicator < canvasRef.current.clientWidth) { - setXIndicator(x++); - setIndicatorX(x++); + const handleSpotlightLeft = useCallback(() => { + if (isIndicatorActive && x >= 0) { + setX(x--); } - }; + }, [isIndicatorActive, setX, x]); - const handleSpotlightUp = () => { - if (isIndicatorActive && yIndicator >= 0) { - setYIndicator(y--); - setIndicatorY(y--); + const handleSpotlightRight = useCallback(() => { + if (isIndicatorActive && x <= canvasRef.current.clientWidth) { + setX(x++); } - }; + }, [canvasRef, isIndicatorActive, setX, x]); + + const handleSpotlightUp = useCallback(() => { + if (isIndicatorActive && y >= 0) { + setY(y--); + } + }, [isIndicatorActive, setY, y]); return ( - x.toString(16).padStart(2, '0')).join(''); } +const getHexColorFromGradient = (canvasRef, x, y) => { + const ctx = canvasRef.current.getContext('2d'); + const imageData = ctx.getImageData(x, y, 1, 1); + return rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); +}; + const generateOppositeColor = (hexColor) => { hexColor = hexColor.replace('#', ''); @@ -32,6 +37,6 @@ const generateOppositeColor = (hexColor) => { export { rgbStringToHex, - spectrumRgbToHex, - generateOppositeColor + generateOppositeColor, + getHexColorFromGradient }; From 5050cb916ba982641f84cdabf82e90a9453a794b Mon Sep 17 00:00:00 2001 From: Paul Beldean Date: Thu, 8 Aug 2024 14:25:05 +0300 Subject: [PATCH 6/7] added accelerator to spectrum color picker --- ColorPickerPOC/SpectrumIndicator.js | 50 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js index df4fd91484..d858968898 100644 --- a/ColorPickerPOC/SpectrumIndicator.js +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -2,7 +2,7 @@ import {is} from '@enact/core/keymap'; import Spottable from '@enact/spotlight/Spottable'; import spotlight from '@enact/spotlight'; import PropTypes from 'prop-types'; -import {useCallback, useEffect} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {getHexColorFromGradient} from './utils'; @@ -11,6 +11,9 @@ import css from './ColorPickerSpectrum.module.less'; const SpottableDiv = Spottable('div'); const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHandler, setIsIndicatorActive, setIndicatorBgColor, setX, setY, x, y}) => { + const [holding, setHolding] = useState(false); + const [prevKey, setPrevKey] = useState(''); + const [stepValue, setStepValue] = useState(1); // resume spotlight when indicator is not active useEffect(() => { @@ -36,7 +39,22 @@ const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHa const hexColor = getHexColorFromGradient(canvasRef, x, y); setIndicatorBgColor(hexColor); } - }, [canvasRef, x, y, isIndicatorActive, setIsIndicatorActive, setIndicatorBgColor]); + + if(isIndicatorActive){ + if(holding){ + if(prevKey === keyCode) { + if(stepValue < 10) { + setStepValue(prevValue => prevValue + 1); + } + } else { + setStepValue(1); + } + } else { + setHolding(true); + setPrevKey(keyCode); + } + } + }, [canvasRef, holding, isIndicatorActive, prevKey, setIndicatorBgColor, setIsIndicatorActive, stepValue, x, y]); const handleOnKeyUp = useCallback(({keyCode}) => { if (is('down', keyCode)) { @@ -52,31 +70,35 @@ const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHa const hexColor = getHexColorFromGradient(canvasRef, x, y); selectedColorHandler(hexColor); } + + setHolding(false); + setPrevKey(''); + setStepValue(1); }, [canvasRef, selectedColorHandler, x, y]); const handleSpotlightDown = useCallback(() => { - if (isIndicatorActive && y <= canvasRef.current.clientHeight - 1) { - setY(y++); + if (isIndicatorActive && y + stepValue <= canvasRef.current.clientHeight - 1) { + setY(y + stepValue); } - }, [canvasRef, isIndicatorActive, setY, y]); + }, [canvasRef, isIndicatorActive, setY, stepValue, y]); const handleSpotlightLeft = useCallback(() => { - if (isIndicatorActive && x >= 0) { - setX(x--); + if (isIndicatorActive && x - stepValue >= 0) { + setX(x - stepValue); } - }, [isIndicatorActive, setX, x]); + }, [isIndicatorActive, setX, stepValue, x]); const handleSpotlightRight = useCallback(() => { - if (isIndicatorActive && x <= canvasRef.current.clientWidth) { - setX(x++); + if (isIndicatorActive && x + stepValue <= canvasRef.current.clientWidth) { + setX(x + stepValue); } - }, [canvasRef, isIndicatorActive, setX, x]); + }, [canvasRef, isIndicatorActive, setX, stepValue, x]); const handleSpotlightUp = useCallback(() => { - if (isIndicatorActive && y >= 0) { - setY(y--); + if (isIndicatorActive && y - stepValue >= 0) { + setY(y - stepValue); } - }, [isIndicatorActive, setY, y]); + }, [isIndicatorActive, setY, stepValue, y]); return ( Date: Fri, 9 Aug 2024 09:55:01 +0300 Subject: [PATCH 7/7] fixed review issues --- ColorPickerPOC/SpectrumIndicator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js index d858968898..d03f51f4aa 100644 --- a/ColorPickerPOC/SpectrumIndicator.js +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -40,10 +40,10 @@ const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHa setIndicatorBgColor(hexColor); } - if(isIndicatorActive){ - if(holding){ - if(prevKey === keyCode) { - if(stepValue < 10) { + if (isIndicatorActive) { + if (holding) { + if (prevKey === keyCode) { + if (stepValue < 10) { setStepValue(prevValue => prevValue + 1); } } else {