UNPKG

reanimated-color-picker

Version:
234 lines (228 loc) 7.73 kB
import React from 'react'; import { Image, View } from 'react-native'; import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated'; import usePickerContext from '../../AppContext'; import { PanelCore } from './PanelCore'; import { styles } from '../../styles'; import Thumb from '../Thumb/Thumb'; import { clamp, getStyle, isRtl } from '../../utils'; /** @see [Panel4](https://alabsi91.github.io/reanimated-color-picker/api/panels/panel4/) */ export function Panel4({ reverseHue = false, reverseHorizontalChannels = false, gestures = [], style = {}, ...props }) { const { hueValue, saturationValue, brightnessValue, onGestureChange, onGestureEnd, ...ctx } = usePickerContext(); const thumbShape = props.thumbShape ?? ctx.thumbShape; const thumbSize = props.thumbSize ?? ctx.thumbSize; const thumbColor = props.thumbColor ?? ctx.thumbColor; const boundedThumb = props.boundedThumb ?? ctx.boundedThumb; const renderThumb = props.renderThumb ?? ctx.renderThumb; const thumbStyle = props.thumbStyle ?? ctx.thumbStyle ?? {}; const thumbScaleAnimationValue = props.thumbScaleUpValue ?? ctx.thumbScaleAnimationValue; const thumbScaleAnimationDuration = props.thumbScaleUpDuration ?? ctx.thumbScaleAnimationDuration; const thumbInnerStyle = props.thumbInnerStyle ?? ctx.thumbInnerStyle ?? {}; const borderRadius = getStyle(style, 'borderRadius') ?? 5; const heightStyle = getStyle(style, 'height') ?? 200; const width = useSharedValue(0); const height = useSharedValue(0); const handleScale = useSharedValue(1); // combined brightness and saturation mapped to 0–200 const brightnessPlusSaturation = useDerivedValue(() => { const base = (2 - saturationValue.value / 100) * brightnessValue.value; return reverseHorizontalChannels ? base : 200 - base; }, [brightnessValue, saturationValue, reverseHorizontalChannels]); const thumbAnimatedStyle = useAnimatedStyle(() => { const length = { x: width.value - (boundedThumb ? thumbSize : 0), y: height.value - (boundedThumb ? thumbSize : 0), }; const thumbOffset = boundedThumb ? 0 : thumbSize / 2; // brightness and saturation const brightnessAndSaturation = (((2 - saturationValue.value / 100) * (brightnessValue.value / 100)) / 2) * 100; const posPercentX = (brightnessAndSaturation / 100) * length.x; const posX = (reverseHorizontalChannels ? posPercentX : length.x - posPercentX) - thumbOffset; // hue const percentY = (hueValue.value / 360) * length.y; const posY = (reverseHue ? percentY : length.y - percentY) - thumbOffset; return { transform: [ { translateX: posX, }, { translateY: posY, }, { scale: handleScale.value, }, ], }; }, [ width, height, saturationValue, brightnessValue, hueValue, handleScale, boundedThumb, thumbSize, reverseHorizontalChannels, reverseHue, ]); const panelImageStyle = useAnimatedStyle(() => { return { // Width and height are intentionally swapped to correct dimensions after the rotation width: height.value, height: width.value, transform: [ { scaleY: reverseHue ? -1 : 1, }, { rotate: '270deg', }, { translateX: ((width.value - height.value) / 2) * (reverseHue ? -1 : 1), }, { translateY: ((width.value - height.value) / 2) * (isRtl ? -1 : 1), }, ], }; }, [height, width, reverseHue]); const onBegin = () => { 'worklet'; handleScale.value = withTiming(thumbScaleAnimationValue, { duration: thumbScaleAnimationDuration, }); }; const onUpdate = (newXValue, newYValue) => { 'worklet'; const maxX = 200; // saturation + brightness const newSaturationValue = clamp(reverseHorizontalChannels ? maxX - newXValue : newXValue, 100); const newBrightnessValue = clamp(reverseHorizontalChannels ? newXValue : maxX - newXValue, 100); if ( hueValue.value === newYValue && saturationValue.value === newSaturationValue && brightnessValue.value === newBrightnessValue ) { return; } hueValue.value = newYValue; saturationValue.value = newSaturationValue; brightnessValue.value = newBrightnessValue; onGestureChange(); }; const onGestureUpdate = ({ x, y }) => { 'worklet'; const maxY = 360; // hue const maxX = 200; // saturation + brightness const lengthX = width.value - (boundedThumb ? thumbSize : 0); const lengthY = height.value - (boundedThumb ? thumbSize : 0); const posX = clamp(x - (boundedThumb ? thumbSize / 2 : 0), lengthX); const posY = clamp(y - (boundedThumb ? thumbSize / 2 : 0), lengthY); const valueX = (posX / lengthX) * maxX; const valueY = (posY / lengthY) * maxY; const newHueValue = reverseHue ? valueY : maxY - valueY; onUpdate(valueX, newHueValue); }; const onEnd = () => { 'worklet'; handleScale.value = withTiming(1, { duration: thumbScaleAnimationDuration, }); onGestureEnd(); }; return /*#__PURE__*/ React.createElement( PanelCore, { style: [ styles.panelContainer, style, { position: 'relative', height: heightStyle, borderWidth: 0, padding: 0, }, ], label: props.accessibilityLabel ?? 'Hue, saturation, and brightness 2D slider', hint: props.accessibilityHint ?? 'Double tap to switch between hue and saturation/brightness', labelX: 'Saturation/Brightness', currentXValue: brightnessPlusSaturation, maxXValue: 200, // saturation + brightness labelY: 'Hue', currentYValue: hueValue, maxYValue: 360, // reverseX={reverseHorizontalChannels} // Handled in onUpdate reverseY: reverseHue, width: width, height: height, gestures: gestures, onGestureUpdate: onGestureUpdate, onBegin: onBegin, onUpdate: onUpdate, onEnd: onEnd, }, /*#__PURE__*/ React.createElement( View, { style: { flex: 1, borderRadius, overflow: 'hidden', }, 'aria-hidden': true, }, /*#__PURE__*/ React.createElement(Animated.Image, { source: require('../../assets/Hue.png'), style: [styles.panelImage, panelImageStyle], resizeMode: 'stretch', }), ), /*#__PURE__*/ React.createElement( View, { style: [ styles.panelImage, { borderRadius, flexDirection: isRtl ? 'row-reverse' : 'row', transform: [ { scaleX: reverseHorizontalChannels ? -1 : 1, }, ], }, ], 'aria-hidden': true, }, /*#__PURE__*/ React.createElement(Image, { source: require('../../assets/blackGradient.png'), style: { flex: 1, }, tintColor: '#fff', resizeMode: 'stretch', }), /*#__PURE__*/ React.createElement(Image, { source: require('../../assets/blackGradient.png'), style: { flex: 1, transform: [ { scaleX: -1, }, ], }, resizeMode: 'stretch', }), ), /*#__PURE__*/ React.createElement(Thumb, { thumbShape: thumbShape, thumbSize: thumbSize, thumbColor: thumbColor, renderThumb: renderThumb, innerStyle: thumbInnerStyle, thumbAnimatedStyle: thumbAnimatedStyle, style: thumbStyle, }), ); }