glimmer-dsl-libui 0.5.18 → 0.5.21

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -41,7 +41,7 @@ module Glimmer
41
41
  super
42
42
  if Glimmer::LibUI.integer_to_boolean(value, allow_nil: false) != Glimmer::LibUI.integer_to_boolean(@last_checked, allow_nil: false)
43
43
  if Glimmer::LibUI.integer_to_boolean(value)
44
- (@parent_proxy.children - [self]).select {|c| c.is_a?(MenuItemProxy)}.each do |menu_item|
44
+ (@parent_proxy.children - [self]).select {|c| c.is_a?(RadioMenuItemProxy)}.each do |menu_item|
45
45
  menu_item.checked = false
46
46
  end
47
47
  end
@@ -1,65 +1,94 @@
1
- class CodeArea
2
- class << self
3
- def languages
4
- require 'rouge'
5
- Rouge::Lexer.all.map {|lexer| lexer.tag}.sort
6
- end
7
-
8
- def lexers
9
- require 'rouge'
10
- Rouge::Lexer.all.sort_by(&:title)
11
- end
12
- end
13
-
14
- include Glimmer::LibUI::CustomControl
15
-
16
- REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/
17
-
18
- option :language, default: 'ruby'
19
- option :theme, default: 'glimmer'
20
- option :code
21
-
22
- body {
23
- area {
24
- rectangle(0, 0, 8000, 8000) {
25
- fill :white
26
- }
27
- text {
28
- default_font family: OS.mac? ? 'Consolas' : 'Courier', size: 13, weight: :medium, italic: :normal, stretch: :normal
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/libui/custom_control'
23
+
24
+ module Glimmer
25
+ module LibUI
26
+ module CustomControl
27
+ class CodeArea
28
+ class << self
29
+ def languages
30
+ require 'rouge'
31
+ Rouge::Lexer.all.map {|lexer| lexer.tag}.sort
32
+ end
33
+
34
+ def lexers
35
+ require 'rouge'
36
+ Rouge::Lexer.all.sort_by(&:title)
37
+ end
38
+ end
39
+
40
+ include Glimmer::LibUI::CustomControl
41
+
42
+ REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/
29
43
 
30
- syntax_highlighting(code).each do |token|
31
- style_data = Rouge::Theme.find(theme).new.style_for(token[:token_type])
32
-
33
- string(token[:token_text]) {
34
- color style_data[:fg] || :black
35
- background style_data[:bg] || :white
44
+ option :language, default: 'ruby'
45
+ option :theme, default: 'glimmer'
46
+ option :code
47
+
48
+ body {
49
+ area {
50
+ rectangle(0, 0, 8000, 8000) {
51
+ fill :white
52
+ }
53
+ text {
54
+ default_font family: OS.mac? ? 'Consolas' : 'Courier', size: 13, weight: :medium, italic: :normal, stretch: :normal
55
+
56
+ syntax_highlighting(code).each do |token|
57
+ style_data = Rouge::Theme.find(theme).new.style_for(token[:token_type])
58
+
59
+ string(token[:token_text]) {
60
+ color style_data[:fg] || :black
61
+ background style_data[:bg] || :white
62
+ }
63
+ end
64
+ }
36
65
  }
66
+ }
67
+
68
+ def lexer
69
+ require 'rouge'
70
+ require 'glimmer-dsl-libui/ext/rouge/theme/glimmer'
71
+ # TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension
72
+ @lexer ||= Rouge::Lexer.find_fancy(language)
73
+ @lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found
74
+ end
75
+
76
+ def syntax_highlighting(text)
77
+ return [] if text.to_s.strip.empty?
78
+ @syntax_highlighting ||= {}
79
+ unless @syntax_highlighting.keys.include?(text)
80
+ lex = lexer.lex(text).to_a
81
+ text_size = 0
82
+ @syntax_highlighting[text] = lex.map do |pair|
83
+ {token_type: pair.first, token_text: pair.last}
84
+ end.each do |hash|
85
+ hash[:token_index] = text_size
86
+ text_size += hash[:token_text].size
87
+ end
88
+ end
89
+ @syntax_highlighting[text]
37
90
  end
38
- }
39
- }
40
- }
41
-
42
- def lexer
43
- require 'rouge'
44
- require 'glimmer-dsl-libui/ext/rouge/theme/glimmer'
45
- # TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension
46
- @lexer ||= Rouge::Lexer.find_fancy(language)
47
- @lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found
48
- end
49
-
50
- def syntax_highlighting(text)
51
- return [] if text.to_s.strip.empty?
52
- @syntax_highlighting ||= {}
53
- unless @syntax_highlighting.keys.include?(text)
54
- lex = lexer.lex(text).to_a
55
- text_size = 0
56
- @syntax_highlighting[text] = lex.map do |pair|
57
- {token_type: pair.first, token_text: pair.last}
58
- end.each do |hash|
59
- hash[:token_index] = text_size
60
- text_size += hash[:token_text].size
61
91
  end
62
92
  end
63
- @syntax_highlighting[text]
64
93
  end
65
94
  end
@@ -1,173 +1,270 @@
1
- class RefinedTable
2
- include Glimmer::LibUI::CustomControl
3
-
4
- option :model_array, default: []
5
- option :table_columns, default: []
6
- option :table_editable, default: false
7
- option :per_page, default: 10
8
- option :page, default: 1
9
- option :visible_page_count, default: false
10
-
11
- attr_accessor :filtered_model_array, :filter_query, :filter_query_page_stack, :paginated_model_array
12
-
13
- before_body do
14
- @filter_query = ''
15
- @filter_query_page_stack = {}
16
- @filtered_model_array = model_array.dup
17
- @filtered_model_array_stack = {@filter_query => @filtered_model_array}
18
- self.page = correct_page(page)
19
- paginate_model_array
20
- end
21
-
22
- body {
23
- vertical_box {
24
- table_filter
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
21
 
26
- table_paginator if page_count > 1
27
-
28
- @table = table {
29
- table_columns.each do |column_name, column_details|
30
- editable_value = on_clicked_value = nil
31
- if column_details.is_a?(Symbol) || column_details.is_a?(String)
32
- column_type = column_details
33
- elsif column_details.is_a?(Hash)
34
- column_type = column_details.keys.first
35
- editable_value = column_details.values.first[:editable] || column_details.values.first['editable']
36
- on_clicked_value = column_details.values.first[:on_clicked] || column_details.values.first['on_clicked']
22
+ require 'csv'
23
+ require 'facets/string/underscore'
24
+
25
+ require 'glimmer/libui/custom_control'
26
+
27
+ module Glimmer
28
+ module LibUI
29
+ module CustomControl
30
+ class RefinedTable
31
+ include Glimmer::LibUI::CustomControl
32
+
33
+ FILTER_DEFAULT = lambda do |row_hash, query|
34
+ text = row_hash.values.map(&:downcase).join(' ')
35
+ if query != @last_query
36
+ @last_query = query
37
+ @query_words = []
38
+ query_text = query.strip
39
+ until query_text.empty?
40
+ exact_term_double_quoted_regexp = /"[^"]+"/
41
+ specific_column_double_quoted_column_name_regexp = /"[^":]+":[^": ]+/
42
+ specific_column_double_quoted_column_value_regexp = /[^": ]+:"[^":]+"/
43
+ specific_column_double_quoted_column_name_and_value_regexp = /"[^":]+":"[^":]+"/
44
+ single_word_regexp = /\S+/
45
+ query_match = (query_text + ' ').match(/^(#{exact_term_double_quoted_regexp}|#{specific_column_double_quoted_column_name_regexp}|#{specific_column_double_quoted_column_value_regexp}|#{specific_column_double_quoted_column_name_and_value_regexp}|#{single_word_regexp})\s+/)
46
+ if query_match && query_match[1]
47
+ query_word = query_match[1]
48
+ query_text = query_text.sub(query_word, '').strip
49
+ query_word = query_word.sub(/^"/, '').sub(/"$/, '') if query_word.start_with?('"') && query_word.end_with?('"') && !query_word.include?(':')
50
+ @query_words << query_word
51
+ end
52
+ end
37
53
  end
38
-
39
- send("#{column_type}_column", column_name) {
40
- editable editable_value unless editable_value.nil?
41
- on_clicked(&on_clicked_value) unless on_clicked_value.nil?
42
- }
43
- end
44
-
45
- editable table_editable
46
- cell_rows <=> [self, :paginated_model_array]
47
- }
48
- }
49
- }
50
-
51
- def table_filter
52
- search_entry {
53
- stretchy false
54
- text <=> [self, :filter_query,
55
- before_write: ->(new_filter_query) {
56
- if new_filter_query != filter_query
57
- if !@filtered_model_array_stack.key?(new_filter_query)
58
- @filtered_model_array_stack[new_filter_query] = model_array.dup.filter do |model|
59
- @table.expand([model])[0].any? do |attribute_value|
60
- attribute_value.to_s.downcase.include?(new_filter_query.downcase)
54
+ @query_words.all? do |word|
55
+ if word.include?(':')
56
+ column_name, column_value = word.split(':')
57
+ column_value = column_value.to_s
58
+ column_name = column_name.sub(/^"/, '').sub(/"$/, '') if column_name.start_with?('"') && column_name.end_with?('"')
59
+ column_value = column_value.sub(/^"/, '').sub(/"$/, '') if column_value.start_with?('"') && column_value.end_with?('"')
60
+ column_human_name = row_hash.keys.find do |table_column_name|
61
+ table_column_name.underscore.start_with?(column_name.underscore)
62
+ end
63
+ if column_human_name
64
+ column_value_words = [column_value.downcase.split].flatten
65
+ column_value_words.all? do |column_value_word|
66
+ row_hash[column_human_name].downcase.include?(column_value_word)
61
67
  end
68
+ else
69
+ text.downcase.include?(word.downcase)
62
70
  end
71
+ else
72
+ text.downcase.include?(word.downcase)
63
73
  end
64
- @filtered_model_array = @filtered_model_array_stack[new_filter_query]
65
- if new_filter_query.size > filter_query.size
66
- @filter_query_page_stack[filter_query] = page
67
- end
68
- self.page = @filter_query_page_stack[new_filter_query] || correct_page(page)
69
- paginate_model_array
70
74
  end
71
- }
72
- ]
73
- }
74
- end
75
-
76
- def table_paginator
77
- horizontal_box {
78
- stretchy false
79
-
80
- button('<<') {
81
- enabled <= [self, :page, on_read: ->(val) {val > 1}]
75
+ end
82
76
 
83
- on_clicked do
84
- unless self.page == 0
85
- self.page = 1
86
- paginate_model_array
87
- end
77
+ option :model_array, default: []
78
+ option :table_columns, default: []
79
+ option :table_editable, default: false
80
+ option :per_page, default: 10
81
+ option :page, default: 1
82
+ option :visible_page_count, default: false
83
+ option :filter_query, default: ''
84
+ option :filter, default: FILTER_DEFAULT
85
+
86
+ attr_accessor :filtered_model_array # filtered model array (intermediary, non-paginated)
87
+ attr_accessor :refined_model_array # paginated filtered model array
88
+ attr_reader :table_proxy
89
+
90
+ before_body do
91
+ init_model_array
88
92
  end
89
- }
90
-
91
- button('<') {
92
- enabled <= [self, :page, on_read: ->(val) {val > 1}]
93
93
 
94
- on_clicked do
95
- unless self.page == 0
96
- self.page = [page - 1, 1].max
97
- paginate_model_array
94
+ after_body do
95
+ filter_model_array
96
+
97
+ observe(self, :model_array) do
98
+ init_model_array
99
+ end
100
+
101
+ observe(self, :filter_query) do
102
+ filter_model_array
98
103
  end
99
104
  end
100
- }
101
-
102
- entry {
103
- text <=> [self, :page,
104
- on_read: :to_s,
105
- on_write: ->(val) { correct_page(val.to_i) },
106
- after_write: ->(val) { paginate_model_array },
107
- ]
108
- }
105
+
106
+ body {
107
+ vertical_box {
108
+ table_filter
109
109
 
110
- if visible_page_count
111
- label {
112
- text <= [self, :paginated_model_array, on_read: ->(val) {"of #{page_count} pages"}]
110
+ table_paginator if page_count > 1
111
+
112
+ @table_proxy = table {
113
+ table_columns.each do |column_name, column_details|
114
+ editable_value = on_clicked_value = nil
115
+ if column_details.is_a?(Symbol) || column_details.is_a?(String)
116
+ column_type = column_details
117
+ elsif column_details.is_a?(Hash)
118
+ column_type = column_details.keys.first
119
+ editable_value = column_details.values.first[:editable] || column_details.values.first['editable']
120
+ on_clicked_value = column_details.values.first[:on_clicked] || column_details.values.first['on_clicked']
121
+ end
122
+
123
+ send("#{column_type}_column", column_name) {
124
+ editable editable_value unless editable_value.nil?
125
+ on_clicked(&on_clicked_value) unless on_clicked_value.nil?
126
+ }
127
+ end
128
+
129
+ editable table_editable
130
+ cell_rows <=> [self, :refined_model_array]
131
+ }
132
+ }
113
133
  }
114
- end
115
-
116
- button('>') {
117
- enabled <= [self, :page, on_read: ->(val) {val < page_count}]
118
134
 
119
- on_clicked do
120
- unless self.page == 0
121
- self.page = [page + 1, page_count].min
122
- paginate_model_array
135
+ def table_filter
136
+ search_entry {
137
+ stretchy false
138
+ text <=> [self, :filter_query]
139
+ }
140
+ end
141
+
142
+ def table_paginator
143
+ horizontal_box {
144
+ stretchy false
145
+
146
+ button('<<') {
147
+ enabled <= [self, :page, on_read: ->(val) {val > 1}]
148
+
149
+ on_clicked do
150
+ unless self.page == 0
151
+ self.page = 1
152
+ paginate_model_array
153
+ end
154
+ end
155
+ }
156
+
157
+ button('<') {
158
+ enabled <= [self, :page, on_read: ->(val) {val > 1}]
159
+
160
+ on_clicked do
161
+ unless self.page == 0
162
+ self.page = [page - 1, 1].max
163
+ paginate_model_array
164
+ end
165
+ end
166
+ }
167
+
168
+ entry {
169
+ text <=> [self, :page,
170
+ on_read: :to_s,
171
+ on_write: ->(val) { correct_page(val.to_i) },
172
+ after_write: ->(val) { paginate_model_array },
173
+ ]
174
+ }
175
+
176
+ if visible_page_count
177
+ label {
178
+ text <= [self, :refined_model_array, on_read: ->(val) {"of #{page_count} pages"}]
179
+ }
180
+ end
181
+
182
+ button('>') {
183
+ enabled <= [self, :page, on_read: ->(val) {val < page_count}]
184
+
185
+ on_clicked do
186
+ unless self.page == 0
187
+ self.page = [page + 1, page_count].min
188
+ paginate_model_array
189
+ end
190
+ end
191
+ }
192
+
193
+ button('>>') {
194
+ enabled <= [self, :page, on_read: ->(val) {val < page_count}]
195
+
196
+ on_clicked do
197
+ unless self.page == 0
198
+ self.page = page_count
199
+ paginate_model_array
200
+ end
201
+ end
202
+ }
203
+ }
204
+ end
205
+
206
+ def init_model_array
207
+ @last_filter_query = nil
208
+ @filter_query_page_stack = {}
209
+ @filtered_model_array = model_array.dup
210
+ @filtered_model_array_stack = {'' => @filtered_model_array}
211
+ self.page = correct_page(page)
212
+ filter_model_array if @table_proxy
213
+ end
214
+
215
+ def filter_model_array
216
+ return unless (@last_filter_query.nil? || filter_query != @last_filter_query)
217
+ if !@filtered_model_array_stack.key?(filter_query)
218
+ table_column_names = @table_proxy.columns.map(&:name)
219
+ @filtered_model_array_stack[filter_query] = model_array.dup.filter do |model|
220
+ row_values = @table_proxy.expand([model])[0].map(&:to_s)
221
+ row_hash = Hash[table_column_names.zip(row_values)]
222
+ filter.call(row_hash, filter_query)
223
+ end
224
+ end
225
+ @filtered_model_array = @filtered_model_array_stack[filter_query]
226
+ if @last_filter_query.nil? || filter_query.size > @last_filter_query.size
227
+ @filter_query_page_stack[filter_query] = correct_page(page)
123
228
  end
229
+ self.page = @filter_query_page_stack[filter_query] || correct_page(page)
230
+ paginate_model_array
231
+ @last_filter_query = filter_query
232
+ end
233
+
234
+ def paginate_model_array
235
+ self.refined_model_array = filtered_model_array[index, limit]
236
+ end
237
+
238
+ def index
239
+ [per_page * (page - 1), 0].max
240
+ end
241
+
242
+ def limit
243
+ [(filtered_model_array.count - index), per_page].min
244
+ end
245
+
246
+ def page_count
247
+ (filtered_model_array.count.to_f / per_page.to_f).ceil
248
+ end
249
+
250
+ def correct_page(page)
251
+ [[page, 1].max, page_count].min
252
+ end
253
+
254
+ # Ensure proxying properties to @table_proxy if body_root (vertical_box) doesn't support them
255
+
256
+ def respond_to?(method_name, *args, &block)
257
+ super || @table_proxy&.respond_to?(method_name, *args, &block)
124
258
  end
125
- }
126
-
127
- button('>>') {
128
- enabled <= [self, :page, on_read: ->(val) {val < page_count}]
129
259
 
130
- on_clicked do
131
- unless self.page == 0
132
- self.page = page_count
133
- paginate_model_array
260
+ def method_missing(method_name, *args, &block)
261
+ if @table_proxy&.respond_to?(method_name, *args, &block)
262
+ @table_proxy&.send(method_name, *args, &block)
263
+ else
264
+ super
134
265
  end
135
266
  end
136
- }
137
- }
138
- end
139
-
140
- def paginate_model_array
141
- self.paginated_model_array = filtered_model_array[index, limit]
142
- end
143
-
144
- def index
145
- [per_page * (page - 1), 0].max
146
- end
147
-
148
- def limit
149
- [(filtered_model_array.count - index), per_page].min
150
- end
151
-
152
- def page_count
153
- (filtered_model_array.count.to_f / per_page.to_f).ceil
154
- end
155
-
156
- def correct_page(page)
157
- [[page, 1].max, page_count].min
158
- end
159
-
160
- # Ensure proxying properties to @table if body_root (vertical_box) doesn't support them
161
-
162
- def respond_to?(method_name, *args, &block)
163
- super || @table&.respond_to?(method_name, *args, &block)
164
- end
165
-
166
- def method_missing(method_name, *args, &block)
167
- if @table&.respond_to?(method_name, *args, &block)
168
- @table&.send(method_name, *args, &block)
169
- else
170
- super
267
+ end
171
268
  end
172
269
  end
173
270
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.18
4
+ version: 0.5.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-04 00:00:00.000000000 Z
11
+ date: 2022-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -275,6 +275,8 @@ files:
275
275
  - VERSION
276
276
  - bin/girb
277
277
  - bin/girb_runner.rb
278
+ - docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md
279
+ - docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md
278
280
  - examples/area_based_custom_controls.rb
279
281
  - examples/area_gallery.rb
280
282
  - examples/area_gallery2.rb