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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ccb6aa0368780ba45a6f29d289f76cf3518fa3af
4
+ data.tar.gz: bed1e1fea17ff3c4f774484fa75c082edb420d06
5
+ SHA512:
6
+ metadata.gz: 7a094e49e315b7f364af1bb4d1468efbf7086e2c54b883440a68a609eb65f24e7df1b462e3e5d60b9cfa4cf9776ce673eb42a471622bfc15a2f9a65028481da5
7
+ data.tar.gz: fbd5e5522acd4f377f1e8610f95120c0912dbe7d80aa70e0e230833de4a4769fda49fdd48c482354c5a291c72fa13a43f815c7b8e400aa9a06eedcbf4f4d9a0e
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /Gemfile.lock
2
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ eval File.read('Gemfile-custom.rb') if File.exist?('Gemfile-custom.rb')
data/Gemfile-custom.rb ADDED
@@ -0,0 +1,7 @@
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
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{\Aspec/.+_spec\.rb\z})
3
+ watch(%r{\Alib/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ watch(%r{\Aspec/support/.+\.rb\z}) { 'spec' }
6
+ end
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ task default: :spec
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
data/lib/uh/layout.rb ADDED
@@ -0,0 +1,161 @@
1
+ require 'forwardable'
2
+
3
+ require 'uh/layout/geo_accessors'
4
+ require 'uh/layout/bar'
5
+ require 'uh/layout/container'
6
+ require 'uh/layout/column'
7
+ require 'uh/layout/column/arranger'
8
+ require 'uh/layout/dumper'
9
+ require 'uh/layout/screen'
10
+ require 'uh/layout/tag'
11
+
12
+ module Uh
13
+ class Layout
14
+ extend Forwardable
15
+ def_delegator :@screens, :current, :current_screen
16
+ def_delegator :current_screen, :==, :current_screen?
17
+ def_delegator :current_screen, :current_tag
18
+ def_delegator :current_tag, :current_column
19
+
20
+ attr_reader :screens, :widgets
21
+
22
+ def initialize
23
+ @screens = Container.new
24
+ @widgets = []
25
+ end
26
+
27
+ def to_s
28
+ Dumper.new(self).to_s
29
+ end
30
+
31
+ def current_client
32
+ current_column and current_column.current_client
33
+ end
34
+
35
+ def include?(client)
36
+ screens.any? { |screen| screen.include? client }
37
+ end
38
+
39
+ def arranger_for_current_tag
40
+ Column::Arranger.new(current_tag.columns, current_tag.geo)
41
+ end
42
+
43
+ def update_widgets
44
+ @widgets.each &:update
45
+ @widgets.each &:redraw
46
+ end
47
+
48
+ def suggest_geo
49
+ (current_column or current_tag).geo.dup
50
+ end
51
+
52
+ def <<(client)
53
+ current_tag.current_column_or_create << client
54
+ current_column.current_client = client
55
+ current_column.arrange_clients
56
+ current_column.show_hide_clients
57
+ client.focus
58
+ update_widgets
59
+ self
60
+ end
61
+ alias push <<
62
+
63
+ def remove(client)
64
+ screen, tag, column = find_client client
65
+ column.remove client
66
+ Column::Arranger.new(tag.columns, tag.geo).redraw
67
+ tag.each_column &:arrange_clients
68
+ column.show_hide_clients
69
+ current_client.focus if current_client
70
+ update_widgets
71
+ end
72
+
73
+ def handle_screen_sel(direction)
74
+ screens.sel direction
75
+ current_client.focus if current_client
76
+ update_widgets
77
+ end
78
+
79
+ def handle_screen_set(direction)
80
+ return unless current_client
81
+ remove client = current_client
82
+ screens.sel direction
83
+ push client
84
+ end
85
+
86
+ def handle_tag_sel(tag_id)
87
+ return unless current_tag.id != tag_id
88
+ current_tag.hide
89
+ current_screen.tags.current = find_tag_or_create tag_id
90
+ current_tag.each_column &:show_hide_clients
91
+ current_client.focus if current_client
92
+ update_widgets
93
+ end
94
+
95
+ def handle_tag_set(tag_id)
96
+ return unless current_client && current_tag.id != tag_id
97
+ 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
105
+ end
106
+
107
+ def handle_column_sel(direction)
108
+ return unless current_tag.columns.any?
109
+ current_tag.columns.sel direction
110
+ current_client.focus
111
+ update_widgets
112
+ end
113
+
114
+ def handle_client_sel(direction)
115
+ return unless current_client
116
+ current_column.clients.sel direction
117
+ current_column.show_hide_clients
118
+ current_client.focus
119
+ update_widgets
120
+ end
121
+
122
+ def handle_client_swap(direction)
123
+ return unless current_client
124
+ current_column.clients.set direction
125
+ update_widgets
126
+ end
127
+
128
+ def handle_client_column_set(direction, arranger: arranger_for_current_tag)
129
+ return unless current_client
130
+ arranger.move_current_client(direction).update_geos
131
+ current_tag.each_column &:arrange_clients
132
+ current_tag.each_column &:show_hide_clients
133
+ update_widgets
134
+ end
135
+
136
+ def handle_kill_current
137
+ current_client and current_client.kill
138
+ end
139
+
140
+
141
+ private
142
+
143
+ def find_client(client)
144
+ screens.each do |screen|
145
+ screen.tags.each do |tag|
146
+ tag.each_column do |column|
147
+ if column.include? client
148
+ return screen, tag, column
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ def find_tag_or_create(tag_id)
156
+ current_screen.tags.find { |e| e.id == tag_id } or Tag.new(tag_id, current_screen.geo).tap do |tag|
157
+ current_screen.tags << tag
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,159 @@
1
+ module Uh
2
+ class Layout
3
+ class Bar
4
+ TEXT_PADDING_X = 1
5
+ TEXT_PADDING_Y = 1
6
+ COLUMN_MARGIN_TOP = 0
7
+ COLUMN_HEIGHT = 2
8
+ COLUMN_PADDING_X = 1
9
+ TAG_PADDING_X = 5
10
+
11
+ include GeoAccessors
12
+
13
+ attr_writer :active
14
+
15
+ def initialize(display, screen, colors)
16
+ @display = display
17
+ @screen = screen
18
+ @geo = build_geo @screen.geo
19
+ @window = @display.create_subwindow @geo
20
+ @pixmap = @display.create_pixmap width, height
21
+ @colors = Hash[colors.map { |k, v| [k, @display.color_by_name(v)] }]
22
+ @on_update = proc { }
23
+ end
24
+
25
+ def active?
26
+ !!@active
27
+ end
28
+
29
+ def on_update(&block)
30
+ @on_update = block
31
+ end
32
+
33
+ def update
34
+ @on_update.call
35
+ end
36
+
37
+ def redraw
38
+ draw_background
39
+ draw_columns @screen.current_tag.columns,
40
+ @screen.current_tag.current_column
41
+ draw_tags @screen.tags, @screen.current_tag
42
+ blit
43
+ end
44
+
45
+ def show
46
+ @window.show
47
+ self
48
+ end
49
+
50
+ def focus
51
+ @window.focus
52
+ self
53
+ end
54
+
55
+
56
+ private
57
+
58
+ def blit
59
+ @pixmap.copy @window
60
+ self
61
+ end
62
+
63
+ def build_geo(layout_geo)
64
+ bar_height = text_line_height * 2 + COLUMN_HEIGHT + 1
65
+
66
+ Uh::Geo.new(
67
+ layout_geo.x,
68
+ layout_geo.height - bar_height,
69
+ layout_geo.width,
70
+ bar_height
71
+ )
72
+ end
73
+
74
+ def active_color
75
+ active? ? @colors[:sel] : @colors[:hi]
76
+ end
77
+
78
+ def text_line_height
79
+ @display.font.height + TEXT_PADDING_Y * 2
80
+ end
81
+
82
+ def column_widget_text_y
83
+ COLUMN_MARGIN_TOP + COLUMN_HEIGHT
84
+ end
85
+
86
+ def column_widget_height
87
+ column_widget_text_y + text_line_height + 1
88
+ end
89
+
90
+ def column_offset_x(column)
91
+ column.x - x
92
+ end
93
+
94
+ def column_text(column)
95
+ text = '%d/%d %s (%s)' % [
96
+ column.clients.index(column.current_client),
97
+ column.clients.size,
98
+ column.current_client.name,
99
+ column.current_client.wclass
100
+ ]
101
+ end
102
+
103
+ def draw_background
104
+ @pixmap.gc_color @colors[:bg]
105
+ @pixmap.draw_rect 0, 0, width, height
106
+ end
107
+
108
+ def draw_columns(columns, current_column)
109
+ columns.each do |column|
110
+ draw_column column, column == current_column
111
+ end
112
+ end
113
+
114
+ def draw_column(column, current)
115
+ @pixmap.gc_color current ? active_color : @colors[:hi]
116
+ @pixmap.draw_rect column_offset_x(column) + COLUMN_PADDING_X,
117
+ COLUMN_MARGIN_TOP,
118
+ column.width - COLUMN_PADDING_X, COLUMN_HEIGHT
119
+ @pixmap.gc_color @colors[:fg]
120
+ text_y =
121
+ column_widget_text_y + @display.font.ascent + TEXT_PADDING_Y
122
+ @pixmap.draw_string column_offset_x(column) + TEXT_PADDING_Y,
123
+ text_y, column_text(column)
124
+ end
125
+
126
+ def draw_tags(tags, current_tag)
127
+ tags.sort_by(&:id).inject(0) do |offset, tag|
128
+ color = if tag == current_tag
129
+ active_color
130
+ elsif tag.clients.any?
131
+ @colors[:hi]
132
+ else
133
+ @colors[:bg]
134
+ end
135
+
136
+ offset + draw_text(
137
+ tag.id, offset, column_widget_height,
138
+ @colors[:fg], color,
139
+ TAG_PADDING_X
140
+ )
141
+ end
142
+ end
143
+
144
+ def draw_text(text, x, y, color_fg = @colors[:fg], color_bg = nil,
145
+ padding_x = TEXT_PADDING_X)
146
+ text = text.to_s
147
+ text_width = text.length * @display.font.width + padding_x * 2
148
+ text_y = y + @display.font.ascent + TEXT_PADDING_Y
149
+ if color_bg
150
+ @pixmap.gc_color color_bg
151
+ @pixmap.draw_rect x, y, text_width, text_line_height
152
+ end
153
+ @pixmap.gc_color color_fg
154
+ @pixmap.draw_string x + padding_x, text_y, text
155
+ text_width
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,44 @@
1
+ module Uh
2
+ class Layout
3
+ class Column
4
+ include GeoAccessors
5
+
6
+ extend Forwardable
7
+ def_delegators :@clients, :empty?, :include?, :remove
8
+ def_delegator :@clients, :current, :current_client
9
+ def_delegator :@clients, :current=, :current_client=
10
+ def_delegator :current_client, :==, :current_client?
11
+
12
+ attr_reader :geo, :clients
13
+
14
+ def initialize(geo)
15
+ @geo = geo.dup
16
+ @clients = Container.new
17
+ end
18
+
19
+ def to_s
20
+ "COL geo: #{@geo}"
21
+ end
22
+
23
+ def <<(client)
24
+ client.geo = @geo.dup
25
+ @clients << client
26
+ self
27
+ end
28
+
29
+ def arrange_clients
30
+ @clients.each do |client|
31
+ client.geo = @geo.dup
32
+ client.moveresize
33
+ end
34
+ end
35
+
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?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,66 @@
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