Merge branch 'master' into mtolmacs/feat/freedraw-research-1

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2026-06-04 16:34:58 +00:00
10 changed files with 56 additions and 45 deletions
+5 -1
View File
@@ -80,7 +80,11 @@ const cssInvert = (
return { r: invertedR, g: invertedG, b: invertedB }; return { r: invertedR, g: invertedG, b: invertedB };
}; };
export const applyDarkModeFilter = (color: string): string => { export const applyDarkModeFilter = (color: string, enable = true): string => {
if (!enable) {
return color;
}
const cached = DARK_MODE_COLORS_CACHE?.get(color); const cached = DARK_MODE_COLORS_CACHE?.get(color);
if (cached) { if (cached) {
return cached; return cached;
+8 -8
View File
@@ -554,10 +554,10 @@ const drawElementOnCanvas = (
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr"); context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
context.save(); context.save();
context.font = getFontString(element); context.font = getFontString(element);
context.fillStyle = context.fillStyle = applyDarkModeFilter(
renderConfig.theme === THEME.DARK element.strokeColor,
? applyDarkModeFilter(element.strokeColor) renderConfig.theme === THEME.DARK,
: element.strokeColor; );
context.textAlign = element.textAlign as CanvasTextAlign; context.textAlign = element.textAlign as CanvasTextAlign;
// Canvas does not support multiline text by default // Canvas does not support multiline text by default
@@ -851,10 +851,10 @@ export const renderElement = (
context.fillStyle = "rgba(0, 0, 200, 0.04)"; context.fillStyle = "rgba(0, 0, 200, 0.04)";
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value; context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
context.strokeStyle = context.strokeStyle = applyDarkModeFilter(
appState.theme === THEME.DARK FRAME_STYLE.strokeColor,
? applyDarkModeFilter(FRAME_STYLE.strokeColor) appState.theme === THEME.DARK,
: FRAME_STYLE.strokeColor; );
// TODO change later to only affect AI frames // TODO change later to only affect AI frames
if (isMagicFrameElement(element)) { if (isMagicFrameElement(element)) {
+4 -1
View File
@@ -163,7 +163,10 @@ export const drawFreeDrawSegments = (
? applyDarkModeFilter(element.strokeColor) ? applyDarkModeFilter(element.strokeColor)
: element.strokeColor; : element.strokeColor;
context.fillStyle = strokeColor; context.fillStyle = applyDarkModeFilter(
strokeColor,
renderConfig.theme === THEME.DARK,
);
const baseRadius = (element.strokeWidth * 1.25) / 2; const baseRadius = (element.strokeWidth * 1.25) / 2;
+8 -15
View File
@@ -218,9 +218,7 @@ export const generateRoughOptions = (
fillWeight: element.strokeWidth / 2, fillWeight: element.strokeWidth / 2,
hachureGap: element.strokeWidth * 4, hachureGap: element.strokeWidth * 4,
roughness: adjustRoughness(element), roughness: adjustRoughness(element),
stroke: isDarkMode stroke: applyDarkModeFilter(element.strokeColor, isDarkMode),
? applyDarkModeFilter(element.strokeColor)
: element.strokeColor,
preserveVertices: preserveVertices:
continuousPath || element.roughness < ROUGHNESS.cartoonist, continuousPath || element.roughness < ROUGHNESS.cartoonist,
}; };
@@ -234,9 +232,7 @@ export const generateRoughOptions = (
options.fillStyle = element.fillStyle; options.fillStyle = element.fillStyle;
options.fill = isTransparent(element.backgroundColor) options.fill = isTransparent(element.backgroundColor)
? undefined ? undefined
: isDarkMode : applyDarkModeFilter(element.backgroundColor, isDarkMode);
? applyDarkModeFilter(element.backgroundColor)
: element.backgroundColor;
if (element.type === "ellipse") { if (element.type === "ellipse") {
options.curveFitting = 1; options.curveFitting = 1;
} }
@@ -249,9 +245,7 @@ export const generateRoughOptions = (
options.fill = options.fill =
element.backgroundColor === "transparent" element.backgroundColor === "transparent"
? undefined ? undefined
: isDarkMode : applyDarkModeFilter(element.backgroundColor, isDarkMode);
? applyDarkModeFilter(element.backgroundColor)
: element.backgroundColor;
} }
return options; return options;
} }
@@ -386,12 +380,11 @@ const getArrowheadShapes = (
return []; return [];
} }
const strokeColor = isDarkMode const strokeColor = applyDarkModeFilter(element.strokeColor, isDarkMode);
? applyDarkModeFilter(element.strokeColor) const backgroundFillColor = applyDarkModeFilter(
: element.strokeColor; canvasBackgroundColor,
const backgroundFillColor = isDarkMode isDarkMode,
? applyDarkModeFilter(canvasBackgroundColor) );
: canvasBackgroundColor;
const cardinalityOneOrManyOffset = -0.25; const cardinalityOneOrManyOffset = -0.25;
const cardinalityZeroCircleScale = 0.8; const cardinalityZeroCircleScale = 0.8;
+4 -3
View File
@@ -1995,9 +1995,10 @@ class App extends React.Component<AppProps, AppState> {
} }
}} }}
style={{ style={{
background: isDarkTheme background: applyDarkModeFilter(
? applyDarkModeFilter(this.state.viewBackgroundColor) this.state.viewBackgroundColor,
: this.state.viewBackgroundColor, isDarkTheme,
),
zIndex: 2, zIndex: 2,
border: "none", border: "none",
display: "block", display: "block",
+3
View File
@@ -7,6 +7,7 @@ import React, {
} from "react"; } from "react";
import { import {
applyDarkModeFilter,
DEFAULT_IMAGE_OPTIONS, DEFAULT_IMAGE_OPTIONS,
DEFAULT_UI_OPTIONS, DEFAULT_UI_OPTIONS,
isShallowEqual, isShallowEqual,
@@ -450,3 +451,5 @@ export function useExcalidrawStateValue(
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export { _useOnAppStateChange as useOnExcalidrawStateChange }; export { _useOnAppStateChange as useOnExcalidrawStateChange };
export { applyDarkModeFilter };
+4 -4
View File
@@ -62,10 +62,10 @@ export const bootstrapCanvas = ({
context.clearRect(0, 0, normalizedWidth, normalizedHeight); context.clearRect(0, 0, normalizedWidth, normalizedHeight);
} }
context.save(); context.save();
context.fillStyle = context.fillStyle = applyDarkModeFilter(
theme === THEME.DARK viewBackgroundColor,
? applyDarkModeFilter(viewBackgroundColor) theme === THEME.DARK,
: viewBackgroundColor; );
context.fillRect(0, 0, normalizedWidth, normalizedHeight); context.fillRect(0, 0, normalizedWidth, normalizedHeight);
context.restore(); context.restore();
} else { } else {
+15 -6
View File
@@ -392,6 +392,13 @@ const renderElementToSvg = (
if (typeof shape === "string") { if (typeof shape === "string") {
// stroke (SVGPathString) — fill inherited from wrapper <g> // stroke (SVGPathString) — fill inherited from wrapper <g>
const path = svgRoot.ownerDocument.createElementNS(SVG_NS, "path"); const path = svgRoot.ownerDocument.createElementNS(SVG_NS, "path");
path.setAttribute(
"fill",
applyDarkModeFilter(
element.strokeColor,
renderConfig.theme === THEME.DARK,
),
);
path.setAttribute("d", shape); path.setAttribute("d", shape);
wrapper.appendChild(path); wrapper.appendChild(path);
} else { } else {
@@ -623,9 +630,10 @@ const renderElementToSvg = (
rect.setAttribute("fill", "none"); rect.setAttribute("fill", "none");
rect.setAttribute( rect.setAttribute(
"stroke", "stroke",
renderConfig.theme === THEME.DARK applyDarkModeFilter(
? applyDarkModeFilter(FRAME_STYLE.strokeColor) FRAME_STYLE.strokeColor,
: FRAME_STYLE.strokeColor, renderConfig.theme === THEME.DARK,
),
); );
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString()); rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
@@ -679,9 +687,10 @@ const renderElementToSvg = (
text.setAttribute("font-size", `${element.fontSize}px`); text.setAttribute("font-size", `${element.fontSize}px`);
text.setAttribute( text.setAttribute(
"fill", "fill",
renderConfig.theme === THEME.DARK applyDarkModeFilter(
? applyDarkModeFilter(element.strokeColor) element.strokeColor,
: element.strokeColor, renderConfig.theme === THEME.DARK,
),
); );
text.setAttribute("text-anchor", textAnchor); text.setAttribute("text-anchor", textAnchor);
text.setAttribute("style", "white-space: pre;"); text.setAttribute("style", "white-space: pre;");
+1 -3
View File
@@ -459,9 +459,7 @@ export const exportToSvg = async (
rect.setAttribute("height", `${height}`); rect.setAttribute("height", `${height}`);
rect.setAttribute( rect.setAttribute(
"fill", "fill",
exportWithDarkMode applyDarkModeFilter(viewBackgroundColor, exportWithDarkMode),
? applyDarkModeFilter(viewBackgroundColor)
: viewBackgroundColor,
); );
svgRoot.appendChild(rect); svgRoot.appendChild(rect);
} }
+4 -4
View File
@@ -392,10 +392,10 @@ export const textWysiwyg = ({
), ),
textAlign, textAlign,
verticalAlign, verticalAlign,
color: color: applyDarkModeFilter(
appState.theme === THEME.DARK updatedTextElement.strokeColor,
? applyDarkModeFilter(updatedTextElement.strokeColor) appState.theme === THEME.DARK,
: updatedTextElement.strokeColor, ),
opacity: updatedTextElement.opacity / 100, opacity: updatedTextElement.opacity / 100,
maxHeight: `${editorMaxHeight}px`, maxHeight: `${editorMaxHeight}px`,
}); });