glimmer-dsl-libui 0.5.18 → 0.5.21

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.
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