mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-22 19:38:00 +00:00
splitting tabs (fixes #49)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
||||
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
|
||||
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
||||
export { ConfigProvider } from './configProvider'
|
||||
|
5
terminus-core/src/components/splitTab.component.scss
Normal file
5
terminus-core/src/components/splitTab.component.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
flex: auto;
|
||||
}
|
@@ -13,6 +13,10 @@ export class SplitContainer {
|
||||
orientation: SplitOrientation = 'h'
|
||||
children: (BaseTabComponent | SplitContainer)[] = []
|
||||
ratios: number[] = []
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
|
||||
allTabs () {
|
||||
let r = []
|
||||
@@ -60,6 +64,14 @@ export class SplitContainer {
|
||||
this.ratios = this.ratios.map(x => x / s)
|
||||
}
|
||||
|
||||
getOffsetRatio (index: number): number {
|
||||
let s = 0
|
||||
for (let i = 0; i < index; i++) {
|
||||
s += this.ratios[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
async serialize () {
|
||||
let children = []
|
||||
for (let child of this.children) {
|
||||
@@ -78,12 +90,23 @@ export class SplitContainer {
|
||||
}
|
||||
}
|
||||
|
||||
interface SpannerInfo {
|
||||
container: SplitContainer
|
||||
index: number
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'splitTab',
|
||||
template: '<ng-container #vc></ng-container>',
|
||||
styles: [
|
||||
':host { position: relative; flex: auto; display: block; }'
|
||||
]
|
||||
selector: 'split-tab',
|
||||
template: `
|
||||
<ng-container #vc></ng-container>
|
||||
<split-tab-spanner
|
||||
*ngFor='let spanner of spanners'
|
||||
[container]='spanner.container'
|
||||
[index]='spanner.index'
|
||||
(change)='layout()'
|
||||
></split-tab-spanner>
|
||||
`,
|
||||
styles: [require('./splitTab.component.scss')],
|
||||
})
|
||||
export class SplitTabComponent extends BaseTabComponent {
|
||||
root: SplitContainer
|
||||
@@ -92,6 +115,7 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
hotkeysSubscription: Subscription
|
||||
focusedTab: BaseTabComponent
|
||||
recoveredState: any
|
||||
spanners: SpannerInfo[] = []
|
||||
|
||||
constructor (
|
||||
private hotkeys: HotkeysService,
|
||||
@@ -297,6 +321,7 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
|
||||
private layout () {
|
||||
this.root.normalize()
|
||||
this.spanners = []
|
||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||
}
|
||||
|
||||
@@ -304,6 +329,11 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
let size = (root.orientation === 'v') ? h : w
|
||||
let sizes = root.ratios.map(x => x * size)
|
||||
|
||||
root.x = x
|
||||
root.y = y
|
||||
root.w = w
|
||||
root.h = h
|
||||
|
||||
let offset = 0
|
||||
root.children.forEach((child, i) => {
|
||||
let childX = (root.orientation === 'v') ? x : (x + offset)
|
||||
@@ -323,6 +353,13 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
element.style.opacity = (child === this.focusedTab) ? 1 : 0.75
|
||||
}
|
||||
offset += sizes[i]
|
||||
|
||||
if (i !== 0) {
|
||||
this.spanners.push({
|
||||
container: root,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
22
terminus-core/src/components/splitTabSpanner.component.scss
Normal file
22
terminus-core/src/components/splitTabSpanner.component.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
:host {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
transition: 0.125s background;
|
||||
|
||||
&.v {
|
||||
cursor: ns-resize;
|
||||
height: 10px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
&.h {
|
||||
cursor: ew-resize;
|
||||
width: 10px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
&:hover, &.active {
|
||||
background: rgba(255, 255, 255, .125);
|
||||
}
|
||||
}
|
87
terminus-core/src/components/splitTabSpanner.component.ts
Normal file
87
terminus-core/src/components/splitTabSpanner.component.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
|
||||
import { SplitContainer } from './splitTab.component'
|
||||
|
||||
@Component({
|
||||
selector: 'split-tab-spanner',
|
||||
template: '',
|
||||
styles: [require('./splitTabSpanner.component.scss')],
|
||||
})
|
||||
export class SplitTabSpannerComponent {
|
||||
@Input() container: SplitContainer
|
||||
@Input() index: number
|
||||
@Output() change = new EventEmitter<void>()
|
||||
@HostBinding('class.active') isActive = false
|
||||
@HostBinding('class.h') isHorizontal = false
|
||||
@HostBinding('class.v') isVertical = true
|
||||
@HostBinding('style.left') cssLeft: string
|
||||
@HostBinding('style.top') cssTop: string
|
||||
@HostBinding('style.width') cssWidth: string
|
||||
@HostBinding('style.height') cssHeight: string
|
||||
private marginOffset = -5
|
||||
|
||||
constructor (private element: ElementRef) { }
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.element.nativeElement.addEventListener('mousedown', e => {
|
||||
this.isActive = true
|
||||
let start = this.isVertical ? e.pageY : e.pageX
|
||||
let current = start
|
||||
let oldPosition = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
|
||||
|
||||
const dragHandler = e => {
|
||||
current = this.isVertical ? e.pageY : e.pageX
|
||||
let newPosition = oldPosition + (current - start)
|
||||
if (this.isVertical) {
|
||||
this.element.nativeElement.style.top = `${newPosition - this.marginOffset}px`
|
||||
} else {
|
||||
this.element.nativeElement.style.left = `${newPosition - this.marginOffset}px`
|
||||
}
|
||||
}
|
||||
|
||||
const offHandler = () => {
|
||||
this.isActive = false
|
||||
document.removeEventListener('mouseup', offHandler)
|
||||
this.element.nativeElement.parentElement.removeEventListener('mousemove', dragHandler)
|
||||
|
||||
let diff = (current - start) / (this.isVertical ? this.element.nativeElement.parentElement.clientHeight : this.element.nativeElement.parentElement.clientWidth)
|
||||
|
||||
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
|
||||
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
|
||||
|
||||
this.container.ratios[this.index - 1] += diff
|
||||
this.container.ratios[this.index] -= diff
|
||||
this.change.emit()
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', offHandler)
|
||||
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
|
||||
})
|
||||
}
|
||||
|
||||
ngOnChanges () {
|
||||
this.isHorizontal = this.container.orientation === 'h'
|
||||
this.isVertical = this.container.orientation === 'v'
|
||||
if (this.isVertical) {
|
||||
this.setDimensions(
|
||||
this.container.x,
|
||||
this.container.y + this.container.h * this.container.getOffsetRatio(this.index),
|
||||
this.container.w,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
this.setDimensions(
|
||||
this.container.x + this.container.w * this.container.getOffsetRatio(this.index),
|
||||
this.container.y,
|
||||
null,
|
||||
this.container.h
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private setDimensions (x: number, y: number, w: number, h: number) {
|
||||
this.cssLeft = `${x}%`
|
||||
this.cssTop = `${y}%`
|
||||
this.cssWidth = w ? `${w}%` : null
|
||||
this.cssHeight = h ? `${h}%` : null
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ import { ToggleComponent } from './components/toggle.component'
|
||||
import { WindowControlsComponent } from './components/windowControls.component'
|
||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
||||
|
||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||
|
||||
@@ -70,6 +71,7 @@ const PROVIDERS = [
|
||||
SafeModeModalComponent,
|
||||
AutofocusDirective,
|
||||
SplitTabComponent,
|
||||
SplitTabSpannerComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
RenameTabModalComponent,
|
||||
|
@@ -37,7 +37,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
if (settingsTab) {
|
||||
this.app.selectTab(settingsTab)
|
||||
} else {
|
||||
this.app.openNewTab(SettingsTabComponent)
|
||||
this.app.openNewTabRaw(SettingsTabComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user