From a529f6403c7456ed876fb0619eaab21bcec41f85 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 4 Jun 2018 14:37:48 -0700 Subject: [PATCH 1/2] Remove current window manager --- .../WindowManager/LinearSplitProvider.ts | 194 ----------- .../WindowManager/RelationalSplitNavigator.ts | 109 ------ .../WindowManager/SingleSplitProvider.ts | 44 --- .../src/Services/WindowManager/WindowDock.ts | 41 --- .../Services/WindowManager/WindowManager.ts | 314 ------------------ .../WindowManager/WindowManagerStore.ts | 145 -------- browser/src/Services/WindowManager/index.ts | 66 ---- .../WindowManager/layoutFromSplitInfo.ts | 69 ---- .../WindowManager/LinearSplitProviderTests.ts | 215 ------------ .../RelationalSplitNavigatorTests.ts.ts | 50 --- .../WindowManager/WindowManagerTests.ts | 115 ------- .../WindowManager/layoutFromSplitInfoTests.ts | 167 ---------- 12 files changed, 1529 deletions(-) delete mode 100644 browser/src/Services/WindowManager/LinearSplitProvider.ts delete mode 100644 browser/src/Services/WindowManager/RelationalSplitNavigator.ts delete mode 100644 browser/src/Services/WindowManager/SingleSplitProvider.ts delete mode 100644 browser/src/Services/WindowManager/WindowDock.ts delete mode 100644 browser/src/Services/WindowManager/WindowManager.ts delete mode 100644 browser/src/Services/WindowManager/WindowManagerStore.ts delete mode 100644 browser/src/Services/WindowManager/index.ts delete mode 100644 browser/src/Services/WindowManager/layoutFromSplitInfo.ts delete mode 100644 browser/test/Services/WindowManager/LinearSplitProviderTests.ts delete mode 100644 browser/test/Services/WindowManager/RelationalSplitNavigatorTests.ts.ts delete mode 100644 browser/test/Services/WindowManager/WindowManagerTests.ts delete mode 100644 browser/test/Services/WindowManager/layoutFromSplitInfoTests.ts diff --git a/browser/src/Services/WindowManager/LinearSplitProvider.ts b/browser/src/Services/WindowManager/LinearSplitProvider.ts deleted file mode 100644 index 012b76d2b0..0000000000 --- a/browser/src/Services/WindowManager/LinearSplitProvider.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * TreeSplitProvider.ts - * - * Composite split provider responsible for managing - * a tree-based hierarchy of horizontal and vertical splits - */ - -import { - Direction, - IAugmentedSplitInfo, - ISplitInfo, - IWindowSplitProvider, - SingleSplitProvider, - SplitDirection, -} from "./index" - -export const getInverseDirection = (splitDirection: SplitDirection): SplitDirection => { - switch (splitDirection) { - case "horizontal": - return "vertical" - case "vertical": - default: - return "horizontal" - } -} - -export class LinearSplitProvider implements IWindowSplitProvider { - constructor( - private _direction: SplitDirection, - private _splitProviders: IWindowSplitProvider[] = [], - ) {} - - public contains(split: IAugmentedSplitInfo): boolean { - return this._getProviderForSplit(split) != null - } - - public close(split: IAugmentedSplitInfo): boolean { - const containingSplit = this._getProviderForSplit(split) - - if (!containingSplit) { - return false - } - - const handled = containingSplit.close(split) - - if (handled) { - return true - } - - // If it was unhandled by the split provider, but the provider contains it, then we'll remove the provider - this._splitProviders = this._splitProviders.filter(prov => prov !== containingSplit) - return true - } - - public split( - split: IAugmentedSplitInfo, - direction: SplitDirection, - referenceSplit?: IAugmentedSplitInfo, - ): boolean { - // If there are no children, we can just match direction - if (this._splitProviders.length === 0) { - this._direction = getInverseDirection(direction) - this._splitProviders.push(new SingleSplitProvider(split)) - return true - } - - // If there is no reference split, we can just tack this split on - if (!referenceSplit) { - this._splitProviders.push(new SingleSplitProvider(split)) - return true - } - - const containingSplit = this._getProviderForSplit(referenceSplit) - - if (!containingSplit) { - return false - } - - const result = containingSplit.split(split, direction, referenceSplit) - - // Containing split handled it, so we're good - if (result) { - return true - } - - // If the split requested is oriented differently, - // create a new provider to handle that - if (direction !== this._direction) { - const singleSplitProvider = new SingleSplitProvider(split) - this._splitProviders.push(singleSplitProvider) - } else { - // Otherwise, we can - let's wrap up the split in a provider - - const previousIndex = this._splitProviders.indexOf(containingSplit) - const elementsBefore = this._splitProviders.slice(0, previousIndex) - const elementsAfter = this._splitProviders.slice( - previousIndex + 1, - this._splitProviders.length, - ) - - const children = [containingSplit, new SingleSplitProvider(split)] - const childSplitProvider = new LinearSplitProvider( - getInverseDirection(this._direction), - children, - ) - - this._splitProviders = [...elementsBefore, childSplitProvider, ...elementsAfter] - } - - return true - } - - public move(split: IAugmentedSplitInfo, direction: Direction): IAugmentedSplitInfo { - if (!split) { - if (this._direction === "horizontal") { - const index = direction === "left" ? this._splitProviders.length - 1 : 0 - return this._splitProviders[index].move(split, direction) - } else { - const index = direction === "up" ? this._splitProviders.length - 1 : 0 - return this._splitProviders[index].move(split, direction) - } - } - - const containingSplit = this._getProviderForSplit(split) - - if (!containingSplit) { - return null - } - - const result = containingSplit.move(split, direction) - - if (result) { - return result - } - - if (!this._canHandleMove(direction)) { - return null - } - - // Since this wasn't handled by the containing split, let's try and handle it - const originalIndex = this._splitProviders.indexOf(containingSplit) - - let increment = -1 - - if ( - (this._direction === "horizontal" && direction === "right") || - (this._direction === "vertical" && direction === "down") - ) { - increment = 1 - } - - const newIndex = originalIndex + increment - - if (newIndex < 0 || newIndex >= this._splitProviders.length) { - return null - } - - // Move into the next split over - return this._splitProviders[newIndex].move(null, direction) - } - - public getState(): ISplitInfo { - if (this._splitProviders.length === 0) { - return null - } - - return { - type: "Split", - direction: this._direction, - splits: this._splitProviders.map(sp => sp.getState()).filter(s => s !== null), - } - } - - private _canHandleMove(direction: Direction): boolean { - switch (direction) { - case "up": - return this._direction === "vertical" - case "down": - return this._direction === "vertical" - case "left": - return this._direction === "horizontal" - case "right": - return this._direction === "horizontal" - default: - return false - } - } - - private _getProviderForSplit(split: IAugmentedSplitInfo): IWindowSplitProvider { - const providers = this._splitProviders.filter(prov => prov.contains(split)) - - return providers.length > 0 ? providers[0] : null - } -} diff --git a/browser/src/Services/WindowManager/RelationalSplitNavigator.ts b/browser/src/Services/WindowManager/RelationalSplitNavigator.ts deleted file mode 100644 index 02ff850f0a..0000000000 --- a/browser/src/Services/WindowManager/RelationalSplitNavigator.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * RelationalSplitProvider.ts - * - * Composite split provider responsible for managing - * navigation relationships between other split provdiers - */ - -import { Direction, getInverseDirection, IAugmentedSplitInfo, IWindowSplitNavigator } from "./index" - -export interface WindowSplitRelationship { - from: IWindowSplitNavigator - to: IWindowSplitNavigator - direction: string -} - -export class RelationalSplitNavigator implements IWindowSplitNavigator { - private _relationships: WindowSplitRelationship[] = [] - private _providers: IWindowSplitNavigator[] = [] - - public setRelationship( - from: IWindowSplitNavigator, - to: IWindowSplitNavigator, - direction: Direction, - ): void { - this._relationships.push({ - from, - to, - direction, - }) - - // Also push the inverse - this._relationships.push({ - from: to, - to: from, - direction: getInverseDirection(direction), - }) - - this._addToProvidersIfNeeded(from) - this._addToProvidersIfNeeded(to) - } - - public contains(split: IAugmentedSplitInfo): boolean { - return this._getContainingSplit(split) !== null - } - - public move(split: IAugmentedSplitInfo, direction: Direction): IAugmentedSplitInfo { - // If there is no current split, that means we are entering - if (split === null) { - // Need to find the furthest split in the *reverse* direction. - // For example, if we are moving *right* into this split, - // we want to grab the furthest *left* split - const reverseDirection = getInverseDirection(direction) - const splitProvider = this._getFurthestSplitInDirection(reverseDirection, null) - return splitProvider.move(null, direction) - } - - const containingSplit = this._getContainingSplit(split) - - if (!containingSplit) { - return null - } - - // Check if the containing split handled it - const moveResult = containingSplit.move(split, direction) - if (moveResult) { - return moveResult - } - - // The containing split couldn't handle it, so let's see if there is a relationship from the containing split - const applicableRelationship = this._relationships.filter( - rel => rel.from === containingSplit && rel.direction === direction, - ) - - if (applicableRelationship.length > 0) { - return applicableRelationship[0].to.move(null, direction) - } else { - return null - } - } - - private _getFurthestSplitInDirection( - direction: Direction, - split: IWindowSplitNavigator, - ): IWindowSplitNavigator { - const splits = this._relationships.filter( - rel => (rel.direction === direction && rel.from === split) || split === null, - ) - - // Base case - there are no further splits in that direction, so return the current one - if (splits.length === 0) { - return split - } - - // Recursive case - take the 'to' split and see if there is anything further - const currentRelationship = splits[0] - return this._getFurthestSplitInDirection(direction, currentRelationship.to) - } - - private _getContainingSplit(split: IAugmentedSplitInfo): IWindowSplitNavigator { - const providers = this._providers.filter(s => s.contains(split)) - return providers.length === 0 ? null : providers[0] - } - - private _addToProvidersIfNeeded(provider: IWindowSplitNavigator): void { - if (this._providers.indexOf(provider) === -1) { - this._providers.push(provider) - } - } -} diff --git a/browser/src/Services/WindowManager/SingleSplitProvider.ts b/browser/src/Services/WindowManager/SingleSplitProvider.ts deleted file mode 100644 index 58fa19def8..0000000000 --- a/browser/src/Services/WindowManager/SingleSplitProvider.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * SingleSplitProvider.ts - * - * Split provider for a leaf node - */ - -import { - Direction, - IAugmentedSplitInfo, - IWindowSplitProvider, - SplitDirection, - SplitOrLeaf, -} from "./index" - -export class SingleSplitProvider implements IWindowSplitProvider { - constructor(private _split: IAugmentedSplitInfo) {} - - public contains(split: IAugmentedSplitInfo): boolean { - return this._split === split - } - - public move(split: IAugmentedSplitInfo, direction: Direction): IAugmentedSplitInfo { - if (split === null) { - return this._split - } else { - return null - } - } - - public split(split: IAugmentedSplitInfo, direction: SplitDirection): boolean { - return false - } - - public close(split: IAugmentedSplitInfo): boolean { - return false - } - - public getState(): SplitOrLeaf { - return { - type: "Leaf", - contents: this._split, - } - } -} diff --git a/browser/src/Services/WindowManager/WindowDock.ts b/browser/src/Services/WindowManager/WindowDock.ts deleted file mode 100644 index b11e072440..0000000000 --- a/browser/src/Services/WindowManager/WindowDock.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * WindowDock.ts - */ - -import { Direction, IAugmentedSplitInfo, IWindowSplitNavigator } from "./index" - -export type DockStateGetter = () => IAugmentedSplitInfo[] - -export class WindowDockNavigator implements IWindowSplitNavigator { - constructor(private _stateGetter: DockStateGetter) {} - - public contains(split: IAugmentedSplitInfo): boolean { - const splits = this._stateGetter() - return splits.indexOf(split) >= 0 - } - - public move(startSplit: IAugmentedSplitInfo, direction: Direction): IAugmentedSplitInfo { - const splits = this._stateGetter() - - const currentIndex = splits.indexOf(startSplit) - - if (currentIndex === -1) { - if (direction === "left") { - return splits[splits.length - 1] - } else if (direction === "right") { - return splits[0] - } else { - return null - } - } - - // TODO: Generalize this - this is baked for a 'left dock' case right now - const newIndex = direction === "left" ? currentIndex - 1 : currentIndex + 1 - - if (newIndex >= 0 && newIndex < splits.length) { - return splits[newIndex] - } else { - return null - } - } -} diff --git a/browser/src/Services/WindowManager/WindowManager.ts b/browser/src/Services/WindowManager/WindowManager.ts deleted file mode 100644 index 5793f30c11..0000000000 --- a/browser/src/Services/WindowManager/WindowManager.ts +++ /dev/null @@ -1,314 +0,0 @@ -/** - * 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, - private _windowManager: WindowManager, - private _id: string, - ) {} - - public hide(): void { - this._store.dispatch({ - type: "HIDE_SPLIT", - splitId: 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() - - 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 - - public get onUnhandledMove(): IEvent { - return this._onUnhandledMoveEvent - } - - private _onFocusChanged = new Event>() - - public get onFocusChanged(): IEvent> { - return this._onFocusChanged - } - - public get splitRoot(): ISplitInfo { - return this._primarySplit.getState() as ISplitInfo - } - - public get store(): Store { - return this._store - } - - public 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 - - 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) { - 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, - }) - - delete this._idToSplit[splitId] - } - - 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() - } - } -} diff --git a/browser/src/Services/WindowManager/WindowManagerStore.ts b/browser/src/Services/WindowManager/WindowManagerStore.ts deleted file mode 100644 index f4cd0b010e..0000000000 --- a/browser/src/Services/WindowManager/WindowManagerStore.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * WindowManagerStore.ts - * - * Redux store for managing window state - */ - -import * as Oni from "oni-api" - -import { Reducer, Store } from "redux" -import { createStore as createReduxStore } from "./../../Redux" - -import { Direction, ISplitInfo, SplitDirection } from "./index" - -export interface IAugmentedSplitInfo extends Oni.IWindowSplit { - // Internal bookkeeping - id: string - - innerSplit: Oni.IWindowSplit - - // Potential API methods - enter?(): void - leave?(): void -} - -export type SplitOrLeaf = ISplitInfo | ISplitLeaf - -export interface ISplitInfo { - type: "Split" - splits: Array> - direction: SplitDirection -} - -export interface ISplitLeaf { - type: "Leaf" - contents: T -} - -type WindowActions = - | { - type: "ADD_DOCK_SPLIT" - dock: Direction - split: IAugmentedSplitInfo - } - | { - type: "SET_PRIMARY_SPLITS" - splits: ISplitInfo - } - | { - type: "SET_FOCUSED_SPLIT" - splitId: string - } - | { - type: "SHOW_SPLIT" - splitId: string - } - | { - type: "HIDE_SPLIT" - splitId: string - } - -export interface DockWindows { - [key: string]: IAugmentedSplitInfo[] -} - -export const DefaultDocksState: DockWindows = { - left: [], - right: [], - up: [], - down: [], -} - -export interface WindowState { - docks: DockWindows - - primarySplit: ISplitInfo - - focusedSplitId: string - hiddenSplits: string[] -} - -export const DefaultWindowState: WindowState = { - docks: DefaultDocksState, - primarySplit: null, - focusedSplitId: null, - hiddenSplits: [], -} - -export const reducer: Reducer = ( - state: WindowState = DefaultWindowState, - action: WindowActions, -) => { - switch (action.type) { - case "SET_PRIMARY_SPLITS": - return { - ...state, - primarySplit: action.splits, - } - case "SET_FOCUSED_SPLIT": - return { - ...state, - focusedSplitId: action.splitId, - } - case "SHOW_SPLIT": - return { - ...state, - hiddenSplits: state.hiddenSplits.filter(s => s !== action.splitId), - } - case "HIDE_SPLIT": - return { - ...state, - hiddenSplits: [ - ...state.hiddenSplits.filter(s => s !== action.splitId), - action.splitId, - ], - } - default: - return { - ...state, - docks: docksReducer(state.docks, action), - } - } -} - -export const docksReducer: Reducer = ( - state: DockWindows = DefaultDocksState, - action: WindowActions, -) => { - switch (action.type) { - case "ADD_DOCK_SPLIT": - return { - ...state, - [action.dock]: [...state[action.dock], action.split], - } - default: - return state - } -} - -export const leftDockSelector = (state: WindowState) => { - return state.docks.left.filter(s => state.hiddenSplits.indexOf(s.id) === -1) -} - -export const createStore = (): Store => { - return createReduxStore("WindowManager", reducer, DefaultWindowState, []) -} diff --git a/browser/src/Services/WindowManager/index.ts b/browser/src/Services/WindowManager/index.ts deleted file mode 100644 index 5909bddac5..0000000000 --- a/browser/src/Services/WindowManager/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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. - */ - -export * from "./layoutFromSplitInfo" -export * from "./LinearSplitProvider" -export * from "./RelationalSplitNavigator" -export * from "./SingleSplitProvider" -export * from "./WindowDock" -export * from "./WindowManager" -export * from "./WindowManagerStore" - -// TODO: Possible API types? -export type Direction = "up" | "down" | "left" | "right" -export type SplitDirection = "horizontal" | "vertical" - -export const getInverseDirection = (direction: Direction): Direction => { - switch (direction) { - case "up": - return "down" - case "down": - return "up" - case "left": - return "right" - case "right": - return "left" - default: - return null - } -} - -import { WindowManager } from "./WindowManager" -import { IAugmentedSplitInfo, SplitOrLeaf } from "./WindowManagerStore" - -/** - * Interface for something that can navigate between window splits - */ -export interface IWindowSplitNavigator { - contains(split: IAugmentedSplitInfo): boolean - move(startSplit: IAugmentedSplitInfo, direction: Direction): IAugmentedSplitInfo -} - -/** - * Interface for something that can manage window splits: - * - Navigating splits - * - Creating a new split - * - Removing a split - * Later - resizing a split? - */ -export interface IWindowSplitProvider extends IWindowSplitNavigator { - split( - newSplit: IAugmentedSplitInfo, - direction: SplitDirection, - referenceSplit?: IAugmentedSplitInfo, - ): boolean - close(split: IAugmentedSplitInfo): boolean - getState(): SplitOrLeaf -} - -export const windowManager = new WindowManager() diff --git a/browser/src/Services/WindowManager/layoutFromSplitInfo.ts b/browser/src/Services/WindowManager/layoutFromSplitInfo.ts deleted file mode 100644 index 19b6178c4b..0000000000 --- a/browser/src/Services/WindowManager/layoutFromSplitInfo.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * layoutFromSplitInfo.ts - * - * Function to layout splits into a particular window - */ - -import * as Oni from "oni-api" - -import { IAugmentedSplitInfo, ISplitInfo, SplitOrLeaf } from "./WindowManagerStore" - -export interface LayoutResultInfo { - rectangle: Oni.Shapes.Rectangle - split: IAugmentedSplitInfo -} - -export interface LayoutResult { - [windowId: string]: LayoutResultInfo -} - -export const layoutFromSplitInfo = ( - splits: ISplitInfo, - width: number, - height: number, -): LayoutResult => { - return layoutFromSplitInfoHelper(splits, Oni.Shapes.Rectangle.create(0, 0, width, height)) -} - -const layoutFromSplitInfoHelper = ( - split: SplitOrLeaf, - rectangle: Oni.Shapes.Rectangle, -): LayoutResult => { - // Base case.. - if (split.type === "Leaf") { - return { - [split.contents.id]: { - rectangle, - split: split.contents, - }, - } - } - - if (split.splits.length === 0) { - return {} - } - - // Recursive case - // - // TODO: Handle specified sizes for the windows. We're just distributing the space evenly, currently. - const splitWidth = - split.direction === "horizontal" ? rectangle.width / split.splits.length : rectangle.width - const splitHeight = - split.direction === "vertical" ? rectangle.height / split.splits.length : rectangle.height - - let ret = {} - - for (let i = 0; i < split.splits.length; i++) { - const x = split.direction === "horizontal" ? rectangle.x + splitWidth * i : rectangle.x - const y = split.direction === "vertical" ? rectangle.y + splitHeight * i : rectangle.y - - const rect = Oni.Shapes.Rectangle.create(x, y, splitWidth, splitHeight) - - ret = { - ...ret, - ...layoutFromSplitInfoHelper(split.splits[i], rect), - } - } - - return ret -} diff --git a/browser/test/Services/WindowManager/LinearSplitProviderTests.ts b/browser/test/Services/WindowManager/LinearSplitProviderTests.ts deleted file mode 100644 index 3853f7cd46..0000000000 --- a/browser/test/Services/WindowManager/LinearSplitProviderTests.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * LinearSplitProviderTests.ts - */ - -import * as assert from "assert" - -import { ISplitInfo, ISplitLeaf, LinearSplitProvider } from "./../../../src/Services/WindowManager" - -import { MockWindowSplit } from "./../../Mocks" - -describe("LinearSplitProviderTests", () => { - let split1: MockWindowSplit - let split2: MockWindowSplit - // let split1Provider: SingleSplitProvider - // let split2Provider: SingleSplitProvider - - let horizontalSplits: LinearSplitProvider - let verticalSplits: LinearSplitProvider - - beforeEach(() => { - split1 = new MockWindowSplit() - split2 = new MockWindowSplit() - - horizontalSplits = new LinearSplitProvider("horizontal") - horizontalSplits.split(split1, "vertical") - horizontalSplits.split(split2, "vertical", split1) - - verticalSplits = new LinearSplitProvider("vertical") - verticalSplits.split(split1, "horizontal") - verticalSplits.split(split2, "horizontal") - }) - - it("contains returns true for included splits", () => { - assert.strictEqual(true, horizontalSplits.contains(split1)) - assert.strictEqual(true, horizontalSplits.contains(split2)) - }) - - describe("move", () => { - describe("horizontal split", () => { - describe("null split", () => { - it("moves in from left", () => { - // Because we're moving in from the left, split2 should be the active split - const result = horizontalSplits.move(null, "left") - assert.strictEqual(result, split2) - }) - - it("moves in from right", () => { - // Because we're moving in from the left, split2 should be the active split - const result = horizontalSplits.move(null, "right") - assert.strictEqual(result, split1) - }) - }) - - describe("moving between splits", () => { - it("moves left->right", () => { - const result = horizontalSplits.move(split1, "right") - assert.strictEqual(result, split2) - }) - - it("moves right->left", () => { - const result = horizontalSplits.move(split1, "right") - assert.strictEqual(result, split2) - }) - }) - }) - - describe("vertical split", () => { - describe("null split", () => { - it("handles up", () => { - // Because we're moving in from the bottom (upwards), split2 should be the active split - const result = verticalSplits.move(null, "up") - assert.strictEqual(result, split2) - }) - }) - }) - }) - - describe("split", () => { - it("creates nested split for different orientation", () => { - const newSplit = new MockWindowSplit() - horizontalSplits.split(newSplit, "vertical", split2) - }) - - it("switches orientation if there is no child", () => { - const splitProvider = new LinearSplitProvider("vertical") - const newSplit = new MockWindowSplit() - splitProvider.split(newSplit, "vertical") - - const state = splitProvider.getState() - - assert.strictEqual(state.direction, "horizontal") - }) - - it("simple vertical split case", () => { - const splitProvider = new LinearSplitProvider("horizontal") - - const mockSplit0 = new MockWindowSplit() - const mockSplit1 = new MockWindowSplit() - - splitProvider.split(mockSplit0, "vertical") - splitProvider.split(mockSplit1, "vertical", mockSplit0) - - const state = splitProvider.getState() - - const splitState0: ISplitLeaf = state.splits[0] as any - const splitState1: ISplitLeaf = state.splits[1] as any - - assert.strictEqual(state.direction, "horizontal") - assert.strictEqual(splitState0.contents, mockSplit0) - assert.strictEqual(splitState1.contents, mockSplit1) - }) - - it("simple horizontal split case", () => { - const splitProvider = new LinearSplitProvider("vertical") - - const mockSplit0 = new MockWindowSplit() - const mockSplit1 = new MockWindowSplit() - - splitProvider.split(mockSplit0, "horizontal") - splitProvider.split(mockSplit1, "horizontal", mockSplit0) - - const state = splitProvider.getState() - - const splitState0: ISplitLeaf = state.splits[0] as any - const splitState1: ISplitLeaf = state.splits[1] as any - - assert.strictEqual(state.direction, "vertical") - assert.strictEqual(splitState0.contents, mockSplit0) - assert.strictEqual(splitState1.contents, mockSplit1) - }) - - // Test split arrangement like: - // 0|1 - // --- - // 0|2 - it("nested horizontal split case", () => { - const splitProvider = new LinearSplitProvider("horizontal") - - const mockSplit0 = new MockWindowSplit() - const mockSplit1 = new MockWindowSplit() - - splitProvider.split(mockSplit0, "vertical") - splitProvider.split(mockSplit1, "vertical", mockSplit0) - - // Now, add a horizontal split against mockSplit1 - const mockSplit2 = new MockWindowSplit() - - splitProvider.split(mockSplit2, "horizontal", mockSplit1) - - const state = splitProvider.getState() - - assert.strictEqual(state.direction, "horizontal", "Verify root is still horizontal") - const verticalSplitLeft = state.splits[0] as ISplitLeaf - assert.strictEqual(verticalSplitLeft.type, "Leaf") - assert.strictEqual(verticalSplitLeft.contents, mockSplit0) - - const verticalSplitRight = state.splits[1] as ISplitInfo - assert.strictEqual(verticalSplitRight.type, "Split") - assert.strictEqual(verticalSplitRight.direction, "vertical", "Verify child is vertical") - - const verticalSplitRightChild0: ISplitLeaf = verticalSplitRight - .splits[0] as any - const verticalSplitRightChild1: ISplitLeaf = verticalSplitRight - .splits[1] as any - - assert.strictEqual(verticalSplitRightChild0.contents, mockSplit1) - assert.strictEqual(verticalSplitRightChild1.contents, mockSplit2) - }) - - // Test split arrangement like: - // 0|2 - // --- - // 111 - - it("nested vertical split case", () => { - const splitProvider = new LinearSplitProvider("vertical") - - const mockSplit0 = new MockWindowSplit("mockSplit0") - const mockSplit1 = new MockWindowSplit("mockSplit1") - - splitProvider.split(mockSplit0, "horizontal") - splitProvider.split(mockSplit1, "horizontal", mockSplit0) - - // Now, add a horizontal split against mockSplit1 - const mockSplit2 = new MockWindowSplit("mockSplit2") - - splitProvider.split(mockSplit2, "vertical", mockSplit0) - - const state = splitProvider.getState() - - assert.strictEqual(state.direction, "vertical", "Verify root is still vertical") - - // Verify bottom leaf is as expected - const horizontalSplitBottom = state.splits[1] as ISplitLeaf - assert.strictEqual(horizontalSplitBottom.type, "Leaf") - assert.strictEqual(horizontalSplitBottom.contents, mockSplit1) - - const horizontalSplitTop = state.splits[0] as ISplitInfo - assert.strictEqual(horizontalSplitTop.type, "Split") - assert.strictEqual( - horizontalSplitTop.direction, - "horizontal", - "Verify child is horizontal", - ) - - const horizontalSplitTopChild0: ISplitLeaf = horizontalSplitTop - .splits[0] as any - const horizontalSplitTopChild1: ISplitLeaf = horizontalSplitTop - .splits[1] as any - - assert.strictEqual(horizontalSplitTopChild0.contents, mockSplit0) - assert.strictEqual(horizontalSplitTopChild1.contents, mockSplit2) - }) - }) -}) diff --git a/browser/test/Services/WindowManager/RelationalSplitNavigatorTests.ts.ts b/browser/test/Services/WindowManager/RelationalSplitNavigatorTests.ts.ts deleted file mode 100644 index 3276542ab2..0000000000 --- a/browser/test/Services/WindowManager/RelationalSplitNavigatorTests.ts.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * RelationalSplitNavigatorTests.ts - */ - -import * as assert from "assert" - -import { - RelationalSplitNavigator, - SingleSplitProvider, -} from "./../../../src/Services/WindowManager" - -import { MockWindowSplit } from "./../../Mocks" - -describe("RelationalSplitNavigator", () => { - let splitProvider: RelationalSplitNavigator - let split1: MockWindowSplit - let split2: MockWindowSplit - let split1Provider: SingleSplitProvider - let split2Provider: SingleSplitProvider - - beforeEach(() => { - splitProvider = new RelationalSplitNavigator() - split1 = new MockWindowSplit() - split2 = new MockWindowSplit() - split1Provider = new SingleSplitProvider(split1) - split2Provider = new SingleSplitProvider(split2) - }) - - it("contains returns true/false", () => { - // Make split2 'right' of split1 - splitProvider.setRelationship(split1Provider, split2Provider, "right") - - assert.strictEqual(true, splitProvider.contains(split1)) - assert.strictEqual(true, splitProvider.contains(split2)) - assert.strictEqual(false, splitProvider.contains(new MockWindowSplit())) - }) - - describe("move", () => { - it("moves from one split to another", () => { - // Make split2 'right' of split1 - splitProvider.setRelationship(split1Provider, split2Provider, "right") - - const forwardResult = splitProvider.move(split1, "right") - assert.strictEqual(forwardResult, split2, "Move in the forward direction works") - - const inverseResult = splitProvider.move(split2, "left") - assert.strictEqual(inverseResult, split1, "Move in the inverse direction works") - }) - }) -}) diff --git a/browser/test/Services/WindowManager/WindowManagerTests.ts b/browser/test/Services/WindowManager/WindowManagerTests.ts deleted file mode 100644 index 8e836d6c90..0000000000 --- a/browser/test/Services/WindowManager/WindowManagerTests.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * WindowManagerTests.ts - */ - -import * as assert from "assert" - -import { ISplitInfo, WindowManager } from "./../../../src/Services/WindowManager" -import { MockWindowSplit } from "./../../Mocks" - -describe("WindowManagerTests", () => { - let windowManager: WindowManager - - beforeEach(() => { - windowManager = new WindowManager() - }) - - it("sends focus to previous split after closing", async () => { - const split1 = new MockWindowSplit("window1") - const split2 = new MockWindowSplit("window2") - const split3 = new MockWindowSplit("window3") - - const handle1 = windowManager.createSplit("horizontal", split1) - const handle2 = windowManager.createSplit("vertical", split2, split1) - - assert.strictEqual(windowManager.activeSplit.id, handle2.id) - - handle2.close() - - assert.strictEqual(windowManager.activeSplit.id, handle1.id) - - const handle3 = windowManager.createSplit("horizontal", split3, split1) - assert.strictEqual(windowManager.activeSplit.id, handle3.id) - - handle3.close() - - assert.strictEqual(windowManager.activeSplit.id, handle1.id) - }) - - it("can get split after a split is closed", async () => { - const split1 = new MockWindowSplit("window1") - const split2 = new MockWindowSplit("window2") - const split3 = new MockWindowSplit("window3") - - windowManager.createSplit("horizontal", split1) - const handle2 = windowManager.createSplit("vertical", split2, split1) - - handle2.close() - - const handle3 = windowManager.createSplit("vertical", split3, split1) - - const handle = windowManager.getSplitHandle(split3) - - assert.strictEqual(handle.id, handle3.id) - }) - - it("respects direction even if a reference split is not passed in", async () => { - const split1 = new MockWindowSplit("window1") - const split2 = new MockWindowSplit("window2") - - const handle1 = windowManager.createSplit("horizontal", split1) - handle1.focus() - - windowManager.createSplit("vertical", split2, split1) - - const splitRoot = windowManager.splitRoot - - const firstChild = splitRoot.splits[0] as ISplitInfo - - assert.strictEqual(firstChild.type, "Split") - assert.strictEqual( - firstChild.direction, - "horizontal", - "Validate the splits are arranged horizontally (it's confusing... but this means they are vertical splits)", - ) - assert.strictEqual(firstChild.splits.length, 2, "Validate both windows are in this split") - }) - - it("#2147 - doesn't leave a split container after closing", async () => { - const split1 = new MockWindowSplit("window1") - const split2 = new MockWindowSplit("window2") - const split3 = new MockWindowSplit("window3") - - windowManager.createSplit("horizontal", split1) - - const split2Handle = windowManager.createSplit("vertical", split2, split1) - const split3Handle = windowManager.createSplit("horizontal", split3, split2) - - split2Handle.close() - split3Handle.close() - - const splitRoot = windowManager.splitRoot - const firstChild = splitRoot.splits[0] as ISplitInfo - - assert.strictEqual(splitRoot.type, "Split") - assert.strictEqual(splitRoot.direction, "vertical") - assert.strictEqual(splitRoot.splits.length, 1) - - assert.strictEqual(firstChild.type, "Split") - assert.strictEqual( - firstChild.direction, - "horizontal", - "Validate the splits are arranged horizontally (it's confusing... but this means they are vertical splits)", - ) - assert.strictEqual( - firstChild.splits.length, - 1, - "Validate there is only a single child in the 'horizontal' split.", - ) - assert.strictEqual( - firstChild.splits[0].type, - "Leaf", - "Validate the child of the 'horizontal' split is a leaf node.", - ) - }) -}) diff --git a/browser/test/Services/WindowManager/layoutFromSplitInfoTests.ts b/browser/test/Services/WindowManager/layoutFromSplitInfoTests.ts deleted file mode 100644 index 310351b489..0000000000 --- a/browser/test/Services/WindowManager/layoutFromSplitInfoTests.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * layoutFromSplitInfoTests.ts - */ - -import * as assert from "assert" -import * as Oni from "oni-api" - -import { ISplitInfo, layoutFromSplitInfo } from "./../../../src/Services/WindowManager" - -import { MockWindowSplit } from "./../../Mocks" - -const resultFromSplitAndRect = (rectangle: Oni.Shapes.Rectangle, split: MockWindowSplit) => ({ - split, - rectangle, -}) - -describe("layoutFromSplitInfoTests", () => { - it("returns empty if no splits", () => { - const emptySplit: ISplitInfo = { - type: "Split", - splits: [] as any, - direction: "horizontal", - } - - const result = layoutFromSplitInfo(emptySplit, 100, 100) - assert.deepEqual(result, {}) - }) - - it("gives full layout to a single item", () => { - const split1 = new MockWindowSplit("windowSplit1") - const split: ISplitInfo = { - type: "Split", - splits: [ - { - type: "Leaf", - contents: split1, - }, - ] as any, - direction: "horizontal", - } - - const result = layoutFromSplitInfo(split, 100, 100) - - assert.deepEqual(result, { - windowSplit1: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(0, 0, 100, 100), - split1, - ), - }) - }) - - it("splits layout between two vertical items", () => { - const split1 = new MockWindowSplit("windowSplit1") - const split2 = new MockWindowSplit("windowSplit2") - const split: ISplitInfo = { - type: "Split", - splits: [ - { - type: "Leaf", - contents: split1, - }, - { - type: "Leaf", - contents: split2, - }, - ] as any, - direction: "horizontal", - } - - const result = layoutFromSplitInfo(split, 100, 100) - - assert.deepEqual(result, { - windowSplit1: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(0, 0, 50, 100), - split1, - ), - windowSplit2: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(50, 0, 50, 100), - split2, - ), - }) - }) - - it("splits layout between two horizontal items", () => { - const split1 = new MockWindowSplit("windowSplit1") - const split2 = new MockWindowSplit("windowSplit2") - - const split: ISplitInfo = { - type: "Split", - splits: [ - { - type: "Leaf", - contents: split1, - }, - { - type: "Leaf", - contents: split2, - }, - ] as any, - direction: "vertical", - } - - const result = layoutFromSplitInfo(split, 100, 100) - - assert.deepEqual(result, { - windowSplit1: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(0, 0, 100, 50), - split1, - ), - windowSplit2: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(0, 50, 100, 50), - split2, - ), - }) - }) - - it("nested layout - splits between a vertical item, and then nested horizontal items", () => { - // 1|2 - // --- - // 1|3 - const split1 = new MockWindowSplit("windowSplit1") - const split2 = new MockWindowSplit("windowSplit2") - const split3 = new MockWindowSplit("windowSplit3") - - const split: ISplitInfo = { - type: "Split", - splits: [ - { - type: "Leaf", - contents: split1, - }, - { - type: "Split", - splits: [ - { - type: "Leaf", - contents: split2, - }, - { - type: "Leaf", - contents: split3, - }, - ], - direction: "vertical", - }, - ] as any, - direction: "horizontal", - } - - const result = layoutFromSplitInfo(split, 100, 100) - - assert.deepEqual(result, { - windowSplit1: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(0, 0, 50, 100), - split1, - ), - windowSplit2: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(50, 0, 50, 50), - split2, - ), - windowSplit3: resultFromSplitAndRect( - Oni.Shapes.Rectangle.create(50, 50, 50, 50), - split3, - ), - }) - }) -}) From 8062a5be0485180066f6b25dea74c2140e9e7471 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 4 Jun 2018 14:48:13 -0700 Subject: [PATCH 2/2] Update window manager --- browser/src/Services/WindowManager.ts | 3 +++ browser/src/UI/Shell/ShellView.tsx | 13 +++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 browser/src/Services/WindowManager.ts diff --git a/browser/src/Services/WindowManager.ts b/browser/src/Services/WindowManager.ts new file mode 100644 index 0000000000..4010ce2473 --- /dev/null +++ b/browser/src/Services/WindowManager.ts @@ -0,0 +1,3 @@ +import { WindowManager } from "oni-core-window-manager" + +export const windowManager = new WindowManager() diff --git a/browser/src/UI/Shell/ShellView.tsx b/browser/src/UI/Shell/ShellView.tsx index f698e4229a..60b1ba919b 100644 --- a/browser/src/UI/Shell/ShellView.tsx +++ b/browser/src/UI/Shell/ShellView.tsx @@ -4,28 +4,27 @@ import * as React from "react" -import { Provider } from "react-redux" - import * as Platform from "./../../Platform" import { focusManager } from "./../../Services/FocusManager" import { inputManager } from "./../../Services/InputManager" import { IThemeColors } from "./../../Services/Themes/ThemeManager" -import * as WindowManager from "./../../Services/WindowManager" +import { windowManager } from "./../../Services/WindowManager" + +import { WindowSplitsView, WindowManager } from "oni-core-window-manager" import { Background } from "./../components/Background" import { ThemeProvider } from "./../components/common" import { Loading } from "./../components/Loading" import StatusBar from "./../components/StatusBar" -import { WindowSplits } from "./../components/WindowSplits" import { WindowTitle } from "./../components/WindowTitle" import { Overlays } from "./OverlayView" interface IShellViewComponentProps { theme: IThemeColors - windowManager: WindowManager.WindowManager + windowManager: WindowManager } const titleBarVisible = Platform.isMac() @@ -48,9 +47,7 @@ export class ShellView extends React.PureComponent
- - - +