mittens_ui 0.0.14 → 0.0.15

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: 2479146dca0d7b1cacd610a6c50d8d04df41c8678db14f1b97950ab62b377a63
4
- data.tar.gz: 3d86bd7ce738e1a530bd983d26a17a6fe4f2255dc3654d4f203624ceaa634138
3
+ metadata.gz: 65ad0d43134bc7e47bcb8d797b70f275a6330426ba073f2f0861fa56a1f8f551
4
+ data.tar.gz: c613e0ddc59df4efdc64a0ee5b4af846c2549e30c562352c52864bfcdc62abe2
5
5
  SHA512:
6
- metadata.gz: 5e780dfaf16f6657fb955e664470c2ba816290b4d2a05d0f002cb8b810008d6dc7fc92c5cd0115bd10176ca83fafff55f9833de65c8006f37e8f479357137021
7
- data.tar.gz: accbcf0f8c5f3114fe5a974a268c4c479bc067eb2aae596450e0e7fa074090c5f81b8a306fbd2028f6a039109781adbe9c784076b2564bb16a04f851024d7cd0
6
+ metadata.gz: 98a84d6b246ec8112a724d9c7cb1ec6b3f147e369aafa8cb8290cdc40b1359ce9bd656f6f8e83555ce5acb99387983eb7a4b6af8beb27388b7b4834c35a29d18
7
+ data.tar.gz: ebda9e48f8553c48b9548d73a7778d0211a07d605e0758f107bdcca33e1877dadd07ff5132db3ca646e0070916675cb4685b150205e7cdce01305ec3a5feeb28
data/README.md CHANGED
@@ -243,27 +243,14 @@ img = MittensUi::Image.new("./assets/animation.gif")
243
243
  ### TableView
244
244
  ```ruby
245
245
  table = MittensUi::TableView.new(
246
- headers: ["Name", "Email"],
247
- data: [
248
- ["John", "john@example.com"],
249
- ["Jane", "jane@example.com"]
250
- ],
246
+ ['Name', 'Email'],
247
+ [['John', 'john@example.com']]
251
248
  )
252
249
 
253
- table.add(["Bob", "bob@example.com"])
254
- table.add(["First", "first@example.com"], :prepend)
250
+ table.add(['Jane', 'jane@example.com'])
255
251
 
256
- table.remove_selected
257
-
258
- puts table.row_count
259
-
260
- puts table.selected_row.inspect
261
-
262
- # Single click on the row
263
252
  table.row_clicked { |row| puts row.inspect }
264
-
265
- # Double click on the row
266
- table.row_double_clicked { |row| puts row.inspect }
253
+ table.row_double_clicked { |row| puts "Double: #{row.inspect}" }
267
254
  ```
268
255
 
269
256
  ### Alert
data/examples/contacts.rb CHANGED
@@ -2,6 +2,7 @@ require '../lib/mittens_ui'
2
2
 
3
3
  app_options = { name: 'contacts', title: 'Contacts', height: 680, width: 600, can_resize: true }.freeze
4
4
 
5
+
5
6
  MittensUi::Application.Window(app_options) do
6
7
  puts MittensUi::Application.store.get(:last_selected_contact)
7
8
 
@@ -25,63 +26,39 @@ MittensUi::Application.Window(app_options) do
25
26
  # --- Contacts Table ---
26
27
  MittensUi::Separator.new(:horizontal, top: 2, bottom: 2)
27
28
 
28
- contacts_table = MittensUi::TableView.new(
29
- ['Name', 'Email', 'Phone'],
30
- [
31
- ['John Appleseed', 'john@example.com', '555-1234'],
32
- ['Jane Doe', 'jane@example.com', '555-5678'],
33
- ['Michael Smith', 'michael.smith@email.com', '555-1001'],
34
- ['Emily Johnson', 'emily.j@email.com', '555-1002'],
35
- ['Chris Evans', 'cevans@email.com', '555-1003'],
36
- ['Olivia Brown', 'olivia.b@email.com', '555-1004'],
37
- ['Daniel Wilson', 'dan.w@email.com', '555-1005'],
38
- ['Sophia Martinez', 'sophia.m@email.com', '555-1006'],
39
- ['James Anderson', 'j.anderson@email.com', '555-1007'],
40
- ['Isabella Thomas', 'isabella.t@email.com', '555-1008'],
41
- ['Benjamin Taylor', 'ben.taylor@email.com', '555-1009'],
42
- ['Mia Moore', 'mia.moore@email.com', '555-1010'],
43
- ['Lucas Jackson', 'lucas.j@email.com', '555-1011'],
44
- ['Charlotte White', 'charlotte.w@email.com', '555-1012'],
45
- ['Henry Harris', 'henry.h@email.com', '555-1013'],
46
- ['Amelia Martin', 'amelia.m@email.com', '555-1014'],
47
- ['Alexander Thompson', 'alex.t@email.com', '555-1015'],
48
- ['Evelyn Garcia', 'evelyn.g@email.com', '555-1016'],
49
- ['William Martinez', 'will.m@email.com', '555-1017'],
50
- ['Harper Robinson', 'harper.r@email.com', '555-1018'],
51
- ['Daniel Clark', 'dan.clark@email.com', '555-1019'],
52
- ['Abigail Rodriguez', 'abigail.r@email.com', '555-1020'],
53
- ['Matthew Lewis', 'matt.lewis@email.com', '555-1021'],
54
- ['Ella Lee', 'ella.lee@email.com', '555-1022'],
55
- ['David Walker', 'd.walker@email.com', '555-1023'],
56
- ['Scarlett Hall', 'scarlett.h@email.com', '555-1024'],
57
- ['Joseph Allen', 'j.allen@email.com', '555-1025'],
58
- ['Grace Young', 'grace.y@email.com', '555-1026'],
59
- ['Samuel King', 'sam.king@email.com', '555-1027'],
60
- ['Chloe Wright', 'chloe.w@email.com', '555-1028'],
61
- ['Andrew Scott', 'andrew.s@email.com', '555-1029'],
62
- ['Victoria Green', 'victoria.g@email.com', '555-1030'],
63
- ['Joshua Adams', 'josh.adams@email.com', '555-1031'],
64
- ['Lily Baker', 'lily.b@email.com', '555-1032'],
65
- ['Ryan Nelson', 'ryan.n@email.com', '555-1033'],
66
- ['Zoey Carter', 'zoey.c@email.com', '555-1034'],
67
- ['Nathan Mitchell', 'nathan.m@email.com', '555-1035'],
68
- ['Hannah Perez', 'hannah.p@email.com', '555-1036'],
69
- ['Aaron Roberts', 'aaron.r@email.com', '555-1037'],
70
- ['Sofia Turner', 'sofia.t@email.com', '555-1038'],
71
- ['Caleb Phillips', 'caleb.p@email.com', '555-1039'],
72
- ['Avery Campbell', 'avery.c@email.com', '555-1040'],
73
- ['Ethan Parker', 'ethan.p@email.com', '555-1041'],
74
- ['Madison Evans', 'madison.e@email.com', '555-1042'],
75
- ['Logan Edwards', 'logan.e@email.com', '555-1043'],
76
- ['Layla Collins', 'layla.c@email.com', '555-1044'],
77
- ['Noah Stewart', 'noah.s@email.com', '555-1045'],
78
- ['Riley Sanchez', 'riley.s@email.com', '555-1046'],
79
- ['Jack Morris', 'jack.m@email.com', '555-1047'],
80
- ['Aria Rogers', 'aria.r@email.com', '555-1048'],
81
- ['Sebastian Reed', 'sebastian.r@email.com', '555-1049'],
82
- ['Nora Cook', 'nora.c@email.com', '555-1050']
29
+ first_names = %w[
30
+ John Jane Michael Emily Chris Olivia Daniel Sophia James Isabella
31
+ Benjamin Mia Lucas Charlotte Henry Amelia Alexander Evelyn William Harper
32
+ Daniel Abigail Matthew Ella David Scarlett Joseph Grace Samuel Chloe
33
+ Andrew Victoria Joshua Lily Ryan Zoey Nathan Hannah Aaron Sofia
34
+ Caleb Avery Ethan Madison Logan Layla Noah Riley Jack Aria
35
+ Sebastian Nora Liam Ava Mason Ella Ethan Harper Logan Grace
36
+ ]
37
+
38
+ last_names = %w[
39
+ Smith Johnson Brown Taylor Anderson Thomas Jackson White Harris Martin
40
+ Thompson Garcia Martinez Robinson Clark Rodriguez Lewis Lee Walker Hall
41
+ Allen Young King Wright Scott Green Adams Baker Nelson Carter Mitchell
42
+ Perez Roberts Turner Phillips Campbell Parker Evans Edwards Collins Stewart
43
+ Sanchez Morris Rogers Reed Cook Morgan Bell Murphy Bailey Rivera Cooper
83
44
  ]
84
- )
45
+
46
+ contacts_data = 1000.times.map do |i|
47
+ first = first_names.sample
48
+ last = last_names.sample
49
+
50
+ name = "#{first} #{last}"
51
+ email = "#{first.downcase}.#{last.downcase}#{i}@example.com"
52
+ phone = "555-#{1000 + i}"
53
+
54
+ [name, email, phone]
55
+ end
56
+
57
+ contacts_table = MittensUi::TableView.new(
58
+ ['Name', 'Email', 'Phone'],
59
+ contacts_data
60
+ )
61
+
85
62
  contacts_table.row_double_clicked do |row|
86
63
  puts "Double clicked: #{row.inspect}"
87
64
  end
@@ -3,13 +3,14 @@
3
3
  require 'mittens_ui/core'
4
4
 
5
5
  module MittensUi
6
- # A simple, but custom table widget built on {Gtk::Grid}.
6
+ # A simple, custom table widget built on Gtk::Grid.
7
7
  #
8
- # Provides:
9
- # - Column headers with sorting (▲ ▼ indicators)
10
- # - Row selection
11
- # - Single-click and double-click callbacks
12
- # - Dynamic row insertion
8
+ # Features:
9
+ # - Sorting with ▲ ▼ indicators
10
+ # - Row selection (mouse + keyboard)
11
+ # - Single & double click callbacks
12
+ # - Pagination for large datasets (auto-enabled > 500 rows)
13
+ # - Built-in pagination UI (Prev / Next buttons + page indicator)
13
14
  # - Dark mode friendly styling
14
15
  #
15
16
  # @example Basic usage
@@ -21,23 +22,28 @@ module MittensUi
21
22
  # @example Add row
22
23
  # table.add(['Jane', 'jane@example.com'])
23
24
  #
24
- # @example Click handlers
25
- # table.row_clicked { |row| puts row.inspect }
26
- # table.row_double_clicked { |row| puts "Double: #{row.inspect}" }
25
+ # @example Pagination (automatic)
26
+ # # Pagination UI appears automatically when data > 500 rows
27
27
  #
28
28
  class TableView < Core
29
29
  attr_reader :data, :headers, :selected_row_idx
30
30
 
31
- # @param headers [Array<String>] column headers
32
- # @param data [Array<Array<String>>] initial rows
33
- # @param options [Hash] Core layout options
31
+ PAGE_THRESHOLD = 500
32
+ PAGE_SIZE = 100
33
+
34
34
  def initialize(headers = [], data = [], options = {})
35
35
  @headers = headers
36
36
  @data = data
37
+
37
38
  @row_widgets = []
38
39
  @header_labels = []
39
40
  @selected_row_idx = nil
40
41
  @sort_directions = {}
42
+ @current_page = 0
43
+
44
+ # ---------------------------
45
+ # GTK Structure
46
+ # ---------------------------
41
47
 
42
48
  @grid = Gtk::Grid.new
43
49
  @grid.set_column_spacing(0)
@@ -46,102 +52,147 @@ module MittensUi
46
52
  @scroller = Gtk::ScrolledWindow.new
47
53
  @scroller.set_policy(:automatic, :automatic)
48
54
  @scroller.set_child(@grid)
55
+ @scroller.set_vexpand(true)
56
+ @scroller.set_min_content_height(300)
57
+ @scroller.set_max_content_height(300)
58
+
59
+ # Pagination UI
60
+ @pagination_box = Gtk::Box.new(:horizontal, 10)
61
+ @pagination_box.set_margin_top(10)
62
+ @pagination_box.set_halign(:center)
49
63
 
50
- # Calculate height of table:
51
- row_height = 24
52
- header_height = 40
53
- max_height = 300
54
- desired_height = [(@data.size * row_height) + header_height, max_height].min
64
+ @prev_btn = Gtk::Button.new(label: '← Prev')
65
+ @next_btn = Gtk::Button.new(label: 'Next →')
66
+ @page_label = Gtk::Label.new('')
55
67
 
56
- # Set the scroller's min and max height
57
- @scroller.set_min_content_height(desired_height)
58
- @scroller.set_max_content_height(desired_height)
68
+ @prev_btn.signal_connect('clicked') { prev_page }
69
+ @next_btn.signal_connect('clicked') { next_page }
59
70
 
60
- super(@scroller, options)
71
+ @pagination_box.append(@prev_btn)
72
+ @pagination_box.append(@page_label)
73
+ @pagination_box.append(@next_btn)
61
74
 
62
- # Click handling
75
+ # Root container (important for Core)
76
+ @container = Gtk::Box.new(:vertical, 0)
77
+ @container.append(@scroller)
78
+ @container.append(@pagination_box)
79
+
80
+ super(@container, options)
81
+
82
+ # Events
63
83
  @on_row_clicked = nil
64
84
  @on_row_double_clicked = nil
65
85
  @last_click_time = nil
66
86
  @last_clicked_row = nil
67
87
 
68
88
  setup_css
89
+ setup_keyboard
90
+
69
91
  render_headers
70
92
  render_rows
93
+ update_pagination_ui
71
94
  end
72
95
 
73
96
  # ---------------------------
74
97
  # Public API
75
98
  # ---------------------------
76
99
 
77
- # Add a row to the table
78
- #
79
- # @param row [Array<String>] row data
80
- # @param direction [Symbol] :append or :prepend
81
- # @return [void]
82
100
  def add(row, direction = :append)
83
101
  return if row.nil? || row.empty?
84
102
 
85
- if direction == :prepend
86
- @data.unshift(row)
87
- else
88
- @data << row
89
- end
103
+ direction == :prepend ? @data.unshift(row) : @data << row
90
104
 
105
+ adjust_page_after_insert
91
106
  render_rows
107
+ update_pagination_ui
92
108
  end
93
109
 
94
- # Replace table data
95
- #
96
- # @param new_data [Array<Array<String>>]
97
110
  def update_data(new_data)
98
111
  @data = new_data
112
+ @current_page = 0
99
113
  render_rows
114
+ update_pagination_ui
100
115
  end
101
116
 
102
- # Check if a row is selected
103
- #
104
- # @param idx [Integer, nil]
105
- # @return [Boolean]
106
117
  def row_selected?(idx = nil)
107
118
  return !@selected_row_idx.nil? if idx.nil?
119
+
108
120
  @selected_row_idx == idx
109
121
  end
110
122
 
111
- # Get selected row data
112
- #
113
- # @return [Array<String>, nil]
114
123
  def selected_row
115
124
  return nil unless @selected_row_idx
125
+
116
126
  @data[@selected_row_idx]
117
127
  end
118
128
 
119
- # Remove selected row
120
- #
121
- # @return [Array<String>, nil]
122
129
  def remove_selected
123
130
  return nil unless @selected_row_idx
124
131
 
125
132
  removed = @data.delete_at(@selected_row_idx)
126
133
  @selected_row_idx = nil
127
134
  render_rows
135
+ update_pagination_ui
128
136
  removed
129
137
  end
130
138
 
131
- # Register single-click handler
132
- #
133
- # @yield [row] row data
134
139
  def row_clicked(&block)
135
140
  @on_row_clicked = block
136
141
  end
137
142
 
138
- # Register double-click handler
139
- #
140
- # @yield [row] row data
141
143
  def row_double_clicked(&block)
142
144
  @on_row_double_clicked = block
143
145
  end
144
146
 
147
+ # ---------------------------
148
+ # Pagination
149
+ # ---------------------------
150
+
151
+ def paginated_data
152
+ return @data if @data.size <= PAGE_THRESHOLD
153
+
154
+ start = @current_page * PAGE_SIZE
155
+ @data.slice(start, PAGE_SIZE) || []
156
+ end
157
+
158
+ def next_page
159
+ return if @data.size <= PAGE_THRESHOLD
160
+
161
+ max_page = (@data.size / PAGE_SIZE.to_f).ceil - 1
162
+ @current_page = [@current_page + 1, max_page].min
163
+ render_rows
164
+ update_pagination_ui
165
+ end
166
+
167
+ def prev_page
168
+ return if @data.size <= PAGE_THRESHOLD
169
+
170
+ @current_page = [@current_page - 1, 0].max
171
+ render_rows
172
+ update_pagination_ui
173
+ end
174
+
175
+ def adjust_page_after_insert
176
+ return if @data.size <= PAGE_THRESHOLD
177
+
178
+ @current_page = (@data.size / PAGE_SIZE.to_f).floor
179
+ end
180
+
181
+ def update_pagination_ui
182
+ if @data.size <= PAGE_THRESHOLD
183
+ @pagination_box.hide
184
+ return
185
+ end
186
+
187
+ total_pages = (@data.size / PAGE_SIZE.to_f).ceil
188
+ @page_label.set_label("#{@current_page + 1} / #{total_pages}")
189
+
190
+ @prev_btn.set_sensitive(@current_page > 0)
191
+ @next_btn.set_sensitive(@current_page < total_pages - 1)
192
+
193
+ @pagination_box.show
194
+ end
195
+
145
196
  # ---------------------------
146
197
  # Rendering
147
198
  # ---------------------------
@@ -151,37 +202,35 @@ module MittensUi
151
202
  def setup_css
152
203
  css = Gtk::CssProvider.new
153
204
  css.load(data: <<~CSS)
154
- frame {
155
- border-radius: 0;
156
- box-shadow: none;
157
- border: none;
158
- }
159
-
160
- .table-cell {
205
+ box.table-cell {
161
206
  padding: 6px 10px;
162
207
  border-bottom: 1px solid @borders;
208
+ background-color: @theme_base_color;
209
+ color: @theme_text_color;
163
210
  }
164
211
 
165
- .header-cell {
212
+ box.header-cell {
166
213
  font-weight: bold;
167
214
  padding: 8px 10px;
168
215
  border-bottom: 2px solid @borders;
169
- background-color: @theme_base_color;
216
+ background-color: shade(@theme_base_color, 0.95);
217
+ color: @theme_text_color;
170
218
  }
171
219
 
172
- .row-even {
173
- background-color: alpha(@theme_bg_color, 0.96);
220
+ box.row-even {
221
+ background-color: shade(@theme_base_color, 1.00);
174
222
  }
175
223
 
176
- .row-odd {
177
- background-color: alpha(@theme_bg_color, 0.90);
224
+ box.row-odd {
225
+ background-color: shade(@theme_base_color, 0.97);
178
226
  }
179
227
 
180
- .table-cell:hover {
181
- background-color: alpha(@theme_selected_bg_color, 0.25);
228
+ box.table-cell:hover {
229
+ background-color: @theme_selected_bg_color;
230
+ color: @theme_selected_fg_color;
182
231
  }
183
232
 
184
- .row-selected {
233
+ box.row-selected {
185
234
  background-color: @theme_selected_bg_color;
186
235
  color: @theme_selected_fg_color;
187
236
  }
@@ -190,7 +239,7 @@ module MittensUi
190
239
  Gtk::StyleContext.add_provider_for_display(
191
240
  Gdk::Display.default,
192
241
  css,
193
- Gtk::StyleProvider::PRIORITY_USER
242
+ Gtk::StyleProvider::PRIORITY_APPLICATION
194
243
  )
195
244
  end
196
245
 
@@ -202,19 +251,15 @@ module MittensUi
202
251
 
203
252
  @header_labels[col_idx] = label
204
253
 
205
- frame = Gtk::Frame.new
206
- frame.set_child(label)
207
- frame.set_hexpand(true)
208
- frame.style_context.add_class('header-cell')
254
+ box = Gtk::Box.new(:horizontal, 0)
255
+ box.append(label)
256
+ box.style_context.add_class('header-cell')
209
257
 
210
258
  gesture = Gtk::GestureClick.new
211
- gesture.set_button(0)
212
- gesture.signal_connect("pressed") do |_g, _n, _x, _y|
213
- sort_column(col_idx)
214
- end
215
- frame.add_controller(gesture)
259
+ gesture.signal_connect('pressed') { sort_column(col_idx) }
260
+ box.add_controller(gesture)
216
261
 
217
- @grid.attach(frame, col_idx, 0, 1, 1)
262
+ @grid.attach(box, col_idx, 0, 1, 1)
218
263
  end
219
264
  end
220
265
 
@@ -222,8 +267,11 @@ module MittensUi
222
267
  @row_widgets.each { |row| row.each { |w| @grid.remove(w) } }
223
268
  @row_widgets.clear
224
269
 
225
- @data.each_with_index do |row, row_idx|
226
- base_class = row_idx.even? ? 'row-even' : 'row-odd'
270
+ rows = paginated_data
271
+
272
+ rows.each_with_index do |row, visible_idx|
273
+ actual_idx = visible_idx + (@current_page * PAGE_SIZE)
274
+ base_class = visible_idx.even? ? 'row-even' : 'row-odd'
227
275
  widget_row = []
228
276
 
229
277
  row.each_with_index do |cell, col_idx|
@@ -231,46 +279,17 @@ module MittensUi
231
279
  label.set_xalign(0.0)
232
280
  label.set_hexpand(true)
233
281
 
234
- frame = Gtk::Frame.new
235
- frame.set_child(label)
236
- frame.set_hexpand(true)
237
- frame.style_context.add_class('table-cell')
238
- frame.style_context.add_class(base_class)
239
- frame.style_context.add_class('row-selected') if row_selected?(row_idx)
240
-
241
- gesture = Gtk::GestureClick.new
242
- gesture.set_button(0)
243
-
244
- gesture.signal_connect("pressed") do |_g, _n, _x, _y|
245
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
246
-
247
- if @last_click_time &&
248
- @last_clicked_row == row_idx &&
249
- (now - @last_click_time) < 0.3
250
-
251
- # DOUBLE CLICK
252
- @on_row_double_clicked&.call(@data[row_idx])
253
- else
254
- # SINGLE CLICK (delayed slightly to avoid conflict)
255
- GLib::Timeout.add(250) do
256
- if @last_clicked_row == row_idx
257
- @on_row_clicked&.call(@data[row_idx])
258
- end
259
- false
260
- end
261
- end
262
-
263
- @last_click_time = now
264
- @last_clicked_row = row_idx
265
- @selected_row_idx = row_idx
266
-
267
- render_rows
268
- end
282
+ box = Gtk::Box.new(:horizontal, 0)
283
+ box.append(label)
269
284
 
270
- frame.add_controller(gesture)
285
+ box.style_context.add_class('table-cell')
286
+ box.style_context.add_class(base_class)
287
+ box.style_context.add_class('row-selected') if row_selected?(actual_idx)
271
288
 
272
- @grid.attach(frame, col_idx, row_idx + 1, 1, 1)
273
- widget_row << frame
289
+ attach_click_handlers(box, actual_idx)
290
+
291
+ @grid.attach(box, col_idx, visible_idx + 1, 1, 1)
292
+ widget_row << box
274
293
  end
275
294
 
276
295
  @row_widgets << widget_row
@@ -280,21 +299,76 @@ module MittensUi
280
299
  @row_widgets.flatten.each(&:show)
281
300
  end
282
301
 
302
+ def attach_click_handlers(widget, row_idx)
303
+ gesture = Gtk::GestureClick.new
304
+ gesture.set_button(0)
305
+
306
+ gesture.signal_connect('pressed') do |_g, _n, _x, _y|
307
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
308
+
309
+ if @last_click_time &&
310
+ @last_clicked_row == row_idx &&
311
+ (now - @last_click_time) < 0.3
312
+
313
+ @on_row_double_clicked&.call(@data[row_idx])
314
+ else
315
+ GLib::Timeout.add(200) do
316
+ @last_clicked_row == row_idx ? @on_row_clicked&.call(@data[row_idx]) : false
317
+ end
318
+ end
319
+
320
+ @last_click_time = now
321
+ @last_clicked_row = row_idx
322
+ @selected_row_idx = row_idx
323
+
324
+ render_rows
325
+ end
326
+
327
+ widget.add_controller(gesture)
328
+ end
329
+
283
330
  def sort_column(col_idx)
284
331
  dir = @sort_directions[col_idx] ? :desc : :asc
285
332
  @sort_directions[col_idx] = !@sort_directions[col_idx]
286
333
 
287
334
  @header_labels.each_with_index do |lbl, i|
288
- lbl.set_label(@headers[i].to_s) if lbl
335
+ lbl.set_label(@headers[i])
289
336
  end
290
337
 
291
- arrow = dir == :asc ? " " : " "
338
+ arrow = dir == :asc ? ' ' : ' '
292
339
  @header_labels[col_idx].set_label(@headers[col_idx] + arrow)
293
340
 
294
341
  @data.sort_by! { |row| row[col_idx].to_s }
295
342
  @data.reverse! if dir == :desc
296
343
 
344
+ render_rows
345
+ update_pagination_ui
346
+ end
347
+
348
+ def setup_keyboard
349
+ controller = Gtk::EventControllerKey.new
350
+
351
+ controller.signal_connect('key-pressed') do |_ctrl, key, _, _|
352
+ case key
353
+ when Gdk::Keyval::KEY_Up
354
+ move_selection(-1)
355
+ when Gdk::Keyval::KEY_Down
356
+ move_selection(1)
357
+ when Gdk::Keyval::KEY_Return
358
+ @on_row_double_clicked&.call(selected_row)
359
+ end
360
+ end
361
+
362
+ @scroller.add_controller(controller)
363
+ end
364
+
365
+ def move_selection(delta)
366
+ return if @data.empty?
367
+
368
+ @selected_row_idx ||= 0
369
+ @selected_row_idx = [[@selected_row_idx + delta, 0].max, @data.size - 1].min
370
+
297
371
  render_rows
298
372
  end
299
373
  end
300
- end
374
+ end
@@ -1,3 +1,3 @@
1
1
  module MittensUi
2
- VERSION = "0.0.14"
2
+ VERSION = "0.0.15"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mittens_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Tuttle