reworked drop zones to allow more pane drop positions

This commit is contained in:
Eugene Pankov 2021-08-02 17:40:54 +02:00
parent 0df5fb4a34
commit 85be974e64
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
3 changed files with 122 additions and 41 deletions

View File

@ -127,8 +127,14 @@ export interface SplitSpannerInfo {
* Represents a tab drop zone
*/
export interface SplitDropZoneInfo {
relativeToTab: BaseTabComponent
side: SplitDirection
container?: SplitContainer
position?: number
relativeTo?: BaseTabComponent|SplitContainer
side?: SplitDirection
x: number
y: number
w: number
h: number
}
/**
@ -370,7 +376,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
/**
* Inserts a new `tab` to the `side` of the `relative` tab
*/
async add (thing: BaseTabComponent|SplitContainer, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
async add (thing: BaseTabComponent|SplitContainer, relative: BaseTabComponent|SplitContainer|null, side: SplitDirection): Promise<void> {
if (thing instanceof SplitTabComponent) {
const tab = thing
thing = tab.root
@ -389,31 +395,40 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
thing.parent = this
}
let target = (relative ? this.getParentOf(relative) : null) ?? this.root
let insertIndex = relative ? target.children.indexOf(relative) : -1
let target = relative ? this.getParentOf(relative) : null
if (!target) {
// Rewrap the root container just in case the orientation isn't compatibile
target = new SplitContainer()
target.orientation = ['l', 'r'].includes(side) ? 'h' : 'v'
target.children = [this.root]
target.ratios = [1]
this.root = target
}
let insertIndex = relative
? target.children.indexOf(relative) + ('tl'.includes(side) ? 0 : 1)
: 'tl'.includes(side) ? 0 : -1
if (
target.orientation === 'v' && ['l', 'r'].includes(side) ||
target.orientation === 'h' && ['t', 'b'].includes(side)
) {
// Inserting into a container but the orientation isn't compatible
const newContainer = new SplitContainer()
newContainer.orientation = target.orientation === 'v' ? 'h' : 'v'
newContainer.orientation = ['l', 'r'].includes(side) ? 'h' : 'v'
newContainer.children = relative ? [relative] : []
newContainer.ratios = [1]
target.children[insertIndex] = newContainer
target.children.splice(relative ? target.children.indexOf(relative) : -1, 1, newContainer)
target = newContainer
insertIndex = 0
}
if (insertIndex === -1) {
insertIndex = 0
} else {
insertIndex += side === 'l' || side === 't' ? 0 : 1
}
for (let i = 0; i < target.children.length; i++) {
target.ratios[i] *= target.children.length / (target.children.length + 1)
}
if (insertIndex === -1) {
insertIndex = target.ratios.length
}
target.ratios.splice(insertIndex, 0, 1 / (target.children.length + 1))
target.children.splice(insertIndex, 0, thing)
@ -570,7 +585,11 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
return
}
this.add(tab, zone.relativeToTab, zone.side)
if (zone.container) {
this.add(tab, zone.container.children[zone.position!], zone.container.orientation === 'h' ? 'r' : 'b')
} else {
this.add(tab, null, zone.side!)
}
this.tabAdopted.next(tab)
}
@ -622,6 +641,38 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
const size = root.orientation === 'v' ? h : w
const sizes = root.ratios.map(ratio => ratio * size)
const thickness = 10
if (root === this.root) {
this._dropZones.push({
x: x - thickness / 2,
y: y + thickness,
w: thickness,
h: h - thickness * 2,
side: 'l',
})
this._dropZones.push({
x,
y: y - thickness / 2,
w,
h: thickness,
side: 't',
})
this._dropZones.push({
x: x + w - thickness / 2,
y: y + thickness,
w: thickness,
h: h - thickness * 2,
side: 'r',
})
this._dropZones.push({
x,
y: y + h - thickness / 2,
w,
h: thickness,
side: 'b',
})
}
root.x = x
root.y = y
@ -655,16 +706,59 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
element.style.width = '90%'
element.style.height = '90%'
}
}
}
for (const side of ['t', 'r', 'b', 'l']) {
offset += sizes[i]
if (i !== root.ratios.length - 1) {
// Spanner area
this._dropZones.push({
relativeToTab: child,
side: side as SplitDirection,
relativeTo: root.children[i],
side: root.orientation === 'v' ? 'b': 'r',
x: root.orientation === 'v' ? childX + thickness : childX + offset - thickness / 2,
y: root.orientation === 'v' ? childY + offset - thickness / 2 : childY + thickness,
w: root.orientation === 'v' ? childW - thickness * 2 : thickness,
h: root.orientation === 'v' ? thickness : childH - thickness * 2,
})
}
// Sides
if (root.orientation === 'v') {
this._dropZones.push({
x: childX,
y: childY + thickness,
w: thickness,
h: childH - thickness * 2,
relativeTo: child,
side: 'l',
})
this._dropZones.push({
x: childX + w - thickness,
y: childY + thickness,
w: thickness,
h: childH - thickness * 2,
relativeTo: child,
side: 'r',
})
} else {
this._dropZones.push({
x: childX + thickness,
y: childY,
w: childW - thickness * 2,
h: thickness,
relativeTo: child,
side: 't',
})
this._dropZones.push({
x: childX + thickness,
y: childY + childH - thickness,
w: childW - thickness * 2,
h: thickness,
relativeTo: child,
side: 'b',
})
}
}
offset += sizes[i]
if (i !== 0) {
this._spanners.push({

View File

@ -9,6 +9,7 @@
flex: 1 1 0;
width: 100%;
height: 100%;
opacity: 0;
background: rgba(255, 255, 255, .125);
border-radius: 5px;
@ -21,6 +22,7 @@
border-radius: 3px;
> div {
opacity: 1;
background: rgba(255, 255, 255, .5);
}
}

View File

@ -34,7 +34,7 @@ export class SplitTabDropZoneComponent extends SelfPositioningComponent {
) {
super(element)
this.subscribeUntilDestroyed(app.tabDragActive$, tab => {
this.isActive = !!tab && tab !== this.parent && tab !== this.dropZone.relativeToTab
this.isActive = !!tab && tab !== this.parent && tab !== this.dropZone.container?.children[this.dropZone.position!]
this.layout()
})
}
@ -44,26 +44,11 @@ export class SplitTabDropZoneComponent extends SelfPositioningComponent {
}
layout () {
const tabElement: HTMLElement|undefined = this.dropZone.relativeToTab.viewContainerEmbeddedRef?.rootNodes[0]
if (!tabElement) {
// being destroyed
return
}
const args = {
t: [0, 0, tabElement.clientWidth, tabElement.clientHeight / 5],
l: [0, tabElement.clientHeight / 5, tabElement.clientWidth / 3, tabElement.clientHeight * 3 / 5],
r: [tabElement.clientWidth * 2 / 3, tabElement.clientHeight / 5, tabElement.clientWidth / 3, tabElement.clientHeight * 3 / 5],
b: [0, tabElement.clientHeight * 4 / 5, tabElement.clientWidth, tabElement.clientHeight / 5],
}[this.dropZone.side]
this.setDimensions(
args[0] + tabElement.offsetLeft,
args[1] + tabElement.offsetTop,
args[2],
args[3],
'px'
this.dropZone.x,
this.dropZone.y,
this.dropZone.w,
this.dropZone.h,
)
}
}