tweak stroke widths

This commit is contained in:
Ryan Di 2025-06-16 22:06:10 +10:00
parent c72c47f0cd
commit b1f3cc50ee
10 changed files with 111 additions and 66 deletions

View File

@ -385,8 +385,9 @@ export const ROUGHNESS = {
export const STROKE_WIDTH = {
thin: 1,
bold: 2,
extraBold: 4,
medium: 2,
bold: 4,
extraBold: 6,
} as const;
export const DEFAULT_ELEMENT_PROPS: {
@ -402,7 +403,7 @@ export const DEFAULT_ELEMENT_PROPS: {
strokeColor: COLOR_PALETTE.black,
backgroundColor: COLOR_PALETTE.transparent,
fillStyle: "solid",
strokeWidth: 2,
strokeWidth: STROKE_WIDTH.medium,
strokeStyle: "solid",
roughness: ROUGHNESS.artist,
opacity: 100,

View File

@ -10,7 +10,7 @@ import type { ExcalidrawFreeDrawElement } from "./types";
export const DRAWING_CONFIGS = {
default: {
streamline: 0.25,
streamline: 0.35,
simplify: 0.25,
},
// for optimal performance, we use a lower streamline and simplify
@ -62,10 +62,7 @@ const calculateVelocityBasedPressure = (
return Math.max(0.1, Math.min(1.0, pressure));
};
export const getFreedrawStroke = (
element: ExcalidrawFreeDrawElement,
debugParams?: { streamline?: number; simplify?: number },
) => {
export const getFreedrawStroke = (element: ExcalidrawFreeDrawElement) => {
// Compose points as [x, y, pressure]
let points: [number, number, number][];
if (element.simulatePressure) {
@ -105,17 +102,15 @@ export const getFreedrawStroke = (
streamline,
simplify,
sizeMapping: ({ pressure: t }) => {
if (element.simulatePressure) {
return t + 0.2;
}
if (element.drawingConfigs?.pressureSensitivity === 0) {
return 1;
return 0.5;
}
const minSize = 0.2;
const maxSize = 2;
return minSize + t * (maxSize - minSize);
if (element.simulatePressure) {
return 0.2 + t * 0.6;
}
return 0.2 + t * 0.8;
},
});
@ -134,14 +129,13 @@ export const getFreedrawStroke = (
*/
export const getFreeDrawSvgPath = (
element: ExcalidrawFreeDrawElement,
debugParams?: { streamline?: number; simplify?: number },
): string => {
// legacy, for backwards compatibility
if (element.drawingConfigs === null) {
return _legacy_getFreeDrawSvgPath(element);
}
return getSvgPathFromStroke(getFreedrawStroke(element, debugParams));
return getSvgPathFromStroke(getFreedrawStroke(element));
};
const roundPoint = (A: Point): string => {

View File

@ -145,26 +145,27 @@ describe("element locking", () => {
queryByTestId(document.body, `strokeWidth-thin`),
).not.toBeChecked();
expect(
queryByTestId(document.body, `strokeWidth-bold`),
queryByTestId(document.body, `strokeWidth-medium`),
).not.toBeChecked();
expect(
queryByTestId(document.body, `strokeWidth-extraBold`),
queryByTestId(document.body, `strokeWidth-bold`),
).not.toBeChecked();
});
it("should show properties of different element types when selected", () => {
const rect = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.bold,
strokeWidth: STROKE_WIDTH.medium,
});
const text = API.createElement({
type: "text",
fontFamily: FONT_FAMILY["Comic Shanns"],
strokeWidth: undefined,
});
API.setElements([rect, text]);
API.setSelectedElements([rect, text]);
expect(queryByTestId(document.body, `strokeWidth-bold`)).toBeChecked();
expect(queryByTestId(document.body, `strokeWidth-medium`)).toBeChecked();
expect(queryByTestId(document.body, `font-family-code`)).toHaveClass(
"active",
);

View File

@ -130,6 +130,7 @@ import {
ArrowheadCrowfootOneOrManyIcon,
strokeWidthFixedIcon,
strokeWidthVariableIcon,
StrokeWidthMediumIcon,
} from "../components/icons";
import { Fonts } from "../fonts";
@ -509,6 +510,33 @@ export const actionChangeFillStyle = register({
},
});
const WIDTHS = [
{
value: STROKE_WIDTH.thin,
text: t("labels.thin"),
icon: StrokeWidthBaseIcon,
testId: "strokeWidth-thin",
},
{
value: STROKE_WIDTH.medium,
text: t("labels.medium"),
icon: StrokeWidthMediumIcon,
testId: "strokeWidth-medium",
},
{
value: STROKE_WIDTH.bold,
text: t("labels.bold"),
icon: StrokeWidthBoldIcon,
testId: "strokeWidth-bold",
},
{
value: STROKE_WIDTH.extraBold,
text: t("labels.extraBold"),
icon: StrokeWidthExtraBoldIcon,
testId: "strokeWidth-extraBold",
},
];
export const actionChangeStrokeWidth = register({
name: "changeStrokeWidth",
label: "labels.strokeWidth",
@ -530,26 +558,11 @@ export const actionChangeStrokeWidth = register({
<div className="buttonList">
<RadioSelection
group="stroke-width"
options={[
{
value: STROKE_WIDTH.thin,
text: t("labels.thin"),
icon: StrokeWidthBaseIcon,
testId: "strokeWidth-thin",
},
{
value: STROKE_WIDTH.bold,
text: t("labels.bold"),
icon: StrokeWidthBoldIcon,
testId: "strokeWidth-bold",
},
{
value: STROKE_WIDTH.extraBold,
text: t("labels.extraBold"),
icon: StrokeWidthExtraBoldIcon,
testId: "strokeWidth-extraBold",
},
]}
options={
appState.activeTool.type === "freedraw"
? WIDTHS
: WIDTHS.slice(0, 3)
}
value={getFormValue(
elements,
app,

View File

@ -1136,7 +1136,7 @@ export const StrokeWidthBaseIcon = createIcon(
modifiedTablerIconProps,
);
export const StrokeWidthBoldIcon = createIcon(
export const StrokeWidthMediumIcon = createIcon(
<path
d="M5 10h10"
stroke="currentColor"
@ -1147,7 +1147,7 @@ export const StrokeWidthBoldIcon = createIcon(
modifiedTablerIconProps,
);
export const StrokeWidthExtraBoldIcon = createIcon(
export const StrokeWidthBoldIcon = createIcon(
<path
d="M5 10h10"
stroke="currentColor"
@ -1158,6 +1158,17 @@ export const StrokeWidthExtraBoldIcon = createIcon(
modifiedTablerIconProps,
);
export const StrokeWidthExtraBoldIcon = createIcon(
<path
d="M5 10h10"
stroke="currentColor"
strokeWidth="5"
strokeLinecap="round"
strokeLinejoin="round"
/>,
modifiedTablerIconProps,
);
export const StrokeStyleSolidIcon = React.memo(({ theme }: { theme: Theme }) =>
createIcon(
<path

View File

@ -3127,7 +3127,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#e03131",
"currentItemStrokeStyle": "dotted",
"currentItemStrokeWidth": 2,
"currentItemStrokeWidth": 4,
"currentItemTextAlign": "left",
"cursorButton": "up",
"defaultSidebarDockedPreference": false,
@ -3241,11 +3241,11 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"seed": 449462985,
"strokeColor": "#e03131",
"strokeStyle": "dotted",
"strokeWidth": 2,
"strokeWidth": 4,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 941653321,
"versionNonce": 1402203177,
"width": 20,
"x": -10,
"y": 0,
@ -3272,14 +3272,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"roundness": {
"type": 3,
},
"seed": 289600103,
"seed": 1898319239,
"strokeColor": "#e03131",
"strokeStyle": "dotted",
"strokeWidth": 2,
"strokeWidth": 4,
"type": "rectangle",
"updated": 1,
"version": 9,
"versionNonce": 640725609,
"version": 10,
"versionNonce": 941653321,
"width": 20,
"x": 20,
"y": 30,
@ -3288,7 +3288,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of elements 1`] = `2`;
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of renders 1`] = `16`;
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of renders 1`] = `17`;
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] redo stack 1`] = `[]`;
@ -3469,6 +3469,29 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
},
"id": "id11",
},
{
"appState": AppStateDelta {
"delta": Delta {
"deleted": {},
"inserted": {},
},
},
"elements": {
"added": {},
"removed": {},
"updated": {
"id3": {
"deleted": {
"strokeWidth": 4,
},
"inserted": {
"strokeWidth": 2,
},
},
},
},
"id": "id13",
},
{
"appState": AppStateDelta {
"delta": Delta {
@ -3490,7 +3513,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
},
},
},
"id": "id13",
"id": "id15",
},
{
"appState": AppStateDelta {
@ -3513,7 +3536,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
},
},
},
"id": "id15",
"id": "id17",
},
{
"appState": AppStateDelta {
@ -3536,7 +3559,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
},
},
},
"id": "id17",
"id": "id19",
},
{
"appState": AppStateDelta {
@ -3565,6 +3588,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"roughness": 2,
"strokeColor": "#e03131",
"strokeStyle": "dotted",
"strokeWidth": 4,
},
"inserted": {
"backgroundColor": "transparent",
@ -3573,11 +3597,12 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"roughness": 1,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
},
},
},
},
"id": "id19",
"id": "id21",
},
]
`;

View File

@ -8913,7 +8913,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -9022,7 +9022,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -12075,7 +12075,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -12130,7 +12130,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -12275,7 +12275,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,

View File

@ -6880,7 +6880,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -9159,7 +9159,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] undo sta
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,
@ -10167,7 +10167,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] undo sta
"drawingConfigs": {
"pressureSensitivity": 1,
"simplify": "0.25000",
"streamline": "0.25000",
"streamline": "0.35000",
},
"fillStyle": "solid",
"frameId": null,

View File

@ -78,7 +78,7 @@ describe("actionStyles", () => {
expect(firstRect.strokeColor).toBe("#e03131");
expect(firstRect.backgroundColor).toBe("#a5d8ff");
expect(firstRect.fillStyle).toBe("cross-hatch");
expect(firstRect.strokeWidth).toBe(2); // Bold: 2
expect(firstRect.strokeWidth).toBe(4); // Bold: 4
expect(firstRect.strokeStyle).toBe("dotted");
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
expect(firstRect.opacity).toBe(60);

View File

@ -381,7 +381,7 @@ describe("contextMenu element", () => {
expect(firstRect.strokeColor).toBe("#e03131");
expect(firstRect.backgroundColor).toBe("#a5d8ff");
expect(firstRect.fillStyle).toBe("cross-hatch");
expect(firstRect.strokeWidth).toBe(2); // Bold: 2
expect(firstRect.strokeWidth).toBe(4); // Bold: 4
expect(firstRect.strokeStyle).toBe("dotted");
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
expect(firstRect.opacity).toBe(60);