bug: panel gap styling

This commit is contained in:
mathuo 2024-05-29 20:08:14 +01:00
parent b81afd45e8
commit ea9dc13992
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
12 changed files with 161 additions and 58 deletions

View File

@ -772,4 +772,26 @@ describe('splitview', () => {
view1.fireChangeEvent({ size: 300 }); view1.fireChangeEvent({ size: 300 });
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
}); });
test('that margins are applied to view sizing', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
margin: 24,
});
splitview.layout(924, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000, LayoutPriority.High);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
expect([view1.size]).toEqual([924]);
splitview.addView(view2);
expect([view1.size, view2.size]).toEqual([450, 450]); // 450 + 24 + 450 = 924
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([292, 292, 292]); // 292 + 24 + 292 + 24 + 292 = 924
});
}); });

View File

@ -870,4 +870,8 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
): Promise<void> { ): Promise<void> {
return this.component.addPopoutGroup(item, options); return this.component.addPopoutGroup(item, options);
} }
setGap(gap: number | undefined): void {
this.component.updateOptions({ gap });
}
} }

View File

@ -14,41 +14,6 @@
.dv-overlay-render-container { .dv-overlay-render-container {
position: relative; position: relative;
} }
.split-view-container {
&.horizontal {
> .view-container > .view {
&:not(:last-child) {
.groupview {
border-right: var(--dv-group-gap-size) solid transparent;
}
}
&:not(:first-child) {
.groupview {
border-left: var(--dv-group-gap-size) solid transparent;
}
}
}
}
&.vertical {
> .view-container > .view {
&:not(:last-child) {
.groupview {
border-bottom: var(--dv-group-gap-size) solid
transparent;
}
}
&:not(:first-child) {
.groupview {
border-top: var(--dv-group-gap-size) solid transparent;
}
}
}
}
}
} }
.groupview { .groupview {

View File

@ -345,6 +345,7 @@ export class DockviewComponent
parentElement: options.parentElement, parentElement: options.parentElement,
disableAutoResizing: options.disableAutoResizing, disableAutoResizing: options.disableAutoResizing,
locked: options.locked, locked: options.locked,
margin: options.gap,
}); });
const gready = document.createElement('div'); const gready = document.createElement('div');
@ -977,6 +978,14 @@ export class DockviewComponent
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!); this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
} }
if (this.gridview.margin !== 0 && options.gap === undefined) {
this.gridview.margin = 0;
}
if (typeof options.gap === 'number') {
this.gridview.margin = options.gap;
}
this.layout(this.gridview.width, this.gridview.height, true); this.layout(this.gridview.width, this.gridview.height, true);
} }

View File

@ -48,6 +48,7 @@ export interface DockviewOptions {
rootOverlayModel?: DroptargetOverlayModel; rootOverlayModel?: DroptargetOverlayModel;
locked?: boolean; locked?: boolean;
disableDnd?: boolean; disableDnd?: boolean;
gap?: number;
} }
export interface DockviewDndOverlayEvent { export interface DockviewDndOverlayEvent {
@ -98,6 +99,7 @@ export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
rootOverlayModel: undefined, rootOverlayModel: undefined,
locked: undefined, locked: undefined,
disableDnd: undefined, disableDnd: undefined,
gap: undefined,
}; };
return Object.keys(properties) as (keyof DockviewOptions)[]; return Object.keys(properties) as (keyof DockviewOptions)[];

View File

@ -35,6 +35,7 @@ export interface BaseGridOptions {
readonly parentElement: HTMLElement; readonly parentElement: HTMLElement;
readonly disableAutoResizing?: boolean; readonly disableAutoResizing?: boolean;
readonly locked?: boolean; readonly locked?: boolean;
readonly margin?: number;
} }
export interface IGridPanelView extends IGridView, IPanel { export interface IGridPanelView extends IGridView, IPanel {
@ -148,7 +149,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
this.gridview = new Gridview( this.gridview = new Gridview(
!!options.proportionalLayout, !!options.proportionalLayout,
options.styles, options.styles,
options.orientation options.orientation,
options.locked,
options.margin
); );
this.gridview.locked = !!options.locked; this.gridview.locked = !!options.locked;

View File

@ -139,6 +139,20 @@ export class BranchNode extends CompositeDisposable implements IView {
this.splitview.disabled = value; this.splitview.disabled = value;
} }
get margin(): number {
return this.splitview.margin;
}
set margin(value: number) {
this.splitview.margin = value;
this.children.forEach((child) => {
if (child instanceof BranchNode) {
child.margin = value;
}
});
}
constructor( constructor(
readonly orientation: Orientation, readonly orientation: Orientation,
readonly proportionalLayout: boolean, readonly proportionalLayout: boolean,
@ -146,6 +160,7 @@ export class BranchNode extends CompositeDisposable implements IView {
size: number, size: number,
orthogonalSize: number, orthogonalSize: number,
disabled: boolean, disabled: boolean,
margin: number | undefined,
childDescriptors?: INodeDescriptor[] childDescriptors?: INodeDescriptor[]
) { ) {
super(); super();
@ -160,6 +175,7 @@ export class BranchNode extends CompositeDisposable implements IView {
orientation: this.orientation, orientation: this.orientation,
proportionalLayout, proportionalLayout,
styles, styles,
margin,
}); });
this.splitview.layout(this.size, this.orthogonalSize); this.splitview.layout(this.size, this.orthogonalSize);
} else { } else {
@ -184,6 +200,7 @@ export class BranchNode extends CompositeDisposable implements IView {
descriptor, descriptor,
proportionalLayout, proportionalLayout,
styles, styles,
margin,
}); });
} }

View File

@ -42,7 +42,8 @@ function flipNode<T extends Node>(
node.styles, node.styles,
size, size,
orthogonalSize, orthogonalSize,
node.disabled node.disabled,
node.margin
); );
let totalSize = 0; let totalSize = 0;
@ -275,6 +276,7 @@ export class Gridview implements IDisposable {
private _root: BranchNode | undefined; private _root: BranchNode | undefined;
private _locked = false; private _locked = false;
private _margin = 0;
private _maximizedNode: private _maximizedNode:
| { leaf: LeafNode; hiddenOnMaximize: LeafNode[] } | { leaf: LeafNode; hiddenOnMaximize: LeafNode[] }
| undefined = undefined; | undefined = undefined;
@ -356,6 +358,15 @@ export class Gridview implements IDisposable {
} }
} }
get margin(): number {
return this._margin;
}
set margin(value: number) {
this._margin = value;
this.root.margin = value;
}
maximizedView(): IGridView | undefined { maximizedView(): IGridView | undefined {
return this._maximizedNode?.leaf.view; return this._maximizedNode?.leaf.view;
} }
@ -466,7 +477,8 @@ export class Gridview implements IDisposable {
this.styles, this.styles,
this.root.size, this.root.size,
this.root.orthogonalSize, this.root.orthogonalSize,
this._locked this.locked,
this.margin
); );
} }
@ -527,7 +539,8 @@ export class Gridview implements IDisposable {
this.styles, this.styles,
node.size, // <- orthogonal size - flips at each depth node.size, // <- orthogonal size - flips at each depth
orthogonalSize, // <- size - flips at each depth, orthogonalSize, // <- size - flips at each depth,
this._locked, this.locked,
this.margin,
children children
); );
} else { } else {
@ -580,7 +593,8 @@ export class Gridview implements IDisposable {
this.styles, this.styles,
this.root.orthogonalSize, this.root.orthogonalSize,
this.root.size, this.root.size,
this._locked this.locked,
this.margin
); );
if (oldRoot.children.length === 0) { if (oldRoot.children.length === 0) {
@ -686,17 +700,24 @@ export class Gridview implements IDisposable {
constructor( constructor(
readonly proportionalLayout: boolean, readonly proportionalLayout: boolean,
readonly styles: ISplitviewStyles | undefined, readonly styles: ISplitviewStyles | undefined,
orientation: Orientation orientation: Orientation,
locked?: boolean,
margin?: number
) { ) {
this.element = document.createElement('div'); this.element = document.createElement('div');
this.element.className = 'grid-view'; this.element.className = 'grid-view';
this._locked = locked ?? false;
this._margin = margin ?? 0;
this.root = new BranchNode( this.root = new BranchNode(
orientation, orientation,
proportionalLayout, proportionalLayout,
styles, styles,
0, 0,
0, 0,
this._locked this.locked,
this.margin
); );
} }
@ -781,7 +802,8 @@ export class Gridview implements IDisposable {
this.styles, this.styles,
parent.size, parent.size,
parent.orthogonalSize, parent.orthogonalSize,
this._locked this.locked,
this.margin
); );
grandParent.addChild(newParent, parent.size, parentIndex); grandParent.addChild(newParent, parent.size, parentIndex);

View File

@ -36,6 +36,7 @@ export interface SplitViewOptions {
readonly descriptor?: ISplitViewDescriptor; readonly descriptor?: ISplitViewDescriptor;
readonly proportionalLayout?: boolean; readonly proportionalLayout?: boolean;
readonly styles?: ISplitviewStyles; readonly styles?: ISplitviewStyles;
readonly margin?: number;
} }
export enum LayoutPriority { export enum LayoutPriority {
@ -110,6 +111,7 @@ export class Splitview {
private _startSnappingEnabled = true; private _startSnappingEnabled = true;
private _endSnappingEnabled = true; private _endSnappingEnabled = true;
private _disabled = false; private _disabled = false;
private _margin = 0;
private readonly _onDidSashEnd = new Emitter<void>(); private readonly _onDidSashEnd = new Emitter<void>();
readonly onDidSashEnd = this._onDidSashEnd.event; readonly onDidSashEnd = this._onDidSashEnd.event;
@ -211,6 +213,14 @@ export class Splitview {
toggleClass(this.element, 'dv-splitview-disabled', value); toggleClass(this.element, 'dv-splitview-disabled', value);
} }
get margin(): number {
return this._margin;
}
set margin(value: number) {
this._margin = value;
}
constructor( constructor(
private readonly container: HTMLElement, private readonly container: HTMLElement,
options: SplitViewOptions options: SplitViewOptions
@ -218,6 +228,8 @@ export class Splitview {
this._orientation = options.orientation; this._orientation = options.orientation;
this.element = this.createContainer(); this.element = this.createContainer();
this.margin = options.margin ?? 0;
this.proportionalLayout = this.proportionalLayout =
options.proportionalLayout === undefined options.proportionalLayout === undefined
? true ? true
@ -785,18 +797,39 @@ export class Splitview {
} }
} }
/**
* Margin explain:
*
* For `n` views in a splitview there will be `n-1` margins `m`.
*
* To fit the margins each view must reduce in size by `(m * (n - 1)) / n`.
*
* For each view `i` the offet must be adjusted by `m * i/(n - 1)`.
*/
private layoutViews(): void { private layoutViews(): void {
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let sum = 0;
const x: number[] = [];
this.updateSashEnablement(); this.updateSashEnablement();
for (let i = 0; i < this.viewItems.length - 1; i++) { if (this.viewItems.length === 0) {
sum += this.viewItems[i].size; return;
x.push(sum); }
const offset = Math.min(Math.max(0, sum - 2), this.size - 4); const sashCount = this.viewItems.length - 1;
const marginReducedSize =
(this.margin * sashCount) / this.viewItems.length;
let totalLeftOffset = 0;
const viewLeftOffsets: number[] = [];
for (let i = 0; i < this.viewItems.length - 1; i++) {
totalLeftOffset += this.viewItems[i].size;
viewLeftOffsets.push(totalLeftOffset);
const offset = Math.min(
Math.max(0, totalLeftOffset - 2),
this.size - this.margin
);
if (this._orientation === Orientation.HORIZONTAL) { if (this._orientation === Orientation.HORIZONTAL) {
this.sashes[i].container.style.left = `${offset}px`; this.sashes[i].container.style.left = `${offset}px`;
@ -808,20 +841,30 @@ export class Splitview {
} }
} }
this.viewItems.forEach((view, i) => { this.viewItems.forEach((view, i) => {
const size = view.size - marginReducedSize;
const offset =
i === 0
? 0
: viewLeftOffsets[i - 1] +
(i / sashCount) * marginReducedSize;
if (this._orientation === Orientation.HORIZONTAL) { if (this._orientation === Orientation.HORIZONTAL) {
view.container.style.width = `${view.size}px`; view.container.style.width = `${size}px`;
view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.left = `${offset}px`;
view.container.style.top = ''; view.container.style.top = '';
view.container.style.height = ''; view.container.style.height = '';
} }
if (this._orientation === Orientation.VERTICAL) { if (this._orientation === Orientation.VERTICAL) {
view.container.style.height = `${view.size}px`; view.container.style.height = `${size}px`;
view.container.style.top = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.top = `${offset}px`;
view.container.style.width = ''; view.container.style.width = '';
view.container.style.left = ''; view.container.style.left = '';
} }
view.view.layout(view.size, this._orthogonalSize); view.view.layout(
view.size - marginReducedSize,
this._orthogonalSize
);
}); });
} }

View File

@ -229,8 +229,6 @@
} }
@mixin dockview-design-replit-mixin { @mixin dockview-design-replit-mixin {
--dv-group-gap-size: 3px;
.dv-resize-container:has(> .groupview) { .dv-resize-container:has(> .groupview) {
border-radius: 8px; border-radius: 8px;
} }

View File

@ -12,7 +12,7 @@ describe('gridview react', () => {
let components: Record< let components: Record<
string, string,
React.FunctionComponent<IGridviewPanelProps> React.FunctionComponent<IGridviewPanelProps>
> >;
beforeEach(() => { beforeEach(() => {
components = { components = {

View File

@ -51,6 +51,12 @@ export const GridActions = (props: {
props.api?.addGroup(); props.api?.addGroup();
}; };
const [gap, setGap] = React.useState(0);
React.useEffect(() => {
props.api?.setGap(gap);
}, [gap, props.api]);
return ( return (
<div className="action-container"> <div className="action-container">
<button className="text-button" onClick={() => onAddPanel()}> <button className="text-button" onClick={() => onAddPanel()}>
@ -89,6 +95,18 @@ export const GridActions = (props: {
<button className="text-button" onClick={onReset}> <button className="text-button" onClick={onReset}>
Reset Reset
</button> </button>
<span style={{ flexGrow: 1 }} />
<div style={{ display: 'flex' }}>
<span style={{ paddingRight: '4px' }}>Gap</span>
<input
type="number"
min={0}
max={20}
step={1}
value={gap}
onChange={(event) => setGap(Number(event.target.value))}
/>
</div>
</div> </div>
); );
}; };