Roughness, opacity and more styles

This commit is contained in:
Paulo Menezes 2020-01-05 19:59:25 -03:00
parent e6d032b6c5
commit e3a450b021
2 changed files with 362 additions and 106 deletions

View file

@ -2,7 +2,7 @@ import React, { ChangeEvent } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import rough from "roughjs/bin/wrappers/rough"; import rough from "roughjs/bin/wrappers/rough";
import { RoughCanvas } from "roughjs/bin/canvas"; import { RoughCanvas } from "roughjs/bin/canvas";
import { SketchPicker } from "react-color"; import { SketchPicker, ColorResult } from "react-color";
import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex"; import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
import { roundRect } from "./roundRect"; import { roundRect } from "./roundRect";
@ -255,6 +255,9 @@ function newElement(
strokeColor: string, strokeColor: string,
backgroundColor: string, backgroundColor: string,
fillStyle: string, fillStyle: string,
strokeWidth: number,
roughness: number,
opacity: number,
width = 0, width = 0,
height = 0 height = 0
) { ) {
@ -268,6 +271,9 @@ function newElement(
strokeColor: strokeColor, strokeColor: strokeColor,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
fillStyle: fillStyle, fillStyle: fillStyle,
strokeWidth: strokeWidth,
roughness: roughness,
opacity: opacity,
seed: randomSeed(), seed: randomSeed(),
draw( draw(
rc: RoughCanvas, rc: RoughCanvas,
@ -747,7 +753,6 @@ function getDiamondPoints(element: ExcalidrawElement) {
} }
function generateDraw(element: ExcalidrawElement) { function generateDraw(element: ExcalidrawElement) {
console.log(element);
if (element.type === "selection") { if (element.type === "selection") {
element.draw = (rc, context, { scrollX, scrollY }) => { element.draw = (rc, context, { scrollX, scrollY }) => {
const fillStyle = context.fillStyle; const fillStyle = context.fillStyle;
@ -765,13 +770,17 @@ function generateDraw(element: ExcalidrawElement) {
return generator.rectangle(0, 0, element.width, element.height, { return generator.rectangle(0, 0, element.width, element.height, {
stroke: element.strokeColor, stroke: element.strokeColor,
fill: element.backgroundColor, fill: element.backgroundColor,
fillStyle: element.fillStyle fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}); });
}); });
element.draw = (rc, context, { scrollX, scrollY }) => { element.draw = (rc, context, { scrollX, scrollY }) => {
context.globalAlpha = element.opacity / 100;
context.translate(element.x + scrollX, element.y + scrollY); context.translate(element.x + scrollX, element.y + scrollY);
rc.draw(shape); rc.draw(shape);
context.translate(-element.x - scrollX, -element.y - scrollY); context.translate(-element.x - scrollX, -element.y - scrollY);
context.globalAlpha = 1;
}; };
} else if (element.type === "diamond") { } else if (element.type === "diamond") {
const shape = withCustomMathRandom(element.seed, () => { const shape = withCustomMathRandom(element.seed, () => {
@ -810,33 +819,56 @@ function generateDraw(element: ExcalidrawElement) {
element.height / 2, element.height / 2,
element.width, element.width,
element.height, element.height,
{ stroke: element.strokeColor, fill: element.backgroundColor } {
stroke: element.strokeColor,
fill: element.backgroundColor,
fillStyle: element.fillStyle,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}
) )
); );
element.draw = (rc, context, { scrollX, scrollY }) => { element.draw = (rc, context, { scrollX, scrollY }) => {
context.globalAlpha = element.opacity / 100;
context.translate(element.x + scrollX, element.y + scrollY); context.translate(element.x + scrollX, element.y + scrollY);
rc.draw(shape); rc.draw(shape);
context.translate(-element.x - scrollX, -element.y - scrollY); context.translate(-element.x - scrollX, -element.y - scrollY);
context.globalAlpha = 1;
}; };
} else if (element.type === "arrow") { } else if (element.type === "arrow") {
const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element); const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
const shapes = withCustomMathRandom(element.seed, () => [ const shapes = withCustomMathRandom(element.seed, () => [
// \ // \
generator.line(x3, y3, x2, y2, { stroke: element.strokeColor }), generator.line(x3, y3, x2, y2, {
stroke: element.strokeColor,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}),
// ----- // -----
generator.line(x1, y1, x2, y2, { stroke: element.strokeColor }), generator.line(x1, y1, x2, y2, {
stroke: element.strokeColor,
strokeWidth: element.strokeWidth,
roughness: element.roughness
}),
// / // /
generator.line(x4, y4, x2, y2, { stroke: element.strokeColor }) generator.line(x4, y4, x2, y2, {
stroke: element.strokeColor,
strokeWidth: element.strokeWidth,
roughness: element.roughness
})
]); ]);
element.draw = (rc, context, { scrollX, scrollY }) => { element.draw = (rc, context, { scrollX, scrollY }) => {
context.globalAlpha = element.opacity / 100;
context.translate(element.x + scrollX, element.y + scrollY); context.translate(element.x + scrollX, element.y + scrollY);
shapes.forEach(shape => rc.draw(shape)); shapes.forEach(shape => rc.draw(shape));
context.translate(-element.x - scrollX, -element.y - scrollY); context.translate(-element.x - scrollX, -element.y - scrollY);
context.globalAlpha = 1;
}; };
return; return;
} else if (isTextElement(element)) { } else if (isTextElement(element)) {
element.draw = (rc, context, { scrollX, scrollY }) => { element.draw = (rc, context, { scrollX, scrollY }) => {
context.globalAlpha = element.opacity / 100;
const font = context.font; const font = context.font;
context.font = element.font; context.font = element.font;
const fillStyle = context.fillStyle; const fillStyle = context.fillStyle;
@ -848,6 +880,7 @@ function generateDraw(element: ExcalidrawElement) {
); );
context.fillStyle = fillStyle; context.fillStyle = fillStyle;
context.font = font; context.font = font;
context.globalAlpha = 1;
}; };
} else { } else {
throw new Error("Unimplemented type " + element.type); throw new Error("Unimplemented type " + element.type);
@ -1068,6 +1101,88 @@ function getSelectedIndices() {
const someElementIsSelected = () => const someElementIsSelected = () =>
elements.some(element => element.isSelected); elements.some(element => element.isSelected);
const someElementIsSelectedIsRectangleOrEllipse = () =>
elements.some(
element =>
element.isSelected &&
(element.type === "rectangle" || element.type === "ellipse")
);
const someElementIsSelectedIsRectangleOrEllipseOrArrow = () =>
elements.some(
element =>
element.isSelected &&
(element.type === "rectangle" ||
element.type === "ellipse" ||
element.type === "arrow")
);
function getSelectedFillStyles() {
const fillStyles = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => element.fillStyle)
)
);
return fillStyles.length === 1 ? fillStyles[0] : "";
}
function getSelectedStrokeWidth() {
const strokeWidth = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => `${element.strokeWidth}`)
)
);
return strokeWidth.length === 1 ? +strokeWidth[0] : "";
}
function getSelectedRoughness() {
const roughness = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => `${element.roughness}`)
)
);
return roughness.length === 1 ? +roughness[0] : "";
}
function getSelectedOpacity() {
const opacity = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => `${element.opacity}`)
)
);
return opacity.length === 1 ? +opacity[0] : "";
}
function getSelectedStrokeColor() {
const strokeColors = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => element.strokeColor)
)
);
return strokeColors.length === 1 ? strokeColors[0] : null;
}
function getSelectedBackgroundColor() {
const backgroundColors = Array.from(
new Set(
elements
.filter(element => element.isSelected)
.map(element => element.backgroundColor)
)
);
return backgroundColors.length === 1 ? backgroundColors[0] : null;
}
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5; const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
const ELEMENT_TRANSLATE_AMOUNT = 1; const ELEMENT_TRANSLATE_AMOUNT = 1;
@ -1232,19 +1347,43 @@ class App extends React.Component<{}, AppState> {
this.setState({ name }); this.setState({ name });
} }
private changeFillStyle = (event: ChangeEvent<HTMLSelectElement>) => { private changeProperty = (callback: (element: ExcalidrawElement) => void) => {
console.log(event.target.value);
elements.forEach(element => { elements.forEach(element => {
console.log("forceUpdate");
if (element.isSelected) { if (element.isSelected) {
element.fillStyle = event.target.value; callback(element);
generateDraw(element);
} }
}); });
console.log("forceUpdate");
this.forceUpdate(); this.forceUpdate();
}; };
private changeFillStyle = (style: string) => {
this.changeProperty(element => (element.fillStyle = style));
};
private changeStrokeWidth = (event: ChangeEvent<HTMLSelectElement>) => {
this.changeProperty(element => (element.strokeWidth = +event.target.value));
};
private changeRoughness = (event: ChangeEvent<HTMLSelectElement>) => {
this.changeProperty(element => (element.roughness = +event.target.value));
};
private changeOpacity = (event: ChangeEvent<HTMLInputElement>) => {
this.changeProperty(element => (element.opacity = +event.target.value));
};
private changeSrokeColor = (color: string) => {
this.changeProperty(element => (element.strokeColor = color));
this.setState({ currentItemStrokeColor: color });
};
private changeBackgroundColor = (color: string) => {
this.changeProperty(element => (element.backgroundColor = color));
this.setState({ currentItemBackgroundColor: color });
};
public render() { public render() {
const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT; const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;
const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP; const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;
@ -1370,85 +1509,6 @@ class App extends React.Component<{}, AppState> {
} }
/> />
</div> </div>
<h5>Shape Stroke Color</h5>
<div>
<button
className="swatch"
style={{
backgroundColor: this.state.currentItemStrokeColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_STROKE
? null
: ColorPicker.SHAPE_STROKE
}))
}
/>
{this.state.currentColorPicker === ColorPicker.SHAPE_STROKE ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
/>
<SketchPicker
color={this.state.currentItemStrokeColor}
onChange={color => {
this.setState({ currentItemStrokeColor: color.hex });
}}
/>
</div>
) : null}
<input
type="text"
className="swatch-input"
value={this.state.currentItemStrokeColor}
onChange={e => {
this.setState({ currentItemStrokeColor: e.target.value });
}}
/>
</div>
<h5>Shape Background Color</h5>
<div>
<button
className="swatch"
style={{
backgroundColor: this.state.currentItemBackgroundColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_BACKGROUND
? null
: ColorPicker.SHAPE_BACKGROUND
}))
}
/>
{this.state.currentColorPicker ===
ColorPicker.SHAPE_BACKGROUND ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
/>
<SketchPicker
color={this.state.currentItemBackgroundColor}
onChange={color => {
this.setState({ currentItemBackgroundColor: color.hex });
}}
/>
</div>
) : null}
<input
type="text"
className="swatch-input"
value={this.state.currentItemStrokeColor}
onChange={e => {
this.setState({ currentItemStrokeColor: e.target.value });
}}
/>
</div>
<button <button
onClick={this.clearCanvas} onClick={this.clearCanvas}
title="Clear the canvas & reset background color" title="Clear the canvas & reset background color"
@ -1501,6 +1561,89 @@ class App extends React.Component<{}, AppState> {
</div> </div>
{someElementIsSelected() && ( {someElementIsSelected() && (
<> <>
<>
<h4>Colors</h4>
<div className='panelColumn'>
<h5>Shape Stroke Color</h5>
<div>
<button
className="swatch"
style={{
backgroundColor: getSelectedStrokeColor() || this.state.currentItemStrokeColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_STROKE
? null
: ColorPicker.SHAPE_STROKE
}))
}
/>
{this.state.currentColorPicker === ColorPicker.SHAPE_STROKE && (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
/>
<SketchPicker
color={this.state.currentItemStrokeColor}
onChange={color => this.changeSrokeColor(color.hex)}
/>
</div>
)}
<input
type="text"
className="swatch-input"
value={getSelectedStrokeColor() || this.state.currentItemStrokeColor}
onChange={e => this.changeSrokeColor(e.target.value)}
/>
</div>
</div>
{someElementIsSelectedIsRectangleOrEllipse() && (
<div className='panelColumn'>
<h5>Shape Background Color</h5>
<div>
<button
className="swatch"
style={{
backgroundColor: getSelectedBackgroundColor() || this.state.currentItemBackgroundColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_BACKGROUND
? null
: ColorPicker.SHAPE_BACKGROUND
}))
}
/>
{this.state.currentColorPicker ===
ColorPicker.SHAPE_BACKGROUND ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
/>
<SketchPicker
color={this.state.currentItemBackgroundColor}
onChange={color => this.changeBackgroundColor(color.hex)}
/>
</div>
) : null}
<input
type="text"
className="swatch-input"
value={getSelectedBackgroundColor() ||
this.state.currentItemBackgroundColor}
onChange={e => this.changeBackgroundColor(e.target.value)}
/>
</div>
</div>
)}
</>
<h4>Shape options</h4> <h4>Shape options</h4>
<div className="panelColumn"> <div className="panelColumn">
<button onClick={this.deleteSelectedElements}>Delete</button> <button onClick={this.deleteSelectedElements}>Delete</button>
@ -1510,19 +1653,125 @@ class App extends React.Component<{}, AppState> {
<button onClick={this.moveAllLeft}>Send to back</button> <button onClick={this.moveAllLeft}>Send to back</button>
</div> </div>
{someElementIsSelectedIsRectangleOrEllipse() && (
<>
<h4>Fill</h4> <h4>Fill</h4>
<div className="panelColumn"> <div className="panelColumn">
<select onChange={this.changeFillStyle}> {/* <select onChange={this.changeFillStyle} value={getSelectedFillStyles()}> */}
<option value="hachure">Hachure</option> <button
<option value="solid">Solid</option> onClick={() => this.changeFillStyle("hachure")}
<option value="zigzag">Zigzag</option> className={
<option value="cross-hatch">Cross-hatch</option> getSelectedFillStyles() === "hachure" ? "active" : ""
<option value="dots">Dots</option> }
<option value="sunburst">Sunburst</option> >
<option value="dashed">Dashed</option> Hachure
<option value="zigzag-line">Zigzag-line</option> </button>
<button
onClick={() => this.changeFillStyle("solid")}
className={
getSelectedFillStyles() === "solid" ? "active" : ""
}
>
Solid
</button>
<button
onClick={() => this.changeFillStyle("zigzag")}
className={
getSelectedFillStyles() === "zigzag" ? "active" : ""
}
>
Zigzag
</button>
<button
onClick={() => this.changeFillStyle("cross-hatch")}
className={
getSelectedFillStyles() === "cross-hatch"
? "active"
: ""
}
>
Cross-hatch
</button>
<button
onClick={() => this.changeFillStyle("dots")}
className={
getSelectedFillStyles() === "dots" ? "active" : ""
}
>
Dots
</button>
<button
onClick={() => this.changeFillStyle("sunburst")}
className={
getSelectedFillStyles() === "sunburst" ? "active" : ""
}
>
Sunburst
</button>
<button
onClick={() => this.changeFillStyle("dashed")}
className={
getSelectedFillStyles() === "dashed" ? "active" : ""
}
>
Dashed
</button>
<button
onClick={() => this.changeFillStyle("zigzag-line")}
className={
getSelectedFillStyles() === "zigzag-line"
? "active"
: ""
}
>
Zigzag-line
</button>
{/* </select> */}
</div>
</>
)}
{someElementIsSelectedIsRectangleOrEllipseOrArrow() && (
<>
<h4>Stroke width</h4>
<div className="panelColumn">
<select
onChange={this.changeStrokeWidth}
value={getSelectedStrokeWidth()}
>
<option hidden disabled value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
</select> </select>
</div> </div>
<h4>Roughness</h4>
<div className="panelColumn">
<select
onChange={this.changeRoughness}
value={getSelectedRoughness()}
>
<option hidden disabled value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
<option value="10">10</option>
</select>
</div>
</>
)}
<h4>Opacity</h4>
<input
type="range"
min="0"
max="100"
onChange={this.changeOpacity}
value={getSelectedOpacity()}
/>
</> </>
)} )}
</div> </div>
@ -1600,7 +1849,10 @@ class App extends React.Component<{}, AppState> {
y, y,
this.state.currentItemStrokeColor, this.state.currentItemStrokeColor,
this.state.currentItemBackgroundColor, this.state.currentItemBackgroundColor,
"hachure" "hachure",
1,
1,
100
); );
let resizeHandle: string | false = false; let resizeHandle: string | false = false;
let isDraggingElements = false; let isDraggingElements = false;

View file

@ -110,6 +110,10 @@ input[type="color"] {
margin: 2px; margin: 2px;
} }
input[type="range"] {
width: 230px;
}
input { input {
margin-right: 5px; margin-right: 5px;
@ -136,7 +140,7 @@ button {
border-color: #d6d4d4; border-color: #d6d4d4;
} }
&:active { &:active, &.active {
background-color: #bdbebc; background-color: #bdbebc;
border-color: #bdbebc; border-color: #bdbebc;
} }