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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Gemfile +5 -0
- data/Gemfile-custom.rb +7 -0
- data/Guardfile +6 -0
- data/Rakefile +5 -0
- data/lib/uh/layout.rb +161 -0
- data/lib/uh/layout/bar.rb +159 -0
- data/lib/uh/layout/column.rb +44 -0
- data/lib/uh/layout/column/arranger.rb +66 -0
- data/lib/uh/layout/container.rb +75 -0
- data/lib/uh/layout/dumper.rb +28 -0
- data/lib/uh/layout/geo_accessors.rb +10 -0
- data/lib/uh/layout/screen.rb +32 -0
- data/lib/uh/layout/tag.rb +47 -0
- data/lib/uh/layout/version.rb +5 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/factories.rb +9 -0
- data/spec/uh/layout/column/arranger_spec.rb +181 -0
- data/spec/uh/layout/column_spec.rb +66 -0
- data/spec/uh/layout/container_spec.rb +170 -0
- data/spec/uh/layout/screen_spec.rb +40 -0
- data/spec/uh/layout/tag_spec.rb +62 -0
- data/spec/uh/layout_spec.rb +414 -0
- data/uh-layout.gemspec +22 -0
- metadata +118 -0
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|