Vector gradient (#558)
|
@ -7,10 +7,20 @@ import {MIXED} from '../../helper/style-path';
|
|||
import noFillIcon from './no-fill.svg';
|
||||
import mixedFillIcon from './mixed-fill.svg';
|
||||
import styles from './color-button.css';
|
||||
import GradientTypes from '../../lib/gradient-types';
|
||||
import log from '../../log/log';
|
||||
|
||||
const colorToBackground = color => {
|
||||
if (color === MIXED || color === null) return 'white';
|
||||
return color;
|
||||
const colorToBackground = (color, color2, gradientType) => {
|
||||
if (color === MIXED || color2 === MIXED) return 'white';
|
||||
if (color === null) color = 'white';
|
||||
if (color2 === null) color2 = 'white';
|
||||
switch (gradientType) {
|
||||
case GradientTypes.SOLID: return color;
|
||||
case GradientTypes.HORIZONTAL: return `linear-gradient(to right, ${color}, ${color2})`;
|
||||
case GradientTypes.VERTICAL: return `linear-gradient(${color}, ${color2})`;
|
||||
case GradientTypes.RADIAL: return `radial-gradient(${color}, ${color2})`;
|
||||
default: log.error(`Unrecognized gradient type: ${gradientType}`);
|
||||
}
|
||||
};
|
||||
|
||||
const ColorButtonComponent = props => (
|
||||
|
@ -23,16 +33,16 @@ const ColorButtonComponent = props => (
|
|||
[styles.outlineSwatch]: props.outline && !(props.color === MIXED)
|
||||
})}
|
||||
style={{
|
||||
background: colorToBackground(props.color)
|
||||
background: colorToBackground(props.color, props.color2, props.gradientType)
|
||||
}}
|
||||
>
|
||||
{props.color === null ? (
|
||||
{props.color === null && (props.gradientType === GradientTypes.SOLID || props.color2 === null) ? (
|
||||
<img
|
||||
className={styles.swatchIcon}
|
||||
draggable={false}
|
||||
src={noFillIcon}
|
||||
/>
|
||||
) : ((props.color === MIXED ? (
|
||||
) : ((props.color === MIXED || (props.gradientType !== GradientTypes.SOLID && props.color2 === MIXED) ? (
|
||||
<img
|
||||
className={styles.swatchIcon}
|
||||
draggable={false}
|
||||
|
@ -46,6 +56,8 @@ const ColorButtonComponent = props => (
|
|||
|
||||
ColorButtonComponent.propTypes = {
|
||||
color: PropTypes.string,
|
||||
color2: PropTypes.string,
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
outline: PropTypes.bool.isRequired
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "../../css/units";
|
||||
|
||||
/* Popover styles */
|
||||
:global(.Popover-body) {
|
||||
background: white;
|
||||
|
@ -13,6 +15,10 @@
|
|||
stroke: #ddd;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swatch-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -39,6 +45,11 @@
|
|||
margin: 8px;
|
||||
}
|
||||
|
||||
.swap-button {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.swatches {
|
||||
margin: 8px;
|
||||
}
|
||||
|
@ -49,6 +60,18 @@
|
|||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.large-swatch-icon {
|
||||
width: 1.75rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.large-swatch {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.active-swatch {
|
||||
|
@ -56,7 +79,23 @@
|
|||
box-shadow: 0px 0px 0px 3px hsla(215, 100%, 65%, 0.2);
|
||||
}
|
||||
|
||||
.swatch > img {
|
||||
.swatch-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.inactive-gradient {
|
||||
filter: saturate(0%);
|
||||
}
|
||||
|
||||
.gradient-picker-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.gradient-picker-row > img + img {
|
||||
margin-left: calc(2 * $grid-unit);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,37 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import parseColor from 'parse-color';
|
||||
|
||||
import Slider from '../forms/slider.jsx';
|
||||
|
||||
import LabeledIconButton from '../labeled-icon-button/labeled-icon-button.jsx';
|
||||
import styles from './color-picker.css';
|
||||
import GradientTypes from '../../lib/gradient-types';
|
||||
import {MIXED} from '../../helper/style-path';
|
||||
|
||||
import eyeDropperIcon from './eye-dropper.svg';
|
||||
import eyeDropperIcon from './icons/eye-dropper.svg';
|
||||
import noFillIcon from '../color-button/no-fill.svg';
|
||||
import mixedFillIcon from '../color-button/mixed-fill.svg';
|
||||
import fillHorzGradientIcon from './icons/fill-horz-gradient-enabled.svg';
|
||||
import fillRadialIcon from './icons/fill-radial-enabled.svg';
|
||||
import fillSolidIcon from './icons/fill-solid-enabled.svg';
|
||||
import fillVertGradientIcon from './icons/fill-vert-gradient-enabled.svg';
|
||||
import swapIcon from './icons/swap.svg';
|
||||
|
||||
const hsvToHex = (h, s, v) =>
|
||||
// Scale hue back up to [0, 360] from [0, 100]
|
||||
parseColor(`hsv(${3.6 * h}, ${s}, ${v})`).hex
|
||||
;
|
||||
|
||||
const messages = defineMessages({
|
||||
swap: {
|
||||
defaultMessage: 'Swap',
|
||||
description: 'Label for button that swaps the two colors in a gradient',
|
||||
id: 'paint.colorPicker.swap'
|
||||
}
|
||||
});
|
||||
class ColorPickerComponent extends React.Component {
|
||||
_makeBackground (channel) {
|
||||
const stops = [];
|
||||
|
@ -41,6 +57,118 @@ class ColorPickerComponent extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<div className={styles.colorPickerContainer}>
|
||||
{this.props.shouldShowGradientTools ? (
|
||||
<div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.gradientPickerRow}>
|
||||
<img
|
||||
className={classNames({
|
||||
[styles.inactiveGradient]: this.props.gradientType !== GradientTypes.SOLID,
|
||||
[styles.clickable]: true
|
||||
})}
|
||||
draggable={false}
|
||||
src={fillSolidIcon}
|
||||
onClick={this.props.onChangeGradientTypeSolid}
|
||||
/>
|
||||
<img
|
||||
className={classNames({
|
||||
[styles.inactiveGradient]:
|
||||
this.props.gradientType !== GradientTypes.HORIZONTAL,
|
||||
[styles.clickable]: true
|
||||
})}
|
||||
draggable={false}
|
||||
src={fillHorzGradientIcon}
|
||||
onClick={this.props.onChangeGradientTypeHorizontal}
|
||||
/>
|
||||
<img
|
||||
className={classNames({
|
||||
[styles.inactiveGradient]: this.props.gradientType !== GradientTypes.VERTICAL,
|
||||
[styles.clickable]: true
|
||||
})}
|
||||
draggable={false}
|
||||
src={fillVertGradientIcon}
|
||||
onClick={this.props.onChangeGradientTypeVertical}
|
||||
/>
|
||||
<img
|
||||
className={classNames({
|
||||
[styles.inactiveGradient]: this.props.gradientType !== GradientTypes.RADIAL,
|
||||
[styles.clickable]: true
|
||||
})}
|
||||
draggable={false}
|
||||
src={fillRadialIcon}
|
||||
onClick={this.props.onChangeGradientTypeRadial}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.divider} />
|
||||
{this.props.gradientType === GradientTypes.SOLID ? null : (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.gradientPickerRow}>
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.clickable]: true,
|
||||
[styles.swatch]: true,
|
||||
[styles.largeSwatch]: true,
|
||||
[styles.activeSwatch]: this.props.colorIndex === 0
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: this.props.color === null || this.props.color === MIXED ?
|
||||
'white' : this.props.color
|
||||
}}
|
||||
onClick={this.props.onSelectColor}
|
||||
>
|
||||
{this.props.color === null ? (
|
||||
<img
|
||||
className={styles.largeSwatchIcon}
|
||||
draggable={false}
|
||||
src={noFillIcon}
|
||||
/>
|
||||
) : this.props.color === MIXED ? (
|
||||
<img
|
||||
className={styles.largeSwatchIcon}
|
||||
draggable={false}
|
||||
src={mixedFillIcon}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<LabeledIconButton
|
||||
className={styles.swapButton}
|
||||
imgSrc={swapIcon}
|
||||
title={this.props.intl.formatMessage(messages.swap)}
|
||||
onClick={this.props.onSwap}
|
||||
/>
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.clickable]: true,
|
||||
[styles.swatch]: true,
|
||||
[styles.largeSwatch]: true,
|
||||
[styles.activeSwatch]: this.props.colorIndex === 1
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: this.props.color2 === null || this.props.color2 === MIXED ?
|
||||
'white' : this.props.color2
|
||||
}}
|
||||
onClick={this.props.onSelectColor2}
|
||||
>
|
||||
{this.props.color2 === null ? (
|
||||
<img
|
||||
className={styles.largeSwatchIcon}
|
||||
draggable={false}
|
||||
src={noFillIcon}
|
||||
/>
|
||||
) : this.props.color2 === MIXED ? (
|
||||
<img
|
||||
className={styles.largeSwatchIcon}
|
||||
draggable={false}
|
||||
src={mixedFillIcon}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.row}>
|
||||
<div className={styles.rowHeader}>
|
||||
<span className={styles.labelName}>
|
||||
|
@ -98,23 +226,27 @@ class ColorPickerComponent extends React.Component {
|
|||
</div>
|
||||
<div className={styles.rowSlider}>
|
||||
<Slider
|
||||
lastSlider
|
||||
background={this._makeBackground('brightness')}
|
||||
value={this.props.brightness}
|
||||
onChange={this.props.onBrightnessChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.divider} />
|
||||
<div className={styles.swatchRow}>
|
||||
<div className={styles.swatches}>
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.clickable]: true,
|
||||
[styles.swatch]: true,
|
||||
[styles.activeSwatch]: this.props.color === null
|
||||
[styles.activeSwatch]:
|
||||
(this.props.colorIndex === 0 && this.props.color === null) ||
|
||||
(this.props.colorIndex === 1 && this.props.color2 === null)
|
||||
})}
|
||||
onClick={this.props.onTransparent}
|
||||
>
|
||||
<img
|
||||
className={styles.swatchIcon}
|
||||
draggable={false}
|
||||
src={noFillIcon}
|
||||
/>
|
||||
|
@ -123,12 +255,14 @@ class ColorPickerComponent extends React.Component {
|
|||
<div className={styles.swatches}>
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.clickable]: true,
|
||||
[styles.swatch]: true,
|
||||
[styles.activeSwatch]: this.props.isEyeDropping
|
||||
})}
|
||||
onClick={this.props.onActivateEyeDropper}
|
||||
>
|
||||
<img
|
||||
className={styles.swatchIcon}
|
||||
draggable={false}
|
||||
src={eyeDropperIcon}
|
||||
/>
|
||||
|
@ -143,14 +277,26 @@ class ColorPickerComponent extends React.Component {
|
|||
ColorPickerComponent.propTypes = {
|
||||
brightness: PropTypes.number.isRequired,
|
||||
color: PropTypes.string,
|
||||
color2: PropTypes.string,
|
||||
colorIndex: PropTypes.number.isRequired,
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
hue: PropTypes.number.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
isEyeDropping: PropTypes.bool.isRequired,
|
||||
onActivateEyeDropper: PropTypes.func.isRequired,
|
||||
onBrightnessChange: PropTypes.func.isRequired,
|
||||
onChangeGradientTypeHorizontal: PropTypes.func.isRequired,
|
||||
onChangeGradientTypeRadial: PropTypes.func.isRequired,
|
||||
onChangeGradientTypeSolid: PropTypes.func.isRequired,
|
||||
onChangeGradientTypeVertical: PropTypes.func.isRequired,
|
||||
onHueChange: PropTypes.func.isRequired,
|
||||
onSaturationChange: PropTypes.func.isRequired,
|
||||
onSelectColor: PropTypes.func.isRequired,
|
||||
onSelectColor2: PropTypes.func.isRequired,
|
||||
onSwap: PropTypes.func,
|
||||
onTransparent: PropTypes.func.isRequired,
|
||||
saturation: PropTypes.number.isRequired
|
||||
saturation: PropTypes.number.isRequired,
|
||||
shouldShowGradientTools: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default ColorPickerComponent;
|
||||
export default injectIntl(ColorPickerComponent);
|
||||
|
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>fill-horz-gradient-enabled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="linearGradient-1">
|
||||
<stop stop-color="#FFFFFF" offset="0%"></stop>
|
||||
<stop stop-color="#4C97FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="fill-horz-gradient-enabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-opacity="0.15">
|
||||
<rect id="Horizontal" stroke="#000000" fill="url(#linearGradient-1)" x="0.5" y="0.5" width="19" height="19" rx="4"></rect>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 875 B |
15
src/components/color-picker/icons/fill-radial-enabled.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51 (57462) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>fill-radial-enabled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<radialGradient cx="50%" cy="50%" fx="50%" fy="50%" r="39.3896484%" id="radialGradient-1">
|
||||
<stop stop-color="#4C97FF" offset="0%"></stop>
|
||||
<stop stop-color="#FFFFFF" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g id="fill-radial-enabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-opacity="0.15">
|
||||
<rect id="Radial" stroke="#000000" fill="url(#radialGradient-1)" x="0.5" y="0.5" width="19" height="19" rx="4"></rect>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 870 B |
10
src/components/color-picker/icons/fill-solid-enabled.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>fill-solid-enabled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="fill-solid-enabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-opacity="0.15">
|
||||
<rect id="Solid" stroke="#000000" fill="#4C97FF" x="0.5" y="0.5" width="19" height="19" rx="4"></rect>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 604 B |
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51 (57462) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>fill-vert-gradient-enabled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="100%" x2="50%" y2="3.061617e-15%" id="linearGradient-1">
|
||||
<stop stop-color="#FFFFFF" offset="0%"></stop>
|
||||
<stop stop-color="#4C97FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="fill-vert-gradient-enabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-opacity="0.15">
|
||||
<rect id="Vertical" stroke="#000000" fill="url(#linearGradient-1)" x="0.5" y="0.5" width="19" height="19" rx="4"></rect>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 881 B |
14
src/components/color-picker/icons/swap.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>swap</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="swap" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Swap-v2" transform="translate(2.000000, 2.000000)" fill="#4C97FF">
|
||||
<path d="M3.69424597,2.00682151 L7.95663608,2.66498231 C8.44536222,2.73588913 8.7900887,3.24405473 8.7176525,3.79585529 C8.66179809,4.24129561 8.34150792,4.58764819 7.95663608,4.64128284 L3.69424597,5.30126176 L3.69424597,6.79666856 C3.69424597,7.22392764 3.23781067,7.42846657 2.97250219,7.13483958 L0.120436084,3.97403142 C-0.0401453614,3.78767373 -0.0401453614,3.48404706 0.120436084,3.30587093 L2.97250219,0.135972153 C3.23781067,-0.157654834 3.69424597,0.0559747078 3.69424597,0.474143173 L3.69424597,2.00682151 Z M12.305754,10.7340942 L12.305754,9.2014159 C12.305754,8.78324744 12.7621893,8.56961789 13.0274978,8.86324488 L15.8795639,12.0331437 C16.0401454,12.2113198 16.0401454,12.5149465 15.8795639,12.7013041 L13.0274978,15.8621123 C12.7621893,16.1557393 12.305754,15.9512004 12.305754,15.5239413 L12.305754,14.0285345 L8.04336392,13.3685556 C7.65849208,13.3149209 7.33820191,12.9685683 7.2823475,12.523128 C7.2099113,11.9713275 7.55463778,11.4631619 8.04336392,11.392255 L12.305754,10.7340942 Z" id="Swap-v1"></path>
|
||||
<path d="M11.2727273,1.45454545 L13.4545455,1.45454545 C14.0567273,1.45454545 14.5454545,1.94327273 14.5454545,2.54545455 L14.5454545,4.72727273 C14.5454545,5.33054545 14.0567273,5.81818182 13.4545455,5.81818182 L11.2727273,5.81818182 C10.6705455,5.81818182 10.1818182,5.33054545 10.1818182,4.72727273 L10.1818182,2.54545455 C10.1818182,1.94327273 10.6705455,1.45454545 11.2727273,1.45454545" id="Fill-6" fill-opacity="0.5"></path>
|
||||
<path d="M2.54545455,10.1818182 L4.72727273,10.1818182 C5.32945455,10.1818182 5.81818182,10.6705455 5.81818182,11.2727273 L5.81818182,13.4545455 C5.81818182,14.0578182 5.32945455,14.5454545 4.72727273,14.5454545 L2.54545455,14.5454545 C1.94327273,14.5454545 1.45454545,14.0578182 1.45454545,13.4545455 L1.45454545,11.2727273 C1.45454545,10.6705455 1.94327273,10.1818182 2.54545455,10.1818182" id="Fill-6-Copy" fill-opacity="0.5"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -8,6 +8,8 @@ import ColorPicker from '../containers/color-picker.jsx';
|
|||
import InputGroup from './input-group/input-group.jsx';
|
||||
import Label from './forms/label.jsx';
|
||||
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
const messages = defineMessages({
|
||||
fill: {
|
||||
id: 'paint.paintEditor.fill',
|
||||
|
@ -25,7 +27,12 @@ const FillColorIndicatorComponent = props => (
|
|||
body={
|
||||
<ColorPicker
|
||||
color={props.fillColor}
|
||||
color2={props.fillColor2}
|
||||
gradientType={props.gradientType}
|
||||
shouldShowGradientTools={props.shouldShowGradientTools}
|
||||
onChangeColor={props.onChangeFillColor}
|
||||
onChangeGradientType={props.onChangeGradientType}
|
||||
onSwap={props.onSwap}
|
||||
/>
|
||||
}
|
||||
isOpen={props.fillColorModalVisible}
|
||||
|
@ -35,6 +42,8 @@ const FillColorIndicatorComponent = props => (
|
|||
<Label text={props.intl.formatMessage(messages.fill)}>
|
||||
<ColorButton
|
||||
color={props.fillColor}
|
||||
color2={props.fillColor2}
|
||||
gradientType={props.gradientType}
|
||||
onClick={props.onOpenFillColor}
|
||||
/>
|
||||
</Label>
|
||||
|
@ -46,11 +55,16 @@ FillColorIndicatorComponent.propTypes = {
|
|||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
fillColor: PropTypes.string,
|
||||
fillColor2: PropTypes.string,
|
||||
fillColorModalVisible: PropTypes.bool.isRequired,
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
intl: intlShape,
|
||||
onChangeFillColor: PropTypes.func.isRequired,
|
||||
onChangeGradientType: PropTypes.func.isRequired,
|
||||
onCloseFillColor: PropTypes.func.isRequired,
|
||||
onOpenFillColor: PropTypes.func.isRequired
|
||||
onOpenFillColor: PropTypes.func.isRequired,
|
||||
onSwap: PropTypes.func.isRequired,
|
||||
shouldShowGradientTools: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(FillColorIndicatorComponent);
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.last {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.handle {
|
||||
left: 100px;
|
||||
width: 26px;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import classNames from 'classnames';
|
||||
import {getEventXY} from '../../lib/touch-utils';
|
||||
|
||||
import styles from './slider.css';
|
||||
|
@ -63,7 +64,10 @@ class SliderComponent extends React.Component {
|
|||
halfHandleWidth;
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
className={classNames({
|
||||
[styles.container]: true,
|
||||
[styles.last]: this.props.lastSlider
|
||||
})}
|
||||
ref={this.setBackground}
|
||||
style={{
|
||||
backgroundImage: this.props.background
|
||||
|
@ -85,6 +89,7 @@ class SliderComponent extends React.Component {
|
|||
|
||||
SliderComponent.propTypes = {
|
||||
background: PropTypes.string,
|
||||
lastSlider: PropTypes.bool,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import ColorButton from './color-button/color-button.jsx';
|
|||
import ColorPicker from '../containers/color-picker.jsx';
|
||||
import InputGroup from './input-group/input-group.jsx';
|
||||
import Label from './forms/label.jsx';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
const messages = defineMessages({
|
||||
stroke: {
|
||||
|
@ -25,6 +26,10 @@ const StrokeColorIndicatorComponent = props => (
|
|||
body={
|
||||
<ColorPicker
|
||||
color={props.strokeColor}
|
||||
color2={null}
|
||||
gradientType={GradientTypes.SOLID}
|
||||
shouldShowGradientTools={false}
|
||||
// @todo handle stroke gradient
|
||||
onChangeColor={props.onChangeStrokeColor}
|
||||
/>
|
||||
}
|
||||
|
@ -36,6 +41,9 @@ const StrokeColorIndicatorComponent = props => (
|
|||
<ColorButton
|
||||
outline
|
||||
color={props.strokeColor}
|
||||
color2={null}
|
||||
gradientType={GradientTypes.SOLID}
|
||||
// @todo handle stroke gradient
|
||||
onClick={props.onOpenStrokeColor}
|
||||
/>
|
||||
</Label>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
|||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
|
||||
import BitBrushModeComponent from '../components/bit-brush-mode/bit-brush-mode.jsx';
|
||||
import BitBrushTool from '../helper/bit-tools/brush-tool';
|
||||
|
@ -33,7 +34,7 @@ class BitBrushMode extends React.Component {
|
|||
if (this.tool && nextProps.bitBrushSize !== this.props.bitBrushSize) {
|
||||
this.tool.setBrushSize(nextProps.bitBrushSize);
|
||||
}
|
||||
|
||||
|
||||
if (nextProps.isBitBrushModeActive && !this.props.isBitBrushModeActive) {
|
||||
this.activateTool();
|
||||
} else if (!nextProps.isBitBrushModeActive && this.props.isBitBrushModeActive) {
|
||||
|
@ -45,6 +46,7 @@ class BitBrushMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
let color = this.props.color;
|
||||
if (!color || color === MIXED) {
|
||||
|
@ -76,6 +78,7 @@ class BitBrushMode extends React.Component {
|
|||
|
||||
BitBrushMode.propTypes = {
|
||||
bitBrushSize: PropTypes.number.isRequired,
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
|
@ -93,6 +96,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.BIT_BRUSH));
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import FillModeComponent from '../components/bit-fill-mode/bit-fill-mode.jsx';
|
|||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import FillTool from '../helper/bit-tools/fill-tool';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
@ -42,6 +43,7 @@ class BitFillMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const fillColorPresent = this.props.color !== MIXED && this.props.color !== null;
|
||||
if (!fillColorPresent) {
|
||||
|
@ -67,6 +69,7 @@ class BitFillMode extends React.Component {
|
|||
}
|
||||
|
||||
BitFillMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
|
@ -80,6 +83,9 @@ const mapStateToProps = state => ({
|
|||
isFillModeActive: state.scratchPaint.mode === Modes.BIT_FILL
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
|||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
|
||||
import BitLineModeComponent from '../components/bit-line-mode/bit-line-mode.jsx';
|
||||
import BitLineTool from '../helper/bit-tools/line-tool';
|
||||
|
@ -33,7 +34,7 @@ class BitLineMode extends React.Component {
|
|||
if (this.tool && nextProps.bitBrushSize !== this.props.bitBrushSize) {
|
||||
this.tool.setLineSize(nextProps.bitBrushSize);
|
||||
}
|
||||
|
||||
|
||||
if (nextProps.isBitLineModeActive && !this.props.isBitLineModeActive) {
|
||||
this.activateTool();
|
||||
} else if (!nextProps.isBitLineModeActive && this.props.isBitLineModeActive) {
|
||||
|
@ -45,6 +46,7 @@ class BitLineMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default line color if fill is MIXED or transparent
|
||||
let color = this.props.color;
|
||||
if (!color || color === MIXED) {
|
||||
|
@ -76,6 +78,7 @@ class BitLineMode extends React.Component {
|
|||
|
||||
BitLineMode.propTypes = {
|
||||
bitBrushSize: PropTypes.number.isRequired,
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
|
@ -93,6 +96,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.BIT_LINE));
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {MIXED} from '../helper/style-path';
|
|||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import OvalTool from '../helper/bit-tools/oval-tool';
|
||||
import OvalModeComponent from '../components/bit-oval-mode/bit-oval-mode.jsx';
|
||||
|
@ -54,6 +55,7 @@ class BitOvalMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const fillColorPresent = this.props.color !== MIXED && this.props.color !== null;
|
||||
if (!fillColorPresent) {
|
||||
|
@ -84,6 +86,7 @@ class BitOvalMode extends React.Component {
|
|||
}
|
||||
|
||||
BitOvalMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
filled: PropTypes.bool,
|
||||
|
@ -109,6 +112,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedLeafItems(), true /* bitmapMode */));
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {MIXED} from '../helper/style-path';
|
|||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import RectTool from '../helper/bit-tools/rect-tool';
|
||||
import RectModeComponent from '../components/bit-rect-mode/bit-rect-mode.jsx';
|
||||
|
@ -54,6 +55,7 @@ class BitRectMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const fillColorPresent = this.props.color !== MIXED && this.props.color !== null;
|
||||
if (!fillColorPresent) {
|
||||
|
@ -84,6 +86,7 @@ class BitRectMode extends React.Component {
|
|||
}
|
||||
|
||||
BitRectMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
filled: PropTypes.bool,
|
||||
|
@ -109,6 +112,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedLeafItems(), true /* bitmapMode */));
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import Modes from '../lib/modes';
|
|||
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
import {getSelectedLeafItems} from '../helper/selection';
|
||||
import BitSelectTool from '../helper/bit-tools/select-tool';
|
||||
import SelectModeComponent from '../components/bit-select-mode/bit-select-mode.jsx';
|
||||
|
@ -39,6 +40,7 @@ class BitSelectMode extends React.Component {
|
|||
return nextProps.isSelectModeActive !== this.props.isSelectModeActive;
|
||||
}
|
||||
activateTool () {
|
||||
this.props.clearGradient();
|
||||
this.tool = new BitSelectTool(
|
||||
this.props.setSelectedItems,
|
||||
this.props.clearSelectedItems,
|
||||
|
@ -62,6 +64,7 @@ class BitSelectMode extends React.Component {
|
|||
}
|
||||
|
||||
BitSelectMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isSelectModeActive: PropTypes.bool.isRequired,
|
||||
|
@ -75,6 +78,9 @@ const mapStateToProps = state => ({
|
|||
selectedItems: state.scratchPaint.selectedItems
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {MIXED} from '../helper/style-path';
|
|||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
|
||||
import BrushModeComponent from '../components/brush-mode/brush-mode.jsx';
|
||||
|
@ -48,6 +49,7 @@ class BrushMode extends React.Component {
|
|||
// TODO: Instead of clearing selection, consider a kind of "draw inside"
|
||||
// analogous to how selection works with eraser
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const {fillColor} = this.props.colorState;
|
||||
if (fillColor === MIXED || fillColor === null) {
|
||||
|
@ -76,6 +78,7 @@ BrushMode.propTypes = {
|
|||
brushModeState: PropTypes.shape({
|
||||
brushSize: PropTypes.number.isRequired
|
||||
}),
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
@ -97,6 +100,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.BRUSH));
|
||||
},
|
||||
|
|
|
@ -5,8 +5,10 @@ import parseColor from 'parse-color';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import {changeColorIndex} from '../reducers/color-index';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {activateEyeDropper} from '../reducers/eye-dropper';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
import ColorPickerComponent from '../components/color-picker/color-picker.jsx';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
@ -36,6 +38,10 @@ class ColorPicker extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'getHsv',
|
||||
'handleChangeGradientTypeHorizontal',
|
||||
'handleChangeGradientTypeRadial',
|
||||
'handleChangeGradientTypeSolid',
|
||||
'handleChangeGradientTypeVertical',
|
||||
'handleHueChange',
|
||||
'handleSaturationChange',
|
||||
'handleBrightnessChange',
|
||||
|
@ -43,7 +49,8 @@ class ColorPicker extends React.Component {
|
|||
'handleActivateEyeDropper'
|
||||
]);
|
||||
|
||||
const hsv = this.getHsv(props.color);
|
||||
const color = props.colorIndex === 0 ? props.color : props.color2;
|
||||
const hsv = this.getHsv(color);
|
||||
this.state = {
|
||||
hue: hsv[0],
|
||||
saturation: hsv[1],
|
||||
|
@ -51,9 +58,11 @@ class ColorPicker extends React.Component {
|
|||
};
|
||||
}
|
||||
componentWillReceiveProps (newProps) {
|
||||
if (this.props.isEyeDropping && this.props.color !== newProps.color) {
|
||||
// color set by eye dropper, so update slider states
|
||||
const hsv = this.getHsv(newProps.color);
|
||||
const color = newProps.colorIndex === 0 ? this.props.color : this.props.color2;
|
||||
const newColor = newProps.colorIndex === 0 ? newProps.color : newProps.color2;
|
||||
const colorSetByEyedropper = this.props.isEyeDropping && color !== newColor;
|
||||
if (colorSetByEyedropper || this.props.colorIndex !== newProps.colorIndex) {
|
||||
const hsv = this.getHsv(newColor);
|
||||
this.setState({
|
||||
hue: hsv[0],
|
||||
saturation: hsv[1],
|
||||
|
@ -98,19 +107,41 @@ class ColorPicker extends React.Component {
|
|||
this.props.onChangeColor
|
||||
);
|
||||
}
|
||||
handleChangeGradientTypeHorizontal () {
|
||||
this.props.onChangeGradientType(GradientTypes.HORIZONTAL);
|
||||
}
|
||||
handleChangeGradientTypeRadial () {
|
||||
this.props.onChangeGradientType(GradientTypes.RADIAL);
|
||||
}
|
||||
handleChangeGradientTypeSolid () {
|
||||
this.props.onChangeGradientType(GradientTypes.SOLID);
|
||||
}
|
||||
handleChangeGradientTypeVertical () {
|
||||
this.props.onChangeGradientType(GradientTypes.VERTICAL);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<ColorPickerComponent
|
||||
brightness={this.state.brightness}
|
||||
color={this.props.color}
|
||||
color2={this.props.color2}
|
||||
colorIndex={this.props.colorIndex}
|
||||
gradientType={this.props.gradientType}
|
||||
hue={this.state.hue}
|
||||
isEyeDropping={this.props.isEyeDropping}
|
||||
saturation={this.state.saturation}
|
||||
shouldShowGradientTools={this.props.shouldShowGradientTools}
|
||||
onActivateEyeDropper={this.handleActivateEyeDropper}
|
||||
onBrightnessChange={this.handleBrightnessChange}
|
||||
onChangeColor={this.props.onChangeColor}
|
||||
onChangeGradientTypeHorizontal={this.handleChangeGradientTypeHorizontal}
|
||||
onChangeGradientTypeRadial={this.handleChangeGradientTypeRadial}
|
||||
onChangeGradientTypeSolid={this.handleChangeGradientTypeSolid}
|
||||
onChangeGradientTypeVertical={this.handleChangeGradientTypeVertical}
|
||||
onHueChange={this.handleHueChange}
|
||||
onSaturationChange={this.handleSaturationChange}
|
||||
onSelectColor={this.props.onSelectColor}
|
||||
onSelectColor2={this.props.onSelectColor2}
|
||||
onSwap={this.props.onSwap}
|
||||
onTransparent={this.handleTransparent}
|
||||
/>
|
||||
);
|
||||
|
@ -119,12 +150,21 @@ class ColorPicker extends React.Component {
|
|||
|
||||
ColorPicker.propTypes = {
|
||||
color: PropTypes.string,
|
||||
color2: PropTypes.string,
|
||||
colorIndex: PropTypes.number.isRequired,
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
isEyeDropping: PropTypes.bool.isRequired,
|
||||
onActivateEyeDropper: PropTypes.func.isRequired,
|
||||
onChangeColor: PropTypes.func.isRequired
|
||||
onChangeColor: PropTypes.func.isRequired,
|
||||
onChangeGradientType: PropTypes.func,
|
||||
onSelectColor: PropTypes.func.isRequired,
|
||||
onSelectColor2: PropTypes.func.isRequired,
|
||||
onSwap: PropTypes.func,
|
||||
shouldShowGradientTools: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
colorIndex: state.scratchPaint.fillMode.colorIndex,
|
||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active
|
||||
});
|
||||
|
||||
|
@ -134,6 +174,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
onActivateEyeDropper: (currentTool, callback) => {
|
||||
dispatch(activateEyeDropper(currentTool, callback));
|
||||
},
|
||||
onSelectColor: () => {
|
||||
dispatch(changeColorIndex(0));
|
||||
},
|
||||
onSelectColor2: () => {
|
||||
dispatch(changeColorIndex(1));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,21 +2,35 @@ import {connect} from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import parseColor from 'parse-color';
|
||||
|
||||
import {changeColorIndex} from '../reducers/color-index';
|
||||
import {changeFillColor} from '../reducers/fill-color';
|
||||
import {changeFillColor2} from '../reducers/fill-color-2';
|
||||
import {changeGradientType} from '../reducers/fill-mode-gradient-type';
|
||||
import {openFillColor, closeFillColor} from '../reducers/modals';
|
||||
import {getSelectedLeafItems} from '../helper/selection';
|
||||
import {setSelectedItems} from '../reducers/selected-items';
|
||||
import Modes from '../lib/modes';
|
||||
import Formats from '../lib/format';
|
||||
import {isBitmap} from '../lib/format';
|
||||
import {isBitmap, isVector} from '../lib/format';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
|
||||
import {applyFillColorToSelection} from '../helper/style-path';
|
||||
import {applyFillColorToSelection,
|
||||
applyGradientTypeToSelection,
|
||||
getRotatedColor,
|
||||
swapColorsInSelection,
|
||||
MIXED} from '../helper/style-path';
|
||||
|
||||
class FillColorIndicator extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleChangeFillColor',
|
||||
'handleCloseFillColor'
|
||||
'handleChangeGradientType',
|
||||
'handleCloseFillColor',
|
||||
'handleSwap'
|
||||
]);
|
||||
|
||||
// Flag to track whether an svg-update-worthy change has been made
|
||||
|
@ -32,56 +46,129 @@ class FillColorIndicator extends React.Component {
|
|||
}
|
||||
handleChangeFillColor (newColor) {
|
||||
// Apply color and update redux, but do not update svg until picker closes.
|
||||
const isDifferent = applyFillColorToSelection(newColor, isBitmap(this.props.format), this.props.textEditTarget);
|
||||
const isDifferent = applyFillColorToSelection(
|
||||
newColor,
|
||||
this.props.colorIndex,
|
||||
this.props.gradientType === GradientTypes.SOLID,
|
||||
isBitmap(this.props.format),
|
||||
this.props.textEditTarget);
|
||||
this._hasChanged = this._hasChanged || isDifferent;
|
||||
this.props.onChangeFillColor(newColor);
|
||||
this.props.onChangeFillColor(newColor, this.props.colorIndex);
|
||||
}
|
||||
handleChangeGradientType (gradientType) {
|
||||
// Apply color and update redux, but do not update svg until picker closes.
|
||||
const isDifferent = applyGradientTypeToSelection(
|
||||
gradientType,
|
||||
isBitmap(this.props.format),
|
||||
this.props.textEditTarget);
|
||||
this._hasChanged = this._hasChanged || isDifferent;
|
||||
const hasSelectedItems = getSelectedLeafItems().length > 0;
|
||||
if (hasSelectedItems) {
|
||||
if (isDifferent) {
|
||||
// Recalculates the swatch colors
|
||||
this.props.setSelectedItems();
|
||||
}
|
||||
}
|
||||
if (this.props.gradientType === GradientTypes.SOLID && gradientType !== GradientTypes.SOLID) {
|
||||
// Generate color 2 and change to the 2nd swatch when switching from solid to gradient
|
||||
if (!hasSelectedItems) {
|
||||
this.props.onChangeFillColor(getRotatedColor(this.props.fillColor), 1);
|
||||
}
|
||||
this.props.onChangeColorIndex(1);
|
||||
}
|
||||
this.props.onChangeGradientType(gradientType);
|
||||
}
|
||||
handleCloseFillColor () {
|
||||
if (!this.props.isEyeDropping) {
|
||||
this.props.onCloseFillColor();
|
||||
}
|
||||
this.props.onChangeColorIndex(0);
|
||||
}
|
||||
handleSwap () {
|
||||
if (getSelectedLeafItems().length) {
|
||||
swapColorsInSelection(
|
||||
isBitmap(this.props.format),
|
||||
this.props.textEditTarget);
|
||||
this.props.setSelectedItems();
|
||||
} else {
|
||||
let color1 = this.props.fillColor;
|
||||
let color2 = this.props.fillColor2;
|
||||
color1 = color1 === null || color1 === MIXED ? color1 : parseColor(color1).hex;
|
||||
color2 = color2 === null || color2 === MIXED ? color2 : parseColor(color2).hex;
|
||||
this.props.onChangeFillColor(color1, 1);
|
||||
this.props.onChangeFillColor(color2, 0);
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<FillColorIndicatorComponent
|
||||
{...this.props}
|
||||
onChangeFillColor={this.handleChangeFillColor}
|
||||
onChangeGradientType={this.handleChangeGradientType}
|
||||
onCloseFillColor={this.handleCloseFillColor}
|
||||
onSwap={this.handleSwap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
colorIndex: state.scratchPaint.fillMode.colorIndex,
|
||||
disabled: state.scratchPaint.mode === Modes.LINE,
|
||||
fillColor: state.scratchPaint.color.fillColor,
|
||||
fillColor2: state.scratchPaint.color.fillColor2,
|
||||
fillColorModalVisible: state.scratchPaint.modals.fillColor,
|
||||
format: state.scratchPaint.format,
|
||||
gradientType: state.scratchPaint.color.gradientType,
|
||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||
mode: state.scratchPaint.mode,
|
||||
shouldShowGradientTools: isVector(state.scratchPaint.format) &&
|
||||
(state.scratchPaint.mode === Modes.SELECT ||
|
||||
state.scratchPaint.mode === Modes.RESHAPE ||
|
||||
state.scratchPaint.mode === Modes.FILL),
|
||||
textEditTarget: state.scratchPaint.textEditTarget
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChangeFillColor: fillColor => {
|
||||
dispatch(changeFillColor(fillColor));
|
||||
onChangeColorIndex: index => {
|
||||
dispatch(changeColorIndex(index));
|
||||
},
|
||||
onChangeFillColor: (fillColor, index) => {
|
||||
if (index === 0) {
|
||||
dispatch(changeFillColor(fillColor));
|
||||
} else if (index === 1) {
|
||||
dispatch(changeFillColor2(fillColor));
|
||||
}
|
||||
},
|
||||
onOpenFillColor: () => {
|
||||
dispatch(openFillColor());
|
||||
},
|
||||
onCloseFillColor: () => {
|
||||
dispatch(closeFillColor());
|
||||
},
|
||||
onChangeGradientType: gradientType => {
|
||||
dispatch(changeGradientType(gradientType));
|
||||
},
|
||||
setSelectedItems: format => {
|
||||
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||
}
|
||||
});
|
||||
|
||||
FillColorIndicator.propTypes = {
|
||||
colorIndex: PropTypes.number.isRequired,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
fillColor: PropTypes.string,
|
||||
fillColor2: PropTypes.string,
|
||||
fillColorModalVisible: PropTypes.bool.isRequired,
|
||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
isEyeDropping: PropTypes.bool.isRequired,
|
||||
onChangeColorIndex: PropTypes.func.isRequired,
|
||||
onChangeFillColor: PropTypes.func.isRequired,
|
||||
onChangeGradientType: PropTypes.func.isRequired,
|
||||
onCloseFillColor: PropTypes.func.isRequired,
|
||||
onUpdateImage: PropTypes.func.isRequired,
|
||||
setSelectedItems: PropTypes.func.isRequired,
|
||||
textEditTarget: PropTypes.number
|
||||
};
|
||||
|
||||
|
|
|
@ -3,14 +3,17 @@ import React from 'react';
|
|||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../lib/modes';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
import FillTool from '../helper/tools/fill-tool';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
import {getRotatedColor, MIXED} from '../helper/style-path';
|
||||
|
||||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
import {changeFillColor2} from '../reducers/fill-color-2';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems} from '../reducers/selected-items';
|
||||
import {clearSelection} from '../helper/selection';
|
||||
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
|
||||
import {changeGradientType} from '../reducers/fill-mode-gradient-type';
|
||||
|
||||
import FillModeComponent from '../components/fill-mode/fill-mode.jsx';
|
||||
|
||||
|
@ -28,11 +31,19 @@ class FillMode extends React.Component {
|
|||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.tool && nextProps.fillColor !== this.props.fillColor) {
|
||||
this.tool.setFillColor(nextProps.fillColor);
|
||||
}
|
||||
if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
|
||||
this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
|
||||
if (this.tool) {
|
||||
if (nextProps.fillColor !== this.props.fillColor) {
|
||||
this.tool.setFillColor(nextProps.fillColor);
|
||||
}
|
||||
if (nextProps.fillColor2 !== this.props.fillColor2) {
|
||||
this.tool.setFillColor2(nextProps.fillColor2);
|
||||
}
|
||||
if (nextProps.hoveredItemId !== this.props.hoveredItemId) {
|
||||
this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
|
||||
}
|
||||
if (nextProps.fillModeGradientType !== this.props.fillModeGradientType) {
|
||||
this.tool.setGradientType(nextProps.fillModeGradientType);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextProps.isFillModeActive && !this.props.isFillModeActive) {
|
||||
|
@ -46,16 +57,35 @@ class FillMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
|
||||
// Force the default fill color if fill is MIXED
|
||||
let fillColor = this.props.fillColor;
|
||||
if (this.props.fillColor === MIXED) {
|
||||
this.props.onChangeFillColor(DEFAULT_COLOR);
|
||||
fillColor = DEFAULT_COLOR;
|
||||
this.props.onChangeFillColor(DEFAULT_COLOR, 0);
|
||||
}
|
||||
const gradientType = this.props.fillModeGradientType ?
|
||||
this.props.fillModeGradientType : this.props.selectModeGradientType;
|
||||
let fillColor2 = this.props.fillColor2;
|
||||
if (gradientType !== this.props.selectModeGradientType) {
|
||||
if (this.props.selectModeGradientType === GradientTypes.SOLID) {
|
||||
fillColor2 = getRotatedColor(fillColor);
|
||||
this.props.onChangeFillColor(fillColor2, 1);
|
||||
}
|
||||
this.props.changeGradientType(gradientType);
|
||||
}
|
||||
if (this.props.fillColor2 === MIXED) {
|
||||
fillColor2 = getRotatedColor(fillColor);
|
||||
this.props.onChangeFillColor(fillColor2, 1);
|
||||
}
|
||||
this.tool = new FillTool(
|
||||
this.props.setHoveredItem,
|
||||
this.props.clearHoveredItem,
|
||||
this.props.onUpdateImage
|
||||
);
|
||||
this.tool.setFillColor(this.props.fillColor === MIXED ? DEFAULT_COLOR : this.props.fillColor);
|
||||
this.tool.setFillColor(fillColor);
|
||||
this.tool.setFillColor2(fillColor2);
|
||||
this.tool.setGradientType(gradientType);
|
||||
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
|
||||
this.tool.activate();
|
||||
}
|
||||
|
@ -75,22 +105,28 @@ class FillMode extends React.Component {
|
|||
}
|
||||
|
||||
FillMode.propTypes = {
|
||||
changeGradientType: PropTypes.func.isRequired,
|
||||
clearHoveredItem: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
fillColor: PropTypes.string,
|
||||
fillColor2: PropTypes.string,
|
||||
fillModeGradientType: PropTypes.oneOf(Object.keys(GradientTypes)),
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
hoveredItemId: PropTypes.number,
|
||||
isFillModeActive: PropTypes.bool.isRequired,
|
||||
onChangeFillColor: PropTypes.func.isRequired,
|
||||
onUpdateImage: PropTypes.func.isRequired,
|
||||
selectModeGradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
setHoveredItem: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
fillModeState: state.scratchPaint.fillMode,
|
||||
fillModeGradientType: state.scratchPaint.fillMode.gradientType, // Last user-selected gradient type
|
||||
fillColor: state.scratchPaint.color.fillColor,
|
||||
fillColor2: state.scratchPaint.color.fillColor2,
|
||||
hoveredItemId: state.scratchPaint.hoveredItemId,
|
||||
isFillModeActive: state.scratchPaint.mode === Modes.FILL
|
||||
isFillModeActive: state.scratchPaint.mode === Modes.FILL,
|
||||
selectModeGradientType: state.scratchPaint.color.gradientType
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setHoveredItem: hoveredItemId => {
|
||||
|
@ -102,11 +138,18 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
changeGradientType: gradientType => {
|
||||
dispatch(changeGradientType(gradientType));
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.FILL));
|
||||
},
|
||||
onChangeFillColor: fillColor => {
|
||||
dispatch(changeFillColor(fillColor));
|
||||
onChangeFillColor: (fillColor, index) => {
|
||||
if (index === 0) {
|
||||
dispatch(changeFillColor(fillColor));
|
||||
} else if (index === 1) {
|
||||
dispatch(changeFillColor2(fillColor));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
|||
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
|
||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import OvalTool from '../helper/tools/oval-tool';
|
||||
|
@ -47,6 +48,7 @@ class OvalMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
|
||||
// If exactly one of fill or stroke color is set, set the other one to transparent.
|
||||
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
|
||||
|
@ -86,6 +88,7 @@ class OvalMode extends React.Component {
|
|||
}
|
||||
|
||||
OvalMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
@ -110,6 +113,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedLeafItems(), false /* bitmapMode */));
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
|||
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
|
||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import RectTool from '../helper/tools/rect-tool';
|
||||
|
@ -47,6 +48,7 @@ class RectMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
|
||||
// If exactly one of fill or stroke color is set, set the other one to transparent.
|
||||
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
|
||||
|
@ -86,6 +88,7 @@ class RectMode extends React.Component {
|
|||
}
|
||||
|
||||
RectMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
@ -110,6 +113,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
setSelectedItems: () => {
|
||||
dispatch(setSelectedItems(getSelectedLeafItems(), false /* bitmapMode */));
|
||||
},
|
||||
|
|
|
@ -55,7 +55,8 @@ class StrokeColorIndicator extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
disabled: state.scratchPaint.mode === Modes.BRUSH ||
|
||||
state.scratchPaint.mode === Modes.TEXT,
|
||||
state.scratchPaint.mode === Modes.TEXT ||
|
||||
state.scratchPaint.mode === Modes.FILL,
|
||||
format: state.scratchPaint.format,
|
||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||
strokeColor: state.scratchPaint.color.strokeColor,
|
||||
|
|
|
@ -33,7 +33,8 @@ class StrokeWidthIndicator extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
disabled: state.scratchPaint.mode === Modes.BRUSH ||
|
||||
state.scratchPaint.mode === Modes.TEXT,
|
||||
state.scratchPaint.mode === Modes.TEXT ||
|
||||
state.scratchPaint.mode === Modes.FILL,
|
||||
strokeWidth: state.scratchPaint.color.strokeWidth,
|
||||
textEditTarget: state.scratchPaint.textEditTarget
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import {changeStrokeColor} from '../reducers/stroke-color';
|
|||
import {changeMode} from '../reducers/modes';
|
||||
import {setTextEditTarget} from '../reducers/text-edit-target';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clearGradient} from '../reducers/selection-gradient-type';
|
||||
|
||||
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import TextTool from '../helper/tools/text-tool';
|
||||
|
@ -60,6 +61,7 @@ class TextMode extends React.Component {
|
|||
}
|
||||
activateTool (nextProps) {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
|
||||
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
|
||||
// If exactly one of fill or stroke color is set, set the other one to transparent.
|
||||
|
@ -116,6 +118,7 @@ class TextMode extends React.Component {
|
|||
|
||||
TextMode.propTypes = {
|
||||
changeFont: PropTypes.func.isRequired,
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
@ -155,6 +158,9 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearGradient());
|
||||
},
|
||||
handleChangeModeBitText: () => {
|
||||
dispatch(changeMode(Modes.BIT_TEXT));
|
||||
},
|
||||
|
|
|
@ -3,13 +3,16 @@ import {getSelectedLeafItems} from './selection';
|
|||
import {isPGTextItem, isPointTextItem} from './item';
|
||||
import {isGroup} from './group';
|
||||
import {getItems} from './selection';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
import parseColor from 'parse-color';
|
||||
import {DEFAULT_COLOR} from '../reducers/fill-color';
|
||||
|
||||
const MIXED = 'scratch-paint/style-path/mixed';
|
||||
|
||||
// Check if the item color matches the incoming color. If the item color is a gradient, we assume
|
||||
// that the incoming color never matches, since we don't support gradients yet.
|
||||
const _colorMatch = function (itemColor, incomingColor) {
|
||||
// @todo check whether the gradient has changed when we support gradients
|
||||
// @todo colorMatch should not be called with gradients as arguments once stroke gradients are supported
|
||||
if (itemColor && itemColor.type === 'gradient') return false;
|
||||
// Either both are null or both are the same color when converted to CSS.
|
||||
return (!itemColor && !incomingColor) ||
|
||||
|
@ -30,31 +33,221 @@ const _getColorStateListeners = function (textEditTargetId) {
|
|||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transparent R, G, B values need to match the other color of the gradient
|
||||
* in order to form a smooth gradient, otherwise it fades through black. This
|
||||
* function gets the transparent color for a given color string.
|
||||
* @param {?string} colorToMatch CSS string of other color of gradient, or null for transparent
|
||||
* @return {string} CSS string for matching color of transparent
|
||||
*/
|
||||
const getColorStringForTransparent = function (colorToMatch) {
|
||||
const color = new paper.Color(colorToMatch);
|
||||
color.alpha = 0;
|
||||
return color.toCSS();
|
||||
};
|
||||
|
||||
// Returns a color shift by 72 of the given color, DEFAULT_COLOR if the given color is null, or null if it is MIXED.
|
||||
const getRotatedColor = function (firstColor) {
|
||||
if (firstColor === MIXED) return null;
|
||||
const color = new paper.Color(firstColor);
|
||||
if (!firstColor || color.alpha === 0) return DEFAULT_COLOR;
|
||||
return parseColor(
|
||||
`hsl(${(color.hue - 72) % 360}, ${color.saturation * 100}, ${Math.max(color.lightness * 100, 10)})`).hex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when setting fill color
|
||||
* @param {string} colorString New color, css format
|
||||
* @param {string} colorString color, css format, or null if completely transparent
|
||||
* @param {number} colorIndex index of color being changed
|
||||
* @param {boolean} isSolidGradient True if is solid gradient. Sometimes the item has a gradient but the color
|
||||
* picker is set to a solid gradient. This happens when a mix of colors and gradient types is selected.
|
||||
* When changing the color in this case, the solid gradient should override the existing gradient on the item.
|
||||
* @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
|
||||
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
||||
* @return {boolean} Whether the color application actually changed visibly.
|
||||
*/
|
||||
const applyFillColorToSelection = function (colorString, bitmapMode, textEditTargetId) {
|
||||
const applyFillColorToSelection = function (colorString, colorIndex, isSolidGradient, bitmapMode, textEditTargetId) {
|
||||
const items = _getColorStateListeners(textEditTargetId);
|
||||
let changed = false;
|
||||
for (let item of items) {
|
||||
if (isPointTextItem(item) && !colorString) {
|
||||
colorString = 'rgba(0,0,0,0)';
|
||||
} else if (item.parent instanceof paper.CompoundPath) {
|
||||
if (item.parent instanceof paper.CompoundPath) {
|
||||
item = item.parent;
|
||||
}
|
||||
|
||||
// In bitmap mode, fill color applies to the stroke if there is a stroke
|
||||
if (bitmapMode && item.strokeColor !== null && item.strokeWidth !== 0) {
|
||||
if (!_colorMatch(item.strokeColor, colorString)) {
|
||||
changed = true;
|
||||
item.strokeColor = colorString;
|
||||
}
|
||||
} else if (!_colorMatch(item.fillColor, colorString)) {
|
||||
} else if (isSolidGradient || !item.fillColor || !item.fillColor.gradient ||
|
||||
!item.fillColor.gradient.stops.length === 2) {
|
||||
// Applying a solid color
|
||||
if (!_colorMatch(item.fillColor, colorString)) {
|
||||
changed = true;
|
||||
if (isPointTextItem(item) && !colorString) {
|
||||
// Allows transparent text to be hit
|
||||
item.fillColor = 'rgba(0,0,0,0)';
|
||||
} else {
|
||||
item.fillColor = colorString;
|
||||
}
|
||||
}
|
||||
} else if (!_colorMatch(item.fillColor.gradient.stops[colorIndex].color, colorString)) {
|
||||
// Changing one color of an existing gradient
|
||||
changed = true;
|
||||
item.fillColor = colorString;
|
||||
const otherIndex = colorIndex === 0 ? 1 : 0;
|
||||
if (colorString === null) {
|
||||
colorString = getColorStringForTransparent(item.fillColor.gradient.stops[otherIndex].color.toCSS());
|
||||
}
|
||||
const colors = [0, 0];
|
||||
colors[colorIndex] = colorString;
|
||||
// If the other color is transparent, its RGB values need to be adjusted for the gradient to be smooth
|
||||
if (item.fillColor.gradient.stops[otherIndex].color.alpha === 0) {
|
||||
colors[otherIndex] = getColorStringForTransparent(colorString);
|
||||
} else {
|
||||
colors[otherIndex] = item.fillColor.gradient.stops[otherIndex].color.toCSS();
|
||||
}
|
||||
// There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient.
|
||||
item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial};
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called to swap gradient colors
|
||||
* @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
|
||||
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
||||
* @return {boolean} Whether the color application actually changed visibly.
|
||||
*/
|
||||
const swapColorsInSelection = function (bitmapMode, textEditTargetId) {
|
||||
const items = _getColorStateListeners(textEditTargetId);
|
||||
let changed = false;
|
||||
for (let item of items) {
|
||||
if (item.parent instanceof paper.CompoundPath) {
|
||||
item = item.parent;
|
||||
}
|
||||
|
||||
if (bitmapMode) {
|
||||
// @todo
|
||||
return;
|
||||
} else if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops.length === 2) {
|
||||
// Only one color; nothing to swap
|
||||
continue;
|
||||
} else if (!item.fillColor.gradient.stops[0].color.equals(item.fillColor.gradient.stops[1].color)) {
|
||||
// Changing one color of an existing gradient
|
||||
changed = true;
|
||||
const colors = [
|
||||
item.fillColor.gradient.stops[1].color.toCSS(),
|
||||
item.fillColor.gradient.stops[0].color.toCSS()
|
||||
];
|
||||
// There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient.
|
||||
item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial};
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when setting gradient type
|
||||
* @param {GradientType} gradientType gradient type
|
||||
* @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
|
||||
* @param {?string} textEditTargetId paper.Item.id of text editing target, if any
|
||||
* @return {boolean} Whether the color application actually changed visibly.
|
||||
*/
|
||||
const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEditTargetId) {
|
||||
const items = _getColorStateListeners(textEditTargetId);
|
||||
let changed = false;
|
||||
for (let item of items) {
|
||||
if (item.parent instanceof paper.CompoundPath) {
|
||||
item = item.parent;
|
||||
}
|
||||
|
||||
let itemColor1;
|
||||
if (item.fillColor === null || item.fillColor.alpha === 0) {
|
||||
// Transparent
|
||||
itemColor1 = null;
|
||||
} else if (!item.fillColor.gradient) {
|
||||
// Solid color
|
||||
itemColor1 = item.fillColor.toCSS();
|
||||
} else if (!item.fillColor.gradient.stops[0] || item.fillColor.gradient.stops[0].color.alpha === 0) {
|
||||
// Gradient where first color is transparent
|
||||
itemColor1 = null;
|
||||
} else {
|
||||
// Gradient where first color is not transparent
|
||||
itemColor1 = item.fillColor.gradient.stops[0].color.toCSS();
|
||||
}
|
||||
|
||||
let itemColor2;
|
||||
if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops[1]) {
|
||||
// If item color is solid or a gradient that has no 2nd color, set the 2nd color based on the first color
|
||||
itemColor2 = getRotatedColor(itemColor1);
|
||||
} else if (item.fillColor.gradient.stops[1].color.alpha === 0) {
|
||||
// Gradient has 2nd color which is transparent
|
||||
itemColor2 = null;
|
||||
} else {
|
||||
// Gradient has 2nd color which is not transparent
|
||||
itemColor2 = item.fillColor.gradient.stops[1].color.toCSS();
|
||||
}
|
||||
|
||||
if (bitmapMode) {
|
||||
// @todo Add when we apply gradients to selections in bitmap mode
|
||||
continue;
|
||||
} else if (gradientType === GradientTypes.SOLID) {
|
||||
if (item.fillColor && item.fillColor.gradient) {
|
||||
changed = true;
|
||||
item.fillColor = itemColor1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemColor1 === null) {
|
||||
itemColor1 = getColorStringForTransparent(itemColor2);
|
||||
}
|
||||
if (itemColor2 === null) {
|
||||
itemColor2 = getColorStringForTransparent(itemColor1);
|
||||
}
|
||||
if (gradientType === GradientTypes.RADIAL) {
|
||||
const hasRadialGradient = item.fillColor && item.fillColor.gradient && item.fillColor.gradient.radial;
|
||||
if (!hasRadialGradient) {
|
||||
changed = true;
|
||||
const halfLongestDimension = Math.max(item.bounds.width, item.bounds.height) / 2;
|
||||
item.fillColor = {
|
||||
gradient: {
|
||||
stops: [itemColor1, itemColor2],
|
||||
radial: true
|
||||
},
|
||||
origin: item.position,
|
||||
destination: item.position.add(new paper.Point(halfLongestDimension, 0))
|
||||
};
|
||||
}
|
||||
} else if (gradientType === GradientTypes.HORIZONTAL) {
|
||||
const hasHorizontalGradient = item.fillColor && item.fillColor.gradient &&
|
||||
!item.fillColor.gradient.radial &&
|
||||
Math.abs(item.fillColor.origin.y - item.fillColor.destination.y) < 1e-8;
|
||||
if (!hasHorizontalGradient) {
|
||||
changed = true;
|
||||
item.fillColor = {
|
||||
gradient: {
|
||||
stops: [itemColor1, itemColor2]
|
||||
},
|
||||
origin: item.bounds.leftCenter,
|
||||
destination: item.bounds.rightCenter
|
||||
};
|
||||
}
|
||||
} else if (gradientType === GradientTypes.VERTICAL) {
|
||||
const hasVerticalGradient = item.fillColor && item.fillColor.gradient && !item.fillColor.gradient.radial &&
|
||||
Math.abs(item.fillColor.origin.x - item.fillColor.destination.x) < 1e-8;
|
||||
if (!hasVerticalGradient) {
|
||||
changed = true;
|
||||
item.fillColor = {
|
||||
gradient: {
|
||||
stops: [itemColor1, itemColor2]
|
||||
},
|
||||
origin: item.bounds.topCenter,
|
||||
destination: item.bounds.bottomCenter
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
|
@ -144,9 +337,11 @@ const applyStrokeWidthToSelection = function (value, textEditTargetId) {
|
|||
*/
|
||||
const getColorsFromSelection = function (selectedItems, bitmapMode) {
|
||||
let selectionFillColorString;
|
||||
let selectionFillColor2String;
|
||||
let selectionStrokeColorString;
|
||||
let selectionStrokeWidth;
|
||||
let selectionThickness;
|
||||
let selectionGradientType;
|
||||
let firstChild = true;
|
||||
|
||||
for (let item of selectedItems) {
|
||||
|
@ -155,47 +350,39 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
|
|||
item = item.parent;
|
||||
}
|
||||
let itemFillColorString;
|
||||
let itemFillColor2String;
|
||||
let itemStrokeColorString;
|
||||
let itemGradientType = GradientTypes.SOLID;
|
||||
|
||||
// handle pgTextItems differently by going through their children
|
||||
if (isPGTextItem(item)) {
|
||||
for (const child of item.children) {
|
||||
for (const path of child.children) {
|
||||
if (!path.data.isPGGlyphRect) {
|
||||
if (path.fillColor) {
|
||||
itemFillColorString = path.fillColor.toCSS();
|
||||
}
|
||||
if (path.strokeColor) {
|
||||
itemStrokeColorString = path.strokeColor.toCSS();
|
||||
}
|
||||
// check every style against the first of the items
|
||||
if (firstChild) {
|
||||
firstChild = false;
|
||||
selectionFillColorString = itemFillColorString;
|
||||
selectionStrokeColorString = itemStrokeColorString;
|
||||
selectionStrokeWidth = path.strokeWidth;
|
||||
}
|
||||
if (itemFillColorString !== selectionFillColorString) {
|
||||
selectionFillColorString = MIXED;
|
||||
}
|
||||
if (itemStrokeColorString !== selectionStrokeColorString) {
|
||||
selectionStrokeColorString = MIXED;
|
||||
}
|
||||
if (selectionStrokeWidth !== path.strokeWidth) {
|
||||
selectionStrokeWidth = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!isGroup(item)) {
|
||||
if (!isGroup(item)) {
|
||||
if (item.fillColor) {
|
||||
// hack bc text items with null fill can't be detected by fill-hitTest anymore
|
||||
if (isPointTextItem(item) && item.fillColor.toCSS() === 'rgba(0,0,0,0)') {
|
||||
if (isPointTextItem(item) && item.fillColor.alpha === 0) {
|
||||
itemFillColorString = null;
|
||||
} else if (item.fillColor.type === 'gradient') {
|
||||
itemFillColorString = MIXED;
|
||||
// Scratch only recognizes 2 color gradients
|
||||
if (item.fillColor.gradient.stops.length === 2) {
|
||||
if (item.fillColor.gradient.radial) {
|
||||
itemGradientType = GradientTypes.RADIAL;
|
||||
} else {
|
||||
// Always use horizontal for linear gradients, since horizontal and vertical gradients
|
||||
// are the same with rotation. We don't want to show MIXED just because anything is rotated.
|
||||
itemGradientType = GradientTypes.HORIZONTAL;
|
||||
}
|
||||
itemFillColorString = item.fillColor.gradient.stops[0].color.alpha === 0 ?
|
||||
null :
|
||||
item.fillColor.gradient.stops[0].color.toCSS();
|
||||
itemFillColor2String = item.fillColor.gradient.stops[1].color.alpha === 0 ?
|
||||
null :
|
||||
item.fillColor.gradient.stops[1].color.toCSS();
|
||||
} else {
|
||||
itemFillColorString = MIXED;
|
||||
itemFillColor2String = MIXED;
|
||||
}
|
||||
} else {
|
||||
itemFillColorString = item.fillColor.toCSS();
|
||||
itemFillColorString = item.fillColor.alpha === 0 ?
|
||||
null :
|
||||
item.fillColor.toCSS();
|
||||
}
|
||||
}
|
||||
if (item.strokeColor) {
|
||||
|
@ -205,14 +392,18 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
|
|||
} else if (item.strokeColor.type === 'gradient') {
|
||||
itemStrokeColorString = MIXED;
|
||||
} else {
|
||||
itemStrokeColorString = item.strokeColor.toCSS();
|
||||
itemStrokeColorString = item.strokeColor.alpha === 0 ?
|
||||
null :
|
||||
item.strokeColor.toCSS();
|
||||
}
|
||||
}
|
||||
// check every style against the first of the items
|
||||
if (firstChild) {
|
||||
firstChild = false;
|
||||
selectionFillColorString = itemFillColorString;
|
||||
selectionFillColor2String = itemFillColor2String;
|
||||
selectionStrokeColorString = itemStrokeColorString;
|
||||
selectionGradientType = itemGradientType;
|
||||
selectionStrokeWidth = item.strokeWidth;
|
||||
if (item.strokeWidth && item.data && item.data.zoomLevel) {
|
||||
selectionThickness = item.strokeWidth / item.data.zoomLevel;
|
||||
|
@ -221,6 +412,14 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
|
|||
if (itemFillColorString !== selectionFillColorString) {
|
||||
selectionFillColorString = MIXED;
|
||||
}
|
||||
if (itemFillColor2String !== selectionFillColor2String) {
|
||||
selectionFillColor2String = MIXED;
|
||||
}
|
||||
if (itemGradientType !== selectionGradientType) {
|
||||
selectionGradientType = GradientTypes.SOLID;
|
||||
selectionFillColorString = MIXED;
|
||||
selectionFillColor2String = MIXED;
|
||||
}
|
||||
if (itemStrokeColorString !== selectionStrokeColorString) {
|
||||
selectionStrokeColorString = MIXED;
|
||||
}
|
||||
|
@ -229,14 +428,27 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Convert selection gradient type from horizontal to vertical if first item is exactly vertical
|
||||
if (selectionGradientType !== GradientTypes.SOLID) {
|
||||
let firstItem = selectedItems[0];
|
||||
if (firstItem.parent instanceof paper.CompoundPath) firstItem = firstItem.parent;
|
||||
const direction = firstItem.fillColor.destination.subtract(firstItem.fillColor.origin);
|
||||
if (Math.abs(direction.angle) === 90) {
|
||||
selectionGradientType = GradientTypes.VERTICAL;
|
||||
}
|
||||
}
|
||||
if (bitmapMode) {
|
||||
return {
|
||||
fillColor: selectionFillColorString ? selectionFillColorString : null,
|
||||
fillColor2: selectionFillColor2String ? selectionFillColor2String : null,
|
||||
gradientType: selectionGradientType,
|
||||
thickness: selectionThickness
|
||||
};
|
||||
}
|
||||
return {
|
||||
fillColor: selectionFillColorString ? selectionFillColorString : null,
|
||||
fillColor2: selectionFillColor2String ? selectionFillColor2String : null,
|
||||
gradientType: selectionGradientType,
|
||||
strokeColor: selectionStrokeColorString ? selectionStrokeColorString : null,
|
||||
strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0
|
||||
};
|
||||
|
@ -282,12 +494,16 @@ const styleShape = function (path, options) {
|
|||
|
||||
export {
|
||||
applyFillColorToSelection,
|
||||
applyGradientTypeToSelection,
|
||||
applyStrokeColorToSelection,
|
||||
applyStrokeWidthToSelection,
|
||||
getColorsFromSelection,
|
||||
getColorStringForTransparent,
|
||||
getRotatedColor,
|
||||
MIXED,
|
||||
styleBlob,
|
||||
styleShape,
|
||||
stylePath,
|
||||
styleCursorPreview
|
||||
styleCursorPreview,
|
||||
swapColorsInSelection
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import paper from '@scratch/paper';
|
||||
import {getHoveredItem} from '../hover';
|
||||
import {expandBy} from '../math';
|
||||
import {getColorStringForTransparent} from '../style-path';
|
||||
import GradientTypes from '../../lib/gradient-types';
|
||||
|
||||
class FillTool extends paper.Tool {
|
||||
static get TOLERANCE () {
|
||||
|
@ -16,7 +18,7 @@ class FillTool extends paper.Tool {
|
|||
this.setHoveredItem = setHoveredItem;
|
||||
this.clearHoveredItem = clearHoveredItem;
|
||||
this.onUpdateImage = onUpdateImage;
|
||||
|
||||
|
||||
// We have to set these functions instead of just declaring them because
|
||||
// paper.js tools hook up the listeners in the setter functions.
|
||||
this.onMouseMove = this.handleMouseMove;
|
||||
|
@ -24,6 +26,9 @@ class FillTool extends paper.Tool {
|
|||
|
||||
// Color to fill with
|
||||
this.fillColor = null;
|
||||
this.fillColor2 = null;
|
||||
this.gradientType = null;
|
||||
|
||||
// The path that's being hovered over.
|
||||
this.fillItem = null;
|
||||
// If we're hovering over a hole in a compound path, we can't just recolor it. This is the
|
||||
|
@ -59,6 +64,12 @@ class FillTool extends paper.Tool {
|
|||
setFillColor (fillColor) {
|
||||
this.fillColor = fillColor;
|
||||
}
|
||||
setFillColor2 (fillColor2) {
|
||||
this.fillColor2 = fillColor2;
|
||||
}
|
||||
setGradientType (gradientType) {
|
||||
this.gradientType = gradientType;
|
||||
}
|
||||
/**
|
||||
* To be called when the hovered item changes. When the select tool hovers over a
|
||||
* new item, it compares against this to see if a hover item change event needs to
|
||||
|
@ -80,6 +91,10 @@ class FillTool extends paper.Tool {
|
|||
const hitItem = hoveredItem ? hoveredItem.data.origItem : null;
|
||||
// Still hitting the same thing
|
||||
if ((!hitItem && !this.fillItem) || this.fillItem === hitItem) {
|
||||
// Only radial gradient needs to be updated
|
||||
if (this.gradientType === GradientTypes.RADIAL) {
|
||||
this._setFillItemColor(this.fillColor, this.fillColor2, this.gradientType, event.point);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.fillItem) {
|
||||
|
@ -114,7 +129,7 @@ class FillTool extends paper.Tool {
|
|||
} else if (this.fillItem.parent instanceof paper.CompoundPath) {
|
||||
this.fillItemOrigColor = hitItem.parent.fillColor;
|
||||
}
|
||||
this._setFillItemColor(this.fillColor);
|
||||
this._setFillItemColor(this.fillColor, this.fillColor2, this.gradientType, event.point);
|
||||
}
|
||||
}
|
||||
handleMouseUp (event) {
|
||||
|
@ -158,14 +173,48 @@ class FillTool extends paper.Tool {
|
|||
item.strokeColor.alpha === 0 ||
|
||||
item.strokeWidth === 0;
|
||||
}
|
||||
_setFillItemColor (color) {
|
||||
if (this.addedFillItem) {
|
||||
this.addedFillItem.fillColor = color;
|
||||
} else if (this.fillItem.parent instanceof paper.CompoundPath) {
|
||||
this.fillItem.parent.fillColor = color;
|
||||
// Either pass in a fully defined paper.Color as color1,
|
||||
// or pass in 2 color strings, a gradient type, and a pointer location
|
||||
_setFillItemColor (color1, color2, gradientType, pointerLocation) {
|
||||
let fillColor;
|
||||
const item = this._getFillItem();
|
||||
if (!item) return;
|
||||
if (color1 instanceof paper.Color || gradientType === GradientTypes.SOLID) {
|
||||
fillColor = color1;
|
||||
} else {
|
||||
this.fillItem.fillColor = color;
|
||||
if (color1 === null) {
|
||||
color1 = getColorStringForTransparent(color2);
|
||||
}
|
||||
if (color2 === null) {
|
||||
color2 = getColorStringForTransparent(color1);
|
||||
}
|
||||
const halfLongestDimension = Math.max(item.bounds.width, item.bounds.height) / 2;
|
||||
const start = gradientType === GradientTypes.RADIAL ? pointerLocation :
|
||||
gradientType === GradientTypes.VERTICAL ? item.bounds.topCenter :
|
||||
gradientType === GradientTypes.HORIZONTAL ? item.bounds.leftCenter :
|
||||
null;
|
||||
const end = gradientType === GradientTypes.RADIAL ? start.add(new paper.Point(halfLongestDimension, 0)) :
|
||||
gradientType === GradientTypes.VERTICAL ? item.bounds.bottomCenter :
|
||||
gradientType === GradientTypes.HORIZONTAL ? item.bounds.rightCenter :
|
||||
null;
|
||||
fillColor = {
|
||||
gradient: {
|
||||
stops: [color1, color2],
|
||||
radial: gradientType === GradientTypes.RADIAL
|
||||
},
|
||||
origin: start,
|
||||
destination: end
|
||||
};
|
||||
}
|
||||
item.fillColor = fillColor;
|
||||
}
|
||||
_getFillItem () {
|
||||
if (this.addedFillItem) {
|
||||
return this.addedFillItem;
|
||||
} else if (this.fillItem && this.fillItem.parent instanceof paper.CompoundPath) {
|
||||
return this.fillItem.parent;
|
||||
}
|
||||
return this.fillItem;
|
||||
}
|
||||
deactivateTool () {
|
||||
if (this.fillItem) {
|
||||
|
|
9
src/lib/gradient-types.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import keyMirror from 'keymirror';
|
||||
|
||||
const GradientTypes = keyMirror({
|
||||
SOLID: null,
|
||||
HORIZONTAL: null,
|
||||
VERTICAL: null,
|
||||
RADIAL: null
|
||||
});
|
||||
export default GradientTypes;
|
36
src/reducers/color-index.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_GRADIENT_TYPE} from './fill-mode-gradient-type';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
const CHANGE_COLOR_INDEX = 'scratch-paint/color-index/CHANGE_COLOR_INDEX';
|
||||
const initialState = 0;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_COLOR_INDEX:
|
||||
if (action.index !== 1 && action.index !== 0) {
|
||||
log.warn(`Invalid color index: ${action.index}`);
|
||||
return state;
|
||||
}
|
||||
return action.index;
|
||||
case CHANGE_GRADIENT_TYPE:
|
||||
if (action.gradientType === GradientTypes.SOLID) return 0;
|
||||
/* falls through */
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
const changeColorIndex = function (index) {
|
||||
return {
|
||||
type: CHANGE_COLOR_INDEX,
|
||||
index: index
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
changeColorIndex
|
||||
};
|
|
@ -1,12 +1,16 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import eyeDropperReducer from './eye-dropper';
|
||||
import fillColorReducer from './fill-color';
|
||||
import fillColor2Reducer from './fill-color-2';
|
||||
import gradientTypeReducer from './selection-gradient-type';
|
||||
import strokeColorReducer from './stroke-color';
|
||||
import strokeWidthReducer from './stroke-width';
|
||||
|
||||
export default combineReducers({
|
||||
eyeDropper: eyeDropperReducer,
|
||||
fillColor: fillColorReducer,
|
||||
fillColor2: fillColor2Reducer,
|
||||
gradientType: gradientTypeReducer,
|
||||
strokeColor: strokeColorReducer,
|
||||
strokeWidth: strokeWidthReducer
|
||||
});
|
||||
|
|
53
src/reducers/fill-color-2.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {CLEAR_GRADIENT} from './selection-gradient-type';
|
||||
import {MIXED, getColorsFromSelection} from '../helper/style-path';
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
|
||||
const CHANGE_FILL_COLOR_2 = 'scratch-paint/fill-color/CHANGE_FILL_COLOR_2';
|
||||
// Matches hex colors
|
||||
const regExp = /^#([0-9a-f]{3}){1,2}$/i;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = null;
|
||||
switch (action.type) {
|
||||
case CHANGE_FILL_COLOR_2:
|
||||
if (!regExp.test(action.fillColor) && action.fillColor !== null && action.fillColor !== MIXED) {
|
||||
log.warn(`Invalid hex color code: ${action.fillColor}`);
|
||||
return state;
|
||||
}
|
||||
return action.fillColor;
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
{
|
||||
// Don't change state if no selection
|
||||
if (!action.selectedItems || !action.selectedItems.length) {
|
||||
return state;
|
||||
}
|
||||
const colors = getColorsFromSelection(action.selectedItems);
|
||||
if (colors.gradientType === GradientTypes.SOLID) {
|
||||
// Gradient type may be solid when multiple gradient types are selected.
|
||||
// In this case, changing the first color should not change the second color.
|
||||
if (colors.fillColor2 === MIXED) return MIXED;
|
||||
return state;
|
||||
}
|
||||
return colors.fillColor2;
|
||||
}
|
||||
case CLEAR_GRADIENT:
|
||||
return null;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
const changeFillColor2 = function (fillColor) {
|
||||
return {
|
||||
type: CHANGE_FILL_COLOR_2,
|
||||
fillColor: fillColor
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
changeFillColor2
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import log from '../log/log';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
import {getColorsFromSelection, MIXED} from '../helper/style-path';
|
||||
|
||||
const CHANGE_FILL_COLOR = 'scratch-paint/fill-color/CHANGE_FILL_COLOR';
|
||||
const DEFAULT_COLOR = '#9966FF';
|
||||
|
@ -12,7 +12,7 @@ const reducer = function (state, action) {
|
|||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_FILL_COLOR:
|
||||
if (!regExp.test(action.fillColor) && action.fillColor !== null) {
|
||||
if (!regExp.test(action.fillColor) && action.fillColor !== null && action.fillColor !== MIXED) {
|
||||
log.warn(`Invalid hex color code: ${action.fillColor}`);
|
||||
return state;
|
||||
}
|
||||
|
|
37
src/reducers/fill-mode-gradient-type.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Gradient type shown in the fill tool. This is the last gradient type explicitly chosen by the user,
|
||||
// and isn't overwritten by changing the selection.
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
import log from '../log/log';
|
||||
|
||||
const CHANGE_GRADIENT_TYPE = 'scratch-paint/fill-mode-gradient-type/CHANGE_GRADIENT_TYPE';
|
||||
const initialState = null;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_GRADIENT_TYPE:
|
||||
if (action.gradientType in GradientTypes) {
|
||||
return action.gradientType;
|
||||
}
|
||||
log.warn(`Gradient type does not exist: ${action.gradientType}`);
|
||||
/* falls through */
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
// Use this for user-initiated gradient type selections only.
|
||||
// See reducers/selection-gradient-type.js for other ways gradient type changes.
|
||||
const changeGradientType = function (gradientType) {
|
||||
return {
|
||||
type: CHANGE_GRADIENT_TYPE,
|
||||
gradientType: gradientType
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
CHANGE_GRADIENT_TYPE,
|
||||
changeGradientType
|
||||
};
|
8
src/reducers/fill-mode.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import fillModeGradientTypeReducer from './fill-mode-gradient-type';
|
||||
import colorIndexReducer from './color-index';
|
||||
|
||||
export default combineReducers({
|
||||
gradientType: fillModeGradientTypeReducer,
|
||||
colorIndex: colorIndexReducer
|
||||
});
|
|
@ -7,6 +7,7 @@ import eraserModeReducer from './eraser-mode';
|
|||
import colorReducer from './color';
|
||||
import clipboardReducer from './clipboard';
|
||||
import fillBitmapShapesReducer from './fill-bitmap-shapes';
|
||||
import fillModeReducer from './fill-mode';
|
||||
import fontReducer from './font';
|
||||
import formatReducer from './format';
|
||||
import hoverReducer from './hover';
|
||||
|
@ -25,6 +26,7 @@ export default combineReducers({
|
|||
clipboard: clipboardReducer,
|
||||
eraserMode: eraserModeReducer,
|
||||
fillBitmapShapes: fillBitmapShapesReducer,
|
||||
fillMode: fillModeReducer,
|
||||
font: fontReducer,
|
||||
format: formatReducer,
|
||||
hoveredItemId: hoverReducer,
|
||||
|
|
44
src/reducers/selection-gradient-type.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Gradient type shown in the select tool
|
||||
import GradientTypes from '../lib/gradient-types';
|
||||
import {getColorsFromSelection} from '../helper/style-path';
|
||||
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||
import {CHANGE_GRADIENT_TYPE} from './fill-mode-gradient-type';
|
||||
import log from '../log/log';
|
||||
|
||||
const CLEAR_GRADIENT = 'scratch-paint/selection-gradient-type/CLEAR_GRADIENT';
|
||||
const initialState = GradientTypes.SOLID;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_GRADIENT_TYPE:
|
||||
if (action.gradientType in GradientTypes) {
|
||||
return action.gradientType;
|
||||
}
|
||||
log.warn(`Gradient type does not exist: ${action.gradientType}`);
|
||||
return state;
|
||||
case CLEAR_GRADIENT:
|
||||
return GradientTypes.SOLID;
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
// Don't change state if no selection
|
||||
if (!action.selectedItems || !action.selectedItems.length) {
|
||||
return state;
|
||||
}
|
||||
return getColorsFromSelection(action.selectedItems, action.bitmapMode).gradientType;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
const clearGradient = function () {
|
||||
return {
|
||||
type: CLEAR_GRADIENT
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
CLEAR_GRADIENT,
|
||||
clearGradient
|
||||
};
|