qt 0.1.0
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 +7 -0
- data/LICENSE +27 -0
- data/README.md +303 -0
- data/Rakefile +94 -0
- data/examples/development_ordered_demos/01_dsl_hello.rb +22 -0
- data/examples/development_ordered_demos/02_live_layout_console.rb +137 -0
- data/examples/development_ordered_demos/03_component_showcase.rb +235 -0
- data/examples/development_ordered_demos/04_paint_simple.rb +147 -0
- data/examples/development_ordered_demos/05_tetris_simple.rb +295 -0
- data/examples/development_ordered_demos/06_timetrap_clockify.rb +759 -0
- data/examples/development_ordered_demos/07_peek_like_recorder.rb +597 -0
- data/examples/qtproject/widgets/itemviews/spreadsheet/main.rb +252 -0
- data/examples/qtproject/widgets/widgetsgallery/main.rb +184 -0
- data/ext/qt_ruby_bridge/extconf.rb +75 -0
- data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +23 -0
- data/ext/qt_ruby_bridge/runtime_events.cpp +408 -0
- data/ext/qt_ruby_bridge/runtime_signals.cpp +212 -0
- data/lib/qt/application_lifecycle.rb +44 -0
- data/lib/qt/bridge.rb +95 -0
- data/lib/qt/children_tracking.rb +15 -0
- data/lib/qt/constants.rb +10 -0
- data/lib/qt/date_time_codec.rb +104 -0
- data/lib/qt/errors.rb +6 -0
- data/lib/qt/event_runtime.rb +139 -0
- data/lib/qt/event_runtime_dispatch.rb +35 -0
- data/lib/qt/event_runtime_qobject_methods.rb +41 -0
- data/lib/qt/generated_constants_runtime.rb +33 -0
- data/lib/qt/inspectable.rb +29 -0
- data/lib/qt/key_sequence_codec.rb +22 -0
- data/lib/qt/native.rb +93 -0
- data/lib/qt/shortcut_compat.rb +30 -0
- data/lib/qt/string_codec.rb +44 -0
- data/lib/qt/variant_codec.rb +78 -0
- data/lib/qt/version.rb +5 -0
- data/lib/qt.rb +47 -0
- data/scripts/generate_bridge/ast_introspection.rb +267 -0
- data/scripts/generate_bridge/auto_method_spec_resolver.rb +37 -0
- data/scripts/generate_bridge/auto_methods.rb +438 -0
- data/scripts/generate_bridge/core_utils.rb +114 -0
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +93 -0
- data/scripts/generate_bridge/ffi_api.rb +46 -0
- data/scripts/generate_bridge/free_function_specs.rb +289 -0
- data/scripts/generate_bridge/spec_discovery.rb +313 -0
- data/scripts/generate_bridge.rb +1113 -0
- metadata +99 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../../../../../lib', __dir__))
|
|
4
|
+
require 'qt'
|
|
5
|
+
|
|
6
|
+
WINDOW_W = 980
|
|
7
|
+
WINDOW_H = 660
|
|
8
|
+
COLS = 6
|
|
9
|
+
|
|
10
|
+
HEADERS = ['Item', 'Date', 'Price', 'Currency', 'Ex. Rate', 'NOK'].freeze
|
|
11
|
+
ROWS_DATA = [
|
|
12
|
+
['AirportBus', '15/6/2006', 150, 'NOK', 1],
|
|
13
|
+
['Flight (Munich)', '15/6/2006', 2350, 'NOK', 1],
|
|
14
|
+
['Lunch', '15/6/2006', -14, 'EUR', 8],
|
|
15
|
+
['Flight (LA)', '21/5/2006', 980, 'EUR', 8],
|
|
16
|
+
['Taxi', '16/6/2006', 5, 'USD', 7],
|
|
17
|
+
['Dinner', '16/6/2006', 120, 'USD', 7],
|
|
18
|
+
['Hotel', '16/6/2006', 300, 'USD', 7],
|
|
19
|
+
['Flight (Oslo)', '18/6/2006', 1240, 'NOK', 1]
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
ROWS = ROWS_DATA.length + 1
|
|
23
|
+
TOTAL_ROW = ROWS - 1
|
|
24
|
+
|
|
25
|
+
BG = 'background-color: #0b111b; border: 1px solid #1f2937;'
|
|
26
|
+
PANEL = 'background-color: #0f172a; border: 1px solid #334155; color: #e2e8f0;'
|
|
27
|
+
TITLE = 'background-color: #111827; border: 1px solid #374151; color: #f8fafc; font-size: 16px; font-weight: 800;'
|
|
28
|
+
INPUT = 'background-color: #0b1220; border: 1px solid #334155; color: #f8fafc; font-size: 12px;'
|
|
29
|
+
BTN = 'background-color: #111827; border: 1px solid #64748b; color: #e2e8f0; font-size: 12px; font-weight: 700;'
|
|
30
|
+
BTN_ACTIVE = 'background-color: #1d4ed8; border: 2px solid #60a5fa; color: #f8fafc; font-size: 12px; font-weight: 800;'
|
|
31
|
+
|
|
32
|
+
TABLE_QSS = [
|
|
33
|
+
'QTableWidget {',
|
|
34
|
+
' background-color: #0b1220;',
|
|
35
|
+
' color: #f8fafc;',
|
|
36
|
+
' border: 1px solid #334155;',
|
|
37
|
+
' gridline-color: #1f2937;',
|
|
38
|
+
'}',
|
|
39
|
+
'QHeaderView::section {',
|
|
40
|
+
' background-color: #3f3f46;',
|
|
41
|
+
' color: #f8fafc;',
|
|
42
|
+
' border: 1px solid #4b5563;',
|
|
43
|
+
' font-weight: 700;',
|
|
44
|
+
'}',
|
|
45
|
+
'QTableCornerButton::section {',
|
|
46
|
+
' background-color: #3f3f46;',
|
|
47
|
+
' border: 1px solid #4b5563;',
|
|
48
|
+
'}'
|
|
49
|
+
].join("\n").freeze
|
|
50
|
+
|
|
51
|
+
app = QApplication.new(0, [])
|
|
52
|
+
window = QWidget.new do |w|
|
|
53
|
+
w.set_window_title('Spreadsheet')
|
|
54
|
+
w.set_geometry(70, 60, WINDOW_W, WINDOW_H)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
bg = QLabel.new(window)
|
|
58
|
+
panel = QLabel.new(window)
|
|
59
|
+
|
|
60
|
+
title = QLabel.new(window)
|
|
61
|
+
title.set_alignment(Qt::AlignCenter)
|
|
62
|
+
title.set_text('Spreadsheet')
|
|
63
|
+
|
|
64
|
+
cell_label = QLabel.new(window)
|
|
65
|
+
cell_label.set_alignment(Qt::AlignCenter)
|
|
66
|
+
cell_label.set_text('Cell: (A1)')
|
|
67
|
+
|
|
68
|
+
formula = QLineEdit.new(window)
|
|
69
|
+
formula.set_placeholder_text('Cell value')
|
|
70
|
+
|
|
71
|
+
apply_btn = QPushButton.new(window)
|
|
72
|
+
apply_btn.set_text('APPLY')
|
|
73
|
+
|
|
74
|
+
recalc_btn = QPushButton.new(window)
|
|
75
|
+
recalc_btn.set_text('RECALCULATE')
|
|
76
|
+
|
|
77
|
+
sheet = QTableWidget.new(window)
|
|
78
|
+
sheet.set_row_count(ROWS)
|
|
79
|
+
sheet.set_column_count(COLS)
|
|
80
|
+
sheet.set_vertical_scroll_mode(Qt::ScrollPerPixel)
|
|
81
|
+
|
|
82
|
+
[180, 160, 120, 140, 120, 120].each_with_index do |width, col|
|
|
83
|
+
sheet.set_column_width(col, width)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
headers = []
|
|
87
|
+
HEADERS.each_with_index do |text, col|
|
|
88
|
+
item = QTableWidgetItem.new
|
|
89
|
+
item.set_text(text)
|
|
90
|
+
item.set_text_alignment(Qt::AlignCenter)
|
|
91
|
+
sheet.set_horizontal_header_item(col, item)
|
|
92
|
+
headers << item
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
matrix = Array.new(ROWS) { Array.new(COLS) }
|
|
96
|
+
|
|
97
|
+
ROWS_DATA.each_with_index do |row_data, row|
|
|
98
|
+
COLS.times do |col|
|
|
99
|
+
item = QTableWidgetItem.new
|
|
100
|
+
value = case col
|
|
101
|
+
when 0 then row_data[0]
|
|
102
|
+
when 1 then row_data[1]
|
|
103
|
+
when 2 then row_data[2]
|
|
104
|
+
when 3 then row_data[3]
|
|
105
|
+
when 4 then row_data[4]
|
|
106
|
+
else 0
|
|
107
|
+
end
|
|
108
|
+
item.set_text(value.to_s)
|
|
109
|
+
item.set_text_alignment(Qt::AlignCenter)
|
|
110
|
+
sheet.set_item(row, col, item)
|
|
111
|
+
matrix[row][col] = item
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
COLS.times do |col|
|
|
116
|
+
item = QTableWidgetItem.new
|
|
117
|
+
value = if col.zero?
|
|
118
|
+
'Total:'
|
|
119
|
+
elsif col == 5
|
|
120
|
+
'0'
|
|
121
|
+
else
|
|
122
|
+
'None'
|
|
123
|
+
end
|
|
124
|
+
item.set_text(value)
|
|
125
|
+
item.set_text_alignment(Qt::AlignCenter)
|
|
126
|
+
sheet.set_item(TOTAL_ROW, col, item)
|
|
127
|
+
matrix[TOTAL_ROW][col] = item
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
int_value = lambda do |text|
|
|
131
|
+
Integer(text.to_s.strip)
|
|
132
|
+
rescue StandardError
|
|
133
|
+
0
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
recompute = lambda do
|
|
137
|
+
total = 0
|
|
138
|
+
ROWS_DATA.length.times do |row|
|
|
139
|
+
price = int_value.call(matrix[row][2].text)
|
|
140
|
+
rate = int_value.call(matrix[row][4].text)
|
|
141
|
+
nok = price * rate
|
|
142
|
+
matrix[row][5].set_text(nok.to_s)
|
|
143
|
+
total += nok
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
matrix[TOTAL_ROW][5].set_text(total.to_s)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
col_label = lambda do |col|
|
|
150
|
+
letters = +''
|
|
151
|
+
n = col + 1
|
|
152
|
+
while n.positive?
|
|
153
|
+
n -= 1
|
|
154
|
+
letters.prepend((65 + (n % 26)).chr)
|
|
155
|
+
n /= 26
|
|
156
|
+
end
|
|
157
|
+
letters
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
sync_formula_with_current = lambda do
|
|
161
|
+
row = sheet.current_row
|
|
162
|
+
col = sheet.current_column
|
|
163
|
+
return if row.negative? || col.negative?
|
|
164
|
+
|
|
165
|
+
item = matrix[row][col]
|
|
166
|
+
return unless item
|
|
167
|
+
|
|
168
|
+
cell_label.set_text("Cell: (#{col_label.call(col)}#{row + 1})")
|
|
169
|
+
formula.set_text(item.text.to_s)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
flash = lambda do |button|
|
|
173
|
+
button.set_style_sheet(BTN_ACTIVE)
|
|
174
|
+
QApplication.process_events
|
|
175
|
+
sleep(0.03)
|
|
176
|
+
button.set_style_sheet(BTN)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
apply_current_cell = lambda do
|
|
180
|
+
row = sheet.current_row
|
|
181
|
+
col = sheet.current_column
|
|
182
|
+
return if row.negative? || col.negative?
|
|
183
|
+
|
|
184
|
+
item = matrix[row][col]
|
|
185
|
+
return unless item
|
|
186
|
+
|
|
187
|
+
item.set_text(formula.text.to_s)
|
|
188
|
+
recompute.call
|
|
189
|
+
sync_formula_with_current.call
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
apply_btn.connect('clicked') do
|
|
193
|
+
flash.call(apply_btn)
|
|
194
|
+
apply_current_cell.call
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
formula.connect('returnPressed') { apply_current_cell.call }
|
|
198
|
+
|
|
199
|
+
recalc_btn.connect('clicked') do
|
|
200
|
+
flash.call(recalc_btn)
|
|
201
|
+
recompute.call
|
|
202
|
+
sync_formula_with_current.call
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
sheet.on(:mouse_button_release) { sync_formula_with_current.call }
|
|
206
|
+
sheet.on(:key_release) { sync_formula_with_current.call }
|
|
207
|
+
|
|
208
|
+
layout_ui = lambda do
|
|
209
|
+
ww = window.width
|
|
210
|
+
wh = window.height
|
|
211
|
+
|
|
212
|
+
bg.set_geometry(0, 0, ww, wh)
|
|
213
|
+
panel.set_geometry(16, 16, ww - 32, wh - 32)
|
|
214
|
+
|
|
215
|
+
title.set_geometry(32, 24, ww - 64, 36)
|
|
216
|
+
|
|
217
|
+
cell_label.set_geometry(32, 68, 130, 32)
|
|
218
|
+
formula.set_geometry(170, 68, ww - 372, 32)
|
|
219
|
+
apply_btn.set_geometry(ww - 194, 68, 74, 32)
|
|
220
|
+
recalc_btn.set_geometry(ww - 112, 68, 80, 32)
|
|
221
|
+
|
|
222
|
+
sheet.set_geometry(32, 112, ww - 64, wh - 144)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
apply_styles = lambda do
|
|
226
|
+
bg.set_style_sheet(BG)
|
|
227
|
+
panel.set_style_sheet(PANEL)
|
|
228
|
+
title.set_style_sheet(TITLE)
|
|
229
|
+
cell_label.set_style_sheet(PANEL)
|
|
230
|
+
formula.set_style_sheet(INPUT)
|
|
231
|
+
apply_btn.set_style_sheet(BTN)
|
|
232
|
+
recalc_btn.set_style_sheet(BTN)
|
|
233
|
+
sheet.set_style_sheet(TABLE_QSS)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
window.on(:resize) { layout_ui.call }
|
|
237
|
+
|
|
238
|
+
recompute.call
|
|
239
|
+
apply_styles.call
|
|
240
|
+
layout_ui.call
|
|
241
|
+
sheet.set_current_cell(0, 0)
|
|
242
|
+
sync_formula_with_current.call
|
|
243
|
+
window.show
|
|
244
|
+
QApplication.process_events
|
|
245
|
+
|
|
246
|
+
# TODO: Replace manual process_events loop with app.exec + QTimer.
|
|
247
|
+
while window.is_visible != 0
|
|
248
|
+
QApplication.process_events
|
|
249
|
+
sleep(0.01)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
app.dispose
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../../../../lib', __dir__))
|
|
4
|
+
require 'qt'
|
|
5
|
+
|
|
6
|
+
WINDOW_W = 980
|
|
7
|
+
WINDOW_H = 640
|
|
8
|
+
SIDEBAR_W = 300
|
|
9
|
+
|
|
10
|
+
THEMES = {
|
|
11
|
+
light: {
|
|
12
|
+
root: 'background-color: #f5f7fb; border: 1px solid #dbe1ec;',
|
|
13
|
+
card: 'background-color: #ffffff; border: 1px solid #d5dce8; color: #0f172a;',
|
|
14
|
+
title: 'background-color: #ffffff; border: 1px solid #cbd5e1; color: #0f172a; font-size: 17px; font-weight: 800;',
|
|
15
|
+
button: 'background-color: #ffffff; border: 1px solid #94a3b8; color: #0f172a; font-size: 12px; font-weight: 700;',
|
|
16
|
+
button_active: 'background-color: #dbeafe; border: 2px solid #2563eb; color: #0f172a; ' \
|
|
17
|
+
'font-size: 12px; font-weight: 800;',
|
|
18
|
+
input: 'background-color: #ffffff; border: 1px solid #a8b3c6; color: #0f172a; font-size: 12px;'
|
|
19
|
+
},
|
|
20
|
+
dark: {
|
|
21
|
+
root: 'background-color: #0b1220; border: 1px solid #1f2a44;',
|
|
22
|
+
card: 'background-color: #101a2d; border: 1px solid #334155; color: #e2e8f0;',
|
|
23
|
+
title: 'background-color: #0f172a; border: 1px solid #334155; color: #f8fafc; font-size: 17px; font-weight: 800;',
|
|
24
|
+
button: 'background-color: #111827; border: 1px solid #475569; color: #e2e8f0; font-size: 12px; font-weight: 700;',
|
|
25
|
+
button_active: 'background-color: #1d4ed8; border: 2px solid #60a5fa; color: #f8fafc; ' \
|
|
26
|
+
'font-size: 12px; font-weight: 800;',
|
|
27
|
+
input: 'background-color: #0b1220; border: 1px solid #475569; color: #e2e8f0; font-size: 12px;'
|
|
28
|
+
}
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
app = QApplication.new(0, [])
|
|
32
|
+
window = QWidget.new do |w|
|
|
33
|
+
w.set_window_title('Qt Ruby Widget Gallery Lite (inspired by PySide widgetsgallery)')
|
|
34
|
+
w.set_geometry(70, 60, WINDOW_W, WINDOW_H)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
root = QLabel.new(window)
|
|
38
|
+
left = QLabel.new(window)
|
|
39
|
+
right = QLabel.new(window)
|
|
40
|
+
|
|
41
|
+
headline = QLabel.new(window)
|
|
42
|
+
headline.set_alignment(Qt::AlignCenter)
|
|
43
|
+
headline.set_text('Widget Gallery Lite')
|
|
44
|
+
|
|
45
|
+
subtitle = QLabel.new(window)
|
|
46
|
+
subtitle.set_alignment(Qt::AlignCenter)
|
|
47
|
+
subtitle.set_text('Inspired by PySide widgetsgallery: theme toggle, interactive controls, status area')
|
|
48
|
+
|
|
49
|
+
name_label = QLabel.new(window)
|
|
50
|
+
name_label.set_alignment(Qt::AlignCenter)
|
|
51
|
+
name_label.set_text('Name input')
|
|
52
|
+
|
|
53
|
+
name_input = QLineEdit.new(window)
|
|
54
|
+
name_input.set_placeholder_text('Type your name...')
|
|
55
|
+
|
|
56
|
+
name_echo = QLabel.new(window)
|
|
57
|
+
name_echo.set_alignment(Qt::AlignCenter)
|
|
58
|
+
name_echo.set_text('Hello, stranger')
|
|
59
|
+
|
|
60
|
+
counter_label = QLabel.new(window)
|
|
61
|
+
counter_label.set_alignment(Qt::AlignCenter)
|
|
62
|
+
counter_label.set_text('Counter: 0')
|
|
63
|
+
|
|
64
|
+
counter = 0
|
|
65
|
+
|
|
66
|
+
actions = [
|
|
67
|
+
{ key: :plus, text: 'INCREMENT' },
|
|
68
|
+
{ key: :minus, text: 'DECREMENT' },
|
|
69
|
+
{ key: :reset, text: 'RESET' },
|
|
70
|
+
{ key: :theme, text: 'TOGGLE THEME' }
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
actions.each do |action|
|
|
74
|
+
action[:view] = QPushButton.new(window)
|
|
75
|
+
action[:view].set_text(action[:text])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
status = QLabel.new(window)
|
|
79
|
+
status.set_alignment(Qt::AlignCenter)
|
|
80
|
+
status.set_text('Ready')
|
|
81
|
+
|
|
82
|
+
current_theme = :light
|
|
83
|
+
|
|
84
|
+
layout_ui = lambda do
|
|
85
|
+
ww = window.width
|
|
86
|
+
wh = window.height
|
|
87
|
+
sidebar_x = ww - SIDEBAR_W
|
|
88
|
+
|
|
89
|
+
root.set_geometry(0, 0, ww, wh)
|
|
90
|
+
|
|
91
|
+
left.set_geometry(16, 16, ww - SIDEBAR_W - 32, wh - 32)
|
|
92
|
+
headline.set_geometry(34, 34, ww - SIDEBAR_W - 68, 40)
|
|
93
|
+
subtitle.set_geometry(34, 82, ww - SIDEBAR_W - 68, 34)
|
|
94
|
+
|
|
95
|
+
name_label.set_geometry(34, 142, ww - SIDEBAR_W - 68, 30)
|
|
96
|
+
name_input.set_geometry(34, 178, ww - SIDEBAR_W - 68, 38)
|
|
97
|
+
name_echo.set_geometry(34, 224, ww - SIDEBAR_W - 68, 38)
|
|
98
|
+
counter_label.set_geometry(34, 272, ww - SIDEBAR_W - 68, 38)
|
|
99
|
+
|
|
100
|
+
right.set_geometry(sidebar_x, 0, SIDEBAR_W, wh)
|
|
101
|
+
actions.each_with_index do |action, idx|
|
|
102
|
+
action[:view].set_geometry(sidebar_x + 18, 28 + (idx * 52), SIDEBAR_W - 36, 40)
|
|
103
|
+
end
|
|
104
|
+
status.set_geometry(sidebar_x + 18, wh - 84, SIDEBAR_W - 36, 56)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
apply_theme = lambda do
|
|
108
|
+
theme = THEMES.fetch(current_theme)
|
|
109
|
+
root.set_style_sheet(theme[:root])
|
|
110
|
+
left.set_style_sheet(theme[:card])
|
|
111
|
+
right.set_style_sheet(theme[:card])
|
|
112
|
+
headline.set_style_sheet(theme[:title])
|
|
113
|
+
subtitle.set_style_sheet(theme[:card])
|
|
114
|
+
name_label.set_style_sheet(theme[:card])
|
|
115
|
+
name_input.set_style_sheet(theme[:input])
|
|
116
|
+
name_echo.set_style_sheet(theme[:card])
|
|
117
|
+
counter_label.set_style_sheet(theme[:card])
|
|
118
|
+
status.set_style_sheet(theme[:card])
|
|
119
|
+
actions.each { |a| a[:view].set_style_sheet(theme[:button]) }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
flash = lambda do |button|
|
|
123
|
+
theme = THEMES.fetch(current_theme)
|
|
124
|
+
button.set_style_sheet(theme[:button_active])
|
|
125
|
+
QApplication.process_events
|
|
126
|
+
sleep(0.03)
|
|
127
|
+
button.set_style_sheet(theme[:button])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
set_name_echo = lambda do
|
|
131
|
+
text = name_input.text.to_s.strip
|
|
132
|
+
name_echo.set_text(text.empty? ? 'Hello, stranger' : "Hello, #{text}")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
set_counter = lambda do
|
|
136
|
+
counter_label.set_text("Counter: #{counter}")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
perform = lambda do |key, view|
|
|
140
|
+
flash.call(view)
|
|
141
|
+
|
|
142
|
+
case key
|
|
143
|
+
when :plus
|
|
144
|
+
counter += 1
|
|
145
|
+
set_counter.call
|
|
146
|
+
status.set_text('Incremented')
|
|
147
|
+
when :minus
|
|
148
|
+
counter -= 1
|
|
149
|
+
set_counter.call
|
|
150
|
+
status.set_text('Decremented')
|
|
151
|
+
when :reset
|
|
152
|
+
counter = 0
|
|
153
|
+
set_counter.call
|
|
154
|
+
name_input.set_text('')
|
|
155
|
+
set_name_echo.call
|
|
156
|
+
status.set_text('Reset all')
|
|
157
|
+
when :theme
|
|
158
|
+
current_theme = (current_theme == :light ? :dark : :light)
|
|
159
|
+
apply_theme.call
|
|
160
|
+
status.set_text("Theme: #{current_theme}")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
actions.each do |action|
|
|
165
|
+
action[:view].connect('clicked') do
|
|
166
|
+
perform.call(action[:key], action[:view])
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
name_input.connect('textChanged') { set_name_echo.call }
|
|
171
|
+
window.on(:resize) { layout_ui.call }
|
|
172
|
+
|
|
173
|
+
layout_ui.call
|
|
174
|
+
apply_theme.call
|
|
175
|
+
window.show
|
|
176
|
+
QApplication.process_events
|
|
177
|
+
|
|
178
|
+
# TODO: Replace manual process_events loop with app.exec + QTimer.
|
|
179
|
+
while window.is_visible != 0
|
|
180
|
+
QApplication.process_events
|
|
181
|
+
sleep(0.01)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
app.dispose
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mkmf'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
PKG_CONFIG = RbConfig::CONFIG['PKG_CONFIG'] || 'pkg-config'
|
|
7
|
+
QT_PACKAGES = %w[Qt6Core Qt6Gui Qt6Widgets].freeze
|
|
8
|
+
MINIMUM_QT_VERSION = Gem::Version.new('6.10.0')
|
|
9
|
+
|
|
10
|
+
def pkg_config(*)
|
|
11
|
+
system(PKG_CONFIG, *, out: File::NULL, err: File::NULL)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pkg_config_capture(*args)
|
|
15
|
+
`#{[PKG_CONFIG, *args].join(' ')}`.strip
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
abort 'pkg-config is required to build qt-ruby bridge.' unless find_executable(PKG_CONFIG)
|
|
19
|
+
|
|
20
|
+
generator = File.expand_path('../../scripts/generate_bridge.rb', __dir__)
|
|
21
|
+
abort "Generator script not found: #{generator}" unless File.exist?(generator)
|
|
22
|
+
|
|
23
|
+
generator_env = {}
|
|
24
|
+
scope = ENV.fetch('QT_RUBY_SCOPE', nil)
|
|
25
|
+
generator_env['QT_RUBY_SCOPE'] = scope if scope && !scope.empty?
|
|
26
|
+
abort 'Failed to generate Qt bridge files.' unless system(generator_env, RbConfig.ruby, generator)
|
|
27
|
+
|
|
28
|
+
missing = QT_PACKAGES.reject { |pkg| pkg_config('--exists', pkg) }
|
|
29
|
+
abort "Missing Qt packages: #{missing.join(', ')}" unless missing.empty?
|
|
30
|
+
|
|
31
|
+
qt_version_str = pkg_config_capture('--modversion', 'Qt6Core')
|
|
32
|
+
qt_version = Gem::Version.new(qt_version_str)
|
|
33
|
+
abort "Qt version #{qt_version} is too old. Require >= #{MINIMUM_QT_VERSION}." if qt_version < MINIMUM_QT_VERSION
|
|
34
|
+
|
|
35
|
+
cflags = pkg_config_capture('--cflags', *QT_PACKAGES)
|
|
36
|
+
libs = pkg_config_capture('--libs', *QT_PACKAGES)
|
|
37
|
+
generated_cpp = if File.exist?('qt_ruby_bridge.cpp')
|
|
38
|
+
File.expand_path('qt_ruby_bridge.cpp')
|
|
39
|
+
else
|
|
40
|
+
File.expand_path('../../build/generated/qt_ruby_bridge.cpp', __dir__)
|
|
41
|
+
end
|
|
42
|
+
runtime_hpp = File.expand_path('../../ext/qt_ruby_bridge/qt_ruby_runtime.hpp', __dir__)
|
|
43
|
+
runtime_cpp_files = %w[
|
|
44
|
+
runtime_events.cpp
|
|
45
|
+
runtime_signals.cpp
|
|
46
|
+
].map { |name| File.expand_path("../../ext/qt_ruby_bridge/#{name}", __dir__) }
|
|
47
|
+
|
|
48
|
+
unless File.exist?(generated_cpp)
|
|
49
|
+
abort "Generated source not found: #{generated_cpp}. Run: ruby scripts/generate_bridge.rb"
|
|
50
|
+
end
|
|
51
|
+
abort "Runtime header not found: #{runtime_hpp}" unless File.exist?(runtime_hpp)
|
|
52
|
+
missing_runtime = runtime_cpp_files.reject { |path| File.exist?(path) }
|
|
53
|
+
abort "Runtime source not found: #{missing_runtime.join(', ')}" unless missing_runtime.empty?
|
|
54
|
+
|
|
55
|
+
local_cpp = File.expand_path('qt_ruby_bridge.cpp')
|
|
56
|
+
FileUtils.cp(generated_cpp, local_cpp) unless File.exist?(local_cpp) && File.identical?(generated_cpp, local_cpp)
|
|
57
|
+
runtime_cpp_files.each do |runtime_cpp|
|
|
58
|
+
local_runtime_cpp = File.expand_path(File.basename(runtime_cpp))
|
|
59
|
+
unless File.exist?(local_runtime_cpp) && File.identical?(runtime_cpp, local_runtime_cpp)
|
|
60
|
+
FileUtils.cp(runtime_cpp, local_runtime_cpp)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
local_runtime_hpp = File.expand_path('qt_ruby_runtime.hpp')
|
|
64
|
+
unless File.exist?(local_runtime_hpp) && File.identical?(runtime_hpp, local_runtime_hpp)
|
|
65
|
+
FileUtils.cp(runtime_hpp, local_runtime_hpp)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# mkmf uses these global variables for compiler/linker/source configuration.
|
|
69
|
+
# rubocop:disable Style/GlobalVars
|
|
70
|
+
$CXXFLAGS = "#{$CXXFLAGS} #{cflags} -std=c++17"
|
|
71
|
+
$LDFLAGS = "#{$LDFLAGS} #{libs}"
|
|
72
|
+
$srcs = ['qt_ruby_bridge.cpp', *runtime_cpp_files.map { |f| File.basename(f) }]
|
|
73
|
+
# rubocop:enable Style/GlobalVars
|
|
74
|
+
|
|
75
|
+
create_makefile('qt/qt_ruby_bridge')
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <QApplication>
|
|
4
|
+
|
|
5
|
+
namespace QtRubyRuntime {
|
|
6
|
+
using EventCallback = void (*)(void*, int, int, int, int, int);
|
|
7
|
+
using SignalCallback = void (*)(void*, int, const char*);
|
|
8
|
+
|
|
9
|
+
// Creates/owns the singleton QApplication used by the Ruby runtime bridge.
|
|
10
|
+
// The implementation records the creating thread as GUI thread for shutdown checks.
|
|
11
|
+
QApplication* qapplication_new(const char* argv0);
|
|
12
|
+
// Performs guarded QApplication teardown.
|
|
13
|
+
// Returns false when teardown is rejected by runtime safety checks.
|
|
14
|
+
bool qapplication_delete(void* app_handle);
|
|
15
|
+
|
|
16
|
+
void set_event_callback(void* callback_ptr);
|
|
17
|
+
void watch_qobject_event(void* object_handle, int event_type);
|
|
18
|
+
void unwatch_qobject_event(void* object_handle, int event_type);
|
|
19
|
+
|
|
20
|
+
void set_signal_callback(void* callback_ptr);
|
|
21
|
+
int qobject_connect_signal(void* object_handle, const char* signal_name);
|
|
22
|
+
int qobject_disconnect_signal(void* object_handle, const char* signal_name);
|
|
23
|
+
} // namespace QtRubyRuntime
|