feature: floating groups

This commit is contained in:
mathuo 2023-04-01 20:57:41 +01:00
parent 1f384c3c65
commit ffd5db273e
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
4 changed files with 436 additions and 0 deletions

View File

@ -329,6 +329,10 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
} }
export class DockviewApi implements CommonApi<SerializedDockview> { export class DockviewApi implements CommonApi<SerializedDockview> {
addFloating() {
return this.component.addFloating();
}
get id(): string { get id(): string {
return this.component.id; return this.component.id;
} }

View File

@ -0,0 +1,106 @@
.dv-resize-container {
position: absolute;
z-index: 9998;
background-color: white;
&.dv-resize-container-dragging {
opacity: 0.2;
}
.dv-resize-handle-top {
height: 4px;
width: calc(100% - 8px);
left: 4px;
top: -2px;
z-index: 9999;
position: absolute;
cursor: ns-resize;
background-color: red;
}
.dv-resize-handle-bottom {
height: 4px;
width: calc(100% - 8px);
left: 4px;
bottom: -2px;
z-index: 9999;
position: absolute;
cursor: ns-resize;
background-color: green;
}
.dv-resize-handle-left {
height: calc(100% - 8px);
width: 4px;
left: -2px;
top: 4px;
z-index: 9999;
position: absolute;
cursor: ew-resize;
background-color: yellow;
}
.dv-resize-handle-right {
height: calc(100% - 8px);
width: 4px;
right: -2px;
top: 4px;
z-index: 9999;
position: absolute;
cursor: ew-resize;
background-color: blue;
}
.dv-resize-handle-topleft {
height: 4px;
width: 4px;
top: -2px;
left: -2px;
z-index: 9999;
position: absolute;
cursor: nw-resize;
background-color: cyan;
}
.dv-resize-handle-topright {
height: 4px;
width: 4px;
right: -2px;
top: -2px;
z-index: 9999;
position: absolute;
cursor: ne-resize;
background-color: cyan;
}
.dv-resize-handle-bottomleft {
height: 4px;
width: 4px;
left: -2px;
bottom: -2px;
z-index: 9999;
position: absolute;
cursor: sw-resize;
background-color: cyan;
}
.dv-resize-handle-bottomright {
height: 4px;
width: 4px;
right: -2px;
bottom: -2px;
z-index: 9999;
position: absolute;
cursor: se-resize;
background-color: cyan;
}
}

View File

@ -0,0 +1,266 @@
import { toggleClass } from '../dom';
import { addDisposableListener, addDisposableWindowListener } from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
export class Overlay extends CompositeDisposable {
private _element: HTMLElement = document.createElement('div');
constructor(
private readonly container: HTMLElement,
private readonly content: HTMLElement,
private readonly options: {
height: number;
width: number;
left: number;
top: number;
}
) {
super();
this.setupOverlay();
this.setupDrag();
this.setupResize('top');
this.setupResize('bottom');
this.setupResize('left');
this.setupResize('right');
this.setupResize('topleft');
this.setupResize('topright');
this.setupResize('bottomleft');
this.setupResize('bottomright');
this._element.appendChild(content);
this.container.appendChild(this._element);
}
private setupResize(
direction:
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'topleft'
| 'topright'
| 'bottomleft'
| 'bottomright'
): void {
const resizeHandleElement = document.createElement('div');
resizeHandleElement.className = `dv-resize-handle-${direction}`;
this._element.appendChild(resizeHandleElement);
const move = new MutableDisposable();
this.addDisposables(
move,
addDisposableListener(resizeHandleElement, 'mousedown', (_) => {
_.preventDefault();
let offset: {
originalY: number;
originalHeight: number;
originalX: number;
originalWidth: number;
} | null = null;
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'mousemove', (e) => {
const rect = this.container.getBoundingClientRect();
const y = e.clientY - rect.top;
const x = e.clientX - rect.left;
const rect2 = this._element.getBoundingClientRect();
if (offset === null) {
offset = {
originalY: y,
originalHeight: rect2.height,
originalX: x,
originalWidth: rect2.width,
};
}
let top: number | null = null;
let height: number | null = null;
let left: number | null = null;
let width: number | null = null;
const MIN_HEIGHT = 20;
const MIN_WIDTH = 20;
function moveTop() {
top = clamp(
y,
0,
offset!.originalY +
offset!.originalHeight -
MIN_HEIGHT
);
height =
offset!.originalY +
offset!.originalHeight -
top;
}
function moveBottom() {
top = offset!.originalY - offset!.originalHeight;
height = clamp(
y - top,
MIN_HEIGHT,
rect.height -
offset!.originalY +
offset!.originalHeight
);
}
function moveLeft() {
left = clamp(
x,
0,
offset!.originalX +
offset!.originalWidth -
MIN_WIDTH
);
width =
offset!.originalX +
offset!.originalWidth -
left;
}
function moveRight() {
left = offset!.originalX - offset!.originalWidth;
width = clamp(
x - left,
MIN_WIDTH,
rect.width -
offset!.originalX +
offset!.originalWidth
);
}
switch (direction) {
case 'top':
moveTop();
break;
case 'bottom':
moveBottom();
break;
case 'left':
moveLeft();
break;
case 'right':
moveRight();
break;
case 'topleft':
moveTop();
moveLeft();
break;
case 'topright':
moveTop();
moveRight();
break;
case 'bottomleft':
moveBottom();
moveLeft();
break;
case 'bottomright':
moveBottom();
moveRight();
break;
}
if (height !== null) {
this._element.style.height = `${height}px`;
}
if (top !== null) {
this._element.style.top = `${top}px`;
}
if (left !== null) {
this._element.style.left = `${left}px`;
}
if (width !== null) {
this._element.style.width = `${width}px`;
}
}),
addDisposableWindowListener(window, 'mouseup', () => {
move.dispose();
})
);
})
);
}
private setupOverlay(): void {
this._element.style.height = `${this.options.height}px`;
this._element.style.width = `${this.options.width}px`;
this._element.style.left = `${this.options.left}px`;
this._element.style.top = `${this.options.top}px`;
//
this._element.className = 'dv-resize-container';
}
private setupDrag(): void {
const move = new MutableDisposable();
this.addDisposables(
move,
addDisposableListener(this._element, 'mousedown', (_) => {
if (_.defaultPrevented) {
return;
}
let offset: { x: number; y: number } | null = null;
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'mousemove', (e) => {
const rect = this.container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
toggleClass(
this._element,
'dv-resize-container-dragging',
true
);
const rect2 = this._element.getBoundingClientRect();
if (offset === null) {
offset = {
x: e.clientX - rect2.left,
y: e.clientY - rect2.top,
};
}
const left = clamp(
Math.max(0, x - offset.x),
0,
rect.width - rect2.width
);
const top = clamp(
Math.max(0, y - offset.y),
0,
rect.height - rect2.height
);
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
}),
addDisposableWindowListener(window, 'mouseup', () => {
toggleClass(
this._element,
'dv-resize-container-dragging',
false
);
move.dispose();
})
);
})
);
}
dispose(): void {
this._element.remove();
}
}

View File

@ -44,6 +44,7 @@ import {
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewPanelModel } from './dockviewPanelModel'; import { DockviewPanelModel } from './dockviewPanelModel';
import { getPanelData } from '../dnd/dataTransfer'; import { getPanelData } from '../dnd/dataTransfer';
import { Overlay } from '../dnd/overlay';
export interface PanelReference { export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void; update: (event: { params: { [key: string]: any } }) => void;
@ -117,6 +118,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onDidAddPanel: Event<IDockviewPanel>; readonly onDidAddPanel: Event<IDockviewPanel>;
readonly onDidLayoutFromJSON: Event<void>; readonly onDidLayoutFromJSON: Event<void>;
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>; readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
addFloating(): void;
} }
export class DockviewComponent export class DockviewComponent
@ -288,6 +290,8 @@ export class DockviewComponent
this._api = new DockviewApi(this); this._api = new DockviewApi(this);
this.updateWatermark(); this.updateWatermark();
this.element.style.position = 'relative';
} }
private orthogonalize(position: Position): DockviewGroupPanel { private orthogonalize(position: Position): DockviewGroupPanel {
@ -984,4 +988,60 @@ export class DockviewComponent
this._onDidRemovePanel.dispose(); this._onDidRemovePanel.dispose();
this._onDidLayoutFromJSON.dispose(); this._onDidLayoutFromJSON.dispose();
} }
//
addFloating() {
const parentDockview = this;
const floatingDockview = new DockviewComponent({
...this.options,
parentElement: undefined,
showDndOverlay: (event) => {
const data = event.getData();
if (data && data.viewId === parentDockview.id) {
return true;
}
return false;
},
});
floatingDockview.onDidDrop((event) => {
const data = event.getData();
if (!data || data.viewId !== parentDockview.id) {
return;
}
if (data.panelId === null) {
const group = parentDockview.removeGroup(
parentDockview.getPanel(data.groupId)!
);
} else {
const panel = parentDockview.removePanel(
parentDockview.getGroupPanel(data.panelId)!
);
parentDockview.moveGroupOrPanel()
}
});
floatingDockview.addPanel({
id: '__test__',
component: 'default',
});
floatingDockview.addPanel({
id: '__test__2__',
component: 'default',
});
const overlay = new Overlay(this.element, floatingDockview.element, {
height: 300,
width: 300,
left: 100,
top: 100,
});
}
} }