glimmer-dsl-swt 4.20.0.1 → 4.20.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +17 -13
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +10 -7
  6. data/docs/reference/GLIMMER_SAMPLES.md +168 -49
  7. data/docs/reference/GLIMMER_STYLE_GUIDE.md +4 -3
  8. data/glimmer-dsl-swt.gemspec +0 -0
  9. data/lib/glimmer/data_binding/shine.rb +3 -1
  10. data/lib/glimmer/data_binding/table_items_binding.rb +2 -2
  11. data/lib/glimmer/dsl/swt/shine_data_binding_expression.rb +6 -3
  12. data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +2 -2
  13. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -0
  14. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +3 -3
  15. data/lib/glimmer/rake_task/scaffold.rb +0 -2
  16. data/lib/glimmer/swt/combo_proxy.rb +48 -0
  17. data/lib/glimmer/swt/display_proxy.rb +11 -8
  18. data/lib/glimmer/swt/proxy_properties.rb +2 -1
  19. data/lib/glimmer/swt/table_proxy.rb +15 -8
  20. data/lib/glimmer/swt/tool_bar_proxy.rb +51 -0
  21. data/lib/glimmer/swt/widget_proxy.rb +8 -2
  22. data/lib/glimmer/ui/custom_shell.rb +3 -3
  23. data/lib/glimmer/ui/custom_widget.rb +5 -2
  24. data/samples/elaborate/calculator.rb +116 -0
  25. data/samples/elaborate/calculator/model/command.rb +105 -0
  26. data/samples/elaborate/calculator/model/command/all_clear.rb +17 -0
  27. data/samples/elaborate/calculator/model/command/command_history.rb +0 -0
  28. data/samples/elaborate/calculator/model/command/equals.rb +18 -0
  29. data/samples/elaborate/calculator/model/command/number.rb +20 -0
  30. data/samples/elaborate/calculator/model/command/operation.rb +27 -0
  31. data/samples/elaborate/calculator/model/command/operation/add.rb +15 -0
  32. data/samples/elaborate/calculator/model/command/operation/divide.rb +15 -0
  33. data/samples/elaborate/calculator/model/command/operation/multiply.rb +15 -0
  34. data/samples/elaborate/calculator/model/command/operation/subtract.rb +15 -0
  35. data/samples/elaborate/calculator/model/command/point.rb +20 -0
  36. data/samples/elaborate/calculator/model/presenter.rb +30 -0
  37. data/samples/elaborate/login.rb +15 -13
  38. data/samples/elaborate/tetris.rb +4 -4
  39. data/samples/elaborate/tetris/model/game.rb +0 -3
  40. data/samples/elaborate/timer.rb +233 -0
  41. data/samples/elaborate/timer/alarm1.wav +0 -0
  42. data/samples/elaborate/timer/sounds/alarm1.wav +0 -0
  43. data/samples/elaborate/user_profile.rb +4 -2
  44. data/samples/elaborate/weather.rb +164 -0
  45. data/samples/hello/hello_cool_bar.rb +147 -0
  46. data/samples/hello/hello_layout.rb +6 -2
  47. data/samples/hello/hello_shell.rb +205 -0
  48. data/samples/hello/hello_table.rb +5 -5
  49. data/samples/hello/hello_text.rb +120 -0
  50. data/samples/hello/hello_tool_bar.rb +143 -0
  51. metadata +25 -2
@@ -0,0 +1,233 @@
1
+ require 'glimmer-dsl-swt'
2
+ require 'os'
3
+
4
+ class Timer
5
+ include Glimmer::UI::CustomShell
6
+
7
+ import 'javax.sound.sampled'
8
+
9
+ FILE_SOUND_ALARM = File.expand_path(File.join('timer', 'alarm1.wav'), __dir__)
10
+ COMMAND_KEY = OS.mac? ? :command : :ctrl
11
+
12
+ ## Add options like the following to configure CustomShell by outside consumers
13
+ #
14
+ # options :title, :background_color
15
+ # option :width, default: 320
16
+ # option :height, default: 240
17
+
18
+ attr_accessor :countdown, :min, :sec
19
+
20
+ ## Use before_body block to pre-initialize variables to use in body
21
+ #
22
+ #
23
+ before_body {
24
+ Display.setAppName('Glimmer Timer')
25
+
26
+ @display = display {
27
+ on_about {
28
+ display_about_dialog
29
+ }
30
+ on_preferences {
31
+ display_about_dialog
32
+ }
33
+ }
34
+
35
+ @min = 0
36
+ @sec = 0
37
+ }
38
+
39
+ ## Use after_body block to setup observers for widgets in body
40
+ #
41
+ after_body {
42
+ Thread.new {
43
+ loop {
44
+ sleep(1)
45
+ if @countdown
46
+ sync_exec {
47
+ @countdown_time = Time.new(1, 1, 1, 0, min, sec)
48
+ @countdown_time -= 1
49
+ self.min = @countdown_time.min
50
+ self.sec = @countdown_time.sec
51
+ if @countdown_time.min <= 0 && @countdown_time.sec <= 0
52
+ stop_countdown
53
+ play_countdown_done_sound
54
+ end
55
+ }
56
+ end
57
+ }
58
+ }
59
+ }
60
+
61
+ ## Add widget content inside custom shell body
62
+ ## Top-most widget must be a shell or another custom shell
63
+ #
64
+ body {
65
+ shell {
66
+ grid_layout # 1 column
67
+
68
+ # Replace example content below with custom shell content
69
+ minimum_size (OS.windows? ? 214 : 200), 114
70
+ image File.join(APP_ROOT, 'package', 'windows', "Timer.ico") if OS.windows?
71
+ text "Glimmer Timer"
72
+
73
+ timer_menu_bar
74
+
75
+ countdown_group
76
+ }
77
+ }
78
+
79
+ def timer_menu_bar
80
+ menu_bar {
81
+ menu {
82
+ text '&Action'
83
+
84
+ menu_item {
85
+ text '&Start'
86
+ accelerator COMMAND_KEY, 's'
87
+ enabled bind(self, :countdown, on_read: :!)
88
+
89
+ on_widget_selected {
90
+ start_countdown
91
+ }
92
+ }
93
+ menu_item {
94
+ text 'St&op'
95
+ enabled bind(self, :countdown)
96
+ accelerator COMMAND_KEY, 'o'
97
+
98
+ on_widget_selected {
99
+ stop_countdown
100
+ }
101
+ }
102
+ unless OS.mac?
103
+ menu_item(:separator)
104
+ menu_item {
105
+ text 'E&xit'
106
+ accelerator :alt, :f4
107
+
108
+ on_widget_selected {
109
+ exit(0)
110
+ }
111
+ }
112
+ end
113
+ }
114
+ menu {
115
+ text '&Help'
116
+
117
+ menu_item {
118
+ text '&About'
119
+ accelerator COMMAND_KEY, :shift, 'a'
120
+
121
+ on_widget_selected {
122
+ display_about_dialog
123
+ }
124
+ }
125
+ }
126
+ }
127
+ end
128
+
129
+ def countdown_group
130
+ group {
131
+ # has grid layout with 1 column by default
132
+ text 'Countdown'
133
+ font height: 20
134
+
135
+ countdown_group_field_composite
136
+
137
+ countdown_group_button_composite
138
+ }
139
+ end
140
+
141
+ def countdown_group_field_composite
142
+ composite {
143
+ row_layout {
144
+ margin_width 0
145
+ margin_height 0
146
+ }
147
+ @min_spinner = spinner {
148
+ text_limit 2
149
+ digits 0
150
+ maximum 59
151
+ selection bind(self, :min)
152
+ enabled bind(self, :countdown, on_read: :!)
153
+ on_widget_default_selected {
154
+ start_countdown
155
+ }
156
+ }
157
+ label {
158
+ text ':'
159
+ font(height: 18) if OS.mac?
160
+ }
161
+ @sec_spinner = spinner {
162
+ text_limit 2
163
+ digits 0
164
+ maximum 59
165
+ selection bind(self, :sec)
166
+ enabled bind(self, :countdown, on_read: :!)
167
+ on_widget_default_selected {
168
+ start_countdown
169
+ }
170
+ }
171
+ }
172
+ end
173
+
174
+ def countdown_group_button_composite
175
+ composite {
176
+ row_layout {
177
+ margin_width 0
178
+ margin_height 0
179
+ }
180
+ @start_button = button {
181
+ text '&Start'
182
+ enabled bind(self, :countdown, on_read: :!)
183
+ on_widget_selected {
184
+ start_countdown
185
+ }
186
+ on_key_pressed { |event|
187
+ start_countdown if event.keyCode == swt(:cr)
188
+ }
189
+ }
190
+ @stop_button = button {
191
+ text 'St&op'
192
+ enabled bind(self, :countdown)
193
+ on_widget_selected {
194
+ stop_countdown
195
+ }
196
+ on_key_pressed { |event|
197
+ stop_countdown if event.keyCode == swt(:cr)
198
+ }
199
+ }
200
+ }
201
+ end
202
+
203
+ def display_about_dialog
204
+ message_box(body_root) {
205
+ text 'About'
206
+ message "Glimmer Timer\n\nCopyright (c) 2007-2021 Andy Maleh"
207
+ }.open
208
+ end
209
+
210
+ def start_countdown
211
+ self.countdown = true
212
+ @stop_button.swt_widget.set_focus
213
+ end
214
+
215
+ def stop_countdown
216
+ self.countdown = false
217
+ @min_spinner.swt_widget.set_focus
218
+ end
219
+
220
+ def play_countdown_done_sound
221
+ begin
222
+ file_or_stream = java.io.File.new(FILE_SOUND_ALARM)
223
+ audio_stream = AudioSystem.get_audio_input_stream(file_or_stream)
224
+ clip = AudioSystem.clip
225
+ clip.open(audio_stream)
226
+ clip.start
227
+ rescue => e
228
+ Glimmer::Config.logger.error e.full_message
229
+ end
230
+ end
231
+ end
232
+
233
+ Timer.launch
Binary file
@@ -23,7 +23,9 @@ require 'glimmer-dsl-swt'
23
23
 
24
24
  include Glimmer
25
25
 
26
- shell {
26
+ # A version of this was featured in a dzone article as an intro to Glimmer syntax:
27
+ # https://dzone.com/articles/an-introduction-glimmer
28
+ shell { |shell_proxy|
27
29
  text "User Profile"
28
30
 
29
31
  composite {
@@ -72,7 +74,7 @@ shell {
72
74
  button {
73
75
  text "close"
74
76
  layout_data :left, :center, true, true
75
- on_widget_selected { exit(0) }
77
+ on_widget_selected { shell_proxy.close }
76
78
  }
77
79
  }
78
80
  }.open
@@ -0,0 +1,164 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-swt'
23
+ require 'net/http'
24
+ require 'json'
25
+ require 'facets/string/titlecase'
26
+
27
+ class Weather
28
+ include Glimmer::UI::CustomShell
29
+
30
+ DEFAULT_FONT_HEIGHT = 30
31
+ DEFAULT_FOREGROUND = :white
32
+
33
+ attr_accessor :city, :temp, :temp_min, :temp_max, :feels_like, :humidity
34
+
35
+ before_body {
36
+ @weather_mutex = Mutex.new
37
+ self.city = 'Montreal, QC, CA'
38
+ fetch_weather!
39
+ }
40
+
41
+ after_body {
42
+ Thread.new do
43
+ loop do
44
+ sleep(10)
45
+ break if body_root.disposed?
46
+ fetch_weather!
47
+ end
48
+ end
49
+ }
50
+
51
+ body {
52
+ shell(:no_resize) {
53
+ grid_layout
54
+
55
+ text 'Glimmer Weather'
56
+ minimum_size 400, 300
57
+ background rgb(135, 176, 235)
58
+
59
+ text {
60
+ layout_data(:center, :center, true, true)
61
+
62
+ text <=> [self, :city]
63
+
64
+ on_key_pressed {|event|
65
+ if event.keyCode == swt(:cr) # carriage return
66
+ Thread.new do
67
+ fetch_weather!
68
+ end
69
+ end
70
+ }
71
+ }
72
+
73
+ tab_folder {
74
+ layout_data(:center, :center, true, true)
75
+
76
+ ['℃', '℉'].each do |temp_unit|
77
+ tab_item {
78
+ grid_layout 2, false
79
+
80
+ text temp_unit
81
+ background :transparent
82
+
83
+ rectangle(0, 0, [:default, -2], [:default, -2], 15, 15) {
84
+ foreground DEFAULT_FOREGROUND
85
+ }
86
+
87
+ %w[temp temp_min temp_max feels_like].each do |field_name|
88
+ temp_field(field_name, temp_unit)
89
+ end
90
+
91
+ humidity_field
92
+ }
93
+ end
94
+ }
95
+ }
96
+ }
97
+
98
+ def temp_field(field_name, temp_unit)
99
+ name_label(field_name)
100
+ label {
101
+ layout_data(:fill, :center, true, false)
102
+ text <= [self, field_name, on_read: ->(t) { "#{kelvin_to_temp_unit(t, temp_unit).to_f.round}°" }]
103
+ font height: DEFAULT_FONT_HEIGHT
104
+ foreground DEFAULT_FOREGROUND
105
+ }
106
+ end
107
+
108
+ def humidity_field
109
+ name_label('humidity')
110
+ label {
111
+ layout_data(:fill, :center, true, false)
112
+ text <= [self, 'humidity', on_read: ->(h) { "#{h.to_f.round}%" }]
113
+ font height: DEFAULT_FONT_HEIGHT
114
+ foreground DEFAULT_FOREGROUND
115
+ }
116
+ end
117
+
118
+ def name_label(field_name)
119
+ label {
120
+ layout_data :fill, :center, false, false
121
+ text field_name.titlecase
122
+ font height: DEFAULT_FONT_HEIGHT
123
+ foreground DEFAULT_FOREGROUND
124
+ }
125
+ end
126
+
127
+ def fetch_weather!
128
+ @weather_mutex.synchronize do
129
+ self.weather_data = JSON.parse(Net::HTTP.get('api.openweathermap.org', "/data/2.5/weather?q=#{city}&appid=1d16d70a9aec3570b5cbd27e6b421330"))
130
+ end
131
+ rescue
132
+ # No Op
133
+ end
134
+
135
+ def weather_data=(data)
136
+ @weather_data = data
137
+ main_data = data['main']
138
+ # temps come back in Kelvin
139
+ self.temp = main_data['temp']
140
+ self.temp_min = main_data['temp_min']
141
+ self.temp_max = main_data['temp_max']
142
+ self.feels_like = main_data['feels_like']
143
+ self.humidity = main_data['humidity']
144
+ end
145
+
146
+ def kelvin_to_temp_unit(kelvin, temp_unit)
147
+ temp_unit == '℃' ? kelvin_to_celsius(kelvin) : kelvin_to_fahrenheit(kelvin)
148
+ end
149
+
150
+ def kelvin_to_celsius(kelvin)
151
+ kelvin - 273.15
152
+ end
153
+
154
+ def celsius_to_fahrenheit(celsius)
155
+ (celsius * 9 / 5 ) + 32
156
+ end
157
+
158
+ def kelvin_to_fahrenheit(kelvin)
159
+ celsius_to_fahrenheit(kelvin_to_celsius(kelvin))
160
+ end
161
+
162
+ end
163
+
164
+ Weather.launch
@@ -0,0 +1,147 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-swt'
23
+
24
+ class HelloCoolBar
25
+ include Glimmer::UI::CustomShell
26
+
27
+ attr_accessor :operation, :font_size
28
+
29
+ def font_size_options
30
+ (10..30).to_a.map(&:to_s)
31
+ end
32
+
33
+ before_body {
34
+ self.font_size = '30'
35
+ }
36
+
37
+ body {
38
+ shell {
39
+ fill_layout(:vertical) {
40
+ margin_width 0
41
+ margin_height 0
42
+ }
43
+
44
+ text 'Hello, Cool Bar!'
45
+
46
+ cool_bar { # optionally takes a :flat style and/or :vertical style if you need vertical layout
47
+ tool_bar {
48
+ tool_item {
49
+ image cut_image # alternatively you can pass an image file path
50
+
51
+ on_widget_selected do
52
+ self.operation = 'Cut'
53
+ end
54
+ }
55
+ tool_item {
56
+ image copy_image # alternatively you can pass an image file path
57
+
58
+ on_widget_selected do
59
+ self.operation = 'Copy'
60
+ end
61
+ }
62
+ tool_item {
63
+ image paste_image # alternatively you can pass an image file path
64
+
65
+ on_widget_selected do
66
+ self.operation = 'Paste'
67
+ end
68
+ }
69
+ }
70
+ tool_bar {
71
+ tool_item {
72
+ text 'Font Size'
73
+ }
74
+ # a combo can be nested in a tool_bar (it auto-generates a tool_item for itself behind the scenes)
75
+ combo {
76
+ selection bind(self, :font_size)
77
+ }
78
+ }
79
+ }
80
+
81
+
82
+ label {
83
+ font <= [self, :font_size, on_read: ->(size) { {height: size.to_i} }]
84
+ text <= [self, :operation]
85
+ text <= [self, :font_size]
86
+ }
87
+ }
88
+ }
89
+
90
+ def cut_image
91
+ # building image on the fly with Canvas Shape DSL
92
+ image(25, 25) {
93
+ rectangle(0, 0, 25, 25) {
94
+ background_pattern 0, 0, 0, 25, :white, :gray
95
+ line(20, 2, 9, 15) {
96
+ line_width 2
97
+ }
98
+ line(5, 2, 16, 15) {
99
+ line_width 2
100
+ }
101
+ oval(2, 15, 8, 8) {
102
+ line_width 2
103
+ }
104
+ oval(16, 15, 8, 8) {
105
+ line_width 2
106
+ }
107
+ }
108
+ }
109
+ end
110
+
111
+ def copy_image
112
+ # building image on the fly with Canvas Shape DSL
113
+ image(25, 25) {
114
+ rectangle(0, 0, 25, 25) {
115
+ background_pattern 0, 0, 0, 25, :white, :gray
116
+ rectangle([:default, 2], [:default, -2], 14, 14, 5, 5) {
117
+ line_width 2
118
+ }
119
+ rectangle([:default, -2], [:default, 2], 14, 14, 5, 5) {
120
+ line_width 2
121
+ }
122
+ }
123
+ }
124
+ end
125
+
126
+ def paste_image
127
+ image(25, 25) {
128
+ rectangle(0, 0, 25, 25) {
129
+ background_pattern 0, 0, 0, 25, :white, :gray
130
+ rectangle(:default, [:default, 1], 15, 20, 5, 5) {
131
+ line_width 2
132
+ }
133
+ line(7, 8, 18, 8) {
134
+ line_width 2
135
+ }
136
+ line(7, 13, 18, 13) {
137
+ line_width 2
138
+ }
139
+ line(7, 18, 18, 18) {
140
+ line_width 2
141
+ }
142
+ }
143
+ }
144
+ end
145
+ end
146
+
147
+ HelloCoolBar.launch