uh-layout 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13e0b24b7af337b1124358fdd2719f9489d753c4
4
- data.tar.gz: c1cc1e138a983f9b32e5237123665ebe57382dfd
3
+ metadata.gz: 47a100f2229b06ab1aa01eeb35e1b8baba73f804
4
+ data.tar.gz: 3cc639552ca1d7cfa766a0d6afb03dab85532b86
5
5
  SHA512:
6
- metadata.gz: 332e7d81affd715faf55fd6f0b424da82b7b0852907052a228c8017458c5a25ae8488ff1c2b3afa0c5c62abfef11ab72c6538557542204132b3c88d515d4dfe2
7
- data.tar.gz: 327f4d842b1e3cd74cfb818991ee0c71ee1777d5767752d6415a1d59a90f041fa0e59af7241fb3df196a2ad166c6232d44e9117c74c0ee3efdbe5377090ea10c
6
+ metadata.gz: 2c0c3f914c932e129fb9eaf10c18a6fe7cc90335adec8f4822aecda6a6856204ceff06e832016d59716de7b08506450f49b0c56c36b53198d6e29bce3a31f621
7
+ data.tar.gz: 8a7ce44e77e6edad1746dc478253a0a742c404538e0b9d771e605ad67a6ecc6c19843a1f984cdec1ed4841d697a467574134aa6568d7105edcbe726f7943328c
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
+ /Gemfile-custom.rb
1
2
  /Gemfile.lock
2
3
  /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
6
+ - ruby-head
7
+ - rbx-2
@@ -0,0 +1,30 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ class FixedWidth
5
+ DEFAULT_WIDTH = 484
6
+
7
+ def initialize(entries, geo, width: DEFAULT_WIDTH)
8
+ @entries = entries
9
+ @geo = geo
10
+ @width = width
11
+ end
12
+
13
+ def arrange
14
+ return if @entries.empty?
15
+ @entries.each_with_index do |column, i|
16
+ column.x = @width * i + @geo.x
17
+ column.y = @geo.y
18
+ column.width = @width
19
+ column.height = @geo.height
20
+ end
21
+ @entries.last.width = @geo.width - (@entries.last.x - @geo.x)
22
+ end
23
+
24
+ def max_count?
25
+ (@geo.width / (@entries.size + 1)) < @width
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ class Stack
5
+ def initialize(entries, geo)
6
+ @entries = entries
7
+ @geo = geo
8
+ end
9
+
10
+ def arrange
11
+ @entries.each { |e| e.geo = @geo.dup }
12
+ end
13
+
14
+ def each_visible
15
+ yield @entries.current if @entries.current
16
+ end
17
+
18
+ def each_hidden
19
+ ([*@entries] - [@entries.current]).each { |e| yield e }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ class VertTile
5
+ def initialize(entries, geo)
6
+ @entries = entries
7
+ @geo = geo
8
+ end
9
+
10
+ def arrange
11
+ entry_height = @geo.height / @entries.size - 1
12
+ @entries.each_with_index do |entry, i|
13
+ entry.geo = @geo.dup
14
+ entry.y = (entry_height + 1) * i
15
+ entry.height = entry_height
16
+ end
17
+ end
18
+
19
+ def each_visible
20
+ @entries.each { |e| yield e }
21
+ end
22
+
23
+ def each_hidden
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Uh
2
+ class Layout
3
+ class ClientColumnMover
4
+ def initialize(columns, columns_max_count)
5
+ @columns = columns
6
+ @columns_max_count = columns_max_count
7
+ end
8
+
9
+ def move_current(direction)
10
+ @columns.current.remove client = @columns.current.current_client
11
+ dest_column = get_or_create_column direction
12
+ dest_column << client
13
+ dest_column.current_client = client
14
+ @columns.current = dest_column
15
+ end
16
+
17
+ def get_or_create_column(direction)
18
+ if candidate = @columns.get(direction)
19
+ candidate
20
+ elsif @columns_max_count
21
+ @columns.get direction, cycle: true
22
+ else
23
+ Column.new(Geo.new).tap do |o|
24
+ case direction
25
+ when :pred then @columns.unshift o
26
+ when :succ then @columns << o
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,6 +1,11 @@
1
1
  module Uh
2
2
  class Layout
3
3
  class Column
4
+ MODES = {
5
+ stack: Arrangers::Stack,
6
+ tile: Arrangers::VertTile
7
+ }.freeze
8
+
4
9
  include GeoAccessors
5
10
 
6
11
  extend Forwardable
@@ -9,11 +14,12 @@ module Uh
9
14
  def_delegator :@clients, :current=, :current_client=
10
15
  def_delegator :current_client, :==, :current_client?
11
16
 
12
- attr_reader :geo, :clients
17
+ attr_reader :geo, :clients, :mode
13
18
 
14
19
  def initialize(geo)
15
20
  @geo = geo.dup
16
21
  @clients = Container.new
22
+ @mode = :stack
17
23
  end
18
24
 
19
25
  def to_s
@@ -26,18 +32,22 @@ module Uh
26
32
  self
27
33
  end
28
34
 
35
+ def mode_toggle
36
+ @mode = MODES.keys[(MODES.keys.index(@mode) + 1) % MODES.keys.size]
37
+ end
38
+
39
+ def arranger
40
+ MODES[@mode].new @clients, @geo
41
+ end
42
+
29
43
  def arrange_clients
30
- @clients.each do |client|
31
- client.geo = @geo.dup
32
- client.moveresize
33
- end
44
+ arranger.arrange
45
+ clients.each &:moveresize
34
46
  end
35
47
 
36
- def show_hide_clients
37
- @clients.each do |client|
38
- client.hide unless client.hidden? || @clients.current == client
39
- end
40
- @clients.current.show if @clients.current && @clients.current.hidden?
48
+ def show_hide_clients(arranger: self.arranger)
49
+ arranger.each_visible { |client| client.show if client.hidden? }
50
+ arranger.each_hidden { |client| client.hide unless client.hidden? }
41
51
  end
42
52
  end
43
53
  end
@@ -4,8 +4,8 @@ module Uh
4
4
  include Enumerable
5
5
 
6
6
  extend Forwardable
7
- def_delegators :@entries, :<<, :[], :each, :empty?, :first, :index, :last,
8
- :size, :unshift
7
+ def_delegators :@entries, :<<, :[], :each, :empty?, :fetch, :first,
8
+ :index, :last, :size, :unshift
9
9
 
10
10
  def initialize(entries = [])
11
11
  @entries = entries
@@ -19,7 +19,8 @@ module Uh
19
19
  end
20
20
 
21
21
  def current=(entry)
22
- @current_index = @entries.index entry if include? entry
22
+ fail ArgumentError, 'unknown entry' unless include? entry
23
+ @current_index = @entries.index entry
23
24
  end
24
25
 
25
26
  def remove(entry)
data/lib/uh/layout/tag.rb CHANGED
@@ -9,6 +9,7 @@ module Uh
9
9
  def_delegator :@columns, :each, :each_column
10
10
  def_delegator :current_column, :==, :current_column?
11
11
  def_delegator :clients, :each, :each_client
12
+ def_delegator :arranger, :max_count?, :columns_max_count?
12
13
 
13
14
  attr_reader :id, :geo, :columns
14
15
 
@@ -39,6 +40,16 @@ module Uh
39
40
  end
40
41
  end
41
42
 
43
+ def arranger
44
+ Arrangers::FixedWidth.new(@columns, @geo)
45
+ end
46
+
47
+ def arrange_columns
48
+ @columns.remove_if &:empty?
49
+ arranger.arrange
50
+ @columns.each &:arrange_clients
51
+ end
52
+
42
53
  def hide
43
54
  clients.each &:hide
44
55
  end
@@ -1,5 +1,5 @@
1
1
  module Uh
2
2
  class Layout
3
- VERSION = '0.1.2'
3
+ VERSION = '0.1.3'
4
4
  end
5
5
  end
data/lib/uh/layout.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  require 'forwardable'
2
2
 
3
- require 'uh/layout/geo_accessors'
3
+ require 'uh/layout/arrangers/fixed_width'
4
+ require 'uh/layout/arrangers/stack'
5
+ require 'uh/layout/arrangers/vert_tile'
4
6
  require 'uh/layout/bar'
7
+ require 'uh/layout/client_column_mover'
5
8
  require 'uh/layout/container'
6
9
  require 'uh/layout/column'
7
- require 'uh/layout/column/arranger'
8
10
  require 'uh/layout/dumper'
9
11
  require 'uh/layout/screen'
10
12
  require 'uh/layout/tag'
11
13
 
12
14
  module Uh
13
15
  class Layout
16
+ Error = Class.new(StandardError)
17
+ ArgumentError = Class.new(Error)
18
+
14
19
  extend Forwardable
15
20
  def_delegator :@screens, :current, :current_screen
16
21
  def_delegator :current_screen, :==, :current_screen?
@@ -36,10 +41,6 @@ module Uh
36
41
  screens.any? { |screen| screen.include? client }
37
42
  end
38
43
 
39
- def arranger_for_current_tag
40
- Column::Arranger.new(current_tag.columns, current_tag.geo)
41
- end
42
-
43
44
  def update_widgets
44
45
  @widgets.each &:update
45
46
  @widgets.each &:redraw
@@ -63,8 +64,7 @@ module Uh
63
64
  def remove(client)
64
65
  screen, tag, column = find_client client
65
66
  column.remove client
66
- Column::Arranger.new(tag.columns, tag.geo).redraw
67
- tag.each_column &:arrange_clients
67
+ tag.arrange_columns
68
68
  column.show_hide_clients
69
69
  current_client.focus if current_client
70
70
  update_widgets
@@ -94,14 +94,11 @@ module Uh
94
94
 
95
95
  def handle_tag_set(tag_id)
96
96
  return unless current_client && current_tag.id != tag_id
97
+ previous_tag_id = current_tag.id
97
98
  remove client = current_client
98
- client.hide
99
- tag = find_tag_or_create tag_id
100
- tag.current_column_or_create << client
101
- Column::Arranger.new(tag.columns, tag.geo).redraw
102
- tag.each_column &:arrange_clients
103
- current_client.focus if current_client
104
- update_widgets
99
+ handle_tag_sel tag_id
100
+ push client
101
+ handle_tag_sel previous_tag_id
105
102
  end
106
103
 
107
104
  def handle_column_sel(direction)
@@ -111,6 +108,13 @@ module Uh
111
108
  update_widgets
112
109
  end
113
110
 
111
+ def handle_column_mode_toggle
112
+ return unless current_column
113
+ current_column.mode_toggle
114
+ current_column.arrange_clients
115
+ current_column.show_hide_clients
116
+ end
117
+
114
118
  def handle_client_sel(direction)
115
119
  return unless current_client
116
120
  current_column.clients.sel direction
@@ -125,10 +129,10 @@ module Uh
125
129
  update_widgets
126
130
  end
127
131
 
128
- def handle_client_column_set(direction, arranger: arranger_for_current_tag)
132
+ def handle_client_column_set(direction, mover: client_mover_for_current_tag)
129
133
  return unless current_client
130
- arranger.move_current_client(direction).update_geos
131
- current_tag.each_column &:arrange_clients
134
+ mover.move_current direction
135
+ current_tag.arrange_columns
132
136
  current_tag.each_column &:show_hide_clients
133
137
  update_widgets
134
138
  end
@@ -157,5 +161,9 @@ module Uh
157
161
  current_screen.tags << tag
158
162
  end
159
163
  end
164
+
165
+ def client_mover_for_current_tag
166
+ ClientColumnMover.new(current_tag.columns, current_tag.columns_max_count?)
167
+ end
160
168
  end
161
169
  end
@@ -1,8 +1,22 @@
1
1
  module Factories
2
+ class Entry
3
+ include Uh::GeoAccessors
4
+
5
+ attr_accessor :geo
6
+
7
+ def initialize(geo)
8
+ @geo = geo
9
+ end
10
+ end
11
+
2
12
  def build_client
3
13
  Uh::WM::Client.new(instance_spy Uh::Window)
4
14
  end
5
15
 
16
+ def build_entry(geo = build_geo)
17
+ Entry.new(geo)
18
+ end
19
+
6
20
  def build_geo(x = 0, y = 0, width = 640, height = 480)
7
21
  Uh::Geo.new(x, y, width, height)
8
22
  end
@@ -0,0 +1,72 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ describe FixedWidth do
5
+ let(:geo) { build_geo 20, 0, 640, 480 }
6
+ let(:entry) { build_entry }
7
+ let(:entries) { Container.new([entry, build_entry]) }
8
+ subject(:arranger) { described_class.new entries, geo, width: 300 }
9
+
10
+ describe '#arrange' do
11
+ it 'decreases first entry width as the optimal width' do
12
+ arranger.arrange
13
+ expect(entries[0].width).to eq 300
14
+ end
15
+
16
+ it 'offsets entries with given geo' do
17
+ arranger.arrange
18
+ expect(entries[0].x).to eq 20
19
+ end
20
+
21
+ it 'moves second entry aside the first entry' do
22
+ arranger.arrange
23
+ expect(entries[1].x).to eq 320
24
+ end
25
+
26
+ it 'increases last entry width to occupy remaining width' do
27
+ arranger.arrange
28
+ expect(entries[1].width).to eq 340
29
+ end
30
+
31
+ it 'copies given geo y' do
32
+ entries[0].y = nil
33
+ arranger.arrange
34
+ expect(entries[0].y).to eq 0
35
+ end
36
+
37
+ it 'copies given geo height' do
38
+ entries[0].height = nil
39
+ arranger.arrange
40
+ expect(entries[0].height).to eq 480
41
+ end
42
+
43
+ context 'without entry' do
44
+ let(:entries) { Container.new([]) }
45
+
46
+ it 'does not raise any error' do
47
+ expect { arranger.arrange }.not_to raise_error
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#max_count?' do
53
+ context 'when a new entry fits in current geo' do
54
+ let(:entries) { Container.new([entry]) }
55
+
56
+ it 'returns false' do
57
+ expect(arranger.max_count?).to be false
58
+ end
59
+ end
60
+
61
+ context 'when current geo can not contain more entry' do
62
+ let(:entries) { Container.new([entry, entry.dup]) }
63
+
64
+ it 'returns true' do
65
+ expect(arranger.max_count?).to be true
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,42 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ describe Stack do
5
+ let(:entry) { build_entry }
6
+ let(:other_entry) { build_entry }
7
+ let(:entries) { Container.new([entry, other_entry]) }
8
+ let(:geo) { build_geo 0, 0, 300, 480 }
9
+ subject(:arranger) { described_class.new entries, geo }
10
+
11
+ describe '#arrange' do
12
+ it 'sets given geo on all entries' do
13
+ arranger.arrange
14
+ expect(entries.map(&:geo)).to all eq geo
15
+ end
16
+ end
17
+
18
+ describe '#each_visible' do
19
+ it 'yields current entry' do
20
+ expect { |b| arranger.each_visible &b }
21
+ .to yield_successive_args entry
22
+ end
23
+
24
+ context 'with no current entry' do
25
+ let(:entries) { Container.new([]) }
26
+
27
+ it 'does not yield' do
28
+ expect { |b| arranger.each_visible &b }.not_to yield_control
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '#each_hidden' do
34
+ it 'yields all entries except current one' do
35
+ expect { |b| arranger.each_hidden &b }
36
+ .to yield_successive_args other_entry
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ module Uh
2
+ class Layout
3
+ module Arrangers
4
+ describe VertTile do
5
+ let(:entry) { build_entry }
6
+ let(:other_entry) { build_entry }
7
+ let(:entries) { Container.new([entry, other_entry]) }
8
+ let(:geo) { build_geo 0, 0, 300, 480 }
9
+ subject(:arranger) { described_class.new entries, geo }
10
+
11
+ describe '#arrange' do
12
+ it 'sets x offset from given geo on all entries' do
13
+ arranger.arrange
14
+ expect(entries.map(&:x)).to all eq 0
15
+ end
16
+
17
+ it 'sets width from given geo on all entries' do
18
+ arranger.arrange
19
+ expect(entries.map(&:width)).to all eq 300
20
+ end
21
+
22
+ it 'splits entries height equally' do
23
+ arranger.arrange
24
+ expect(entries.map(&:height)).to all eq 239
25
+ end
26
+
27
+ it 'adds a margin between entries' do
28
+ arranger.arrange
29
+ expect(entries[1].y - entries[0].height - entries[0].y).to eq 1
30
+ end
31
+ end
32
+
33
+ describe '#each_visible' do
34
+ it 'yields all entries' do
35
+ expect { |b| arranger.each_visible &b }
36
+ .to yield_successive_args entry, other_entry
37
+ end
38
+ end
39
+
40
+ describe '#each_hidden' do
41
+ it 'yields no entry' do
42
+ expect { |b| arranger.each_hidden &b }.not_to yield_control
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,96 @@
1
+ module Uh
2
+ class Layout
3
+ describe ClientColumnMover do
4
+ let(:client) { build_client }
5
+ let(:column) { Column.new(build_geo) }
6
+ let(:columns) { Container.new([column]) }
7
+ let(:columns_max_count) { false }
8
+ subject(:mover) { described_class.new columns, columns_max_count }
9
+
10
+ describe '#move_current' do
11
+ shared_examples 'moves current client' do |expected_column_index|
12
+ it 'removes current client from origin column' do
13
+ mover.move_current :succ
14
+ expect(column).not_to include client
15
+ end
16
+
17
+ it 'adds current client in the destination column' do
18
+ mover.move_current :succ
19
+ expect(columns[expected_column_index]).to include client
20
+ end
21
+
22
+ it 'updates destination column as the current one' do
23
+ mover.move_current :succ
24
+ expect(columns.current).to be columns[expected_column_index]
25
+ end
26
+
27
+ it 'preserves current client as the current one' do
28
+ expect { mover.move_current :succ }
29
+ .not_to change { columns.current.current_client }
30
+ end
31
+ end
32
+
33
+ context 'given one column with one client' do
34
+ before { column << client }
35
+
36
+ include_examples 'moves current client', 1
37
+ end
38
+
39
+ context 'given one column with many clients' do
40
+ before { column << client << client.dup }
41
+
42
+ include_examples 'moves current client', 1
43
+ end
44
+
45
+ context 'given two columns' do
46
+ let(:columns) { Container.new([column, Column.new(build_geo)]) }
47
+
48
+ before { columns[1] << client.dup }
49
+
50
+ context 'when origin column has many clients' do
51
+ before { column << client << client.dup }
52
+
53
+ include_examples 'moves current client', 1
54
+ end
55
+
56
+ context 'when origin column has one client' do
57
+ before { column << client }
58
+
59
+ include_examples 'moves current client', 1
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#get_or_create_column' do
65
+ let(:columns) { Container.new([column, Column.new(build_geo)]) }
66
+
67
+ it 'returns the consecutive column in given direction' do
68
+ expect(mover.get_or_create_column :succ).to be columns[1]
69
+ end
70
+
71
+ context 'when current column is last in given direction' do
72
+ before { columns.current = columns[1] }
73
+
74
+ context 'when max columns count is not reached' do
75
+ it 'appends a new column' do
76
+ expect(mover.get_or_create_column :succ).to be columns[2]
77
+ end
78
+
79
+ it 'prepends a new column' do
80
+ columns.current = columns[0]
81
+ expect(mover.get_or_create_column :pred).to be columns[0]
82
+ end
83
+ end
84
+
85
+ context 'when max columns count is reached' do
86
+ let(:columns_max_count) { true }
87
+
88
+ it 'returns the consecutive column in given direction' do
89
+ expect(mover.get_or_create_column :succ).to be columns[0]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -5,7 +5,7 @@ module Uh
5
5
  let(:other_geo) { build_geo 640, 0, 320, 240 }
6
6
  let(:client) { build_client }
7
7
  let(:other_client) { build_client }
8
- subject(:column) { described_class.new(geo) }
8
+ subject(:column) { described_class.new geo }
9
9
 
10
10
  it 'has a copy to given geo' do
11
11
  expect(column.geo).to eq(geo).and not_be geo
@@ -15,6 +15,10 @@ module Uh
15
15
  expect(column).to be_empty
16
16
  end
17
17
 
18
+ it 'has :stack as default mode' do
19
+ expect(column.mode).to be :stack
20
+ end
21
+
18
22
  describe '#<<' do
19
23
  before { column << client }
20
24
 
@@ -31,13 +35,43 @@ module Uh
31
35
  end
32
36
  end
33
37
 
38
+ describe '#mode_toggle' do
39
+ it 'toggles mode from stack to tile' do
40
+ expect { column.mode_toggle }
41
+ .to change { column.mode }.from(:stack).to(:tile)
42
+ end
43
+
44
+ it 'toggles mode from tile to stack' do
45
+ column.mode_toggle
46
+ expect { column.mode_toggle }
47
+ .to change { column.mode }.from(:tile).to(:stack)
48
+ end
49
+
50
+ end
51
+
52
+ describe '#arranger' do
53
+ context 'when column mode is stack' do
54
+ it 'returns a stack arranger' do
55
+ expect(column.arranger).to be_an Arrangers::Stack
56
+ end
57
+ end
58
+
59
+ context 'when column mode is tile' do
60
+ it 'returns a vertical tile arranger' do
61
+ column.mode_toggle
62
+ expect(column.arranger).to be_an Arrangers::VertTile
63
+ end
64
+ end
65
+ end
66
+
34
67
  describe '#arrange_clients' do
35
68
  before { column << client << other_client }
36
69
 
37
- it 'updates clients geometries' do
38
- column.width = 320
70
+ it 'arranges clients' do
71
+ arranger = instance_spy Arrangers::Stack
72
+ allow(column).to receive(:arranger) { arranger }
73
+ expect(arranger).to receive :arrange
39
74
  column.arrange_clients
40
- expect(column.clients.map(&:geo)).to all eq column.geo
41
75
  end
42
76
 
43
77
  it 'moveresizes clients' do
@@ -47,18 +81,24 @@ module Uh
47
81
  end
48
82
 
49
83
  describe '#show_hide_clients' do
50
- before { column << client << other_client }
84
+ let(:arranger) { double 'arranger', each_visible: nil, each_hidden: nil }
51
85
 
52
- it 'shows current client only' do
86
+ it 'shows visible clients when they are hidden' do
87
+ allow(arranger).to receive(:each_visible)
88
+ .and_yield(client.hide)
89
+ .and_yield(other_client.show)
90
+ expect(client).to receive :show
53
91
  expect(other_client).not_to receive :show
54
- expect(client.hide).to receive :show
55
- column.show_hide_clients
92
+ column.show_hide_clients arranger: arranger
56
93
  end
57
94
 
58
- it 'hides non-current clients' do
59
- expect(client).not_to receive :hide
60
- expect(other_client.show).to receive :hide
61
- column.show_hide_clients
95
+ it 'hides hidden clients except those already hidden' do
96
+ allow(arranger).to receive(:each_hidden)
97
+ .and_yield(client.show)
98
+ .and_yield(other_client.hide)
99
+ expect(client).to receive :hide
100
+ expect(other_client).not_to receive :hide
101
+ column.show_hide_clients arranger: arranger
62
102
  end
63
103
  end
64
104
  end
@@ -42,9 +42,9 @@ module Uh
42
42
  end
43
43
 
44
44
  context 'when given argument is not an entry' do
45
- it 'does not change current entry' do
46
- expect { container.current = :baz }
47
- .not_to change { container.current }
45
+ it 'raises ArgumentError' do
46
+ expect { container.current = :unknown_entry }
47
+ .to raise_error Layout::ArgumentError
48
48
  end
49
49
  end
50
50
  end
@@ -91,10 +91,10 @@ module Uh
91
91
  end
92
92
 
93
93
  context 'when given entry is the only one' do
94
- let(:entries) { [:foo] }
94
+ let(:entries) { [:bar] }
95
95
 
96
96
  it 'has no more current entry' do
97
- container.remove :foo
97
+ container.remove :bar
98
98
  expect(container.current).to be nil
99
99
  end
100
100
  end
@@ -4,7 +4,7 @@ module Uh
4
4
  let(:geo) { build_geo }
5
5
  let(:other_geo) { build_geo 640, 0, 320, 240 }
6
6
  let(:client) { build_client }
7
- subject(:screen) { described_class.new(0, geo) }
7
+ subject(:screen) { described_class.new 0, geo }
8
8
 
9
9
  it 'has one default tag with id 1 assigned' do
10
10
  expect(screen.tags).to include an_object_having_attributes id: '1'
@@ -6,11 +6,12 @@ module Uh
6
6
  let(:client) { build_client }
7
7
  let(:other_client) { build_client }
8
8
  let(:column) { Column.new(geo) }
9
- subject(:tag) { described_class.new('1', geo) }
9
+ subject(:tag) { described_class.new '1', geo }
10
10
 
11
11
  describe '.new' do
12
12
  it 'raises error unless id converts to string' do
13
- expect { described_class.new(1, geo) }.to raise_error(ArgumentError)
13
+ expect { described_class.new 1, geo }
14
+ .to raise_error(Layout::ArgumentError)
14
15
  end
15
16
  end
16
17
 
@@ -40,7 +41,7 @@ module Uh
40
41
  end
41
42
 
42
43
  it 'returns the new column' do
43
- expect(tag.current_column_or_create).to eq tag.columns.current
44
+ expect(tag.current_column_or_create).to be tag.columns.fetch 0
44
45
  end
45
46
  end
46
47
 
@@ -57,6 +58,34 @@ module Uh
57
58
  end
58
59
  end
59
60
  end
61
+
62
+ describe '#arranger' do
63
+ it 'returns a fixed width arranger' do
64
+ expect(tag.arranger).to be_an Arrangers::FixedWidth
65
+ end
66
+ end
67
+
68
+ describe '#arrange_columns' do
69
+ before { tag.columns << column }
70
+
71
+ it 'purges empty columns' do
72
+ tag.arrange_columns
73
+ expect(tag.columns).to be_empty
74
+ end
75
+
76
+ it 'arranges columns' do
77
+ arranger = instance_spy Arrangers::FixedWidth
78
+ allow(tag).to receive(:arranger) { arranger }
79
+ expect(arranger).to receive :arrange
80
+ tag.arrange_columns
81
+ end
82
+
83
+ it 'arranges columns clients' do
84
+ column << client
85
+ expect(column).to receive :arrange_clients
86
+ tag.arrange_columns
87
+ end
88
+ end
60
89
  end
61
90
  end
62
91
  end
@@ -23,17 +23,6 @@ module Uh
23
23
  end
24
24
  end
25
25
 
26
- describe '#arranger_for_current_tag' do
27
- it 'returns an arranger for current tag columns and geo' do
28
- expect(layout.arranger_for_current_tag)
29
- .to respond_to(:update_geos)
30
- .and have_attributes(
31
- columns: layout.current_tag.columns,
32
- geo: layout.current_tag.geo
33
- )
34
- end
35
- end
36
-
37
26
  describe '#update_widgets' do
38
27
  it 'updates widgets' do
39
28
  expect(layout.widgets).to all receive :update
@@ -110,13 +99,8 @@ module Uh
110
99
  expect(layout).not_to include client
111
100
  end
112
101
 
113
- it 'redraws columns with an arranger' do
114
- expect_any_instance_of(Layout::Column::Arranger).to receive :redraw
115
- layout.remove client
116
- end
117
-
118
- it 'arranges clients in removed client tag columns' do
119
- expect(layout.current_tag.columns).to all receive :arrange_clients
102
+ it 'arranges columns in removed client tag' do
103
+ expect(layout.current_tag).to receive :arrange_columns
120
104
  layout.remove client
121
105
  end
122
106
 
@@ -229,8 +213,8 @@ module Uh
229
213
  expect(origin_tag).not_to include client
230
214
  end
231
215
 
232
- it 'shows and hides clients in current column' do
233
- expect(layout.current_column).to receive :show_hide_clients
216
+ it 'removes current client from layout' do
217
+ expect(layout).to receive(:remove).with client
234
218
  layout.handle_tag_set '2'
235
219
  end
236
220
 
@@ -245,10 +229,9 @@ module Uh
245
229
  expect(dest_tag).to include client
246
230
  end
247
231
 
248
- it 'arranges clients in given tag columns' do
249
- layout.current_screen.tags << tag = Layout::Tag.new('2', geo)
250
- expect(tag.current_column_or_create).to receive :arrange_clients
232
+ it 'preserves current tag' do
251
233
  layout.handle_tag_set '2'
234
+ expect(layout.current_tag.id).to eq '1'
252
235
  end
253
236
 
254
237
  it 'updates widgets' do
@@ -290,6 +273,33 @@ module Uh
290
273
  end
291
274
  end
292
275
 
276
+ describe '#handle_column_mode_toggle' do
277
+ context 'without column' do
278
+ it 'does not raise any error' do
279
+ expect { layout.handle_column_mode_toggle }.not_to raise_error
280
+ end
281
+ end
282
+
283
+ context 'with a column' do
284
+ before { layout << client }
285
+
286
+ it 'toggles current column mode' do
287
+ expect(layout.current_column).to receive :mode_toggle
288
+ layout.handle_column_mode_toggle
289
+ end
290
+
291
+ it 'arranges current column clients' do
292
+ expect(layout.current_column).to receive :arrange_clients
293
+ layout.handle_column_mode_toggle
294
+ end
295
+
296
+ it 'shows and hides clients in current column' do
297
+ expect(layout.current_column).to receive :show_hide_clients
298
+ layout.handle_column_mode_toggle
299
+ end
300
+ end
301
+ end
302
+
293
303
  describe '#handle_client_sel' do
294
304
  context 'without client' do
295
305
  it 'does not raise any error' do
@@ -358,22 +368,17 @@ module Uh
358
368
  end
359
369
 
360
370
  context 'with one column and two clients' do
361
- let(:arranger) { instance_spy Layout::Column::Arranger }
371
+ let(:mover) { instance_spy Layout::ClientColumnMover }
362
372
 
363
373
  before { layout << other_client << client }
364
374
 
365
- it 'moves current client with column arranger' do
366
- expect(arranger).to receive(:move_current_client).with(:succ)
367
- layout.handle_client_column_set :succ, arranger: arranger
368
- end
369
-
370
- it 'updates columns geos with column arranger' do
371
- expect(arranger).to receive :update_geos
372
- layout.handle_client_column_set :succ, arranger: arranger
375
+ it 'moves current client with given client column mover' do
376
+ expect(mover).to receive(:move_current).with(:succ)
377
+ layout.handle_client_column_set :succ, mover: mover
373
378
  end
374
379
 
375
- it 'arranges clients in current tag columns' do
376
- expect(layout.current_tag.columns).to all receive :arrange_clients
380
+ it 'arranges current tag columns' do
381
+ expect(layout.current_tag).to receive :arrange_columns
377
382
  layout.handle_client_column_set :succ
378
383
  end
379
384
 
data/uh-layout.gemspec CHANGED
@@ -15,8 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
16
16
 
17
17
 
18
- s.add_dependency 'uh', '~> 0.1.2'
19
-
18
+ s.add_development_dependency 'uh'
20
19
  s.add_development_dependency 'rake', '~> 10.4'
21
20
  s.add_development_dependency 'rspec', '~> 3.2'
22
21
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uh-layout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thibault Jouan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-04 00:00:00.000000000 Z
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uh
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.2
20
- type: :runtime
19
+ version: '0'
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.2
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -60,23 +60,28 @@ extra_rdoc_files: []
60
60
  files:
61
61
  - ".gitignore"
62
62
  - ".rspec"
63
+ - ".travis.yml"
63
64
  - Gemfile
64
- - Gemfile-custom.rb
65
65
  - Guardfile
66
66
  - Rakefile
67
67
  - lib/uh/layout.rb
68
+ - lib/uh/layout/arrangers/fixed_width.rb
69
+ - lib/uh/layout/arrangers/stack.rb
70
+ - lib/uh/layout/arrangers/vert_tile.rb
68
71
  - lib/uh/layout/bar.rb
72
+ - lib/uh/layout/client_column_mover.rb
69
73
  - lib/uh/layout/column.rb
70
- - lib/uh/layout/column/arranger.rb
71
74
  - lib/uh/layout/container.rb
72
75
  - lib/uh/layout/dumper.rb
73
- - lib/uh/layout/geo_accessors.rb
74
76
  - lib/uh/layout/screen.rb
75
77
  - lib/uh/layout/tag.rb
76
78
  - lib/uh/layout/version.rb
77
79
  - spec/spec_helper.rb
78
80
  - spec/support/factories.rb
79
- - spec/uh/layout/column/arranger_spec.rb
81
+ - spec/uh/layout/arrangers/fixed_width_spec.rb
82
+ - spec/uh/layout/arrangers/stack_spec.rb
83
+ - spec/uh/layout/arrangers/vert_tile_spec.rb
84
+ - spec/uh/layout/client_column_mover_spec.rb
80
85
  - spec/uh/layout/column_spec.rb
81
86
  - spec/uh/layout/container_spec.rb
82
87
  - spec/uh/layout/screen_spec.rb
@@ -109,7 +114,10 @@ summary: Simple layout for uh
109
114
  test_files:
110
115
  - spec/spec_helper.rb
111
116
  - spec/support/factories.rb
112
- - spec/uh/layout/column/arranger_spec.rb
117
+ - spec/uh/layout/arrangers/fixed_width_spec.rb
118
+ - spec/uh/layout/arrangers/stack_spec.rb
119
+ - spec/uh/layout/arrangers/vert_tile_spec.rb
120
+ - spec/uh/layout/client_column_mover_spec.rb
113
121
  - spec/uh/layout/column_spec.rb
114
122
  - spec/uh/layout/container_spec.rb
115
123
  - spec/uh/layout/screen_spec.rb
data/Gemfile-custom.rb DELETED
@@ -1,7 +0,0 @@
1
- group :development, :test do
2
- gem 'uh', path: "#{ENV['HOME']}/src/my/uh"
3
- gem 'pry'
4
- gem 'listen', require: false, path: "#{ENV['HOME']}/src/sys/listen"
5
- gem 'rb-kqueue', require: false
6
- gem 'guard-rspec', require: false
7
- end
@@ -1,66 +0,0 @@
1
- module Uh
2
- class Layout
3
- class Column
4
- class Arranger
5
- OPTIMAL_WIDTH = 484
6
-
7
- attr_reader :columns, :geo
8
-
9
- def initialize(columns, geo, column_width: OPTIMAL_WIDTH)
10
- @columns = columns
11
- @geo = geo
12
- @column_width = column_width
13
- end
14
-
15
- def redraw
16
- purge
17
- update_geos
18
- yield if block_given?
19
- end
20
-
21
- def purge
22
- @columns.remove_if &:empty?
23
- end
24
-
25
- def move_current_client(direction)
26
- return self unless @columns.current.current_client
27
- @columns.current.remove client = @columns.current.current_client
28
- dest_column = get_or_create_column direction
29
- dest_column << client
30
- dest_column.current_client = client
31
- purge
32
- @columns.current = dest_column
33
- self
34
- end
35
-
36
- def get_or_create_column(direction)
37
- if candidate = @columns.get(direction)
38
- candidate
39
- elsif max_columns_count?
40
- @columns.get direction, cycle: true
41
- else
42
- Column.new(@geo).tap do |o|
43
- case direction
44
- when :pred then @columns.unshift o
45
- when :succ then @columns << o
46
- end
47
- end
48
- end
49
- end
50
-
51
- def max_columns_count?
52
- (@geo.width / (@columns.size + 1)) < @column_width
53
- end
54
-
55
- def update_geos
56
- return if @columns.empty?
57
- @columns.each_with_index do |column, i|
58
- column.x = @column_width * i + @geo.x
59
- column.width = @column_width
60
- end
61
- @columns.last.width = @geo.width - (@columns.last.x - @geo.x)
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,10 +0,0 @@
1
- module Uh
2
- class Layout
3
- module GeoAccessors
4
- extend Forwardable
5
- def_delegators :@geo,
6
- :x, :y, :width, :height,
7
- :x=, :y=, :width=, :height=
8
- end
9
- end
10
- end
@@ -1,181 +0,0 @@
1
- module Uh
2
- class Layout
3
- class Column
4
- describe Arranger do
5
- let(:geo) { build_geo }
6
- let(:client) { build_client }
7
- let(:column_width) { 300 }
8
- let(:column) { Column.new(geo) }
9
- let(:columns) { Container.new([column]) }
10
- subject(:arranger) { described_class.new columns, geo,
11
- column_width: column_width }
12
-
13
- describe '#redraw' do
14
- it 'purges columns' do
15
- expect(arranger).to receive :purge
16
- arranger.redraw
17
- end
18
-
19
- it 'updates columns geo' do
20
- expect(arranger).to receive :update_geos
21
- arranger.redraw
22
- end
23
-
24
- it 'yields given block' do
25
- expect { |b| arranger.redraw &b }.to yield_control
26
- end
27
- end
28
-
29
- describe '#purge' do
30
- it 'removes empty columns' do
31
- arranger.purge
32
- expect(columns).to be_empty
33
- end
34
- end
35
-
36
- describe '#move_current_client' do
37
- shared_examples 'moves current client' do |expected_column_index|
38
- it 'removes current client from origin column' do
39
- arranger.move_current_client :succ
40
- expect(column).not_to include client
41
- end
42
-
43
- it 'adds current client in the destination column' do
44
- arranger.move_current_client :succ
45
- expect(columns[expected_column_index]).to include client
46
- end
47
-
48
- it 'updates destination column as the current one' do
49
- arranger.move_current_client :succ
50
- expect(columns.current).to be columns[expected_column_index]
51
- end
52
-
53
- it 'preserves current client as the current one' do
54
- expect { arranger.move_current_client :succ }
55
- .not_to change { columns.current.current_client }
56
- end
57
-
58
- it 'does not leave empty columns' do
59
- expect(columns.none? &:empty?).to be true
60
- end
61
- end
62
-
63
- it 'returns self' do
64
- expect(arranger.move_current_client :succ).to be arranger
65
- end
66
-
67
- context 'given one column with one client' do
68
- before { column << client }
69
-
70
- include_examples 'moves current client', 0
71
- end
72
-
73
- context 'given one column with many clients' do
74
- before { column << client << client.dup }
75
-
76
- include_examples 'moves current client', 1
77
- end
78
-
79
- context 'given two columns' do
80
- let(:columns) { Container.new([column, Column.new(geo)]) }
81
-
82
- before { columns[1] << client.dup }
83
-
84
- context 'when origin column has many clients' do
85
- before { column << client << client.dup }
86
-
87
- include_examples 'moves current client', 1
88
- end
89
-
90
- context 'when origin column has one client' do
91
- before { column << client }
92
-
93
- include_examples 'moves current client', 0
94
- end
95
- end
96
- end
97
-
98
- describe '#get_or_create_column' do
99
- let(:columns) { Container.new([column, Column.new(geo)]) }
100
-
101
- it 'returns the consecutive column in given direction' do
102
- expect(arranger.get_or_create_column :succ).to be columns[1]
103
- end
104
-
105
- context 'when current column is last in given direction' do
106
- before { columns.current = columns[1] }
107
-
108
- context 'when max columns count is not reached' do
109
- before { geo.width = 4096 }
110
-
111
- it 'appends a new column' do
112
- expect(arranger.get_or_create_column :succ).to be columns[2]
113
- end
114
-
115
- it 'prepends a new column' do
116
- columns.current = columns[0]
117
- expect(arranger.get_or_create_column :pred).to be columns[0]
118
- end
119
- end
120
-
121
- context 'when max columns count is reached' do
122
- it 'returns the consecutive column in given direction' do
123
- expect(arranger.get_or_create_column :succ).to be columns[0]
124
- end
125
- end
126
- end
127
- end
128
-
129
- describe '#max_columns_count?' do
130
- context 'when a new column fits in current geo' do
131
- it 'returns false' do
132
- expect(arranger.max_columns_count?).to be false
133
- end
134
- end
135
-
136
- context 'when current geo can not contain more column' do
137
- let(:columns) { Container.new([column, Column.new(geo)]) }
138
-
139
- it 'returns true' do
140
- expect(arranger.max_columns_count?).to be true
141
- end
142
- end
143
- end
144
-
145
- describe '#update_geos' do
146
- let(:columns) { Container.new([column, Column.new(geo)]) }
147
-
148
- before { geo.x = 20 }
149
-
150
- it 'decreases first column width as the optimal column width' do
151
- arranger.update_geos
152
- expect(columns[0].width).to eq 300
153
- end
154
-
155
- it 'offsets each column with given geo' do
156
- arranger.update_geos
157
- expect(columns[0].x).to eq 20
158
- end
159
-
160
- it 'moves second column aside the first column' do
161
- arranger.update_geos
162
- expect(columns[1].x).to eq 320
163
- end
164
-
165
- it 'increases last column width to occupy remaining width' do
166
- arranger.update_geos
167
- expect(columns[1].width).to eq 340
168
- end
169
-
170
- context 'without columns' do
171
- let(:columns) { Container.new([]) }
172
-
173
- it 'does not raise any error' do
174
- expect { arranger.update_geos }.not_to raise_error
175
- end
176
- end
177
- end
178
- end
179
- end
180
- end
181
- end