From 74c8044c739314485c553994908653d9f2ce6a9d Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Wed, 4 Sep 2024 22:07:47 -0700 Subject: [PATCH] Add gap size setting (#325) Adds a new setting for the gap size between tiles in a layout. Also updates the resize handle calculations so they are dynamically generated based on the gap size. Also updates the styling for the resize handles to be more robust. This also updates the default gap size to 3px. This also slims out the Block Frame padding so it is just enough that the blocks don't overlap when there's no gap. --- frontend/app/app.tsx | 1 - frontend/app/block/block.less | 2 +- frontend/app/store/global.ts | 1 - frontend/app/tab/tabcontent.tsx | 26 ++++++++---- frontend/app/view/codeeditor/codeeditor.tsx | 2 - frontend/layout/lib/TileLayout.tsx | 8 ++-- frontend/layout/lib/layoutModel.ts | 46 +++++++++++---------- frontend/layout/lib/tilelayout.less | 14 ++++--- frontend/layout/lib/types.ts | 26 +++++++----- frontend/types/gotypes.d.ts | 1 + pkg/wconfig/defaultconfig/settings.json | 3 +- pkg/wconfig/metaconsts.go | 1 + pkg/wconfig/settingsconfig.go | 1 + 13 files changed, 75 insertions(+), 57 deletions(-) diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 0ebddc56..5d821bfc 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -91,7 +91,6 @@ function AppSettingsUpdater() { (windowSettings?.["window:transparent"] || windowSettings?.["window:blur"]) ?? false; const opacity = util.boundNumber(windowSettings?.["window:opacity"] ?? 0.8, 0, 1); let baseBgColor = windowSettings?.["window:bgcolor"]; - console.log("window settings", windowSettings); if (isTransparentOrBlur) { document.body.classList.add("is-transparent"); const rootStyles = getComputedStyle(document.documentElement); diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index ba55d76b..2e696230 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -62,7 +62,7 @@ &.block-frame-default { position: relative; - padding: 2px; + padding: 1px; .block-frame-default-inner { background-color: var(--block-bg-color); diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 9e1e5e5c..5f0e5220 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -360,7 +360,6 @@ function handleWSEventMessage(msg: WSEventType) { } if (msg.eventtype == "config") { const fullConfig = (msg.data as WatcherUpdate).fullconfig; - console.log("fullConfig", fullConfig); globalStore.set(atoms.fullConfigAtom, fullConfig); return; } diff --git a/frontend/app/tab/tabcontent.tsx b/frontend/app/tab/tabcontent.tsx index c6a3597e..cca7dba9 100644 --- a/frontend/app/tab/tabcontent.tsx +++ b/frontend/app/tab/tabcontent.tsx @@ -4,23 +4,30 @@ import { Block } from "@/app/block/block"; import { CenteredDiv } from "@/element/quickelems"; import { ContentRenderer, NodeModel, PreviewRenderer, TileLayout } from "@/layout/index"; -import { getApi } from "@/store/global"; +import { TileLayoutContents } from "@/layout/lib/types"; +import { atoms, getApi } from "@/store/global"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; -import { useAtomValue } from "jotai"; +import { atom, useAtomValue } from "jotai"; import * as React from "react"; import { useMemo } from "react"; import "./tabcontent.less"; +const tileGapSizeAtom = atom((get) => { + const settings = get(atoms.settingsAtom); + return settings["window:tilegapsize"]; +}); + const TabContent = React.memo(({ tabId }: { tabId: string }) => { const oref = useMemo(() => WOS.makeORef("tab", tabId), [tabId]); const loadingAtom = useMemo(() => WOS.getWaveObjectLoadingAtom(oref), [oref]); const tabLoading = useAtomValue(loadingAtom); const tabAtom = useMemo(() => WOS.getWaveObjectAtom(oref), [oref]); const tabData = useAtomValue(tabAtom); + const tileGapSize = useAtomValue(tileGapSizeAtom); const tileLayoutContents = useMemo(() => { - const renderBlock: ContentRenderer = (nodeModel: NodeModel) => { + const renderContent: ContentRenderer = (nodeModel: NodeModel) => { return ; }; @@ -33,12 +40,13 @@ const TabContent = React.memo(({ tabId }: { tabId: string }) => { } return { - renderContent: renderBlock, - renderPreview: renderPreview, - tabId: tabId, - onNodeDelete: onNodeDelete, - }; - }, [tabId]); + renderContent, + renderPreview, + tabId, + onNodeDelete, + gapSizePx: tileGapSize, + } as TileLayoutContents; + }, [tabId, tileGapSize]); if (tabLoading) { return ( diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index 31632e7b..816db811 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -75,7 +75,6 @@ interface CodeEditorProps { const minimapEnabledAtom = atom((get) => { const settings = get(atoms.settingsAtom); - console.log("settings", settings); return settings["editor:minimapenabled"] ?? false; }); @@ -108,7 +107,6 @@ export function CodeEditor({ text, language, filename, onChange, onMount }: Code const editorOpts = useMemo(() => { const opts = defaultEditorOptions(); - console.log("minimapEnabled", minimapEnabled); opts.minimap.enabled = minimapEnabled; return opts; }, [minimapEnabled]); diff --git a/frontend/layout/lib/TileLayout.tsx b/frontend/layout/lib/TileLayout.tsx index c79146ea..612539bd 100644 --- a/frontend/layout/lib/TileLayout.tsx +++ b/frontend/layout/lib/TileLayout.tsx @@ -101,13 +101,15 @@ function TileLayoutComponent({ tabAtom, contents, getCursorPoint }: TileLayoutPr }, 50); }, []); + const gapSizePx = useAtomValue(layoutModel.gapSizePx); + const animationTimeS = useAtomValue(layoutModel.animationTimeS); const tileStyle = useMemo( () => ({ - "--gap-size-px": `${layoutModel.gapSizePx}px`, - "--animation-time-s": `${layoutModel.animationTimeS}s`, + "--gap-size-px": `${gapSizePx}px`, + "--animation-time-s": `${animationTimeS}s`, }) as CSSProperties, - [layoutModel.gapSizePx, layoutModel.animationTimeS] + [gapSizePx, animationTimeS] ); return ( diff --git a/frontend/layout/lib/layoutModel.ts b/frontend/layout/lib/layoutModel.ts index ceaaa80c..08a97395 100644 --- a/frontend/layout/lib/layoutModel.ts +++ b/frontend/layout/lib/layoutModel.ts @@ -56,7 +56,7 @@ interface ResizeContext { afterNodeStartSize: number; } -const DefaultGapSizePx = 5; +const DefaultGapSizePx = 3; const MinNodeSizePx = 40; const DefaultAnimationTimeS = 0.15; @@ -96,12 +96,12 @@ export class LayoutModel { /** * The size of the gap between nodes in CSS pixels. */ - gapSizePx: number; + gapSizePx: PrimitiveAtom; /** * The time a transition animation takes, in seconds. */ - animationTimeS: number; + animationTimeS: PrimitiveAtom; /** * List of nodes that are leafs and should be rendered as a DisplayNode. @@ -183,13 +183,7 @@ export class LayoutModel { * The resize handle size is double the gap size, or double the default gap size, whichever is greater. * @see gapSizePx @see DefaultGapSizePx */ - private resizeHandleSizePx: number; - /** - * Half of the size of the resize handles, in CSS pixels. - * - * @see resizeHandleSizePx This is just a precomputed halving of the resize handle size. - */ - private halfResizeHandleSizePx: number; + private resizeHandleSizePx: Atom; /** * A context used by the resize handles to keep track of precomputed values for the current resize operation. */ @@ -219,10 +213,12 @@ export class LayoutModel { this.renderContent = renderContent; this.renderPreview = renderPreview; this.onNodeDelete = onNodeDelete; - this.gapSizePx = gapSizePx ?? DefaultGapSizePx; - this.halfResizeHandleSizePx = this.gapSizePx > 5 ? this.gapSizePx : DefaultGapSizePx; - this.resizeHandleSizePx = 2 * this.halfResizeHandleSizePx; - this.animationTimeS = animationTimeS ?? DefaultAnimationTimeS; + this.gapSizePx = atom(gapSizePx ?? DefaultGapSizePx); + this.resizeHandleSizePx = atom((get) => { + const gapSizePx = get(this.gapSizePx); + return 2 * (gapSizePx > 5 ? gapSizePx : DefaultGapSizePx); + }); + this.animationTimeS = atom(animationTimeS ?? DefaultAnimationTimeS); this.lastTreeStateGeneration = -1; this.leafs = atom([]); @@ -292,9 +288,14 @@ export class LayoutModel { * @param contents Contains callbacks provided by the TileLayout component. */ registerTileLayout(contents: TileLayoutContents) { + console.log("registerTileLayout", contents); this.renderContent = contents.renderContent; this.renderPreview = contents.renderPreview; this.onNodeDelete = contents.onNodeDelete; + if (contents.gapSizePx !== undefined) { + console.log("setting gapSizePx", contents.gapSizePx); + this.setter(this.gapSizePx, contents.gapSizePx); + } } /** @@ -470,8 +471,9 @@ export class LayoutModel { pendingAction?.type === LayoutTreeActionType.ResizeNode ? (pendingAction as LayoutTreeResizeNodeAction) : null; + const resizeHandleSizePx = this.getter(this.resizeHandleSizePx); const callback = (node: LayoutNode) => - this.updateTreeHelper(node, newAdditionalProps, newLeafs, resizeAction); + this.updateTreeHelper(node, newAdditionalProps, newLeafs, resizeHandleSizePx, resizeAction); if (balanceTree) this.treeState.rootNode = balanceNode(this.treeState.rootNode, callback); else walkNodes(this.treeState.rootNode, callback); @@ -499,6 +501,7 @@ export class LayoutModel { node: LayoutNode, additionalPropsMap: Record, leafs: LayoutNode[], + resizeHandleSizePx: number, resizeAction?: LayoutTreeResizeNodeAction ) { /** @@ -567,15 +570,16 @@ export class LayoutModel { // We only want the resize handles in between nodes, this ensures we have n-1 handles. if (lastChildRect) { const resizeHandleIndex = resizeHandles.length; + const halfResizeHandleSizePx = resizeHandleSizePx / 2; const resizeHandleDimensions: Dimensions = { top: nodeIsRow ? lastChildRect.top - : lastChildRect.top + lastChildRect.height - this.halfResizeHandleSizePx, + : lastChildRect.top + lastChildRect.height - halfResizeHandleSizePx, left: nodeIsRow - ? lastChildRect.left + lastChildRect.width - this.halfResizeHandleSizePx + ? lastChildRect.left + lastChildRect.width - halfResizeHandleSizePx : lastChildRect.left, - width: nodeIsRow ? this.resizeHandleSizePx : lastChildRect.width, - height: nodeIsRow ? lastChildRect.height : this.resizeHandleSizePx, + width: nodeIsRow ? resizeHandleSizePx : lastChildRect.width, + height: nodeIsRow ? lastChildRect.height : resizeHandleSizePx, }; resizeHandles.push({ id: `${node.id}-${resizeHandleIndex}`, @@ -584,8 +588,7 @@ export class LayoutModel { transform: setTransform(resizeHandleDimensions, true, false), flexDirection: node.flexDirection, centerPx: - (nodeIsRow ? resizeHandleDimensions.left : resizeHandleDimensions.top) + - this.halfResizeHandleSizePx, + (nodeIsRow ? resizeHandleDimensions.left : resizeHandleDimensions.top) + halfResizeHandleSizePx, }); } lastChildRect = rect; @@ -750,7 +753,6 @@ export class LayoutModel { if (!this.nodeModels.has(nodeid)) { this.nodeModels.set(nodeid, { additionalProps: addlPropsAtom, - animationTimeS: this.animationTimeS, innerRect: atom((get) => { const addlProps = get(addlPropsAtom); const numLeafs = get(this.numLeafs); diff --git a/frontend/layout/lib/tilelayout.less b/frontend/layout/lib/tilelayout.less index 2daff040..c03ab478 100644 --- a/frontend/layout/lib/tilelayout.less +++ b/frontend/layout/lib/tilelayout.less @@ -49,14 +49,14 @@ cursor: ew-resize; .line { height: 100%; - margin-left: calc(var(--gap-size-px) - 1px); - border-left: 2px solid var(--accent-color); + width: calc(50% + 1px); + border-right: 2px solid var(--accent-color); } } &.flex-column { cursor: ns-resize; .line { - margin-top: calc(var(--gap-size-px) - 1px); + height: calc(50% + 1px); border-bottom: 2px solid var(--accent-color); } } @@ -80,7 +80,6 @@ } &.magnified { - border-radius: calc(var(--block-border-radius) + 4px); background-color: var(--block-bg-solid-color); z-index: var(--zindex-layout-magnified-node); } @@ -110,8 +109,11 @@ overflow: hidden; } - &:not(:only-child) .tile-leaf { - padding: calc(var(--gap-size-px) / 2); + &:not(:only-child), + &:not(.magnified) { + .tile-leaf { + padding: calc(var(--gap-size-px) / 2); + } } } diff --git a/frontend/layout/lib/types.ts b/frontend/layout/lib/types.ts index 7144a8d8..13e74262 100644 --- a/frontend/layout/lib/types.ts +++ b/frontend/layout/lib/types.ts @@ -274,6 +274,21 @@ export const DefaultNodeSize = 10; * nothing in here is specific to the TileLayout itself */ export interface TileLayoutContents { + /** + * The tabId with which this TileLayout is associated. + */ + tabId?: string; + + /** + * The class name to use for the top-level div of the tile layout. + */ + className?: string; + + /** + * The gap between tiles in a layout, in CSS pixels. + */ + gapSizePx?: number; + /** * A callback that accepts the data from the leaf node and displays the leaf contents to the user. */ @@ -287,21 +302,11 @@ export interface TileLayoutContents { * @param data The contents of the node that was deleted. */ onNodeDelete?: (data: TabLayoutData) => Promise; - /** - * The class name to use for the top-level div of the tile layout. - */ - className?: string; - /** * A callback for getting the cursor point in reference to the current window. This removes Electron as a runtime dependency, allowing for better integration with Storybook. * @returns The cursor position relative to the current window. */ getCursorPoint?: () => Point; - - /** - * tabId this TileLayout is associated with - */ - tabId?: string; } export interface ResizeHandleProps { @@ -325,7 +330,6 @@ export interface LayoutNodeAdditionalProps { export interface NodeModel { additionalProps: Atom; - animationTimeS: number; innerRect: Atom; blockNum: Atom; numLeafs: Atom; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index b1504758..d784d2b6 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -412,6 +412,7 @@ declare global { "window:opacity"?: number; "window:bgcolor"?: string; "window:reducedmotion"?: boolean; + "window:tilegapsize": number; "telemetry:*"?: boolean; "telemetry:enabled"?: boolean; }; diff --git a/pkg/wconfig/defaultconfig/settings.json b/pkg/wconfig/defaultconfig/settings.json index 8c5cb044..78699883 100644 --- a/pkg/wconfig/defaultconfig/settings.json +++ b/pkg/wconfig/defaultconfig/settings.json @@ -5,5 +5,6 @@ "autoupdate:enabled": true, "autoupdate:installonquit": true, "autoupdate:intervalms": 3600000, - "editor:minimapenabled": true + "editor:minimapenabled": true, + "window:tilegapsize": 3 } diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 2d57fae1..6d7dfc80 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -41,6 +41,7 @@ const ( ConfigKey_WindowOpacity = "window:opacity" ConfigKey_WindowBgColor = "window:bgcolor" ConfigKey_WindowReducedMotion = "window:reducedmotion" + ConfigKey_WindowTileGapSize = "window:tilegapsize" ConfigKey_TelemetryClear = "telemetry:*" ConfigKey_TelemetryEnabled = "telemetry:enabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 0c77371f..bc28718d 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -73,6 +73,7 @@ type SettingsType struct { WindowOpacity float64 `json:"window:opacity,omitempty"` WindowBgColor string `json:"window:bgcolor,omitempty"` WindowReducedMotion bool `json:"window:reducedmotion,omitempty"` + WindowTileGapSize int8 `json:"window:tilegapsize"` TelemetryClear bool `json:"telemetry:*,omitempty"` TelemetryEnabled bool `json:"telemetry:enabled,omitempty"`