From 5e1b2e269608c3aa57cc400729574e40be95cc46 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 17:40:04 -0700 Subject: [PATCH] Manage the active pane at the model level --- package.json | 2 +- spec/pane-container-model-spec.coffee | 36 ++++++++++++++++++++++++--- spec/pane-model-spec.coffee | 3 ++- src/pane-axis-model.coffee | 15 ++++------- src/pane-container-model.coffee | 23 ++++++++++++++--- src/pane-model.coffee | 29 +++++++++++++++++---- 6 files changed, 84 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index e23497b3e..90da3ba15 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.9.0", + "theorist": "~0.10.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index 9379a2c8d..5052b2830 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -11,6 +11,36 @@ describe "PaneContainerModel", -> expect(pane3A.focused).toBe true containerB = containerA.testSerialization() - [pane1A, pane2A, pane3A] = containerB.getPanes() - expect(pane3A.focusContext).toBe containerB.focusContext - expect(pane3A.focused).toBe true + [pane1B, pane2B, pane3B] = containerB.getPanes() + expect(pane3B.focusContext).toBe containerB.focusContext + expect(pane3B.focused).toBe true + + describe "::activePane", -> + [container, pane1, pane2] = [] + + beforeEach -> + pane1 = new PaneModel + container = new PaneContainerModel(root: pane1) + + it "references the first pane if no pane has been focused", -> + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + + it "references the most recently focused pane", -> + pane2 = pane1.splitRight() + expect(container.activePane).toBe pane2 + expect(pane1.active).toBe false + expect(pane2.active).toBe true + pane1.focus() + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + expect(pane2.active).toBe false + + it "is reassigned to the next pane if the current active pane is unfocused and destroyed", -> + pane2 = pane1.splitRight() + pane2.blur() + pane2.destroy() + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + pane1.destroy() + expect(container.activePane).toBe null diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 7add9e949..f17b141c9 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -88,7 +88,8 @@ describe "PaneModel", -> describe "::removeItemAtIndex(index)", -> describe "when the removal of the item causes blur to be called on the pane model", -> it "remains focused if it was before the item was removed", -> - pane = new PaneModel(items: ["A", "B", "C"], focusContext: new FocusContext) + pane = new PaneModel(items: ["A", "B", "C"]) + container = new PaneContainerModel(root: pane) pane.on 'item-removed', -> pane.blur() pane.focus() pane.removeItemAtIndex(0) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index a4495f417..d35e88032 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -12,19 +12,14 @@ class PaneAxisModel extends Model Serializable.includeInto(this) Delegator.includeInto(this) - @delegatesMethod 'focusNextPane', toProperty: 'parent' + @delegatesProperty 'focusContext', toProperty: 'container' - @property 'focusContext' - - constructor: ({@focusContext, @orientation, children}) -> + constructor: ({@container, @orientation, children}) -> @children = Sequence.fromArray(children ? []) - @subscribe @$focusContext, (focusContext) => - child.focusContext = focusContext for child in @children - @subscribe @children.onEach (child) => child.parent = this - child.focusContext = @focusContext + child.container = @container @subscribe child, 'destroyed', => @removeChild(child) @subscribe @children.onRemoval (child) => @unsubscribe(child) @@ -33,8 +28,8 @@ class PaneAxisModel extends Model @when @children.$length.becomesLessThan(1), 'destroy' deserializeParams: (params) -> - {focusContext} = params - params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {focusContext}) + {container} = params + params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {container}) params serializeParams: -> diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 7d704a41c..ebbf250db 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -2,6 +2,7 @@ Serializable = require 'serializable' {find} = require 'underscore-plus' FocusContext = require './focus-context' +PaneModel = require './pane-model' module.exports = class PaneContainerModel extends Model @@ -10,18 +11,23 @@ class PaneContainerModel extends Model @properties root: null - focusContext: -> new FocusContext + focusContext: null + activePane: null constructor: -> super + + @focusContext ?= new FocusContext + @subscribe @$root, (root) => if root? root.parent = this - root.focusContext = @focusContext + root.container = this + @activePane ?= root if root instanceof PaneModel deserializeParams: (params) -> - params.focusContext ?= new FocusContext - params.root = atom.deserializers.deserialize(params.root, focusContext: params.focusContext) + @focusContext ?= params.focusContext ? new FocusContext + params.root = atom.deserializers.deserialize(params.root, container: this) params serializeParams: (params) -> @@ -57,3 +63,12 @@ class PaneContainerModel extends Model true else false + + makeNextPaneActive: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@activePane) + nextIndex = (currentIndex + 1) % panes.length + @activePane = panes[nextIndex] + else + @activePane = null diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 60a768b56..8fc2e0fa3 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -13,26 +13,38 @@ class PaneModel extends Model Focusable.includeInto(this) @properties + container: null activeItem: null + @behavior 'active', -> + @$container + .flatMapLatest((container) -> container?.$activePane) + .map((activePane) => activePane is this) + .distinctUntilChanged() + constructor: (params) -> super - @focus() if params?.focused - @items = Sequence.fromArray(params?.items ? []) @activeItem ?= @items[0] + @subscribe @$container, (container) => + @focusContext = container?.focusContext + @subscribe @items.onEach (item) => if typeof item.on is 'function' @subscribe item, 'destroyed', => @removeItem(item) @subscribe @items.onRemoval (item, index) => - @unsubscribe item + @unsubscribe item if typeof item.on is 'function' @emit 'item-removed', item, index @when @items.$length.becomesLessThan(1), 'destroy' + @when @$focused, => @makeActive() + + @focus() if params?.focused + serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() @@ -46,6 +58,10 @@ class PaneModel extends Model getViewClass: -> Pane ?= require './pane' + isActive: -> @active + + makeActive: -> @container.activePane = this + getPanes: -> [this] # Public: Returns all contained views. @@ -144,7 +160,10 @@ class PaneModel extends Model # Private: Called by model superclass destroyed: -> item.destroy?() for item in @items.slice() - @parent.focusNextPane() if @focused + if @focused + @container.focusNextPane() + else if @isActive() + @container.makeNextPaneActive() # Public: Prompt the user to save the given item. promptToSaveItem: (item) -> @@ -223,7 +242,7 @@ class PaneModel extends Model split: (orientation, side, params) -> if @parent.orientation isnt orientation - @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) + @parent.replaceChild(this, new PaneAxisModel({@container, orientation, children: [this]})) newPane = new @constructor(params) switch side