UNPKG

reanimated-color-picker

Version:
218 lines (212 loc) 7.1 kB
import React, { useCallback } from 'react'; import { ImageBackground, StyleSheet } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import usePickerContext from '../../../AppContext'; import { styles } from '../../../styles'; import Thumb from '../../Thumb/Thumb'; import { ConditionalRendering, HSVA2HSLA_string } from '../../../utils'; export function HueCircular({ children, gestures = [], style = {}, containerStyle = {}, rotate = 0, ...props }) { const { hueValue, saturationValue, brightnessValue, onGestureChange, onGestureEnd, ...ctx } = usePickerContext(); const thumbShape = props.thumbShape ?? ctx.thumbShape, thumbSize = props.thumbSize ?? ctx.thumbSize, thumbColor = props.thumbColor ?? ctx.thumbColor, renderThumb = props.renderThumb ?? ctx.renderThumb, thumbStyle = props.thumbStyle ?? ctx.thumbStyle ?? {}, sliderThickness = props.sliderThickness ?? ctx.sliderThickness, thumbScaleAnimationValue = props.thumbScaleAnimationValue ?? ctx.thumbScaleAnimationValue, thumbScaleAnimationDuration = props.thumbScaleAnimationDuration ?? ctx.thumbScaleAnimationDuration, adaptSpectrum = props.adaptSpectrum ?? ctx.adaptSpectrum, thumbInnerStyle = props.thumbInnerStyle ?? ctx.thumbInnerStyle ?? {}; const isGestureActive = useSharedValue(false); const width = useSharedValue(0); const borderRadius = useSharedValue(0); const borderRadiusStyle = useAnimatedStyle( () => ({ borderRadius: borderRadius.value, }), [borderRadius], ); const handleScale = useSharedValue(1); const handleStyle = useAnimatedStyle(() => { const center = width.value / 2, rotatedHue = (hueValue.value - rotate) % 360, distance = (width.value - sliderThickness) / 2, angle = (rotatedHue * Math.PI) / 180, posY = width.value - (Math.sin(angle) * distance + center) - thumbSize / 2, posX = width.value - (Math.cos(angle) * distance + center) - thumbSize / 2; return { transform: [ { translateX: posX, }, { translateY: posY, }, { scale: handleScale.value, }, { rotate: rotatedHue + 90 + 'deg', }, ], }; }, [width, hueValue, handleScale]); const activeSaturationStyle = useAnimatedStyle(() => { if (!adaptSpectrum) return {}; return { backgroundColor: HSVA2HSLA_string(0, 0, brightnessValue.value, 1 - saturationValue.value / 100), }; }, [brightnessValue, saturationValue]); const activeBrightnessStyle = useAnimatedStyle(() => { if (!adaptSpectrum) return {}; return { backgroundColor: HSVA2HSLA_string(0, 0, 0, 1 - brightnessValue.value / 100), }; }, [brightnessValue]); const clipViewStyle = useAnimatedStyle(() => { return { position: 'absolute', width: width.value - sliderThickness * 2, height: width.value - sliderThickness * 2, borderRadius: width.value / 2, }; }, [width]); const onGestureUpdate = ({ x, y }) => { 'worklet'; if (!isGestureActive.value) return; const center = width.value / 2, dx = center - x, dy = center - y, theta = Math.atan2(dy, dx) * (180 / Math.PI), // [0 - 180] range angle = theta < 0 ? 360 + theta : theta, // [0 - 360] range newHueValue = (angle + rotate) % 360; if (hueValue.value === newHueValue) return; hueValue.value = newHueValue; onGestureChange(); }; const onGestureBegin = event => { 'worklet'; const R = width.value / 2, dx = R - event.x, dy = R - event.y, clickR = Math.sqrt(dx * dx + dy * dy); // Check if the press is outside the circle if (clickR > R) { isGestureActive.value = false; return; } // check if the press inside the circle (not on the actual slider) const innerR = width.value / 2 - sliderThickness; if (clickR < innerR) { isGestureActive.value = false; return; } isGestureActive.value = true; handleScale.value = withTiming(thumbScaleAnimationValue, { duration: thumbScaleAnimationDuration, }); onGestureUpdate(event); }; const onGestureFinish = () => { 'worklet'; isGestureActive.value = false; handleScale.value = withTiming(1, { duration: thumbScaleAnimationDuration, }); onGestureEnd(); }; const pan = Gesture.Pan().onBegin(onGestureBegin).onUpdate(onGestureUpdate).onEnd(onGestureFinish); const tap = Gesture.Tap().onEnd(onGestureFinish); const longPress = Gesture.LongPress().onEnd(onGestureFinish); const composed = Gesture.Simultaneous(Gesture.Exclusive(pan, tap, longPress), ...gestures); const onLayout = useCallback(({ nativeEvent: { layout } }) => { const layoutWidth = layout.width; width.value = layoutWidth; borderRadius.value = withTiming(layoutWidth / 2, { duration: 5, }); }, []); return /*#__PURE__*/ React.createElement( GestureDetector, { gesture: composed, }, /*#__PURE__*/ React.createElement( Animated.View, { onLayout: onLayout, style: [ styles.panel_container, { justifyContent: 'center', alignItems: 'center', }, style, { position: 'relative', aspectRatio: 1, borderWidth: 0, padding: 0, }, borderRadiusStyle, ], }, /*#__PURE__*/ React.createElement( ImageBackground, { source: require('../../../assets/circularHue.png'), style: [ styles.panel_image, { transform: [ { rotate: -rotate + 'deg', }, ], }, ], resizeMode: 'stretch', }, /*#__PURE__*/ React.createElement( ConditionalRendering, { if: adaptSpectrum, }, /*#__PURE__*/ React.createElement(Animated.View, { style: [borderRadiusStyle, activeBrightnessStyle, StyleSheet.absoluteFillObject], }), /*#__PURE__*/ React.createElement(Animated.View, { style: [borderRadiusStyle, activeSaturationStyle, StyleSheet.absoluteFillObject], }), ), ), /*#__PURE__*/ React.createElement( Animated.View, { style: [ clipViewStyle, { backgroundColor: '#fff', }, containerStyle, ], }, children, ), /*#__PURE__*/ React.createElement(Thumb, { channel: 'h', thumbShape: thumbShape, thumbSize: thumbSize, thumbColor: thumbColor, renderThumb: renderThumb, innerStyle: thumbInnerStyle, handleStyle: handleStyle, style: thumbStyle, adaptSpectrum: adaptSpectrum, }), ), ); }