mirror of
https://github.com/excalidraw/excalidraw
synced 2025-07-25 13:58:22 +08:00
add stroke sensivity action
This commit is contained in:
@ -47,6 +47,7 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isFreeDrawElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isLineElement,
|
isLineElement,
|
||||||
isTextElement,
|
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 <PressureSensitivityRange updateData={updateData} app={app} />;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const actionChangeOpacity = register({
|
export const actionChangeOpacity = register({
|
||||||
name: "changeOpacity",
|
name: "changeOpacity",
|
||||||
label: "labels.opacity",
|
label: "labels.opacity",
|
||||||
@ -1857,3 +1878,78 @@ export const actionChangeArrowType = register({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const PressureSensitivityRange = ({
|
||||||
|
updateData,
|
||||||
|
app,
|
||||||
|
}: {
|
||||||
|
updateData: (value: number) => void;
|
||||||
|
app: AppClassProperties;
|
||||||
|
}) => {
|
||||||
|
const rangeRef = useRef<HTMLInputElement>(null);
|
||||||
|
const valueRef = useRef<HTMLDivElement>(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 (
|
||||||
|
<label className="control-label">
|
||||||
|
{t("labels.pressureSensitivity")}
|
||||||
|
<div className="range-wrapper">
|
||||||
|
<input
|
||||||
|
style={{
|
||||||
|
["--color-slider-track" as string]: hasCommonPressureSensitivity
|
||||||
|
? undefined
|
||||||
|
: "var(--button-bg)",
|
||||||
|
}}
|
||||||
|
ref={rangeRef}
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="10"
|
||||||
|
onChange={(event) => {
|
||||||
|
updateData(+event.target.value / 100);
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
className="range-input"
|
||||||
|
data-testid="pressure-sensitivity"
|
||||||
|
/>
|
||||||
|
<div className="value-bubble" ref={valueRef}>
|
||||||
|
{value !== 0 ? value : null}
|
||||||
|
</div>
|
||||||
|
<div className="zero-label">0</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -13,6 +13,7 @@ export {
|
|||||||
actionChangeStrokeWidth,
|
actionChangeStrokeWidth,
|
||||||
actionChangeFillStyle,
|
actionChangeFillStyle,
|
||||||
actionChangeSloppiness,
|
actionChangeSloppiness,
|
||||||
|
actionChangePressureSensitivity,
|
||||||
actionChangeOpacity,
|
actionChangeOpacity,
|
||||||
actionChangeFontSize,
|
actionChangeFontSize,
|
||||||
actionChangeFontFamily,
|
actionChangeFontFamily,
|
||||||
|
@ -69,6 +69,7 @@ export type ActionName =
|
|||||||
| "changeStrokeStyle"
|
| "changeStrokeStyle"
|
||||||
| "changeArrowhead"
|
| "changeArrowhead"
|
||||||
| "changeArrowType"
|
| "changeArrowType"
|
||||||
|
| "changePressureSensitivity"
|
||||||
| "changeOpacity"
|
| "changeOpacity"
|
||||||
| "changeFontSize"
|
| "changeFontSize"
|
||||||
| "toggleCanvasMenu"
|
| "toggleCanvasMenu"
|
||||||
|
@ -169,8 +169,12 @@ export const SelectedShapeActions = ({
|
|||||||
renderAction("changeStrokeWidth")}
|
renderAction("changeStrokeWidth")}
|
||||||
|
|
||||||
{(appState.activeTool.type === "freedraw" ||
|
{(appState.activeTool.type === "freedraw" ||
|
||||||
targetElements.some((element) => element.type === "freedraw")) &&
|
targetElements.some((element) => element.type === "freedraw")) && (
|
||||||
renderAction("changeStrokeShape")}
|
<>
|
||||||
|
{renderAction("changeStrokeShape")}
|
||||||
|
{renderAction("changePressureSensitivity")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
{(hasStrokeStyle(appState.activeTool.type) ||
|
||||||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
||||||
|
@ -7588,6 +7588,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
simulatePressure,
|
simulatePressure,
|
||||||
|
pressureSensitivity: this.state.currentItemPressureSensitivity,
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
points: [pointFrom<LocalPoint>(0, 0)],
|
points: [pointFrom<LocalPoint>(0, 0)],
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"strokeStyle_dotted": "Dotted",
|
"strokeStyle_dotted": "Dotted",
|
||||||
"sloppiness": "Sloppiness",
|
"sloppiness": "Sloppiness",
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
|
"pressureSensitivity": "Stroke sensitivity",
|
||||||
"textAlign": "Text align",
|
"textAlign": "Text align",
|
||||||
"edges": "Edges",
|
"edges": "Edges",
|
||||||
"sharp": "Sharp",
|
"sharp": "Sharp",
|
||||||
|
Reference in New Issue
Block a user