feat: better default radius sizes for rectangles (#5553)

Co-authored-by: Ryan <diweihao@bytedance.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Ryan Di 2022-12-08 23:48:49 +08:00 committed by GitHub
parent 65d84a5d5a
commit 5854ac3eed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1861 additions and 818 deletions

View File

@ -153,11 +153,7 @@ const flipElement = (
let initialPointsCoords; let initialPointsCoords;
if (isLinearElement(element)) { if (isLinearElement(element)) {
initialPointsCoords = getElementPointsCoords( initialPointsCoords = getElementPointsCoords(element, element.points);
element,
element.points,
element.strokeSharpness,
);
} }
const initialElementAbsoluteCoords = getElementAbsoluteCoords(element); const initialElementAbsoluteCoords = getElementAbsoluteCoords(element);
@ -215,11 +211,7 @@ const flipElement = (
// Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin. // Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin.
// There's still room for improvement since when the line roughness is > 1 // There's still room for improvement since when the line roughness is > 1
// we still have a small offset of the origin when fliipping the element. // we still have a small offset of the origin when fliipping the element.
const finalPointsCoords = getElementPointsCoords( const finalPointsCoords = getElementPointsCoords(element, element.points);
element,
element.points,
element.strokeSharpness,
);
const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0]; const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0];
const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2]; const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2];

View File

@ -42,6 +42,7 @@ import {
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
FONT_FAMILY, FONT_FAMILY,
ROUNDNESS,
VERTICAL_ALIGN, VERTICAL_ALIGN,
} from "../constants"; } from "../constants";
import { import {
@ -57,7 +58,7 @@ import {
import { import {
isBoundToContainer, isBoundToContainer,
isLinearElement, isLinearElement,
isLinearElementType, isUsingAdaptiveRadius,
} from "../element/typeChecks"; } from "../element/typeChecks";
import { import {
Arrowhead, Arrowhead,
@ -72,7 +73,7 @@ import { getLanguage, t } from "../i18n";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { randomInteger } from "../random"; import { randomInteger } from "../random";
import { import {
canChangeSharpness, canChangeRoundness,
canHaveArrowheads, canHaveArrowheads,
getCommonAttributeOfSelectedElements, getCommonAttributeOfSelectedElements,
getSelectedElements, getSelectedElements,
@ -848,69 +849,71 @@ export const actionChangeVerticalAlign = register({
}, },
}); });
export const actionChangeSharpness = register({ export const actionChangeRoundness = register({
name: "changeSharpness", name: "changeRoundness",
trackEvent: false, trackEvent: false,
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
const targetElements = getTargetElements(
getNonDeletedElements(elements),
appState,
);
const shouldUpdateForNonLinearElements = targetElements.length
? targetElements.every((el) => !isLinearElement(el))
: !isLinearElementType(appState.activeTool.type);
const shouldUpdateForLinearElements = targetElements.length
? targetElements.every(isLinearElement)
: isLinearElementType(appState.activeTool.type);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
strokeSharpness: value, roundness:
value === "round"
? {
type: isUsingAdaptiveRadius(el.type)
? ROUNDNESS.ADAPTIVE_RADIUS
: ROUNDNESS.PROPORTIONAL_RADIUS,
}
: null,
}), }),
), ),
appState: { appState: {
...appState, ...appState,
currentItemStrokeSharpness: shouldUpdateForNonLinearElements currentItemRoundness: value,
? value
: appState.currentItemStrokeSharpness,
currentItemLinearStrokeSharpness: shouldUpdateForLinearElements
? value
: appState.currentItemLinearStrokeSharpness,
}, },
commitToHistory: true, commitToHistory: true,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData }) => {
<fieldset> const targetElements = getTargetElements(
<legend>{t("labels.edges")}</legend> getNonDeletedElements(elements),
<ButtonIconSelect appState,
group="edges" );
options={[
{ const hasLegacyRoundness = targetElements.some(
value: "sharp", (el) => el.roundness?.type === ROUNDNESS.LEGACY,
text: t("labels.sharp"), );
icon: EdgeSharpIcon,
}, return (
{ <fieldset>
value: "round", <legend>{t("labels.edges")}</legend>
text: t("labels.round"), <ButtonIconSelect
icon: EdgeRoundIcon, group="edges"
}, options={[
]} {
value={getFormValue( value: "sharp",
elements, text: t("labels.sharp"),
appState, icon: EdgeSharpIcon,
(element) => element.strokeSharpness, },
(canChangeSharpness(appState.activeTool.type) && {
(isLinearElementType(appState.activeTool.type) value: "round",
? appState.currentItemLinearStrokeSharpness text: t("labels.round"),
: appState.currentItemStrokeSharpness)) || icon: EdgeRoundIcon,
null, },
)} ]}
onChange={(value) => updateData(value)} value={getFormValue(
/> elements,
</fieldset> appState,
), (element) =>
hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
(canChangeRoundness(appState.activeTool.type) &&
appState.currentItemRoundness) ||
null,
)}
onChange={(value) => updateData(value)}
/>
</fieldset>
);
},
}); });
export const actionChangeArrowhead = register({ export const actionChangeArrowhead = register({

View File

@ -77,6 +77,7 @@ export const actionPasteStyles = register({
fillStyle: elementStylesToCopyFrom?.fillStyle, fillStyle: elementStylesToCopyFrom?.fillStyle,
opacity: elementStylesToCopyFrom?.opacity, opacity: elementStylesToCopyFrom?.opacity,
roughness: elementStylesToCopyFrom?.roughness, roughness: elementStylesToCopyFrom?.roughness,
roundness: elementStylesToCopyFrom?.roundness,
}); });
if (isTextElement(newElement)) { if (isTextElement(newElement)) {

View File

@ -91,7 +91,7 @@ export type ActionName =
| "ungroup" | "ungroup"
| "goToCollaborator" | "goToCollaborator"
| "addToLibrary" | "addToLibrary"
| "changeSharpness" | "changeRoundness"
| "alignTop" | "alignTop"
| "alignBottom" | "alignBottom"
| "alignLeft" | "alignLeft"

View File

@ -28,12 +28,11 @@ export const getDefaultAppState = (): Omit<
currentItemFillStyle: "hachure", currentItemFillStyle: "hachure",
currentItemFontFamily: DEFAULT_FONT_FAMILY, currentItemFontFamily: DEFAULT_FONT_FAMILY,
currentItemFontSize: DEFAULT_FONT_SIZE, currentItemFontSize: DEFAULT_FONT_SIZE,
currentItemLinearStrokeSharpness: "round",
currentItemOpacity: 100, currentItemOpacity: 100,
currentItemRoughness: 1, currentItemRoughness: 1,
currentItemStartArrowhead: null, currentItemStartArrowhead: null,
currentItemStrokeColor: oc.black, currentItemStrokeColor: oc.black,
currentItemStrokeSharpness: "sharp", currentItemRoundness: "round",
currentItemStrokeStyle: "solid", currentItemStrokeStyle: "solid",
currentItemStrokeWidth: 1, currentItemStrokeWidth: 1,
currentItemTextAlign: DEFAULT_TEXT_ALIGN, currentItemTextAlign: DEFAULT_TEXT_ALIGN,
@ -120,7 +119,7 @@ const APP_STATE_STORAGE_CONF = (<
currentItemFillStyle: { browser: true, export: false, server: false }, currentItemFillStyle: { browser: true, export: false, server: false },
currentItemFontFamily: { browser: true, export: false, server: false }, currentItemFontFamily: { browser: true, export: false, server: false },
currentItemFontSize: { browser: true, export: false, server: false }, currentItemFontSize: { browser: true, export: false, server: false },
currentItemLinearStrokeSharpness: { currentItemRoundness: {
browser: true, browser: true,
export: false, export: false,
server: false, server: false,
@ -129,7 +128,6 @@ const APP_STATE_STORAGE_CONF = (<
currentItemRoughness: { browser: true, export: false, server: false }, currentItemRoughness: { browser: true, export: false, server: false },
currentItemStartArrowhead: { browser: true, export: false, server: false }, currentItemStartArrowhead: { browser: true, export: false, server: false },
currentItemStrokeColor: { browser: true, export: false, server: false }, currentItemStrokeColor: { browser: true, export: false, server: false },
currentItemStrokeSharpness: { browser: true, export: false, server: false },
currentItemStrokeStyle: { browser: true, export: false, server: false }, currentItemStrokeStyle: { browser: true, export: false, server: false },
currentItemStrokeWidth: { browser: true, export: false, server: false }, currentItemStrokeWidth: { browser: true, export: false, server: false },
currentItemTextAlign: { browser: true, export: false, server: false }, currentItemTextAlign: { browser: true, export: false, server: false },

View File

@ -172,7 +172,7 @@ const commonProps = {
opacity: 100, opacity: 100,
roughness: 1, roughness: 1,
strokeColor: colors.elementStroke[0], strokeColor: colors.elementStroke[0],
strokeSharpness: "sharp", roundness: null,
strokeStyle: "solid", strokeStyle: "solid",
strokeWidth: 1, strokeWidth: 1,
verticalAlign: VERTICAL_ALIGN.MIDDLE, verticalAlign: VERTICAL_ALIGN.MIDDLE,
@ -322,7 +322,7 @@ const chartBaseElements = (
text: spreadsheet.title, text: spreadsheet.title,
x: x + chartWidth / 2, x: x + chartWidth / 2,
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE, y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
strokeSharpness: "sharp", roundness: null,
strokeStyle: "solid", strokeStyle: "solid",
textAlign: "center", textAlign: "center",
}) })

View File

@ -5,7 +5,7 @@ import { ExcalidrawElement, PointerType } from "../element/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { useDevice } from "../components/App"; import { useDevice } from "../components/App";
import { import {
canChangeSharpness, canChangeRoundness,
canHaveArrowheads, canHaveArrowheads,
getTargetElements, getTargetElements,
hasBackground, hasBackground,
@ -110,9 +110,9 @@ export const SelectedShapeActions = ({
</> </>
)} )}
{(canChangeSharpness(appState.activeTool.type) || {(canChangeRoundness(appState.activeTool.type) ||
targetElements.some((element) => canChangeSharpness(element.type))) && ( targetElements.some((element) => canChangeRoundness(element.type))) && (
<>{renderAction("changeSharpness")}</> <>{renderAction("changeRoundness")}</>
)} )}
{(hasText(appState.activeTool.type) || {(hasText(appState.activeTool.type) ||

View File

@ -70,6 +70,7 @@ import {
MQ_RIGHT_SIDEBAR_MIN_WIDTH, MQ_RIGHT_SIDEBAR_MIN_WIDTH,
MQ_SM_MAX_WIDTH, MQ_SM_MAX_WIDTH,
POINTER_BUTTON, POINTER_BUTTON,
ROUNDNESS,
SCROLL_TIMEOUT, SCROLL_TIMEOUT,
TAP_TWICE_TIMEOUT, TAP_TWICE_TIMEOUT,
TEXT_TO_CENTER_SNAP_THRESHOLD, TEXT_TO_CENTER_SNAP_THRESHOLD,
@ -134,6 +135,7 @@ import {
isInitializedImageElement, isInitializedImageElement,
isLinearElement, isLinearElement,
isLinearElementType, isLinearElementType,
isUsingAdaptiveRadius,
} from "../element/typeChecks"; } from "../element/typeChecks";
import { import {
ExcalidrawBindableElement, ExcalidrawBindableElement,
@ -1658,9 +1660,9 @@ class App extends React.Component<AppProps, AppState> {
fillStyle: this.state.currentItemFillStyle, fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth, strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roundness: null,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemStrokeSharpness,
text, text,
fontSize: this.state.currentItemFontSize, fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily, fontFamily: this.state.currentItemFontFamily,
@ -2569,7 +2571,7 @@ class App extends React.Component<AppProps, AppState> {
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemStrokeSharpness, roundness: null,
text: "", text: "",
fontSize: this.state.currentItemFontSize, fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily, fontFamily: this.state.currentItemFontFamily,
@ -4072,7 +4074,7 @@ class App extends React.Component<AppProps, AppState> {
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemLinearStrokeSharpness, roundness: null,
simulatePressure: event.pressure === 0.5, simulatePressure: event.pressure === 0.5,
locked: false, locked: false,
}); });
@ -4128,8 +4130,8 @@ class App extends React.Component<AppProps, AppState> {
strokeWidth: this.state.currentItemStrokeWidth, strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
roundness: null,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemLinearStrokeSharpness,
locked: false, locked: false,
}); });
@ -4215,7 +4217,10 @@ class App extends React.Component<AppProps, AppState> {
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemLinearStrokeSharpness, roundness:
this.state.currentItemRoundness === "round"
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
: null,
startArrowhead, startArrowhead,
endArrowhead, endArrowhead,
locked: false, locked: false,
@ -4266,7 +4271,14 @@ class App extends React.Component<AppProps, AppState> {
strokeStyle: this.state.currentItemStrokeStyle, strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness, roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity, opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemStrokeSharpness, roundness:
this.state.currentItemRoundness === "round"
? {
type: isUsingAdaptiveRadius(elementType)
? ROUNDNESS.ADAPTIVE_RADIUS
: ROUNDNESS.PROPORTIONAL_RADIUS,
}
: null,
locked: false, locked: false,
}); });

View File

@ -216,6 +216,32 @@ export const TEXT_ALIGN = {
export const ELEMENT_READY_TO_ERASE_OPACITY = 20; export const ELEMENT_READY_TO_ERASE_OPACITY = 20;
// Radius represented as 25% of element's largest side (width/height).
// Used for LEGACY and PROPORTIONAL_RADIUS algorithms, or when the element is
// below the cutoff size.
export const DEFAULT_PROPORTIONAL_RADIUS = 0.25;
// Fixed radius for the ADAPTIVE_RADIUS algorithm. In pixels.
export const DEFAULT_ADAPTIVE_RADIUS = 32;
// roundness type (algorithm)
export const ROUNDNESS = {
// Used for legacy rounding (rectangles), which currently works the same
// as PROPORTIONAL_RADIUS, but we need to differentiate for UI purposes and
// forwards-compat.
LEGACY: 1,
// Used for linear elements & diamonds
PROPORTIONAL_RADIUS: 2,
// Current default algorithm for rectangles, using fixed pixel radius.
// It's working similarly to a regular border-radius, but attemps to make
// radius visually similar across differnt element sizes, especially
// very large and very small elements.
//
// NOTE right now we don't allow configuration and use a constant radius
// (see DEFAULT_ADAPTIVE_RADIUS constant)
ADAPTIVE_RADIUS: 3,
} as const;
export const COOKIES = { export const COOKIES = {
AUTH_STATE_COOKIE: "excplus-auth", AUTH_STATE_COOKIE: "excplus-auth",
} as const; } as const;

View File

@ -3,6 +3,7 @@ import {
ExcalidrawSelectionElement, ExcalidrawSelectionElement,
ExcalidrawTextElement, ExcalidrawTextElement,
FontFamilyValues, FontFamilyValues,
StrokeRoundness,
} from "../element/types"; } from "../element/types";
import { import {
AppState, AppState,
@ -17,7 +18,7 @@ import {
isInvisiblySmallElement, isInvisiblySmallElement,
refreshTextDimensions, refreshTextDimensions,
} from "../element"; } from "../element";
import { isLinearElementType, isTextElement } from "../element/typeChecks"; import { isTextElement, isUsingAdaptiveRadius } from "../element/typeChecks";
import { randomId } from "../random"; import { randomId } from "../random";
import { import {
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
@ -25,6 +26,7 @@ import {
DEFAULT_VERTICAL_ALIGN, DEFAULT_VERTICAL_ALIGN,
PRECEDING_ELEMENT_KEY, PRECEDING_ELEMENT_KEY,
FONT_FAMILY, FONT_FAMILY,
ROUNDNESS,
} from "../constants"; } from "../constants";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { LinearElementEditor } from "../element/linearElementEditor"; import { LinearElementEditor } from "../element/linearElementEditor";
@ -74,6 +76,8 @@ const restoreElementWithProperties = <
customData?: ExcalidrawElement["customData"]; customData?: ExcalidrawElement["customData"];
/** @deprecated */ /** @deprecated */
boundElementIds?: readonly ExcalidrawElement["id"][]; boundElementIds?: readonly ExcalidrawElement["id"][];
/** @deprecated */
strokeSharpness?: StrokeRoundness;
/** metadata that may be present in elements during collaboration */ /** metadata that may be present in elements during collaboration */
[PRECEDING_ELEMENT_KEY]?: string; [PRECEDING_ELEMENT_KEY]?: string;
}, },
@ -112,9 +116,17 @@ const restoreElementWithProperties = <
height: element.height || 0, height: element.height || 0,
seed: element.seed ?? 1, seed: element.seed ?? 1,
groupIds: element.groupIds ?? [], groupIds: element.groupIds ?? [],
strokeSharpness: roundness: element.roundness
element.strokeSharpness ?? ? element.roundness
(isLinearElementType(element.type) ? "round" : "sharp"), : element.strokeSharpness === "round"
? {
// for old elements that would now use adaptive radius algo,
// use legacy algo instead
type: isUsingAdaptiveRadius(element.type)
? ROUNDNESS.LEGACY
: ROUNDNESS.PROPORTIONAL_RADIUS,
}
: null,
boundElements: element.boundElementIds boundElements: element.boundElementIds
? element.boundElementIds.map((id) => ({ type: "arrow", id })) ? element.boundElementIds.map((id) => ({ type: "arrow", id }))
: element.boundElements ?? [], : element.boundElements ?? [],

View File

@ -1,3 +1,4 @@
import { ROUNDNESS } from "../constants";
import { getElementAbsoluteCoords, getElementBounds } from "./bounds"; import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types"; import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
@ -22,6 +23,7 @@ const _ce = ({
backgroundColor: "#000", backgroundColor: "#000",
fillStyle: "solid", fillStyle: "solid",
strokeWidth: 1, strokeWidth: 1,
roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
roughness: 0, roughness: 0,
opacity: 1, opacity: 1,
x, x,

View File

@ -378,7 +378,7 @@ const generateLinearElementShape = (
const options = generateRoughOptions(element); const options = generateRoughOptions(element);
const method = (() => { const method = (() => {
if (element.strokeSharpness !== "sharp") { if (element.roundness) {
return "curve"; return "curve";
} }
if (options.fill) { if (options.fill) {
@ -561,16 +561,12 @@ export const getResizedElementAbsoluteCoords = (
} else { } else {
// Line // Line
const gen = rough.generator(); const gen = rough.generator();
const curve = const curve = !element.roundness
element.strokeSharpness === "sharp" ? gen.linearPath(
? gen.linearPath( points as [number, number][],
points as [number, number][], generateRoughOptions(element),
generateRoughOptions(element), )
) : gen.curve(points as [number, number][], generateRoughOptions(element));
: gen.curve(
points as [number, number][],
generateRoughOptions(element),
);
const ops = getCurvePathOps(curve); const ops = getCurvePathOps(curve);
bounds = getMinMaxXYFromCurvePathOps(ops); bounds = getMinMaxXYFromCurvePathOps(ops);
@ -588,12 +584,11 @@ export const getResizedElementAbsoluteCoords = (
export const getElementPointsCoords = ( export const getElementPointsCoords = (
element: ExcalidrawLinearElement, element: ExcalidrawLinearElement,
points: readonly (readonly [number, number])[], points: readonly (readonly [number, number])[],
sharpness: ExcalidrawElement["strokeSharpness"],
): [number, number, number, number] => { ): [number, number, number, number] => {
// This might be computationally heavey // This might be computationally heavey
const gen = rough.generator(); const gen = rough.generator();
const curve = const curve =
sharpness === "sharp" element.roundness == null
? gen.linearPath( ? gen.linearPath(
points as [number, number][], points as [number, number][],
generateRoughOptions(element), generateRoughOptions(element),

View File

@ -25,6 +25,7 @@ import {
ExcalidrawFreeDrawElement, ExcalidrawFreeDrawElement,
ExcalidrawImageElement, ExcalidrawImageElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,
StrokeRoundness,
} from "./types"; } from "./types";
import { getElementAbsoluteCoords, getCurvePathOps, Bounds } from "./bounds"; import { getElementAbsoluteCoords, getCurvePathOps, Bounds } from "./bounds";
@ -419,7 +420,12 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
if (args.check === isInsideCheck) { if (args.check === isInsideCheck) {
const hit = shape.some((subshape) => const hit = shape.some((subshape) =>
hitTestCurveInside(subshape, relX, relY, element.strokeSharpness), hitTestCurveInside(
subshape,
relX,
relY,
element.roundness ? "round" : "sharp",
),
); );
if (hit) { if (hit) {
return true; return true;
@ -851,7 +857,7 @@ const hitTestCurveInside = (
drawable: Drawable, drawable: Drawable,
x: number, x: number,
y: number, y: number,
sharpness: ExcalidrawElement["strokeSharpness"], roundness: StrokeRoundness,
) => { ) => {
const ops = getCurvePathOps(drawable); const ops = getCurvePathOps(drawable);
const points: Mutable<Point>[] = []; const points: Mutable<Point>[] = [];
@ -875,7 +881,7 @@ const hitTestCurveInside = (
} }
} }
if (points.length >= 4) { if (points.length >= 4) {
if (sharpness === "sharp") { if (roundness === "sharp") {
return isPointInPolygon(points, x, y); return isPointInPolygon(points, x, y);
} }
const polygonPoints = pointsOnBezierCurves(points, 10, 5); const polygonPoints = pointsOnBezierCurves(points, 10, 5);

View File

@ -527,7 +527,7 @@ export class LinearElementEditor {
endPoint[0], endPoint[0],
endPoint[1], endPoint[1],
); );
if (element.points.length > 2 && element.strokeSharpness === "round") { if (element.points.length > 2 && element.roundness) {
distance = getBezierCurveLength(element, endPoint); distance = getBezierCurveLength(element, endPoint);
} }
@ -541,7 +541,7 @@ export class LinearElementEditor {
endPointIndex: number, endPointIndex: number,
) { ) {
let segmentMidPoint = centerPoint(startPoint, endPoint); let segmentMidPoint = centerPoint(startPoint, endPoint);
if (element.points.length > 2 && element.strokeSharpness === "round") { if (element.points.length > 2 && element.roundness) {
const controlPoints = getControlPointsForBezierCurve( const controlPoints = getControlPointsForBezierCurve(
element, element,
element.points[endPointIndex], element.points[endPointIndex],
@ -1221,16 +1221,8 @@ export class LinearElementEditor {
offsetY: number, offsetY: number,
otherUpdates?: { startBinding?: PointBinding; endBinding?: PointBinding }, otherUpdates?: { startBinding?: PointBinding; endBinding?: PointBinding },
) { ) {
const nextCoords = getElementPointsCoords( const nextCoords = getElementPointsCoords(element, nextPoints);
element, const prevCoords = getElementPointsCoords(element, element.points);
nextPoints,
element.strokeSharpness || "round",
);
const prevCoords = getElementPointsCoords(
element,
element.points,
element.strokeSharpness || "round",
);
const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2; const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2;
const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2; const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2;
const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2; const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2;

View File

@ -1,7 +1,7 @@
import { duplicateElement } from "./newElement"; import { duplicateElement } from "./newElement";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { API } from "../tests/helpers/api"; import { API } from "../tests/helpers/api";
import { FONT_FAMILY } from "../constants"; import { FONT_FAMILY, ROUNDNESS } from "../constants";
import { isPrimitive } from "../utils"; import { isPrimitive } from "../utils";
const assertCloneObjects = (source: any, clone: any) => { const assertCloneObjects = (source: any, clone: any) => {
@ -25,7 +25,7 @@ it("clones arrow element", () => {
fillStyle: "hachure", fillStyle: "hachure",
strokeWidth: 1, strokeWidth: 1,
strokeStyle: "solid", strokeStyle: "solid",
strokeSharpness: "round", roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
roughness: 1, roughness: 1,
opacity: 100, opacity: 100,
}); });
@ -71,7 +71,7 @@ it("clones text element", () => {
fillStyle: "hachure", fillStyle: "hachure",
strokeWidth: 1, strokeWidth: 1,
strokeStyle: "solid", strokeStyle: "solid",
strokeSharpness: "round", roundness: null,
roughness: 1, roughness: 1,
opacity: 100, opacity: 100,
text: "hello", text: "hello",

View File

@ -62,14 +62,15 @@ const _newElementBase = <T extends ExcalidrawElement>(
height = 0, height = 0,
angle = 0, angle = 0,
groupIds = [], groupIds = [],
strokeSharpness, roundness = null,
boundElements = null, boundElements = null,
link = null, link = null,
locked, locked,
...rest ...rest
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">, }: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
) => { ) => {
const element = { // assign type to guard against excess properties
const element: Merge<ExcalidrawGenericElement, { type: T["type"] }> = {
id: rest.id || randomId(), id: rest.id || randomId(),
type, type,
x, x,
@ -85,7 +86,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
roughness, roughness,
opacity, opacity,
groupIds, groupIds,
strokeSharpness, roundness,
seed: rest.seed ?? randomInteger(), seed: rest.seed ?? randomInteger(),
version: rest.version || 1, version: rest.version || 1,
versionNonce: rest.versionNonce ?? 0, versionNonce: rest.versionNonce ?? 0,

View File

@ -152,3 +152,5 @@ export const isBoundToContainer = (
isTextElement(element) isTextElement(element)
); );
}; };
export const isUsingAdaptiveRadius = (type: string) => type === "rectangle";

View File

@ -1,5 +1,11 @@
import { Point } from "../types"; import { Point } from "../types";
import { FONT_FAMILY, TEXT_ALIGN, THEME, VERTICAL_ALIGN } from "../constants"; import {
FONT_FAMILY,
ROUNDNESS,
TEXT_ALIGN,
THEME,
VERTICAL_ALIGN,
} from "../constants";
export type ChartType = "bar" | "line"; export type ChartType = "bar" | "line";
export type FillStyle = "hachure" | "cross-hatch" | "solid"; export type FillStyle = "hachure" | "cross-hatch" | "solid";
@ -9,7 +15,8 @@ export type Theme = typeof THEME[keyof typeof THEME];
export type FontString = string & { _brand: "fontString" }; export type FontString = string & { _brand: "fontString" };
export type GroupId = string; export type GroupId = string;
export type PointerType = "mouse" | "pen" | "touch"; export type PointerType = "mouse" | "pen" | "touch";
export type StrokeSharpness = "round" | "sharp"; export type StrokeRoundness = "round" | "sharp";
export type RoundnessType = ValueOf<typeof ROUNDNESS>;
export type StrokeStyle = "solid" | "dashed" | "dotted"; export type StrokeStyle = "solid" | "dashed" | "dotted";
export type TextAlign = typeof TEXT_ALIGN[keyof typeof TEXT_ALIGN]; export type TextAlign = typeof TEXT_ALIGN[keyof typeof TEXT_ALIGN];
@ -25,7 +32,7 @@ type _ExcalidrawElementBase = Readonly<{
fillStyle: FillStyle; fillStyle: FillStyle;
strokeWidth: number; strokeWidth: number;
strokeStyle: StrokeStyle; strokeStyle: StrokeStyle;
strokeSharpness: StrokeSharpness; roundness: null | { type: RoundnessType; value?: number };
roughness: number; roughness: number;
opacity: number; opacity: number;
width: number; width: number;

View File

@ -1,6 +1,15 @@
import { NormalizedZoomValue, Point, Zoom } from "./types"; import { NormalizedZoomValue, Point, Zoom } from "./types";
import { LINE_CONFIRM_THRESHOLD } from "./constants"; import {
import { ExcalidrawLinearElement, NonDeleted } from "./element/types"; DEFAULT_ADAPTIVE_RADIUS,
LINE_CONFIRM_THRESHOLD,
DEFAULT_PROPORTIONAL_RADIUS,
ROUNDNESS,
} from "./constants";
import {
ExcalidrawElement,
ExcalidrawLinearElement,
NonDeleted,
} from "./element/types";
import { getShapeForElement } from "./renderer/renderElement"; import { getShapeForElement } from "./renderer/renderElement";
import { getCurvePathOps } from "./element/bounds"; import { getCurvePathOps } from "./element/bounds";
@ -266,6 +275,29 @@ export const getGridPoint = (
return [x, y]; return [x, y];
}; };
export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
if (
element.roundness?.type === ROUNDNESS.PROPORTIONAL_RADIUS ||
element.roundness?.type === ROUNDNESS.LEGACY
) {
return x * DEFAULT_PROPORTIONAL_RADIUS;
}
if (element.roundness?.type === ROUNDNESS.ADAPTIVE_RADIUS) {
const fixedRadiusSize = element.roundness?.value ?? DEFAULT_ADAPTIVE_RADIUS;
const CUTOFF_SIZE = fixedRadiusSize / DEFAULT_PROPORTIONAL_RADIUS;
if (x <= CUTOFF_SIZE) {
return x * DEFAULT_PROPORTIONAL_RADIUS;
}
return fixedRadiusSize;
}
return 0;
};
export const getControlPointsForBezierCurve = ( export const getControlPointsForBezierCurve = (
element: NonDeleted<ExcalidrawLinearElement>, element: NonDeleted<ExcalidrawLinearElement>,
endPoint: Point, endPoint: Point,

View File

@ -11,6 +11,12 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section. Please add the latest change on the top under the correct section.
--> -->
## Unreleased
### Excalidraw schema
- Merged `appState.currentItemStrokeSharpness` and `appState.currentItemLinearStrokeSharpness` into `appState.currentItemRoundness`. Renamed `changeSharpness` action to `changeRoundness`. Excalidraw element's `strokeSharpness` was changed to `roundness`. Check the PR for types and more details [#5553](https://github.com/excalidraw/excalidraw/pull/5553).
## 0.13.0 (2022-10-27) ## 0.13.0 (2022-10-27)
### Excalidraw API ### Excalidraw API

View File

@ -13,7 +13,7 @@ import {
withBatchedUpdates, withBatchedUpdates,
withBatchedUpdatesThrottled, withBatchedUpdatesThrottled,
} from "../../../utils"; } from "../../../utils";
import { EVENT } from "../../../constants"; import { EVENT, ROUNDNESS } from "../../../constants";
import { distance2d } from "../../../math"; import { distance2d } from "../../../math";
import { fileOpen } from "../../../data/filesystem"; import { fileOpen } from "../../../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils"; import { loadSceneOrLibraryFromBlob } from "../../utils";
@ -244,7 +244,10 @@ export default function App() {
locked: false, locked: false,
link: null, link: null,
updated: 1, updated: 1,
strokeSharpness: "round", roundness: {
type: ROUNDNESS.ADAPTIVE_RADIUS,
value: 32,
},
}, },
], ],
null, null,

View File

@ -68,7 +68,7 @@ const excalidrawDiagram = {
roughness: 1, roughness: 1,
opacity: 100, opacity: 100,
groupIds: [], groupIds: [],
strokeSharpness: "sharp", roundness: null,
seed: 1041657908, seed: 1041657908,
version: 120, version: 120,
versionNonce: 1188004276, versionNonce: 1188004276,

View File

@ -27,7 +27,7 @@ import { RoughGenerator } from "roughjs/bin/generator";
import { RenderConfig } from "../scene/types"; import { RenderConfig } from "../scene/types";
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils"; import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
import { isPathALoop } from "../math"; import { getCornerRadius, isPathALoop } from "../math";
import rough from "roughjs/bin/rough"; import rough from "roughjs/bin/rough";
import { AppState, BinaryFiles, Zoom } from "../types"; import { AppState, BinaryFiles, Zoom } from "../types";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
@ -424,10 +424,10 @@ const generateElementShape = (
switch (element.type) { switch (element.type) {
case "rectangle": case "rectangle":
if (element.strokeSharpness === "round") { if (element.roundness) {
const w = element.width; const w = element.width;
const h = element.height; const h = element.height;
const r = Math.min(w, h) * 0.25; const r = getCornerRadius(Math.min(w, h), element);
shape = generator.path( shape = generator.path(
`M ${r} 0 L ${w - r} 0 Q ${w} 0, ${w} ${r} L ${w} ${ `M ${r} 0 L ${w - r} 0 Q ${w} 0, ${w} ${r} L ${w} ${
h - r h - r
@ -451,32 +451,36 @@ const generateElementShape = (
case "diamond": { case "diamond": {
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
getDiamondPoints(element); getDiamondPoints(element);
if (element.strokeSharpness === "round") { if (element.roundness) {
const verticalRadius = getCornerRadius(
Math.abs(topX - leftX),
element,
);
const horizontalRadius = getCornerRadius(
Math.abs(rightY - topY),
element,
);
shape = generator.path( shape = generator.path(
`M ${topX + (rightX - topX) * 0.25} ${ `M ${topX + verticalRadius} ${topY + horizontalRadius} L ${
topY + (rightY - topY) * 0.25 rightX - verticalRadius
} L ${rightX - (rightX - topX) * 0.25} ${ } ${rightY - horizontalRadius}
rightY - (rightY - topY) * 0.25
}
C ${rightX} ${rightY}, ${rightX} ${rightY}, ${ C ${rightX} ${rightY}, ${rightX} ${rightY}, ${
rightX - (rightX - bottomX) * 0.25 rightX - verticalRadius
} ${rightY + (bottomY - rightY) * 0.25} } ${rightY + horizontalRadius}
L ${bottomX + (rightX - bottomX) * 0.25} ${ L ${bottomX + verticalRadius} ${bottomY - horizontalRadius}
bottomY - (bottomY - rightY) * 0.25
}
C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${ C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${
bottomX - (bottomX - leftX) * 0.25 bottomX - verticalRadius
} ${bottomY - (bottomY - leftY) * 0.25} } ${bottomY - horizontalRadius}
L ${leftX + (bottomX - leftX) * 0.25} ${ L ${leftX + verticalRadius} ${leftY + horizontalRadius}
leftY + (bottomY - leftY) * 0.25 C ${leftX} ${leftY}, ${leftX} ${leftY}, ${leftX + verticalRadius} ${
leftY - horizontalRadius
} }
C ${leftX} ${leftY}, ${leftX} ${leftY}, ${ L ${topX - verticalRadius} ${topY + horizontalRadius}
leftX + (topX - leftX) * 0.25 C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
} ${leftY - (leftY - topY) * 0.25} topY + horizontalRadius
L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25} }`,
C ${topX} ${topY}, ${topX} ${topY}, ${
topX + (rightX - topX) * 0.25
} ${topY + (rightY - topY) * 0.25}`,
generateRoughOptions(element, true), generateRoughOptions(element, true),
); );
} else { } else {
@ -515,7 +519,7 @@ const generateElementShape = (
// curve is always the first element // curve is always the first element
// this simplifies finding the curve for an element // this simplifies finding the curve for an element
if (element.strokeSharpness === "sharp") { if (!element.roundness) {
if (options.fill) { if (options.fill) {
shape = [generator.polygon(points as [number, number][], options)]; shape = [generator.polygon(points as [number, number][], options)];
} else { } else {

View File

@ -24,7 +24,7 @@ export const hasStrokeStyle = (type: string) =>
type === "arrow" || type === "arrow" ||
type === "line"; type === "line";
export const canChangeSharpness = (type: string) => export const canChangeRoundness = (type: string) =>
type === "rectangle" || type === "rectangle" ||
type === "arrow" || type === "arrow" ||
type === "line" || type === "line" ||

View File

@ -12,7 +12,7 @@ export {
hasStrokeWidth, hasStrokeWidth,
hasStrokeStyle, hasStrokeStyle,
canHaveArrowheads, canHaveArrowheads,
canChangeSharpness, canChangeRoundness,
getElementAtPosition, getElementAtPosition,
hasText, hasText,
getElementsAtPosition, getElementsAtPosition,

File diff suppressed because it is too large Load Diff

View File

@ -29,11 +29,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "arrow", "type": "arrow",
@ -62,9 +64,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "diamond", "type": "diamond",
@ -93,9 +97,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "ellipse", "type": "ellipse",
@ -135,11 +141,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",
@ -168,9 +176,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",

View File

@ -14,9 +14,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 401146281, "seed": 401146281,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
@ -43,9 +45,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
@ -72,9 +76,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
@ -106,9 +112,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
@ -140,9 +148,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 449462985, "seed": 449462985,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
@ -186,6 +196,9 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 401146281, "seed": 401146281,
"startArrowhead": null, "startArrowhead": null,
"startBinding": Object { "startBinding": Object {
@ -194,7 +207,6 @@ Object {
"gap": 10, "gap": 10,
}, },
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",

View File

@ -34,11 +34,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "arrow", "type": "arrow",
@ -85,11 +87,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",

File diff suppressed because it is too large Load Diff

View File

@ -27,11 +27,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "arrow", "type": "arrow",
@ -71,11 +73,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "round",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",
@ -102,9 +106,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "diamond", "type": "diamond",
@ -131,9 +137,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "ellipse", "type": "ellipse",
@ -160,9 +168,11 @@ Object {
"locked": false, "locked": false,
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": 337897, "seed": 337897,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",

View File

@ -27,11 +27,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": Any<Number>, "seed": Any<Number>,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "arrow", "type": "arrow",
@ -62,9 +64,11 @@ Object {
"locked": false, "locked": false,
"opacity": 10, "opacity": 10,
"roughness": 2, "roughness": 2,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"strokeColor": "red", "strokeColor": "red",
"strokeSharpness": "round",
"strokeStyle": "dashed", "strokeStyle": "dashed",
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
@ -95,9 +99,11 @@ Object {
"locked": false, "locked": false,
"opacity": 10, "opacity": 10,
"roughness": 2, "roughness": 2,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"strokeColor": "red", "strokeColor": "red",
"strokeSharpness": "round",
"strokeStyle": "dashed", "strokeStyle": "dashed",
"strokeWidth": 2, "strokeWidth": 2,
"type": "ellipse", "type": "ellipse",
@ -128,9 +134,11 @@ Object {
"locked": false, "locked": false,
"opacity": 10, "opacity": 10,
"roughness": 2, "roughness": 2,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"strokeColor": "red", "strokeColor": "red",
"strokeSharpness": "round",
"strokeStyle": "dashed", "strokeStyle": "dashed",
"strokeWidth": 2, "strokeWidth": 2,
"type": "diamond", "type": "diamond",
@ -160,10 +168,12 @@ Object {
"points": Array [], "points": Array [],
"pressures": Array [], "pressures": Array [],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"simulatePressure": true, "simulatePressure": true,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "freedraw", "type": "freedraw",
@ -203,11 +213,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": Any<Number>, "seed": Any<Number>,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",
@ -247,11 +259,13 @@ Object {
], ],
], ],
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 2,
},
"seed": Any<Number>, "seed": Any<Number>,
"startArrowhead": null, "startArrowhead": null,
"startBinding": null, "startBinding": null,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "line", "type": "line",
@ -283,9 +297,11 @@ Object {
"opacity": 100, "opacity": 100,
"originalText": "text", "originalText": "text",
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"text": "text", "text": "text",
@ -320,9 +336,11 @@ Object {
"opacity": 100, "opacity": 100,
"originalText": "test", "originalText": "test",
"roughness": 1, "roughness": 1,
"roundness": Object {
"type": 3,
},
"seed": Any<Number>, "seed": Any<Number>,
"strokeColor": "#000000", "strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"text": "", "text": "",

View File

@ -10,7 +10,7 @@ import { API } from "../helpers/api";
import { getDefaultAppState } from "../../appState"; import { getDefaultAppState } from "../../appState";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../data/types";
import { NormalizedZoomValue } from "../../types"; import { NormalizedZoomValue } from "../../types";
import { FONT_FAMILY } from "../../constants"; import { FONT_FAMILY, ROUNDNESS } from "../../constants";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../element/mutateElement";
const mockSizeHelper = jest.spyOn(sizeHelpers, "isInvisiblySmallElement"); const mockSizeHelper = jest.spyOn(sizeHelpers, "isInvisiblySmallElement");
@ -255,7 +255,7 @@ describe("restoreElements", () => {
width: 100, width: 100,
height: 200, height: 200,
groupIds: ["1", "2", "3"], groupIds: ["1", "2", "3"],
strokeSharpness: "round", roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
}); });
elements.push(element); elements.push(element);

View File

@ -15,7 +15,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
roughness: 1, roughness: 1,
opacity: 100, opacity: 100,
groupIds: [], groupIds: [],
strokeSharpness: "sharp", roundness: null,
seed: 1041657908, seed: 1041657908,
version: 120, version: 120,
versionNonce: 1188004276, versionNonce: 1188004276,

View File

@ -8,7 +8,7 @@ import {
FileId, FileId,
} from "../../element/types"; } from "../../element/types";
import { newElement, newTextElement, newLinearElement } from "../../element"; import { newElement, newTextElement, newLinearElement } from "../../element";
import { DEFAULT_VERTICAL_ALIGN } from "../../constants"; import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
import { getDefaultAppState } from "../../appState"; import { getDefaultAppState } from "../../appState";
import { GlobalTestState, createEvent, fireEvent } from "../test-utils"; import { GlobalTestState, createEvent, fireEvent } from "../test-utils";
import fs from "fs"; import fs from "fs";
@ -18,6 +18,7 @@ import { getMimeType } from "../../data/blob";
import { newFreeDrawElement, newImageElement } from "../../element/newElement"; import { newFreeDrawElement, newImageElement } from "../../element/newElement";
import { Point } from "../../types"; import { Point } from "../../types";
import { getSelectedElements } from "../../scene/selection"; import { getSelectedElements } from "../../scene/selection";
import { isLinearElementType } from "../../element/typeChecks";
const readFile = util.promisify(fs.readFile); const readFile = util.promisify(fs.readFile);
@ -89,7 +90,7 @@ export class API {
fillStyle?: ExcalidrawGenericElement["fillStyle"]; fillStyle?: ExcalidrawGenericElement["fillStyle"];
strokeWidth?: ExcalidrawGenericElement["strokeWidth"]; strokeWidth?: ExcalidrawGenericElement["strokeWidth"];
strokeStyle?: ExcalidrawGenericElement["strokeStyle"]; strokeStyle?: ExcalidrawGenericElement["strokeStyle"];
strokeSharpness?: ExcalidrawGenericElement["strokeSharpness"]; roundness?: ExcalidrawGenericElement["roundness"];
roughness?: ExcalidrawGenericElement["roughness"]; roughness?: ExcalidrawGenericElement["roughness"];
opacity?: ExcalidrawGenericElement["opacity"]; opacity?: ExcalidrawGenericElement["opacity"];
// text props // text props
@ -125,7 +126,20 @@ export class API {
const appState = h?.state || getDefaultAppState(); const appState = h?.state || getDefaultAppState();
const base = { const base: Omit<
ExcalidrawGenericElement,
| "id"
| "width"
| "height"
| "type"
| "seed"
| "version"
| "versionNonce"
| "isDeleted"
| "groupIds"
| "link"
| "updated"
> = {
x, x,
y, y,
angle: rest.angle ?? 0, angle: rest.angle ?? 0,
@ -135,8 +149,17 @@ export class API {
fillStyle: rest.fillStyle ?? appState.currentItemFillStyle, fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth, strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth,
strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle, strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
strokeSharpness: roundness: (
rest.strokeSharpness ?? appState.currentItemStrokeSharpness, rest.roundness === undefined
? appState.currentItemRoundness === "round"
: rest.roundness
)
? {
type: isLinearElementType(type)
? ROUNDNESS.PROPORTIONAL_RADIUS
: ROUNDNESS.ADAPTIVE_RADIUS,
}
: null,
roughness: rest.roughness ?? appState.currentItemRoughness, roughness: rest.roughness ?? appState.currentItemRoughness,
opacity: rest.opacity ?? appState.currentItemOpacity, opacity: rest.opacity ?? appState.currentItemOpacity,
boundElements: rest.boundElements ?? null, boundElements: rest.boundElements ?? null,

View File

@ -20,6 +20,7 @@ import { resize, rotate } from "./utils";
import { getBoundTextElementPosition, wrapText } from "../element/textElement"; import { getBoundTextElementPosition, wrapText } from "../element/textElement";
import { getMaxContainerWidth } from "../element/newElement"; import { getMaxContainerWidth } from "../element/newElement";
import * as textElementUtils from "../element/textElement"; import * as textElementUtils from "../element/textElement";
import { ROUNDNESS } from "../constants";
const renderScene = jest.spyOn(Renderer, "renderScene"); const renderScene = jest.spyOn(Renderer, "renderScene");
@ -51,7 +52,7 @@ describe("Test Linear Elements", () => {
const createTwoPointerLinearElement = ( const createTwoPointerLinearElement = (
type: ExcalidrawLinearElement["type"], type: ExcalidrawLinearElement["type"],
strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp", roundness: ExcalidrawElement["roundness"] = null,
roughness: ExcalidrawLinearElement["roughness"] = 0, roughness: ExcalidrawLinearElement["roughness"] = 0,
) => { ) => {
const line = API.createElement({ const line = API.createElement({
@ -65,7 +66,7 @@ describe("Test Linear Elements", () => {
[0, 0], [0, 0],
[p2[0] - p1[0], p2[1] - p1[1]], [p2[0] - p1[0], p2[1] - p1[1]],
], ],
strokeSharpness, roundness,
}); });
h.elements = [line]; h.elements = [line];
@ -75,7 +76,7 @@ describe("Test Linear Elements", () => {
const createThreePointerLinearElement = ( const createThreePointerLinearElement = (
type: ExcalidrawLinearElement["type"], type: ExcalidrawLinearElement["type"],
strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp", roundness: ExcalidrawElement["roundness"] = null,
roughness: ExcalidrawLinearElement["roughness"] = 0, roughness: ExcalidrawLinearElement["roughness"] = 0,
) => { ) => {
//dragging line from midpoint //dragging line from midpoint
@ -92,7 +93,7 @@ describe("Test Linear Elements", () => {
[p3[0], p3[1]], [p3[0], p3[1]],
[p2[0] - p1[0], p2[1] - p1[1]], [p2[0] - p1[0], p2[1] - p1[1]],
], ],
strokeSharpness, roundness,
}); });
h.elements = [line]; h.elements = [line];
mouse.clickAt(p1[0], p1[1]); mouse.clickAt(p1[0], p1[1]);
@ -286,7 +287,7 @@ describe("Test Linear Elements", () => {
`); `);
}); });
it("should update the midpoints when element sharpness changed", async () => { it("should update the midpoints when element roundness changed", async () => {
createThreePointerLinearElement("line"); createThreePointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement; const line = h.elements[0] as ExcalidrawLinearElement;
@ -299,7 +300,7 @@ describe("Test Linear Elements", () => {
h.state, h.state,
); );
// update sharpness // update roundness
fireEvent.click(screen.getByTitle("Round")); fireEvent.click(screen.getByTitle("Round"));
expect(renderScene).toHaveBeenCalledTimes(12); expect(renderScene).toHaveBeenCalledTimes(12);
@ -325,7 +326,9 @@ describe("Test Linear Elements", () => {
}); });
it("should update all the midpoints when element position changed", async () => { it("should update all the midpoints when element position changed", async () => {
createThreePointerLinearElement("line", "round"); createThreePointerLinearElement("line", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
const line = h.elements[0] as ExcalidrawLinearElement; const line = h.elements[0] as ExcalidrawLinearElement;
expect(line.points.length).toEqual(3); expect(line.points.length).toEqual(3);
@ -370,8 +373,8 @@ describe("Test Linear Elements", () => {
`); `);
}); });
describe("When edges are sharp", () => { describe("When edges are round", () => {
// This is the expected midpoint for line with sharp edge // This is the expected midpoint for line with round edge
// hence hardcoding it so if later some bug is introduced // hence hardcoding it so if later some bug is introduced
// this will fail and we can fix it // this will fail and we can fix it
const firstSegmentMidpoint: Point = [55, 45]; const firstSegmentMidpoint: Point = [55, 45];
@ -525,7 +528,9 @@ describe("Test Linear Elements", () => {
let line: ExcalidrawLinearElement; let line: ExcalidrawLinearElement;
beforeEach(() => { beforeEach(() => {
line = createThreePointerLinearElement("line", "round"); line = createThreePointerLinearElement("line", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
expect(line.points.length).toEqual(3); expect(line.points.length).toEqual(3);
enterLineEditingMode(line); enterLineEditingMode(line);
@ -768,7 +773,9 @@ describe("Test Linear Elements", () => {
}); });
it("should return correct position for arrow with odd points", () => { it("should return correct position for arrow with odd points", () => {
createThreePointerLinearElement("arrow", "round"); createThreePointerLinearElement("arrow", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
const arrow = h.elements[0] as ExcalidrawLinearElement; const arrow = h.elements[0] as ExcalidrawLinearElement;
const { textElement, container } = createBoundTextElement( const { textElement, container } = createBoundTextElement(
DEFAULT_TEXT, DEFAULT_TEXT,
@ -788,7 +795,9 @@ describe("Test Linear Elements", () => {
}); });
it("should return correct position for arrow with even points", () => { it("should return correct position for arrow with even points", () => {
createThreePointerLinearElement("arrow", "round"); createThreePointerLinearElement("arrow", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
const arrow = h.elements[0] as ExcalidrawLinearElement; const arrow = h.elements[0] as ExcalidrawLinearElement;
const { textElement, container } = createBoundTextElement( const { textElement, container } = createBoundTextElement(
DEFAULT_TEXT, DEFAULT_TEXT,
@ -903,7 +912,9 @@ describe("Test Linear Elements", () => {
}); });
it("should not rotate the bound text and update position of bound text and bounding box correctly when arrow rotated", () => { it("should not rotate the bound text and update position of bound text and bounding box correctly when arrow rotated", () => {
createThreePointerLinearElement("arrow", "round"); createThreePointerLinearElement("arrow", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
const arrow = h.elements[0] as ExcalidrawLinearElement; const arrow = h.elements[0] as ExcalidrawLinearElement;
@ -967,7 +978,9 @@ describe("Test Linear Elements", () => {
}); });
it("should resize and position the bound text and bounding box correctly when 3 pointer arrow element resized", () => { it("should resize and position the bound text and bounding box correctly when 3 pointer arrow element resized", () => {
createThreePointerLinearElement("arrow", "round"); createThreePointerLinearElement("arrow", {
type: ROUNDNESS.PROPORTIONAL_RADIUS,
});
const arrow = h.elements[0] as ExcalidrawLinearElement; const arrow = h.elements[0] as ExcalidrawLinearElement;

View File

@ -15,12 +15,11 @@ Object {
"currentItemFillStyle": "hachure", "currentItemFillStyle": "hachure",
"currentItemFontFamily": 1, "currentItemFontFamily": 1,
"currentItemFontSize": 20, "currentItemFontSize": 20,
"currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100, "currentItemOpacity": 100,
"currentItemRoughness": 1, "currentItemRoughness": 1,
"currentItemRoundness": "round",
"currentItemStartArrowhead": null, "currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000", "currentItemStrokeColor": "#000000",
"currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid", "currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1, "currentItemStrokeWidth": 1,
"currentItemTextAlign": "left", "currentItemTextAlign": "left",

View File

@ -95,7 +95,7 @@ exports[`exportToSvg with elements that have a link 1`] = `
exports[`exportToSvg with exportEmbedScene 1`] = ` exports[`exportToSvg with exportEmbedScene 1`] = `
" "
<!-- svg-source:excalidraw --> <!-- svg-source:excalidraw -->
<!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SPW/CMFx1MDAxMN35XHUwMDE1kbtcIpGk4aNstFRVpapcdTAwMWRcdTAwMTiQWnUw8YVYMbaxXHUwMDFkPoT477VccsRtxNpcclx1MDAwZpbu+b278907dKJcYpm9XHUwMDA0NI5cdTAwMTDscswoUXiLulx1MDAwZd+A0lRw+5T6WIta5Z5ZXHUwMDFhI8e9XHUwMDFlXHUwMDEzVlBcbm1OfGCwXHUwMDAybrRlfNk4ilx1MDAwZf62L5Q41Wau1lx1MDAxZpOiopyk63w1fJtOXj691JN2lpMlWVx1MDAxM+9d4fthXHUwMDEzbykxpcWSOG6wXHUwMDEy6LI0LVx1MDAxMPMlc21cdTAwMDZEXHUwMDFiJSp4XHUwMDEyTCjXyF3sTyi9wHm1VKLmJHCSPsaLXCJwXG7K2Mzs2WlcdTAwMDA4L2tcdTAwMDWoVWF+abGFNzot7ICDypZcXJZcdTAwMWO0/qNcdTAwMTFcdTAwMTLn1Oxbv3L9yVfip/vdzl9iJc95kHbBr85cdTAwMDCIT5Ulg/7wIVx1MDAxZTUvYb9JXHUwMDFht9F3wf2uk2Q0iuMsXHUwMDFkXHUwMDBlXHUwMDFhXHUwMDA21VO7auPTXHUwMDE2mGlcYnN0I3xcdTAwMGU24DVjzWMtXHQ+icJXXHUwMDE55VWbZ11VXcl9cSmheCU4QVx1MDAxZT92b0a7XHUwMDE57X+MXHUwMDA2jFGp4Ww0e/thICzlzNj8lnKyXHUwMDFk2lDYPl5ZbOGP03ubusWCa/Zw7Fx1MDAxY39cdTAwMDCLqmbvIn0=<!-- payload-end --> <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SsW7CMFx1MDAxMN35iihdkUjSQChcdTAwMWItVVWpalx1MDAwN1x1MDAwNqRWXHUwMDFkTHxJrFx1MDAxONvEXHUwMDBlXHUwMDEwIf69tlx1MDAwM3GJXHUwMDE4O9aDpXt+7+58945cdTAwMDPP81UjwJ95Plx1MDAxY1JEXHSu0N5cdTAwMWZcdTAwMWF8XHUwMDA3lSSc6afIxpLXVWqZhVJiNlx1MDAxYVGuXHUwMDA1XHUwMDA1l6rlXHUwMDAzhVxyMCU140vHnne0t34h2Kh2q2r7Mc9KwnC0TTfJ22L+8mmllnTQnDiMu7gxhe+TLt5cdTAwMTOsXG6NhUHQYVx1MDAwNZC8UD1cdTAwMTCxnJo2XHUwMDFkXCJVxUt44pRXppG7wFx1MDAxZVd6jdIyr3jNsOOEY4TWmeNkhNKlamg7XHUwMDAwlFx1MDAxNnVcdTAwMDV+r8Lq0mJcdTAwMGbvdJLrXHUwMDAxO5UumVx1MDAxN1xmpLzScIFSoprer0x/4lx1MDAxNdvpfv/OwPA5XHUwMDAzqyl1hVx1MDAwMbDNXHUwMDEwh5Nx8lx1MDAxMEy7XHUwMDE3t9YwXG766DtndsVhOJ1cdTAwMDZBXHUwMDFjJZOOQeRCb1jZtFx1MDAxOaJcdTAwMTLc+ExcdTAwMTPPbvtXjdRcdTAwMDKjVuR+SFx0K/s8babyRu6LOTFBXHUwMDFizrBv8dPw31///vpTf1x1MDAwMaVESDj7S992XHUwMDA2Plx1MDAxMmKpdH5Nad3m71xi7Fx1MDAxZm/sM7PH6K07zT7BNHs8XHJOP7VXYMUifQ==<!-- payload-end -->
<defs> <defs>
<style class=\\"style-fonts\\"> <style class=\\"style-fonts\\">
@font-face { @font-face {

View File

@ -13,6 +13,7 @@ import {
FileId, FileId,
ExcalidrawImageElement, ExcalidrawImageElement,
Theme, Theme,
StrokeRoundness,
} from "./element/types"; } from "./element/types";
import { SHAPES } from "./shapes"; import { SHAPES } from "./shapes";
import { Point as RoughPoint } from "roughjs/bin/geometry"; import { Point as RoughPoint } from "roughjs/bin/geometry";
@ -134,10 +135,9 @@ export type AppState = {
currentItemFontFamily: FontFamilyValues; currentItemFontFamily: FontFamilyValues;
currentItemFontSize: number; currentItemFontSize: number;
currentItemTextAlign: TextAlign; currentItemTextAlign: TextAlign;
currentItemStrokeSharpness: ExcalidrawElement["strokeSharpness"];
currentItemStartArrowhead: Arrowhead | null; currentItemStartArrowhead: Arrowhead | null;
currentItemEndArrowhead: Arrowhead | null; currentItemEndArrowhead: Arrowhead | null;
currentItemLinearStrokeSharpness: ExcalidrawElement["strokeSharpness"]; currentItemRoundness: StrokeRoundness;
viewBackgroundColor: string; viewBackgroundColor: string;
scrollX: number; scrollX: number;
scrollY: number; scrollY: number;