Skip to content

Commit

Permalink
Figma Plugin: Handle colors that are part of common styles and border…
Browse files Browse the repository at this point in the history
…-radius (#7588)
  • Loading branch information
NigelBreslaw authored Feb 11, 2025
1 parent e21efee commit bfcd56d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 96 deletions.
10 changes: 1 addition & 9 deletions tools/figma-inspector/backend/code.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

import {
getStore,
setStore,
listenTS,
dispatchTS,
getStatus,
updateUI,
getSlintSnippet,
} from "./utils/code-utils";
import { getSlintSnippet } from "./utils/property-parsing.js";

if (figma.editorType === "dev" && figma.mode === "codegen") {
figma.codegen.on("generate", async ({ node }) => {
Expand Down
87 changes: 0 additions & 87 deletions tools/figma-inspector/backend/utils/code-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,90 +43,3 @@ export const getStore = async (key: string) => {
export const setStore = async (key: string, value: string) => {
await figma.clientStorage.setAsync(key, value);
};

export function getStatus(selectionCount: number) {
if (selectionCount === 0) {
return "Please select a layer";
}
if (selectionCount > 1) {
return "Please select only one layer";
}
return "Slint properties:";
}

const itemsToKeep = [
"color",
"font-family",
"font-size",
"font-weight",
"width",
"height",
"fill",
"opacity",
"border-radius",
"fill",
"stroke-width",
"stroke",
];

type StyleObject = {
[key: string]: string;
};

function transformStyle(styleObj: StyleObject): string {
const filteredEntries = Object.entries(styleObj)
.filter(([key]) => itemsToKeep.includes(key))
.map(([key, value]) => {
let finalKey = key;
let finalValue = value;

switch (key) {
case "fill":
finalKey = "background";
break;
case "stroke":
finalKey = "border-color";
break;
case "stroke-width":
finalKey = "border-width";
break;
case "font-family":
finalValue = `"${value}"`;
break;
}

if (value.includes("linear-gradient")) {
return ` ${finalKey}: @${finalValue}`;
}

return ` ${finalKey}: ${finalValue}`;
});

return filteredEntries.length > 0 ? `${filteredEntries.join(";\n")};` : "";
}

export async function updateUI() {
const title = getStatus(figma.currentPage.selection.length);
let slintProperties = "";

if (figma.currentPage.selection.length === 1) {
const cssProperties =
await figma.currentPage.selection[0].getCSSAsync();
slintProperties = transformStyle(cssProperties);
}

dispatchTS("updatePropertiesCallback", { title, slintProperties });
}

export async function getSlintSnippet(): Promise<string> {
const cssProperties = await figma.currentPage.selection[0].getCSSAsync();
const slintProperties = transformStyle(cssProperties);

let elementName = "Rectangle";
const node = figma.currentPage.selection[0].type;
if (node === "TEXT") {
elementName = "Text";
}

return `${elementName} {\n${slintProperties}\n}`;
}
133 changes: 133 additions & 0 deletions tools/figma-inspector/backend/utils/property-parsing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

const itemsToKeep = [
"color",
"font-family",
"font-size",
"font-weight",
"width",
"height",
"fill",
"opacity",
"border-radius",
"stroke-width",
"stroke",
];

type StyleObject = {
[key: string]: string;
};

export async function getSlintSnippet(): Promise<string> {
const cssProperties = await figma.currentPage.selection[0].getCSSAsync();
const slintProperties = transformStyle(cssProperties);

let elementName = "Rectangle";
const node = figma.currentPage.selection[0].type;
if (node === "TEXT") {
elementName = "Text";
}

return `${elementName} {\n${slintProperties}\n}`;
}

function transformStyle(styleObj: StyleObject): string {
const filteredEntries = Object.entries(styleObj)
.filter(([key]) => itemsToKeep.includes(key))
.map(([key, value]) => {
let finalKey = key;
let finalValue = value;

switch (key) {
case "fill":
finalKey = "background";
break;
case "stroke":
finalKey = "border-color";
break;
case "stroke-width":
finalKey = "border-width";
break;
case "font-family":
finalValue = `"${value}"`;
break;
}

if (key === "color") {
return ` ${finalKey}: ${getColor(figma.currentPage.selection[0])};`;
}
if (key === "border-radius") {
const borderRadius = getBorderRadius();
if (borderRadius !== null) {
return borderRadius;
}
}

if (value.includes("linear-gradient")) {
return ` ${finalKey}: @${finalValue};`;
}

return ` ${finalKey}: ${finalValue};`;
});

return filteredEntries.length > 0 ? `${filteredEntries.join("\n")}` : "";
}

function rgbToHex({ r, g, b }) {
const red = Math.round(r * 255);
const green = Math.round(g * 255);
const blue = Math.round(b * 255);

return (
"#" +
[red, green, blue].map((x) => x.toString(16).padStart(2, "0")).join("")
);
}

// Manually get the color for now as the CSS API returns figma variables which for now is not supported.
function getColor(node: SceneNode): string | null {
if ("fills" in node && Array.isArray(node.fills) && node.fills.length > 0) {
const fillColor = node.fills[0].color;
return rgbToHex(fillColor);
}

return null;
}

function getBorderRadius(): string | null {
const node = figma.currentPage.selection[0];
console.log("node", node);

if (!("cornerRadius" in node)) {
return null;
}

const cornerRadius = node.cornerRadius;

// Single border value
if (typeof cornerRadius === "number") {
return ` border-radius: ${cornerRadius}px;`;
}

// Multiple border values
const corners = [
{ prop: "topLeftRadius", slint: "border-top-left-radius" },
{ prop: "topRightRadius", slint: "border-top-right-radius" },
{ prop: "bottomLeftRadius", slint: "border-bottom-left-radius" },
{ prop: "bottomRightRadius", slint: "border-bottom-right-radius" },
];

const validCorners = corners.filter(
(corner) =>
corner.prop in node &&
typeof node[corner.prop] === "number" &&
node[corner.prop] > 0,
);

const radiusStrings = validCorners.map((corner, index) => {
return ` ${corner.slint}: ${node[corner.prop]}px;`;
});

return radiusStrings.length > 0 ? radiusStrings.join("\n") : null;
}

0 comments on commit bfcd56d

Please sign in to comment.