glimmer-dsl-libui 0.7.3 → 0.7.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c1ff2eeecd4520bb427d6a2ca0abb85ea41f5b3b92d859253b59c9574c6c899
4
- data.tar.gz: 907fa263dc715be72bf022258da6cb58aaef70e2d647ee17c29f9eda942547e5
3
+ metadata.gz: 5f7eb8d1c417de9910ff49f86592694558a3333f4840971f65be1232adf7408b
4
+ data.tar.gz: 9cdf9733b7026aae945ec183ba4551ee2bbe120454c0a094c02a99bd5338069a
5
5
  SHA512:
6
- metadata.gz: 2f8cd302076fc5e5cdced272facbd12c9af556cc0c5d1c2a3cdd271676316c80f302b496613334db749c67a8ecff2ca88bb50b3157583bce693ea7a9f77b277b
7
- data.tar.gz: f5062b028e2c4ca0eb1e804842d7940f43c4bfb8d1212b9075a0ca223c71f9730bb36c83345029e4d4eaee30d6056851621e4726d619c7f5209c0b08bde9f74d
6
+ metadata.gz: 5a6c290b75e8301cadd8c837be2612dd4e653192577ea0c8f2493d4fa4a0b7198f2f8008c99d7adeef42320e5b4d86be339b37fe9ca588ee4cc9e22ad36c0a48
7
+ data.tar.gz: b51a3e1e878b9adde67cc6670f682df5d7ab09a6678b8b16655fbcb0d5e3770b5119f867ea58c4b025a78e043ed56cd0309f86eccdec0b90d31c19b375a0007b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.7.4
4
+
5
+ - `table` `sortable` property (default: `true`) to enable automatic table sorting support when `cell_rows` is an `Array` (does not sort if `cell_rows` is a lazy enumerable)
6
+ - Fix issue with not auto-checking checkboxes for zero-or-many table selection in `examples/basic_table_selection2.rb`
7
+ - Rename `examples/basic_table_selection.rb` to `examples/basic_table_selection3.rb` and add new `examples/basic_table_selection.rb` with automated `table` selection via `sortable` property (default: `true`)
8
+ - Disable automatic sorting by default in `refined_table` (set its `table` `sortable` property to `false`) since it does not sort over the entire collection, yet the visible collection only
9
+ - Disable `sortable` sorting for `table` `button_column` since it does not make sense for it
10
+
3
11
  ## 0.7.3
4
12
 
5
13
  - `table` `selection` data-binding support
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.7.3
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.7.4
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  ### The Quickest Way From Zero To GUI
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
@@ -544,7 +544,7 @@ gem install glimmer-dsl-libui
544
544
  Or install via Bundler `Gemfile`:
545
545
 
546
546
  ```ruby
547
- gem 'glimmer-dsl-libui', '~> 0.7.3'
547
+ gem 'glimmer-dsl-libui', '~> 0.7.4'
548
548
  ```
549
549
 
550
550
  Test that installation worked by running the [Meta-Example](#examples):
@@ -824,7 +824,7 @@ There are additional useful `Glimmer::LibUI` operations that are not found in `L
824
824
 
825
825
  The `table` control must first declare its columns via one of these column keywords (mentioned in [Supported Keywords](#supported-keywords)):
826
826
  - `background_color_column`: expects color cell values
827
- - `button_column`: expects `String` cell values
827
+ - `button_column`: expects `String` cell values and a nested `on_clicked` listener that gets triggerd when a button is clicked
828
828
  - `checkbox_column`: expects Boolean cell values
829
829
  - `checkbox_text_column`: expects dual-element `Array` of Boolean and `String` cell values
830
830
  - `checkbox_text_color_column`: expects triple-element `Array` of Boolean, `String`, and color cell values
@@ -842,6 +842,22 @@ Note that the `cell_rows` property declaration results in "implicit data-binding
842
842
  - Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
843
843
  - Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
844
844
 
845
+ More details about table data-binding can be found in [examples/basic_table.rb](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md#basic-table) or other `table` [basic examples](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md) and [advanced examples](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md).
846
+
847
+ The `table` control supports table selection and table sorting automatically as smart defaults, which can also be configured if needed as per the options below.
848
+
849
+ There are other properties that `table` supports:
850
+ - `selection_mode` (`Symbol`) [default: `:zero_or_one`]: sets selection mode to `:one`, `:zero_or_one`, `:zero_or_many`, or `:none`
851
+ - `selection` (`Integer` or `Array` of `Integer`s): a single `Integer` row index for `:one` and `:zero_or_one` selection modes, or an `Array` of `Integer` row indexes if selection mode is `:zero_or_many`
852
+ - `header_visible` (Boolean): shows or hides column headers
853
+ - `sortable` (Boolean) [default: `true`]: enables automatic table sorting support
854
+
855
+ To handle `table` sorting manually, the following can be set inside a table column:
856
+ - `sort_indicator` (`Symbol`): sets sort indicator to ascending or descending with the value being `:ascending`, `:descending`, `:asc`, `:desc`, `:a`, or `:d`
857
+ - `on_clicked` (`Proc`): this listener is triggered when a table column is clicked
858
+
859
+ More details about table selection and table sorting can be found in [examples/basic_table_selection.rb](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md#basic-table-selection).
860
+
845
861
  ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
846
862
 
847
863
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.3
1
+ 0.7.4
@@ -1,6 +1,45 @@
1
1
  require 'glimmer-dsl-libui'
2
2
 
3
3
  class BasicTableSelection
4
+ TableColumnPresenter = Struct.new(:name,
5
+ :column,
6
+ :table_presenter,
7
+ keyword_init: true)
8
+
9
+ TablePresenter = Struct.new(:data,
10
+ :column_names,
11
+ :selection_mode,
12
+ :selection,
13
+ :header_visible,
14
+ :sortable,
15
+ keyword_init: true) do
16
+ def selection_items
17
+ data.size.times.map { |row| "Row #{row} Selection" }
18
+ end
19
+
20
+ def toggle_header_visible
21
+ self.header_visible = !(header_visible.nil? || header_visible)
22
+ end
23
+
24
+ def toggle_sortable
25
+ self.sortable = !(sortable.nil? || sortable)
26
+ end
27
+
28
+ def column_presenters
29
+ @column_presenters ||= column_names.each_with_index.map do |column_name, column|
30
+ TableColumnPresenter.new(name: column_name, column: column, table_presenter: self)
31
+ end
32
+ end
33
+
34
+ def selected_row
35
+ selection && data[selection]
36
+ end
37
+
38
+ def selected_rows
39
+ selection && selection.is_a?(Array) && selection.map { |row| data[row] }
40
+ end
41
+ end
42
+
4
43
  include Glimmer::LibUI::Application
5
44
 
6
45
  before_body do
@@ -11,10 +50,38 @@ class BasicTableSelection
11
50
  %w[horse neigh],
12
51
  %w[cow moo]
13
52
  ]
14
- @one_table_data = data.dup
15
- @zero_or_one_table_data = data.dup
16
- @zero_or_many_table_data = data.dup
17
- @none_table_data = data.dup
53
+ @one_table_presenter = TablePresenter.new(
54
+ data: data.dup,
55
+ column_names: ['Name', 'Description'],
56
+ selection_mode: :one, # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified)
57
+ selection: 2, # initial selection row index (could be nil too or just left off, defaulting to 0)
58
+ header_visible: nil, # defaults to true
59
+ sortable: nil, # defaults to true
60
+ )
61
+ @zero_or_one_table_presenter = TablePresenter.new(
62
+ data: data.dup,
63
+ column_names: ['Name', 'Description'],
64
+ selection_mode: :zero_or_one, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified)
65
+ selection: nil, # initial selection row index (could be an integer too or just left off, defaulting to nil)
66
+ header_visible: nil, # defaults to true
67
+ sortable: nil, # defaults to true
68
+ )
69
+ @zero_or_many_table_presenter = TablePresenter.new(
70
+ data: data.dup,
71
+ column_names: ['Name', 'Description'],
72
+ selection_mode: :zero_or_many, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified)
73
+ selection: [0, 2, 4], # initial selection row index (could be an integer too or just left off, defaulting to nil)
74
+ header_visible: nil, # defaults to true
75
+ sortable: nil, # defaults to true
76
+ )
77
+ @none_table_presenter = TablePresenter.new(
78
+ data: data.dup,
79
+ column_names: ['Name', 'Description'],
80
+ selection_mode: :none, # other values are :zero_or_many , :zero_or_one, :one (default is :zero_or_one if not specified)
81
+ selection: nil, # defaults to nil
82
+ header_visible: nil, # defaults to true
83
+ sortable: nil, # defaults to true
84
+ )
18
85
  end
19
86
 
20
87
  body {
@@ -26,42 +93,37 @@ class BasicTableSelection
26
93
  stretchy false
27
94
 
28
95
  @one_table_selection_radio_buttons = radio_buttons {
29
- items @one_table_data.size.times.map { |row| "Row #{row} Selection" }
30
-
31
- on_selected do |rb|
32
- @one_table.selection = [rb.selected]
33
- end
96
+ items @one_table_presenter.selection_items
97
+ selected <=> [@one_table_presenter, :selection]
34
98
  }
35
99
  }
36
-
37
- button('Toggle Table Header Visibility') {
100
+
101
+ horizontal_box {
38
102
  stretchy false
39
103
 
40
- on_clicked do
41
- @one_table.header_visible = !@one_table.header_visible
42
- end
43
- }
44
-
45
- @one_table = table {
46
- text_column('Animal') {
47
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
48
-
49
- on_clicked do |tc, column|
50
- sort_one_table_column(tc, column)
104
+ button('Toggle Table Header Visibility') {
105
+ on_clicked do
106
+ @one_table_presenter.toggle_header_visible
51
107
  end
52
108
  }
53
- text_column('Description') {
54
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
55
-
56
- on_clicked do |tc, column|
57
- sort_one_table_column(tc, column)
109
+
110
+ button('Toggle Table Sortability') {
111
+ on_clicked do
112
+ @one_table_presenter.toggle_sortable # toggles sortable attribute to false or true
58
113
  end
59
114
  }
115
+ }
116
+
117
+ @one_table = table {
118
+ @one_table_presenter.column_presenters.each do |column_presenter|
119
+ text_column(column_presenter.name)
120
+ end
60
121
 
61
- cell_rows @one_table_data
62
- selection_mode :one # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified)
63
- selection 2 # initial selection row index (could be nil too or just left off, defaulting to 0)
64
- # header_visible true # default
122
+ cell_rows @one_table_presenter.data
123
+ selection_mode <= [@one_table_presenter, :selection_mode]
124
+ selection <=> [@one_table_presenter, :selection]
125
+ header_visible <= [@one_table_presenter, :header_visible]
126
+ sortable <= [@one_table_presenter, :sortable]
65
127
 
66
128
  on_row_clicked do |t, row|
67
129
  puts "Row Clicked: #{row}"
@@ -77,54 +139,48 @@ class BasicTableSelection
77
139
  puts "Selection Changed: #{selection.inspect}"
78
140
  puts "Added Selection: #{added_selection.inspect}"
79
141
  puts "Removed Selection: #{removed_selection.inspect}"
80
- @one_table_selection_radio_buttons.selected = selection
81
142
  end
82
143
  }
83
144
  }
84
145
  }
85
-
146
+
86
147
  tab_item('Zero-Or-One') {
87
148
  vertical_box {
88
149
  vertical_box {
89
150
  stretchy false
90
151
 
91
152
  @zero_or_one_table_selection_radio_buttons = radio_buttons {
92
- items @zero_or_one_table_data.size.times.map { |row| "Row #{row} Selection" }
93
-
94
- on_selected do |rb|
95
- @zero_or_one_table.selection = [rb.selected]
96
- end
153
+ items @zero_or_one_table_presenter.selection_items
154
+ selected <=> [@zero_or_one_table_presenter, :selection]
97
155
  }
98
156
  }
99
157
 
100
- button('Toggle Table Header Visibility') {
158
+ horizontal_box {
101
159
  stretchy false
102
160
 
103
- on_clicked do
104
- @zero_or_one_table.header_visible = !@zero_or_one_table.header_visible
105
- end
106
- }
107
-
108
- @zero_or_one_table = table {
109
- text_column('Animal') {
110
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
111
-
112
- on_clicked do |tc, column|
113
- sort_zero_or_one_table_column(tc, column)
161
+ button('Toggle Table Header Visibility') {
162
+ on_clicked do
163
+ @zero_or_one_table_presenter.toggle_header_visible
114
164
  end
115
165
  }
116
- text_column('Description') {
117
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
118
-
119
- on_clicked do |tc, column|
120
- sort_zero_or_one_table_column(tc, column)
166
+
167
+ button('Toggle Table Sortability') {
168
+ on_clicked do
169
+ @zero_or_one_table_presenter.toggle_sortable # toggles sortable attribute to false or true
121
170
  end
122
171
  }
172
+ }
173
+
174
+ @zero_or_one_table = table {
175
+ @zero_or_one_table_presenter.column_presenters.each do |column_presenter|
176
+ text_column(column_presenter.name)
177
+ end
123
178
 
124
- cell_rows @zero_or_one_table_data
125
- selection_mode :zero_or_one # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified)
126
- # selection 0 # initial selection row index (could be nil too or just left off)
127
- # header_visible true # default
179
+ cell_rows @zero_or_one_table_presenter.data
180
+ selection_mode <= [@zero_or_one_table_presenter, :selection_mode]
181
+ selection <=> [@zero_or_one_table_presenter, :selection]
182
+ header_visible <= [@zero_or_one_table_presenter, :header_visible]
183
+ sortable <= [@zero_or_one_table_presenter, :sortable]
128
184
 
129
185
  on_row_clicked do |t, row|
130
186
  puts "Row Clicked: #{row}"
@@ -140,7 +196,6 @@ class BasicTableSelection
140
196
  puts "Selection Changed: #{selection.inspect}"
141
197
  puts "Added Selection: #{added_selection.inspect}"
142
198
  puts "Removed Selection: #{removed_selection.inspect}"
143
- @zero_or_one_table_selection_radio_buttons.selected = selection
144
199
  end
145
200
  }
146
201
  }
@@ -151,49 +206,46 @@ class BasicTableSelection
151
206
  vertical_box {
152
207
  stretchy false
153
208
 
154
- @zero_or_many_table_selection_checkboxes = @zero_or_many_table_data.size.times.map do |row|
209
+ @zero_or_many_table_selection_checkboxes = @zero_or_many_table_presenter.data.size.times.map do |row|
155
210
  checkbox("Row #{row} Selection") {
156
- on_toggled do |c|
157
- table_selection = @zero_or_many_table.selection.to_a
158
- if c.checked?
159
- table_selection << row unless table_selection.include?(row)
160
- else
161
- table_selection.delete(row) if table_selection.include?(row)
162
- end
163
- @zero_or_many_table.selection = table_selection
164
- end
211
+ checked <=> [@zero_or_many_table_presenter, :selection,
212
+ on_read: ->(selection_rows) {selection_rows.to_a.include?(row)},
213
+ on_write: ->(checked_value) {
214
+ checked_value ?
215
+ (@zero_or_many_table_presenter.selection.to_a + [row]).uniq :
216
+ @zero_or_many_table_presenter.selection.to_a.reject {|v| v == row }
217
+ },
218
+ ]
165
219
  }
166
220
  end
167
221
  }
168
222
 
169
- button('Toggle Table Header Visibility') {
223
+ horizontal_box {
170
224
  stretchy false
171
225
 
172
- on_clicked do
173
- @zero_or_many_table.header_visible = !@zero_or_many_table.header_visible
174
- end
175
- }
176
-
177
- @zero_or_many_table = table {
178
- text_column('Animal') {
179
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
180
-
181
- on_clicked do |tc, column|
182
- sort_zero_or_many_table_column(tc, column)
226
+ button('Toggle Table Header Visibility') {
227
+ on_clicked do
228
+ @zero_or_many_table_presenter.toggle_header_visible
183
229
  end
184
230
  }
185
- text_column('Description') {
186
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
187
-
188
- on_clicked do |tc, column|
189
- sort_zero_or_many_table_column(tc, column)
231
+
232
+ button('Toggle Table Sortability') {
233
+ on_clicked do
234
+ @zero_or_many_table_presenter.toggle_sortable # toggles sortable attribute to false or true
190
235
  end
191
236
  }
237
+ }
238
+
239
+ @zero_or_many_table = table {
240
+ @zero_or_many_table_presenter.column_presenters.each do |column_presenter|
241
+ text_column(column_presenter.name)
242
+ end
192
243
 
193
- cell_rows @zero_or_many_table_data
194
- selection_mode :zero_or_many # other values are :none , :zero_or_one , and :one (default is :zero_or_one if not specified)
195
- selection 0, 2, 4 # initial selection row indexes (could be empty array too or just left off)
196
- # header_visible true # default
244
+ cell_rows @zero_or_many_table_presenter.data
245
+ selection_mode <= [@zero_or_many_table_presenter, :selection_mode]
246
+ selection <=> [@zero_or_many_table_presenter, :selection]
247
+ header_visible <= [@zero_or_many_table_presenter, :header_visible]
248
+ sortable <= [@zero_or_many_table_presenter, :sortable]
197
249
 
198
250
  on_row_clicked do |t, row|
199
251
  puts "Row Clicked: #{row}"
@@ -209,46 +261,39 @@ class BasicTableSelection
209
261
  puts "Selection Changed: #{selection.inspect}"
210
262
  puts "Added Selection: #{added_selection.inspect}"
211
263
  puts "Removed Selection: #{removed_selection.inspect}"
212
- removed_selection&.each do |selected_row|
213
- @zero_or_many_table_selection_checkboxes[selected_row].checked = false
214
- end
215
- added_selection&.each do |selected_row|
216
- @zero_or_many_table_selection_checkboxes[selected_row].checked = true
217
- end
218
264
  end
219
265
  }
220
266
  }
221
267
  }
222
-
268
+
223
269
  tab_item('None') {
224
270
  vertical_box {
225
- button('Toggle Table Header Visibility') {
271
+ horizontal_box {
226
272
  stretchy false
227
273
 
228
- on_clicked do
229
- @none_table.header_visible = !@none_table.header_visible
230
- end
231
- }
232
-
233
- @none_table = table {
234
- text_column('Animal') {
235
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
236
-
237
- on_clicked do |tc, column|
238
- sort_none_table_column(tc, column)
274
+ button('Toggle Table Header Visibility') {
275
+ on_clicked do
276
+ @none_table_presenter.toggle_header_visible
239
277
  end
240
278
  }
241
- text_column('Description') {
242
- # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
243
-
244
- on_clicked do |tc, column|
245
- sort_none_table_column(tc, column)
279
+
280
+ button('Toggle Table Sortability') {
281
+ on_clicked do
282
+ @none_table_presenter.toggle_sortable # toggles sortable attribute to false or true
246
283
  end
247
284
  }
285
+ }
286
+
287
+ @none_table = table {
288
+ @none_table_presenter.column_presenters.each do |column_presenter|
289
+ text_column(column_presenter.name)
290
+ end
248
291
 
249
- cell_rows @none_table_data
250
- selection_mode :none # other values are :zero_or_many , :zero_or_one, :one (default is :zero_or_one if not specified)
251
- # header_visible true # default
292
+ cell_rows @none_table_presenter.data
293
+ selection_mode <= [@none_table_presenter, :selection_mode]
294
+ selection <=> [@none_table_presenter, :selection]
295
+ header_visible <= [@none_table_presenter, :header_visible]
296
+ sortable <= [@none_table_presenter, :sortable]
252
297
 
253
298
  on_row_clicked do |t, row|
254
299
  puts "Row Clicked: #{row}"
@@ -264,40 +309,6 @@ class BasicTableSelection
264
309
  }
265
310
  }
266
311
  }
267
-
268
- def sort_one_table_column(tc, column)
269
- puts "Clicked column #{column}: #{tc.name}"
270
- selected_row = @one_table.selection && @one_table_data[@one_table.selection]
271
- tc.toggle_sort_indicator
272
- @one_table_data.sort_by! { |row_data| row_data[column] }
273
- @one_table_data.reverse! if tc.sort_indicator == :descending
274
- @one_table.selection = @one_table_data.index(selected_row)
275
- end
276
-
277
- def sort_zero_or_one_table_column(tc, column)
278
- puts "Clicked column #{column}: #{tc.name}"
279
- selected_row = @zero_or_one_table.selection && @zero_or_one_table_data[@zero_or_one_table.selection]
280
- tc.toggle_sort_indicator
281
- @zero_or_one_table_data.sort_by! { |row_data| row_data[column] }
282
- @zero_or_one_table_data.reverse! if tc.sort_indicator == :descending
283
- @zero_or_one_table.selection = @zero_or_one_table_data.index(selected_row)
284
- end
285
-
286
- def sort_zero_or_many_table_column(tc, column)
287
- puts "Clicked column #{column}: #{tc.name}"
288
- selected_rows = @zero_or_many_table.selection&.map { |row| @zero_or_many_table_data[row] }
289
- tc.toggle_sort_indicator
290
- @zero_or_many_table_data.sort_by! { |row_data| row_data[column] }
291
- @zero_or_many_table_data.reverse! if tc.sort_indicator == :descending
292
- @zero_or_many_table.selection = selected_rows&.map {|row_data| @zero_or_many_table_data.index(row_data) }
293
- end
294
-
295
- def sort_none_table_column(tc, column)
296
- puts "Clicked column #{column}: #{tc.name}"
297
- tc.toggle_sort_indicator
298
- @none_table_data.sort_by! { |row_data| row_data[column] }
299
- @none_table_data.reverse! if tc.sort_indicator == :descending
300
- end
301
312
  end
302
313
 
303
314
  BasicTableSelection.launch
@@ -126,6 +126,7 @@ class BasicTableSelection
126
126
  selection_mode <= [@one_table_presenter, :selection_mode]
127
127
  selection <=> [@one_table_presenter, :selection]
128
128
  header_visible <= [@one_table_presenter, :header_visible]
129
+ sortable false # disable default sorting behavior to demonstrate manual sorting
129
130
 
130
131
  on_row_clicked do |t, row|
131
132
  puts "Row Clicked: #{row}"
@@ -181,6 +182,7 @@ class BasicTableSelection
181
182
  selection_mode <= [@zero_or_one_table_presenter, :selection_mode]
182
183
  selection <=> [@zero_or_one_table_presenter, :selection]
183
184
  header_visible <= [@zero_or_one_table_presenter, :header_visible]
185
+ sortable false # disable default sorting behavior to demonstrate manual sorting
184
186
 
185
187
  on_row_clicked do |t, row|
186
188
  puts "Row Clicked: #{row}"
@@ -208,15 +210,14 @@ class BasicTableSelection
208
210
 
209
211
  @zero_or_many_table_selection_checkboxes = @zero_or_many_table_presenter.data.size.times.map do |row|
210
212
  checkbox("Row #{row} Selection") {
211
- on_toggled do |c|
212
- table_selection = @zero_or_many_table_presenter.selection.to_a
213
- if c.checked?
214
- table_selection << row unless table_selection.include?(row)
215
- else
216
- table_selection.delete(row) if table_selection.include?(row)
217
- end
218
- @zero_or_many_table_presenter.selection = table_selection
219
- end
213
+ checked <=> [@zero_or_many_table_presenter, :selection,
214
+ on_read: ->(selection_rows) {selection_rows.to_a.include?(row)},
215
+ on_write: ->(checked_value) {
216
+ checked_value ?
217
+ (@zero_or_many_table_presenter.selection.to_a + [row]).uniq :
218
+ @zero_or_many_table_presenter.selection.to_a.reject {|v| v == row }
219
+ },
220
+ ]
220
221
  }
221
222
  end
222
223
  }
@@ -245,6 +246,7 @@ class BasicTableSelection
245
246
  selection_mode <= [@zero_or_many_table_presenter, :selection_mode]
246
247
  selection <=> [@zero_or_many_table_presenter, :selection]
247
248
  header_visible <= [@zero_or_many_table_presenter, :header_visible]
249
+ sortable false # disable default sorting behavior to demonstrate manual sorting
248
250
 
249
251
  on_row_clicked do |t, row|
250
252
  puts "Row Clicked: #{row}"
@@ -291,6 +293,7 @@ class BasicTableSelection
291
293
  selection_mode <= [@none_table_presenter, :selection_mode]
292
294
  selection <=> [@none_table_presenter, :selection]
293
295
  header_visible <= [@none_table_presenter, :header_visible]
296
+ sortable false # disable default sorting behavior to demonstrate manual sorting
294
297
 
295
298
  on_row_clicked do |t, row|
296
299
  puts "Row Clicked: #{row}"
@@ -0,0 +1,307 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class BasicTableSelection
4
+ include Glimmer::LibUI::Application
5
+
6
+ before_body do
7
+ data = [
8
+ %w[cat meow],
9
+ %w[dog woof],
10
+ %w[chicken cock-a-doodle-doo],
11
+ %w[horse neigh],
12
+ %w[cow moo]
13
+ ]
14
+ @one_table_data = data.dup
15
+ @zero_or_one_table_data = data.dup
16
+ @zero_or_many_table_data = data.dup
17
+ @none_table_data = data.dup
18
+ end
19
+
20
+ body {
21
+ window('Basic Table Selection', 400, 300) {
22
+ tab {
23
+ tab_item('One') {
24
+ vertical_box {
25
+ vertical_box {
26
+ stretchy false
27
+
28
+ @one_table_selection_radio_buttons = radio_buttons {
29
+ items @one_table_data.size.times.map { |row| "Row #{row} Selection" }
30
+
31
+ on_selected do |rb|
32
+ @one_table.selection = [rb.selected]
33
+ end
34
+ }
35
+ }
36
+
37
+ button('Toggle Table Header Visibility') {
38
+ stretchy false
39
+
40
+ on_clicked do
41
+ @one_table.header_visible = !@one_table.header_visible
42
+ end
43
+ }
44
+
45
+ @one_table = table {
46
+ text_column('Animal') {
47
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
48
+
49
+ on_clicked do |tc, column|
50
+ sort_one_table_column(tc, column)
51
+ end
52
+ }
53
+ text_column('Description') {
54
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
55
+
56
+ on_clicked do |tc, column|
57
+ sort_one_table_column(tc, column)
58
+ end
59
+ }
60
+
61
+ cell_rows @one_table_data
62
+ selection_mode :one # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified)
63
+ selection 2 # initial selection row index (could be nil too or just left off, defaulting to 0)
64
+ # header_visible true # default
65
+ sortable false # disable default sorting behavior to demonstrate manual sorting
66
+
67
+ on_row_clicked do |t, row|
68
+ puts "Row Clicked: #{row}"
69
+ end
70
+
71
+ on_row_double_clicked do |t, row|
72
+ puts "Row Double Clicked: #{row}"
73
+ end
74
+
75
+ on_selection_changed do |t, selection, added_selection, removed_selection|
76
+ # selection is an array or nil if selection mode is zero_or_many
77
+ # otherwise, selection is a single index integer or nil when not selected
78
+ puts "Selection Changed: #{selection.inspect}"
79
+ puts "Added Selection: #{added_selection.inspect}"
80
+ puts "Removed Selection: #{removed_selection.inspect}"
81
+ @one_table_selection_radio_buttons.selected = selection
82
+ end
83
+ }
84
+ }
85
+ }
86
+
87
+ tab_item('Zero-Or-One') {
88
+ vertical_box {
89
+ vertical_box {
90
+ stretchy false
91
+
92
+ @zero_or_one_table_selection_radio_buttons = radio_buttons {
93
+ items @zero_or_one_table_data.size.times.map { |row| "Row #{row} Selection" }
94
+
95
+ on_selected do |rb|
96
+ @zero_or_one_table.selection = [rb.selected]
97
+ end
98
+ }
99
+ }
100
+
101
+ button('Toggle Table Header Visibility') {
102
+ stretchy false
103
+
104
+ on_clicked do
105
+ @zero_or_one_table.header_visible = !@zero_or_one_table.header_visible
106
+ end
107
+ }
108
+
109
+ @zero_or_one_table = table {
110
+ text_column('Animal') {
111
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
112
+
113
+ on_clicked do |tc, column|
114
+ sort_zero_or_one_table_column(tc, column)
115
+ end
116
+ }
117
+ text_column('Description') {
118
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
119
+
120
+ on_clicked do |tc, column|
121
+ sort_zero_or_one_table_column(tc, column)
122
+ end
123
+ }
124
+
125
+ cell_rows @zero_or_one_table_data
126
+ selection_mode :zero_or_one # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified)
127
+ # selection 0 # initial selection row index (could be nil too or just left off)
128
+ # header_visible true # default
129
+ sortable false # disable default sorting behavior to demonstrate manual sorting
130
+
131
+ on_row_clicked do |t, row|
132
+ puts "Row Clicked: #{row}"
133
+ end
134
+
135
+ on_row_double_clicked do |t, row|
136
+ puts "Row Double Clicked: #{row}"
137
+ end
138
+
139
+ on_selection_changed do |t, selection, added_selection, removed_selection|
140
+ # selection is an array or nil if selection mode is zero_or_many
141
+ # otherwise, selection is a single index integer or nil when not selected
142
+ puts "Selection Changed: #{selection.inspect}"
143
+ puts "Added Selection: #{added_selection.inspect}"
144
+ puts "Removed Selection: #{removed_selection.inspect}"
145
+ @zero_or_one_table_selection_radio_buttons.selected = selection
146
+ end
147
+ }
148
+ }
149
+ }
150
+
151
+ tab_item('Zero-Or-Many') {
152
+ vertical_box {
153
+ vertical_box {
154
+ stretchy false
155
+
156
+ @zero_or_many_table_selection_checkboxes = @zero_or_many_table_data.size.times.map do |row|
157
+ checkbox("Row #{row} Selection") {
158
+ on_toggled do |c|
159
+ table_selection = @zero_or_many_table.selection.to_a
160
+ if c.checked?
161
+ table_selection << row unless table_selection.include?(row)
162
+ else
163
+ table_selection.delete(row) if table_selection.include?(row)
164
+ end
165
+ @zero_or_many_table.selection = table_selection
166
+ end
167
+ }
168
+ end
169
+ }
170
+
171
+ button('Toggle Table Header Visibility') {
172
+ stretchy false
173
+
174
+ on_clicked do
175
+ @zero_or_many_table.header_visible = !@zero_or_many_table.header_visible
176
+ end
177
+ }
178
+
179
+ @zero_or_many_table = table {
180
+ text_column('Animal') {
181
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
182
+
183
+ on_clicked do |tc, column|
184
+ sort_zero_or_many_table_column(tc, column)
185
+ end
186
+ }
187
+ text_column('Description') {
188
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
189
+
190
+ on_clicked do |tc, column|
191
+ sort_zero_or_many_table_column(tc, column)
192
+ end
193
+ }
194
+
195
+ cell_rows @zero_or_many_table_data
196
+ selection_mode :zero_or_many # other values are :none , :zero_or_one , and :one (default is :zero_or_one if not specified)
197
+ selection 0, 2, 4 # initial selection row indexes (could be empty array too or just left off)
198
+ # header_visible true # default
199
+ sortable false # disable default sorting behavior to demonstrate manual sorting
200
+
201
+ on_row_clicked do |t, row|
202
+ puts "Row Clicked: #{row}"
203
+ end
204
+
205
+ on_row_double_clicked do |t, row|
206
+ puts "Row Double Clicked: #{row}"
207
+ end
208
+
209
+ on_selection_changed do |t, selection, added_selection, removed_selection|
210
+ # selection is an array or nil if selection mode is zero_or_many
211
+ # otherwise, selection is a single index integer or nil when not selected
212
+ puts "Selection Changed: #{selection.inspect}"
213
+ puts "Added Selection: #{added_selection.inspect}"
214
+ puts "Removed Selection: #{removed_selection.inspect}"
215
+ removed_selection&.each do |selected_row|
216
+ @zero_or_many_table_selection_checkboxes[selected_row].checked = false
217
+ end
218
+ added_selection&.each do |selected_row|
219
+ @zero_or_many_table_selection_checkboxes[selected_row].checked = true
220
+ end
221
+ end
222
+ }
223
+ }
224
+ }
225
+
226
+ tab_item('None') {
227
+ vertical_box {
228
+ button('Toggle Table Header Visibility') {
229
+ stretchy false
230
+
231
+ on_clicked do
232
+ @none_table.header_visible = !@none_table.header_visible
233
+ end
234
+ }
235
+
236
+ @none_table = table {
237
+ text_column('Animal') {
238
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
239
+
240
+ on_clicked do |tc, column|
241
+ sort_none_table_column(tc, column)
242
+ end
243
+ }
244
+ text_column('Description') {
245
+ # sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default)
246
+
247
+ on_clicked do |tc, column|
248
+ sort_none_table_column(tc, column)
249
+ end
250
+ }
251
+
252
+ cell_rows @none_table_data
253
+ selection_mode :none # other values are :zero_or_many , :zero_or_one, :one (default is :zero_or_one if not specified)
254
+ # header_visible true # default
255
+ sortable false # disable default sorting behavior to demonstrate manual sorting
256
+
257
+ on_row_clicked do |t, row|
258
+ puts "Row Clicked: #{row}"
259
+ end
260
+
261
+ on_row_double_clicked do |t, row|
262
+ puts "Row Double Clicked: #{row}"
263
+ end
264
+ }
265
+ }
266
+ }
267
+
268
+ }
269
+ }
270
+ }
271
+
272
+ def sort_one_table_column(tc, column)
273
+ puts "Clicked column #{column}: #{tc.name}"
274
+ selected_row = @one_table.selection && @one_table_data[@one_table.selection]
275
+ tc.toggle_sort_indicator
276
+ @one_table_data.sort_by! { |row_data| row_data[column] }
277
+ @one_table_data.reverse! if tc.sort_indicator == :descending
278
+ @one_table.selection = @one_table_data.index(selected_row)
279
+ end
280
+
281
+ def sort_zero_or_one_table_column(tc, column)
282
+ puts "Clicked column #{column}: #{tc.name}"
283
+ selected_row = @zero_or_one_table.selection && @zero_or_one_table_data[@zero_or_one_table.selection]
284
+ tc.toggle_sort_indicator
285
+ @zero_or_one_table_data.sort_by! { |row_data| row_data[column] }
286
+ @zero_or_one_table_data.reverse! if tc.sort_indicator == :descending
287
+ @zero_or_one_table.selection = @zero_or_one_table_data.index(selected_row)
288
+ end
289
+
290
+ def sort_zero_or_many_table_column(tc, column)
291
+ puts "Clicked column #{column}: #{tc.name}"
292
+ selected_rows = @zero_or_many_table.selection&.map { |row| @zero_or_many_table_data[row] }
293
+ tc.toggle_sort_indicator
294
+ @zero_or_many_table_data.sort_by! { |row_data| row_data[column] }
295
+ @zero_or_many_table_data.reverse! if tc.sort_indicator == :descending
296
+ @zero_or_many_table.selection = selected_rows&.map {|row_data| @zero_or_many_table_data.index(row_data) }
297
+ end
298
+
299
+ def sort_none_table_column(tc, column)
300
+ puts "Clicked column #{column}: #{tc.name}"
301
+ tc.toggle_sort_indicator
302
+ @none_table_data.sort_by! { |row_data| row_data[column] }
303
+ @none_table_data.reverse! if tc.sort_indicator == :descending
304
+ end
305
+ end
306
+
307
+ BasicTableSelection.launch
Binary file
@@ -101,6 +101,12 @@ module Glimmer
101
101
 
102
102
  def handle_listener(listener_name, &listener)
103
103
  column_listeners_for(listener_name) << listener
104
+ # TODO fix this by adding a `on_button_clicked` listener in the future to separate it from `on_clicked` on the column header
105
+ begin
106
+ super # attempt to handle listener natively if this column supports it (button_column)
107
+ rescue => e
108
+ # No Op
109
+ end
104
110
  end
105
111
 
106
112
  def column_listeners
@@ -37,6 +37,28 @@ module Glimmer
37
37
  include Glimmer::FiddleConsumer
38
38
 
39
39
  CUSTOM_LISTENER_NAMES = ['on_changed', 'on_edited']
40
+ DEFAULT_COLUMN_SORT_BLOCK = lambda do |table_cell_row, column, table_proxy|
41
+ if table_cell_row.is_a?(Array)
42
+ value = table_cell_row[column]
43
+ else
44
+ attribute = table_proxy.column_attributes[column]
45
+ value = table_cell_row.send(attribute)
46
+ end
47
+ if value.is_a?(Array)
48
+ # This is needed to not crash on sorting an unsortable array
49
+ value = value.map do |element|
50
+ case element
51
+ when true
52
+ 1
53
+ when false
54
+ 0
55
+ else
56
+ element
57
+ end
58
+ end
59
+ end
60
+ value
61
+ end
40
62
 
41
63
  attr_reader :model_handler, :model, :table_params, :columns
42
64
 
@@ -66,6 +88,7 @@ module Glimmer
66
88
  configure_selection
67
89
  configure_header_visible
68
90
  configure_column_sort_indicators
91
+ configure_sorting
69
92
  end
70
93
 
71
94
  def post_initialize_child(child)
@@ -183,6 +206,7 @@ module Glimmer
183
206
  result = ::LibUI.table_header_visible(@libui)
184
207
  LibUI.integer_to_boolean(result)
185
208
  end
209
+ alias header_visible? header_visible
186
210
 
187
211
  def header_visible=(value)
188
212
  @header_visible = value
@@ -194,6 +218,17 @@ module Glimmer
194
218
  end
195
219
  alias set_header_visible header_visible=
196
220
 
221
+ def sortable
222
+ @sortable = true if @sortable.nil?
223
+ @sortable
224
+ end
225
+ alias sortable? sortable
226
+
227
+ def sortable=(value)
228
+ @sortable = value
229
+ end
230
+ alias set_sortable sortable=
231
+
197
232
  def column_attributes
198
233
  @column_attributes ||= columns.select {|column| column.is_a?(Column)}.map(&:name).map(&:underscore)
199
234
  end
@@ -625,16 +660,14 @@ module Glimmer
625
660
  def register_column_listeners
626
661
  # register accumulated column listeners after table content is closed
627
662
  return if @columns.nil? || @columns.empty?
628
- if @columns.any? {|column| column.is_a?(Column) && !column.column_listeners_for('on_clicked').empty? }
663
+ if @columns.any? {|column| column.is_a?(Column)}
629
664
  ::LibUI.table_header_on_clicked(@libui) do |_, column_index|
630
665
  actual_columns = @columns.select {|column| column.is_a?(Column)}
631
666
  column = actual_columns[column_index]
632
- if column.is_a?(Column)
667
+ if column.is_a?(Column) && !column.is_a?(Column::ButtonColumnProxy)
633
668
  column_listeners = column.column_listeners_for('on_clicked')
634
- if !column_listeners.empty?
635
- column_listeners.each do |column_listener|
636
- column_listener.call(column, column_index)
637
- end
669
+ column_listeners.each do |column_listener|
670
+ column_listener.call(column, column_index)
638
671
  end
639
672
  end
640
673
  end
@@ -654,8 +687,47 @@ module Glimmer
654
687
  end
655
688
 
656
689
  def configure_column_sort_indicators
657
- column_proxies.each {|c| c.configure_sort_indicator }
690
+ column_proxies.each(&:configure_sort_indicator)
691
+ end
692
+
693
+ def configure_sorting
694
+ if sortable?
695
+ columns.each do |column_object|
696
+ next unless column_object.is_a?(Column) && !column_object.is_a?(Column::ButtonColumnProxy)
697
+ column_object.on_clicked do |column_proxy, column|
698
+ sort_by_column(column_proxy, column)
699
+ end
700
+ end
701
+ end
702
+ end
703
+
704
+ def sort_by_column(column_proxy, column)
705
+ return unless sortable? && cell_rows.is_a?(Array)
706
+ old_selection = backup_selection
707
+ column_proxy.toggle_sort_indicator
708
+ cell_rows.sort_by! {|table_cell_row| DEFAULT_COLUMN_SORT_BLOCK.call(table_cell_row, column, self) }
709
+ cell_rows.reverse! if column_proxy.sort_indicator == :descending
710
+ restore_selection(old_selection)
711
+ end
712
+
713
+ def backup_selection
714
+ if selection_mode == ::LibUI::TableSelectionModeZeroOrMany
715
+ selected_rows = selection&.map { |row| cell_rows[row] }
716
+ else
717
+ selected_row = selection && cell_rows[selection]
718
+ end
658
719
  end
720
+
721
+ def restore_selection(old_selection)
722
+ if selection_mode == ::LibUI::TableSelectionModeZeroOrMany
723
+ selected_rows = old_selection
724
+ self.selection = selected_rows&.map {|row_data| cell_rows.index(row_data) }
725
+ else
726
+ selected_row = old_selection
727
+ self.selection = cell_rows.index(selected_row)
728
+ end
729
+ end
730
+
659
731
  end
660
732
  end
661
733
  end
@@ -126,6 +126,7 @@ module Glimmer
126
126
  end
127
127
 
128
128
  editable table_editable
129
+ sortable false # TODO disabled for now until we support it correctly in the future
129
130
  cell_rows <=> [self, :refined_model_array]
130
131
  }
131
132
  }
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.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-19 00:00:00.000000000 Z
11
+ date: 2023-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -358,6 +358,7 @@ files:
358
358
  - examples/basic_table_progress_bar.rb
359
359
  - examples/basic_table_selection.rb
360
360
  - examples/basic_table_selection2.rb
361
+ - examples/basic_table_selection3.rb
361
362
  - examples/basic_transform.rb
362
363
  - examples/basic_transform2.rb
363
364
  - examples/basic_window.rb