diff --git a/package-lock.json b/package-lock.json index 3917e20..d429ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.1", "@mui/material": "^7.3.1", + "clsx": "^2.1.1", "debounce": "^2.2.0", "es-toolkit": "^1.52.0", "eventemitter3": "^5.0.4", @@ -1605,6 +1606,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index 2e2768c..5d93aa6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.1", "@mui/material": "^7.3.1", + "clsx": "^2.1.1", "debounce": "^2.2.0", "es-toolkit": "^1.52.0", "eventemitter3": "^5.0.4", diff --git a/src/pages/edit/Editor/renderer/Node/components/Interface/Value/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Interface/Value/index.tsx new file mode 100644 index 0000000..e197d38 --- /dev/null +++ b/src/pages/edit/Editor/renderer/Node/components/Interface/Value/index.tsx @@ -0,0 +1,73 @@ +import clsx from "clsx"; +import { + type SimulationValue, + wrappingIncrementSimulationValue, +} from "../../../../../../../../store/simulation"; +import { stringifySimulationValue } from "../../../../../store/slices/core"; +import styles from "./style.module.css"; + +/** Values up to this bit width are shown bit by bit */ +const maxBitWidthToShowBits = 8; +/** Values up to this bit width are shown with wider bits */ +const maxBitWidthToShowWideBits = 4; + +type Props = { + value: SimulationValue; + /** `null` if the value is read-only */ + onChange: ((value: SimulationValue) => void) | null; +}; + +export function CCComponentEditorRendererNodeInterfaceRendererValue({ + value, + onChange, +}: Props) { + if (value.length > maxBitWidthToShowBits) { + const label = stringifySimulationValue(value); + return onChange ? ( + + ) : ( + {label} + ); + } + + const isSingleBit = value.length === 1; + return ( + + {value.map((bit, index) => { + const className = clsx(styles.bit, bit && styles.on); + const label = isSingleBit ? (bit ? "ON" : "OFF") : bit ? "1" : "0"; + return onChange ? ( + + ) : ( + // biome-ignore lint/suspicious/noArrayIndexKey: bits are identified by their index + + {label} + + ); + })} + + ); +} diff --git a/src/pages/edit/Editor/renderer/Node/components/Interface/Value/style.module.css b/src/pages/edit/Editor/renderer/Node/components/Interface/Value/style.module.css new file mode 100644 index 0000000..1027f7f --- /dev/null +++ b/src/pages/edit/Editor/renderer/Node/components/Interface/Value/style.module.css @@ -0,0 +1,60 @@ +.text { + padding: 0; + border: none; + background: none; + color: inherit; + font: inherit; + text-align: center; + + &:is(button) { + cursor: pointer; + } +} + +.bits { + display: flex; + align-self: center; + overflow: hidden; + border: 0.5px solid var(--color-border); + border-radius: 2px; + + &.single { + align-self: stretch; + } +} + +.bit { + display: flex; + justify-content: center; + align-items: center; + width: 10px; + height: 12px; + padding: 0; + border: none; + background: var(--color-white); + color: inherit; + font: inherit; + font-size: 8px; + text-align: center; + + & + & { + border-left: 0.5px solid var(--color-border); + } + + &.on { + background: var(--color-primary-light); + color: var(--color-white); + } + + .single > & { + flex: 1; + } + + .wide > & { + width: 16px; + } + + &:is(button) { + cursor: pointer; + } +} diff --git a/src/pages/edit/Editor/renderer/Node/components/Interface/geometry.ts b/src/pages/edit/Editor/renderer/Node/components/Interface/geometry.ts index 95e8568..1a8a612 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Interface/geometry.ts +++ b/src/pages/edit/Editor/renderer/Node/components/Interface/geometry.ts @@ -5,7 +5,7 @@ import type { CCComponentEditorRendererNodeLayoutSource, } from "../../types"; -const bodyWidth = 70; +const bodyWidth = 90; const arrowDepth = 10; export const ccComponentEditorRendererNodeInterfaceLayoutConstants = { diff --git a/src/pages/edit/Editor/renderer/Node/components/Interface/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Interface/index.tsx index e53aa82..0c718b1 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Interface/index.tsx +++ b/src/pages/edit/Editor/renderer/Node/components/Interface/index.tsx @@ -2,14 +2,14 @@ import nullthrows from "nullthrows"; import { ccIntrinsicComponentTypes } from "../../../../../../../store/intrinsics/types"; import { useStore } from "../../../../../../../store/react"; import { useComponentPin } from "../../../../../../../store/react/selectors"; -import { wrappingIncrementSimulationValue } from "../../../../../../../store/simulation"; +import type { SimulationValue } from "../../../../../../../store/simulation"; import { useComponentEditorStore } from "../../../../store"; -import { stringifySimulationValue } from "../../../../store/slices/core"; import nodeStyles from "../../style.module.css"; import type { CCComponentEditorRendererNodeRendererProps } from "../../types"; import { ccComponentEditorRendererNodeInterfaceLayoutConstants as constants } from "./geometry"; import { CCComponentEditorRendererNodeInterfaceRendererRenameButton } from "./RenameButton"; import styles from "./style.module.css"; +import { CCComponentEditorRendererNodeInterfaceRendererValue } from "./Value"; export function CCComponentEditorRendererNodeInterfaceRenderer( props: CCComponentEditorRendererNodeRendererProps, @@ -26,29 +26,23 @@ export function CCComponentEditorRendererNodeInterfaceRenderer( ); const isEditMode = editorState.editorMode === "edit"; - const { value, onClick } = isEditMode - ? { value: null, onClick: null } + const simulation = isEditMode + ? null : componentPin.type === "input" ? (() => { const inputValueKey = { componentPinId: componentPin.id, timeStep: editorState.timeStep, }; - const value = nullthrows(editorState.getInputValue(inputValueKey)); return { - value: stringifySimulationValue(value), - onClick: () => - editorState.setInputValue( - inputValueKey, - wrappingIncrementSimulationValue(value), - ), + value: nullthrows(editorState.getInputValue(inputValueKey)), + onChange: (value: SimulationValue) => + editorState.setInputValue(inputValueKey, value), }; })() : { - value: stringifySimulationValue( - nullthrows(editorState.getNodePinValue(nodePin.id)), - ), - onClick: null, + value: nullthrows(editorState.getNodePinValue(nodePin.id)), + onChange: null, }; const { bodyWidth, arrowDepth, width, height } = constants; @@ -65,16 +59,6 @@ export function CCComponentEditorRendererNodeInterfaceRenderer( points={points} strokeLinejoin="round" className={nodeStyles.shape} - {...(onClick - ? { - onPointerDown: (e: React.PointerEvent) => { - // Prevent the node from being selected or dragged - e.stopPropagation(); - onClick(); - }, - style: { cursor: "pointer" }, - } - : {})} /> - {isEditMode ? ( + {simulation === null ? (
{componentPin.name} {componentPin.name} - {value} +
)}
diff --git a/src/pages/edit/Editor/renderer/Node/components/Interface/style.module.css b/src/pages/edit/Editor/renderer/Node/components/Interface/style.module.css index 0e2eca1..dd1cf41 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Interface/style.module.css +++ b/src/pages/edit/Editor/renderer/Node/components/Interface/style.module.css @@ -26,12 +26,9 @@ justify-content: center; height: 100%; padding: 0 4px; + gap: 1px; & .name { font-size: 8px; } - - & .value { - text-align: center; - } } diff --git a/src/style.css b/src/style.css index 9436309..aee1a98 100644 --- a/src/style.css +++ b/src/style.css @@ -12,6 +12,7 @@ --color-white: #ffffff; --color-text-primary: #444444; --color-primary: #009966; + --color-primary-light: #33ad84; --color-error: #ff0000; --color-border: #bbbbbb; --color-editor-background: #fafafa; diff --git a/src/style.ts b/src/style.ts index c21e4c1..3843eb2 100644 --- a/src/style.ts +++ b/src/style.ts @@ -6,6 +6,7 @@ export const theme = { white: "#ffffff", textPrimary: "#444444", primary: "#009966", + primaryLight: "#33ad84", error: "#ff0000", border: "#bbbbbb", editorBackground: "#fafafa",