uh-layout 0.1.1

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.
@@ -0,0 +1,75 @@
1
+ module Uh
2
+ class Layout
3
+ class Container
4
+ include Enumerable
5
+
6
+ extend Forwardable
7
+ def_delegators :@entries, :<<, :[], :each, :empty?, :first, :index, :last,
8
+ :size, :unshift
9
+
10
+ def initialize(entries = [])
11
+ @entries = entries
12
+ @current_index = 0
13
+ end
14
+
15
+ alias to_ary entries
16
+
17
+ def current
18
+ @entries[@current_index]
19
+ end
20
+
21
+ def current=(entry)
22
+ @current_index = @entries.index entry if include? entry
23
+ end
24
+
25
+ def remove(entry)
26
+ fail ArgumentError, 'unknown entry' unless include? entry
27
+ @entries.delete_at @entries.index entry
28
+ @current_index -= 1 unless @current_index == 0
29
+ self
30
+ end
31
+
32
+ def remove_if
33
+ @entries.each { |e| remove e if yield e }
34
+ end
35
+
36
+ def get(direction, cycle: false)
37
+ index = @current_index.send direction
38
+ if cycle
39
+ @entries[index % @entries.size]
40
+ else
41
+ index >= 0 ? self[index] : nil
42
+ end
43
+ end
44
+
45
+ def sel(direction)
46
+ @current_index = @current_index.send(direction) % @entries.size
47
+ end
48
+
49
+ def set(direction)
50
+ new_index = @current_index.send direction
51
+ if new_index.between? 0, @entries.size - 1
52
+ swap @current_index, new_index
53
+ @current_index = new_index
54
+ else
55
+ rotate direction
56
+ @current_index = new_index % @entries.size
57
+ end
58
+ end
59
+
60
+ def swap(a, b)
61
+ @entries[a], @entries[b] = @entries[b], @entries[a]
62
+ end
63
+
64
+
65
+ private
66
+
67
+ def rotate(direction)
68
+ case direction
69
+ when :pred then @entries = @entries.push @entries.shift
70
+ when :succ then @entries = @entries.unshift @entries.pop
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ module Uh
2
+ class Layout
3
+ class Dumper
4
+ def initialize(layout)
5
+ @layout = layout
6
+ end
7
+
8
+ def to_s
9
+ @layout.screens.inject('') do |m, screen|
10
+ m << "%s%s\n" % [@layout.current_screen?(screen) ? '*' : ' ', screen]
11
+ screen.tags.each do |tag|
12
+ m << " %s%s\n" % [screen.current_tag?(tag) ? '*' : ' ', tag]
13
+ tag.columns.each do |column|
14
+ m << " %s%s\n" % [tag.current_column?(column) ? '*' : ' ', column]
15
+ column.clients.each do |client|
16
+ m << " %s%s\n" % [
17
+ column.current_client?(client) ? '*' : ' ',
18
+ client
19
+ ]
20
+ end
21
+ end
22
+ end
23
+ m
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,32 @@
1
+ module Uh
2
+ class Layout
3
+ class Screen
4
+ include GeoAccessors
5
+
6
+ extend Forwardable
7
+ def_delegator :@tags, :current, :current_tag
8
+ def_delegator :current_tag, :==, :current_tag?
9
+
10
+ attr_reader :id, :tags, :geo
11
+
12
+ def initialize(id, geo)
13
+ @id = id
14
+ @geo = geo.dup
15
+ @tags = Container.new([Tag.new('1', @geo)])
16
+ end
17
+
18
+ def to_s
19
+ "SCREEN ##{@id}, geo: #{@geo}"
20
+ end
21
+
22
+ def height=(value)
23
+ @geo.height = value
24
+ @tags.each { |tag| tag.height = value }
25
+ end
26
+
27
+ def include?(client)
28
+ @tags.any? { |tag| tag.include? client }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module Uh
2
+ class Layout
3
+ class Tag
4
+ include GeoAccessors
5
+
6
+ extend Forwardable
7
+ def_delegator :@columns, :current, :current_column
8
+ def_delegator :@columns, :current=, :current_column=
9
+ def_delegator :@columns, :each, :each_column
10
+ def_delegator :current_column, :==, :current_column?
11
+ def_delegator :clients, :each, :each_client
12
+
13
+ attr_reader :id, :geo, :columns
14
+
15
+ def initialize(id, geo)
16
+ unless id.kind_of? String
17
+ fail ArgumentError, "expect `id' to be a String, #{id.class} given"
18
+ end
19
+ @id = id
20
+ @geo = geo.dup
21
+ @columns = Container.new
22
+ end
23
+
24
+ def to_s
25
+ "TAG ##{@id}, geo: #{@geo}"
26
+ end
27
+
28
+ def clients
29
+ @columns.inject([]) { |m, column| m + column.clients }
30
+ end
31
+
32
+ def include?(client)
33
+ @columns.any? { |column| column.include? client }
34
+ end
35
+
36
+ def current_column_or_create
37
+ current_column or Column.new(@geo).tap do |column|
38
+ @columns << column
39
+ end
40
+ end
41
+
42
+ def hide
43
+ clients.each &:hide
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ module Uh
2
+ class Layout
3
+ VERSION = '0.1.1'
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'uh'
2
+ require 'uh/wm'
3
+ require 'uh/layout'
4
+
5
+ Dir['spec/support/**/*.rb'].map { |e| require e.gsub 'spec/', '' }
6
+
7
+ RSpec::Matchers.define_negated_matcher :not_be, :be
8
+
9
+ RSpec.configure do |config|
10
+ config.include Factories
11
+
12
+ config.mock_with :rspec do |mocks|
13
+ mocks.verify_doubled_constant_names = true
14
+ mocks.verify_partial_doubles = true
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Factories
2
+ def build_client
3
+ Uh::WM::Client.new(instance_spy Uh::Window)
4
+ end
5
+
6
+ def build_geo(x = 0, y = 0, width = 640, height = 480)
7
+ Uh::Geo.new(x, y, width, height)
8
+ end
9
+ end
@@ -0,0 +1,181 @@
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
@@ -0,0 +1,66 @@
1
+ module Uh
2
+ class Layout
3
+ describe Column do
4
+ let(:geo) { build_geo }
5
+ let(:other_geo) { build_geo 640, 0, 320, 240 }
6
+ let(:client) { build_client }
7
+ let(:other_client) { build_client }
8
+ subject(:column) { described_class.new(geo) }
9
+
10
+ it 'has a copy to given geo' do
11
+ expect(column.geo).to eq(geo).and not_be geo
12
+ end
13
+
14
+ it 'has no client assigned' do
15
+ expect(column).to be_empty
16
+ end
17
+
18
+ describe '#<<' do
19
+ before { column << client }
20
+
21
+ it 'assigns column geo copy to given client' do
22
+ expect(client.geo).to eq(column.geo).and not_be column.geo
23
+ end
24
+
25
+ it 'adds given client' do
26
+ expect(column.clients).to include client
27
+ end
28
+
29
+ it 'returns self' do
30
+ expect(column << client).to be column
31
+ end
32
+ end
33
+
34
+ describe '#arrange_clients' do
35
+ before { column << client << other_client }
36
+
37
+ it 'updates clients geometries' do
38
+ column.width = 320
39
+ column.arrange_clients
40
+ expect(column.clients.map(&:geo)).to all eq column.geo
41
+ end
42
+
43
+ it 'moveresizes clients' do
44
+ expect([client, other_client]).to all receive :moveresize
45
+ column.arrange_clients
46
+ end
47
+ end
48
+
49
+ describe '#show_hide_clients' do
50
+ before { column << client << other_client }
51
+
52
+ it 'shows current client only' do
53
+ expect(other_client).not_to receive :show
54
+ expect(client.hide).to receive :show
55
+ column.show_hide_clients
56
+ end
57
+
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
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end