uh-layout 0.1.1

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