mirror of https://github.com/onivim/oni.git
326 lines
8.8 KiB
TypeScript
326 lines
8.8 KiB
TypeScript
/**
|
|
* WindowManager.ts
|
|
*
|
|
* Responsible for managing state of the editor collection, and
|
|
* switching between active editors.
|
|
*
|
|
* It also provides convenience methods for hooking events
|
|
* to the active editor, and managing transitions between editors.
|
|
*/
|
|
|
|
import { remote } from "electron"
|
|
|
|
import { Store } from "redux"
|
|
|
|
import * as Oni from "oni-api"
|
|
import { Event, IEvent } from "oni-types"
|
|
|
|
import { Direction, SplitDirection } from "./index"
|
|
import { LinearSplitProvider } from "./LinearSplitProvider"
|
|
import { RelationalSplitNavigator } from "./RelationalSplitNavigator"
|
|
import { WindowDockNavigator } from "./WindowDock"
|
|
|
|
import {
|
|
createStore,
|
|
IAugmentedSplitInfo,
|
|
ISplitInfo,
|
|
leftDockSelector,
|
|
WindowState,
|
|
} from "./WindowManagerStore"
|
|
|
|
export class WindowSplitHandle implements Oni.WindowSplitHandle {
|
|
public get id(): string {
|
|
return this._id
|
|
}
|
|
|
|
public get isVisible(): boolean {
|
|
return this._store.getState().hiddenSplits.indexOf(this._id) === -1
|
|
}
|
|
|
|
public get isFocused(): boolean {
|
|
return this._store.getState().focusedSplitId === this._id
|
|
}
|
|
|
|
constructor(
|
|
private _store: Store<WindowState>,
|
|
private _windowManager: WindowManager,
|
|
private _id: string,
|
|
) {}
|
|
|
|
public hide(): void {
|
|
this._store.dispatch({
|
|
type: "HIDE_SPLIT",
|
|
splitId: this._id,
|
|
})
|
|
|
|
this._windowManager.swapToPreviousSplit(this._id)
|
|
}
|
|
|
|
public show(): void {
|
|
this._store.dispatch({
|
|
type: "SHOW_SPLIT",
|
|
splitId: this._id,
|
|
})
|
|
}
|
|
|
|
public focus(): void {
|
|
// TODO:
|
|
this._windowManager.focusSplit(this._id)
|
|
}
|
|
|
|
public setSize(size: number): void {
|
|
// TODO
|
|
}
|
|
|
|
public close(): void {
|
|
this._windowManager.close(this._id)
|
|
}
|
|
}
|
|
|
|
export class AugmentedWindow implements IAugmentedSplitInfo {
|
|
public get id(): string {
|
|
return this._id
|
|
}
|
|
|
|
constructor(private _id: string, private _innerSplit: Oni.IWindowSplit | any) {}
|
|
|
|
public get innerSplit(): Oni.IWindowSplit {
|
|
return this._innerSplit
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
return this._innerSplit.render()
|
|
}
|
|
|
|
public enter(): void {
|
|
if (this._innerSplit.enter) {
|
|
this._innerSplit.enter()
|
|
}
|
|
}
|
|
|
|
public leave(): void {
|
|
if (this._innerSplit.leave) {
|
|
this._innerSplit.leave()
|
|
}
|
|
}
|
|
}
|
|
|
|
export class WindowManager {
|
|
private _lastId: number = 0
|
|
private _idToSplit: { [key: string]: IAugmentedSplitInfo } = {}
|
|
|
|
private _onUnhandledMoveEvent = new Event<Direction>()
|
|
|
|
private _leftDock: WindowDockNavigator = null
|
|
private _primarySplit: LinearSplitProvider
|
|
private _rootNavigator: RelationalSplitNavigator
|
|
|
|
// Queue of recently focused windows, to fall-back to
|
|
// when closing a window.
|
|
private _focusQueue: string[] = []
|
|
|
|
private _store: Store<WindowState>
|
|
|
|
public get onUnhandledMove(): IEvent<Direction> {
|
|
return this._onUnhandledMoveEvent
|
|
}
|
|
|
|
private _onFocusChanged = new Event<ISplitInfo<Oni.IWindowSplit>>()
|
|
|
|
public get onFocusChanged(): IEvent<ISplitInfo<Oni.IWindowSplit>> {
|
|
return this._onFocusChanged
|
|
}
|
|
|
|
public get splitRoot(): ISplitInfo<Oni.IWindowSplit> {
|
|
return this._primarySplit.getState() as ISplitInfo<Oni.IWindowSplit>
|
|
}
|
|
|
|
public get store(): Store<WindowState> {
|
|
return this._store
|
|
}
|
|
|
|
get activeSplitHandle(): WindowSplitHandle {
|
|
return new WindowSplitHandle(this._store, this, this.activeSplit.id)
|
|
}
|
|
|
|
private get activeSplit(): IAugmentedSplitInfo {
|
|
const focusedSplit = this._store.getState().focusedSplitId
|
|
|
|
if (!focusedSplit) {
|
|
return null
|
|
}
|
|
|
|
return this._idToSplit[focusedSplit]
|
|
}
|
|
|
|
constructor() {
|
|
this._rootNavigator = new RelationalSplitNavigator()
|
|
|
|
const browserWindow = remote.getCurrentWindow()
|
|
|
|
browserWindow.on("blur", () => {
|
|
if (this.activeSplit) {
|
|
this.activeSplit.leave()
|
|
}
|
|
})
|
|
|
|
browserWindow.on("focus", () => {
|
|
if (this.activeSplit) {
|
|
this.activeSplit.enter()
|
|
}
|
|
})
|
|
|
|
this._store = createStore()
|
|
this._leftDock = new WindowDockNavigator(() => leftDockSelector(this._store.getState()))
|
|
this._primarySplit = new LinearSplitProvider("horizontal")
|
|
this._rootNavigator.setRelationship(this._leftDock, this._primarySplit, "right")
|
|
}
|
|
|
|
public createSplit(
|
|
splitLocation: Direction | SplitDirection,
|
|
newSplit: Oni.IWindowSplit,
|
|
referenceSplit?: Oni.IWindowSplit,
|
|
): WindowSplitHandle {
|
|
const nextId = this._lastId++
|
|
const windowId = "oni.window." + nextId.toString()
|
|
|
|
const augmentedWindow = new AugmentedWindow(windowId, newSplit)
|
|
|
|
this._idToSplit[windowId] = augmentedWindow
|
|
|
|
switch (splitLocation) {
|
|
case "right":
|
|
case "up":
|
|
case "down":
|
|
case "left": {
|
|
this._store.dispatch({
|
|
type: "ADD_DOCK_SPLIT",
|
|
dock: splitLocation,
|
|
split: augmentedWindow,
|
|
})
|
|
break
|
|
}
|
|
case "horizontal":
|
|
case "vertical":
|
|
const augmentedRefSplit =
|
|
this._getAugmentedWindowSplitFromSplit(referenceSplit) || this.activeSplit
|
|
this._primarySplit.split(augmentedWindow, splitLocation, augmentedRefSplit)
|
|
const newState = this._primarySplit.getState() as ISplitInfo<Oni.IWindowSplit>
|
|
|
|
this._store.dispatch({
|
|
type: "SET_PRIMARY_SPLITS",
|
|
splits: newState,
|
|
})
|
|
|
|
this._focusNewSplit(augmentedWindow)
|
|
}
|
|
|
|
return new WindowSplitHandle(this._store, this, windowId)
|
|
}
|
|
|
|
public getSplitHandle(split: Oni.IWindowSplit): WindowSplitHandle {
|
|
const augmentedSplit = this._getAugmentedWindowSplitFromSplit(split)
|
|
return new WindowSplitHandle(this._store, this, augmentedSplit.id)
|
|
}
|
|
|
|
public move(direction: Direction): void {
|
|
const focusedSplit = this._store.getState().focusedSplitId
|
|
|
|
if (!focusedSplit) {
|
|
return
|
|
}
|
|
|
|
const activeSplit = this._idToSplit[focusedSplit]
|
|
|
|
if (!activeSplit) {
|
|
return
|
|
}
|
|
|
|
const newSplit = this._rootNavigator.move(activeSplit, direction)
|
|
|
|
if (newSplit) {
|
|
this._focusNewSplit(newSplit)
|
|
} else {
|
|
this._onUnhandledMoveEvent.dispatch(direction)
|
|
}
|
|
}
|
|
|
|
public moveLeft(): void {
|
|
this.move("left")
|
|
}
|
|
|
|
public moveRight(): void {
|
|
this.move("right")
|
|
}
|
|
|
|
public moveUp(): void {
|
|
this.move("up")
|
|
}
|
|
|
|
public moveDown(): void {
|
|
this.move("down")
|
|
}
|
|
|
|
public close(splitId: string) {
|
|
this.swapToPreviousSplit(splitId)
|
|
delete this._idToSplit[splitId]
|
|
}
|
|
|
|
// Swaps focus to the most recently focused window, and hides
|
|
// the passed split.
|
|
public swapToPreviousSplit(splitId: string) {
|
|
const currentActiveSplit = this.activeSplit
|
|
|
|
// Send focus back to most recently focused window
|
|
if (currentActiveSplit.id === splitId) {
|
|
const candidateSplits = this._focusQueue.filter(
|
|
f => f !== splitId && this._idToSplit[f],
|
|
)
|
|
|
|
this._focusQueue = candidateSplits
|
|
|
|
if (this._focusQueue.length > 0) {
|
|
const splitToFocus = this._focusQueue[0]
|
|
this._focusNewSplit(this._idToSplit[splitToFocus])
|
|
}
|
|
}
|
|
|
|
const split = this._idToSplit[splitId]
|
|
this._primarySplit.close(split)
|
|
|
|
const state = this._primarySplit.getState()
|
|
this._store.dispatch({
|
|
type: "SET_PRIMARY_SPLITS",
|
|
splits: state,
|
|
})
|
|
}
|
|
|
|
public focusSplit(splitId: string): void {
|
|
const split = this._idToSplit[splitId]
|
|
this._focusNewSplit(split)
|
|
}
|
|
|
|
private _getAugmentedWindowSplitFromSplit(split: Oni.IWindowSplit): IAugmentedSplitInfo {
|
|
const augmentedWindows = Object.values(this._idToSplit)
|
|
return augmentedWindows.find(aw => aw.innerSplit === split) || null
|
|
}
|
|
|
|
private _focusNewSplit(newSplit: any): void {
|
|
if (this.activeSplit && this.activeSplit.leave) {
|
|
this.activeSplit.leave()
|
|
}
|
|
|
|
this._store.dispatch({
|
|
type: "SET_FOCUSED_SPLIT",
|
|
splitId: newSplit.id,
|
|
})
|
|
|
|
const filteredSplits = this._focusQueue.filter(f => f !== newSplit.id)
|
|
this._focusQueue = [newSplit.id, ...filteredSplits]
|
|
|
|
if (newSplit && newSplit.enter) {
|
|
newSplit.enter()
|
|
}
|
|
}
|
|
}
|