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.
This commit is contained in:
Evan Simkowitz 2024-09-04 22:07:47 -07:00 committed by GitHub
parent 072730f7eb
commit 74c8044c73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 75 additions and 57 deletions

View File

@ -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);

View File

@ -62,7 +62,7 @@
&.block-frame-default {
position: relative;
padding: 2px;
padding: 1px;
.block-frame-default-inner {
background-color: var(--block-bg-color);

View File

@ -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;
}

View File

@ -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<Tab>(oref), [oref]);
const tabData = useAtomValue(tabAtom);
const tileGapSize = useAtomValue(tileGapSizeAtom);
const tileLayoutContents = useMemo(() => {
const renderBlock: ContentRenderer = (nodeModel: NodeModel) => {
const renderContent: ContentRenderer = (nodeModel: NodeModel) => {
return <Block key={nodeModel.blockId} nodeModel={nodeModel} preview={false} />;
};
@ -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 (

View File

@ -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]);

View File

@ -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 (

View File

@ -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<number>;
/**
* The time a transition animation takes, in seconds.
*/
animationTimeS: number;
animationTimeS: PrimitiveAtom<number>;
/**
* 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<number>;
/**
* 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<string, LayoutNodeAdditionalProps>,
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);

View File

@ -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,10 +109,13 @@
overflow: hidden;
}
&:not(:only-child) .tile-leaf {
&:not(:only-child),
&:not(.magnified) {
.tile-leaf {
padding: calc(var(--gap-size-px) / 2);
}
}
}
&.animate {
.tile-node,

View File

@ -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<void>;
/**
* 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<LayoutNodeAdditionalProps>;
animationTimeS: number;
innerRect: Atom<CSSProperties>;
blockNum: Atom<number>;
numLeafs: Atom<number>;

View File

@ -412,6 +412,7 @@ declare global {
"window:opacity"?: number;
"window:bgcolor"?: string;
"window:reducedmotion"?: boolean;
"window:tilegapsize": number;
"telemetry:*"?: boolean;
"telemetry:enabled"?: boolean;
};

View File

@ -5,5 +5,6 @@
"autoupdate:enabled": true,
"autoupdate:installonquit": true,
"autoupdate:intervalms": 3600000,
"editor:minimapenabled": true
"editor:minimapenabled": true,
"window:tilegapsize": 3
}

View File

@ -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"

View File

@ -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"`