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 +4 -4
- data/README.md +4 -17
- data/examples/contacts.rb +33 -56
- data/lib/mittens_ui/table_view.rb +195 -121
- data/lib/mittens_ui/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65ad0d43134bc7e47bcb8d797b70f275a6330426ba073f2f0861fa56a1f8f551
|
|
4
|
+
data.tar.gz: c613e0ddc59df4efdc64a0ee5b4af846c2549e30c562352c52864bfcdc62abe2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
247
|
-
|
|
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([
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
6
|
+
# A simple, custom table widget built on Gtk::Grid.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
# -
|
|
10
|
-
# - Row selection
|
|
11
|
-
# - Single
|
|
12
|
-
# -
|
|
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
|
|
25
|
-
#
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
@
|
|
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
|
-
|
|
71
|
+
@pagination_box.append(@prev_btn)
|
|
72
|
+
@pagination_box.append(@page_label)
|
|
73
|
+
@pagination_box.append(@next_btn)
|
|
61
74
|
|
|
62
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
220
|
+
box.row-even {
|
|
221
|
+
background-color: shade(@theme_base_color, 1.00);
|
|
174
222
|
}
|
|
175
223
|
|
|
176
|
-
.row-odd {
|
|
177
|
-
background-color:
|
|
224
|
+
box.row-odd {
|
|
225
|
+
background-color: shade(@theme_base_color, 0.97);
|
|
178
226
|
}
|
|
179
227
|
|
|
180
|
-
.table-cell:hover {
|
|
181
|
-
background-color:
|
|
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::
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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.
|
|
212
|
-
|
|
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(
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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]
|
|
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
|
data/lib/mittens_ui/version.rb
CHANGED