uh-layout 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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