diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 30c9e74343..c5e6f61613 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -47,6 +47,7 @@ import { isArrowElement, isBoundToContainer, isElbowArrow, + isFreeDrawElement, isLinearElement, isLineElement, isTextElement, @@ -669,6 +670,26 @@ export const actionChangeStrokeStyle = register({ ), }); +export const actionChangePressureSensitivity = register({ + name: "changePressureSensitivity", + label: "labels.pressureSensitivity", + trackEvent: false, + perform: (elements, appState, value) => { + return { + elements, + appState: { ...appState, currentItemPressureSensitivity: value }, + captureUpdate: CaptureUpdateAction.IMMEDIATELY, + }; + }, + PanelComponent: ({ app, appState, updateData }) => { + if (appState.activeTool.type !== "freedraw") { + return null; + } + + return ; + }, +}); + export const actionChangeOpacity = register({ name: "changeOpacity", label: "labels.opacity", @@ -1857,3 +1878,78 @@ export const actionChangeArrowType = register({ ); }, }); + +const PressureSensitivityRange = ({ + updateData, + app, +}: { + updateData: (value: number) => void; + app: AppClassProperties; +}) => { + const rangeRef = useRef(null); + const valueRef = useRef(null); + const selectedElements = app.scene.getSelectedElements(app.state); + + let hasCommonPressureSensitivity = true; + const firstElement = selectedElements.find(isFreeDrawElement); + const leastCommonPressureSensitivity = selectedElements + .filter(isFreeDrawElement) + .reduce((acc, element) => { + const sensitivity = element.pressureSensitivity ?? 1; + if (acc != null && acc !== sensitivity) { + hasCommonPressureSensitivity = false; + } + if (acc == null || acc > sensitivity) { + return sensitivity; + } + return acc; + }, firstElement?.pressureSensitivity ?? null); + + const value = Math.round( + (leastCommonPressureSensitivity ?? + app.state.currentItemPressureSensitivity) * 100, + ); + + useEffect(() => { + if (rangeRef.current && valueRef.current) { + const rangeElement = rangeRef.current; + const valueElement = valueRef.current; + const inputWidth = rangeElement.offsetWidth; + const thumbWidth = 15; + const position = + (value / 100) * (inputWidth - thumbWidth) + thumbWidth / 2; + valueElement.style.left = `${position}px`; + rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${value}%, var(--button-bg) ${value}%, var(--button-bg) 100%)`; + } + }, [value]); + + return ( + + {t("labels.pressureSensitivity")} + + { + updateData(+event.target.value / 100); + }} + value={value} + className="range-input" + data-testid="pressure-sensitivity" + /> + + {value !== 0 ? value : null} + + 0 + + + ); +}; diff --git a/packages/excalidraw/actions/index.ts b/packages/excalidraw/actions/index.ts index f37747aebd..54bdbdaaf8 100644 --- a/packages/excalidraw/actions/index.ts +++ b/packages/excalidraw/actions/index.ts @@ -13,6 +13,7 @@ export { actionChangeStrokeWidth, actionChangeFillStyle, actionChangeSloppiness, + actionChangePressureSensitivity, actionChangeOpacity, actionChangeFontSize, actionChangeFontFamily, diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts index e6f3631263..adfc8cefa1 100644 --- a/packages/excalidraw/actions/types.ts +++ b/packages/excalidraw/actions/types.ts @@ -69,6 +69,7 @@ export type ActionName = | "changeStrokeStyle" | "changeArrowhead" | "changeArrowType" + | "changePressureSensitivity" | "changeOpacity" | "changeFontSize" | "toggleCanvasMenu" diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index 60dab78f4f..5b3015a4bd 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -169,8 +169,12 @@ export const SelectedShapeActions = ({ renderAction("changeStrokeWidth")} {(appState.activeTool.type === "freedraw" || - targetElements.some((element) => element.type === "freedraw")) && - renderAction("changeStrokeShape")} + targetElements.some((element) => element.type === "freedraw")) && ( + <> + {renderAction("changeStrokeShape")} + {renderAction("changePressureSensitivity")} + > + )} {(hasStrokeStyle(appState.activeTool.type) || targetElements.some((element) => hasStrokeStyle(element.type))) && ( diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 15b5e5b6fd..45fc5109d8 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -7588,6 +7588,7 @@ class App extends React.Component { opacity: this.state.currentItemOpacity, roundness: null, simulatePressure, + pressureSensitivity: this.state.currentItemPressureSensitivity, locked: false, frameId: topLayerFrame ? topLayerFrame.id : null, points: [pointFrom(0, 0)], diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index 736c417225..d9904838fc 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -32,6 +32,7 @@ "strokeStyle_dotted": "Dotted", "sloppiness": "Sloppiness", "opacity": "Opacity", + "pressureSensitivity": "Stroke sensitivity", "textAlign": "Text align", "edges": "Edges", "sharp": "Sharp",