From 2317c6835e9bfaa920b7cd6197644629261a934f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 10:41:48 -0700 Subject: [PATCH] At the model layer: Focus next pane when a focused pane is destroyed This incorporates the Focusable mixin into PaneModel and ensures that all panes in the same pane container share a single focus context. --- spec/pane-model-spec.coffee | 12 +++++++++++- src/pane-axis-model.coffee | 18 +++++++++++++++-- src/pane-container-model.coffee | 34 ++++++++++++++++++++++++++++++++- src/pane-model.coffee | 17 ++++++++++------- 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index db6bdac48..794507ba5 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -80,7 +80,7 @@ describe "PaneModel", -> it "focuses the new pane, even if the current pane isn't focused", -> expect(pane1.focused).toBe false - pane2 = pane1.split() + pane2 = pane1.splitRight() expect(pane2.focused).toBe true describe "::destroy()", -> @@ -110,3 +110,13 @@ describe "PaneModel", -> expect(container.root.children).toEqual [pane1, pane2] pane2.destroy() expect(container.root).toBe pane1 + + describe "if the pane is focused", -> + it "shifts focus to the next pane", -> + pane2 = pane1.splitRight() + pane3 = pane2.splitRight() + pane2.focus() + expect(pane2.focused).toBe true + expect(pane3.focused).toBe false + pane2.destroy() + expect(pane3.focused).toBe true diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index 824727815..d23e93493 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -1,4 +1,6 @@ {Model, Sequence} = require 'theorist' +{flatten} = require 'underscore-plus' +Delegator = require 'delegato' Serializable = require 'serializable' PaneRow = null @@ -8,15 +10,24 @@ module.exports = class PaneAxisModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) + Delegator.includeInto(this) + + @delegatesMethod 'focusNextPane', toProperty: 'parent' + + @property 'focusContext' constructor: ({@orientation, children}) -> @children = Sequence.fromArray(children ? []) - @children.onEach (child) => + @subscribe @$focusContext, (focusContext) => + child.focusContext = focusContext for child in @children + + @subscribe @children.onEach (child) => child.parent = this + child.focusContext = @focusContext @subscribe child, 'destroyed', => @removeChild(child) - @children.onRemoval (child) => @unsubscribe(child) + @subscribe @children.onRemoval (child) => @unsubscribe(child) @when @children.$length.becomesLessThan(2), 'reparentLastChild' @when @children.$length.becomesLessThan(1), 'destroy' @@ -34,6 +45,9 @@ class PaneAxisModel extends Model else PaneRow ?= require './pane-row' + getPanes: -> + flatten(@children.map (child) -> child.getPanes()) + addChild: (child, index=@children.length) -> @children.splice(index, 0, child) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 1aa975cd7..d4029886e 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -1,5 +1,7 @@ {Model} = require 'theorist' Serializable = require 'serializable' +{find} = require 'underscore-plus' +FocusContext = require './focus-context' module.exports = class PaneContainerModel extends Model @@ -8,10 +10,13 @@ class PaneContainerModel extends Model @properties root: null + focusContext: -> new FocusContext constructor: -> super - @subscribe @$root, (root) => root?.parent = this + @subscribe @$root.filterDefined(), (root) => + root.parent = this + root.focusContext = @focusContext deserializeParams: (params) -> params.root = atom.deserializers.deserialize(params.root) @@ -23,3 +28,30 @@ class PaneContainerModel extends Model replaceChild: (oldChild, newChild) -> throw new Error("Replacing non-existent child") if oldChild isnt @root @root = newChild + + getPanes: -> + @root?.getPanes() ? [] + + getFocusedPane: -> + find @getPanes(), (pane) -> pane.focused + + focusNextPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + nextIndex = (currentIndex + 1) % panes.length + panes[nextIndex].focus() + true + else + false + + focusPreviousPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + previousIndex = currentIndex - 1 + previousIndex = panes.length - 1 if previousIndex < 0 + panes[previousIndex].focus() + true + else + false diff --git a/src/pane-model.coffee b/src/pane-model.coffee index be9b651cb..ed3a5e8de 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -3,17 +3,18 @@ {Model} = require 'theorist' Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' +Focusable = require './focusable' Pane = null module.exports = class PaneModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) + Focusable.includeInto(this) @properties items: -> [] activeItem: null - focused: false constructor: -> super @@ -32,11 +33,7 @@ class PaneModel extends Model getViewClass: -> Pane ?= require './pane' - focus: -> - @focused = true - - blur: -> - @focused = false + getPanes: -> [this] # Public: Returns all contained views. getItems: -> @@ -132,6 +129,10 @@ class PaneModel extends Model destroyInactiveItems: -> @destroyItem(item) for item in @getItems() when item isnt @activeItem + # Private: Called by model superclass + destroyed: -> + @parent.focusNextPane() if @focused + # Public: Prompt the user to save the given item. promptToSaveItem: (item) -> return true unless item.shouldPromptToSave?() @@ -211,8 +212,10 @@ class PaneModel extends Model if @parent.orientation isnt orientation @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) - newPane = new @constructor(extend({focused: true}, params)) + newPane = new @constructor(params) switch side when 'before' then @parent.insertChildBefore(this, newPane) when 'after' then @parent.insertChildAfter(this, newPane) + + newPane.focus() newPane