glimmer-dsl-swt 4.20.0.0 → 4.20.0.5
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/CHANGELOG.md +26 -0
- data/README.md +18 -14
- data/VERSION +1 -1
- data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +11 -7
- data/docs/reference/GLIMMER_SAMPLES.md +170 -17
- data/docs/reference/GLIMMER_STYLE_GUIDE.md +4 -3
- data/glimmer-dsl-swt.gemspec +0 -0
- data/lib/glimmer/dsl/swt/widget_expression.rb +2 -0
- data/lib/glimmer/dsl/swt/widget_listener_expression.rb +3 -3
- data/lib/glimmer/rake_task/scaffold.rb +0 -2
- data/lib/glimmer/swt/combo_proxy.rb +48 -0
- data/lib/glimmer/swt/display_proxy.rb +11 -8
- data/lib/glimmer/swt/tool_bar_proxy.rb +51 -0
- data/lib/glimmer/swt/widget_proxy.rb +7 -1
- data/lib/glimmer/ui/custom_shell.rb +3 -3
- data/lib/glimmer/ui/custom_widget.rb +5 -2
- data/samples/elaborate/calculator.rb +116 -0
- data/samples/elaborate/calculator/model/command.rb +105 -0
- data/samples/elaborate/calculator/model/command/all_clear.rb +17 -0
- data/samples/elaborate/calculator/model/command/command_history.rb +0 -0
- data/samples/elaborate/calculator/model/command/equals.rb +18 -0
- data/samples/elaborate/calculator/model/command/number.rb +20 -0
- data/samples/elaborate/calculator/model/command/operation.rb +27 -0
- data/samples/elaborate/calculator/model/command/operation/add.rb +15 -0
- data/samples/elaborate/calculator/model/command/operation/divide.rb +15 -0
- data/samples/elaborate/calculator/model/command/operation/multiply.rb +15 -0
- data/samples/elaborate/calculator/model/command/operation/subtract.rb +15 -0
- data/samples/elaborate/calculator/model/command/point.rb +20 -0
- data/samples/elaborate/calculator/model/presenter.rb +30 -0
- data/samples/elaborate/login.rb +15 -13
- data/samples/elaborate/tetris.rb +4 -4
- data/samples/elaborate/tetris/model/game.rb +0 -3
- data/samples/elaborate/timer.rb +233 -0
- data/samples/elaborate/timer/alarm1.wav +0 -0
- data/samples/elaborate/timer/sounds/alarm1.wav +0 -0
- data/samples/elaborate/user_profile.rb +4 -2
- data/samples/elaborate/weather.rb +164 -0
- data/samples/hello/hello_composite.rb +71 -0
- data/samples/hello/hello_cool_bar.rb +147 -0
- data/samples/hello/hello_layout.rb +243 -0
- data/samples/hello/hello_shell.rb +205 -0
- data/samples/hello/hello_text.rb +120 -0
- data/samples/hello/hello_tool_bar.rb +143 -0
- metadata +28 -3
@@ -0,0 +1,17 @@
|
|
1
|
+
class Calculator
|
2
|
+
module Model
|
3
|
+
class Command
|
4
|
+
class AllClear < Command
|
5
|
+
keywords 'AC', 'c', 'C', 8.chr, 27.chr, 127.chr
|
6
|
+
|
7
|
+
def execute
|
8
|
+
self.result = '0'
|
9
|
+
self.number1 = nil
|
10
|
+
self.number2 = nil
|
11
|
+
self.operation = nil
|
12
|
+
command_history.clear
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Calculator
|
2
|
+
module Model
|
3
|
+
class Command
|
4
|
+
class Equals < Command
|
5
|
+
keywords '=', "\r"
|
6
|
+
|
7
|
+
def execute
|
8
|
+
if number1 && number2 && operation
|
9
|
+
self.result = operation.calculate.to_s
|
10
|
+
self.number1 = self.result
|
11
|
+
else
|
12
|
+
self.result = last_result || '0'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Calculator
|
2
|
+
module Model
|
3
|
+
class Command
|
4
|
+
class Number < Command
|
5
|
+
keywords ('0'..'9').to_a
|
6
|
+
|
7
|
+
def execute
|
8
|
+
self.result = last_result.nil? || (!last_command.is_a?(Number) && !last_command.is_a?(Point)) ? button : last_result + button
|
9
|
+
if operation.nil? || last_command.is_a?(Equals)
|
10
|
+
self.number1 = self.result
|
11
|
+
self.number2 = nil
|
12
|
+
self.operation = nil
|
13
|
+
else
|
14
|
+
self.number2 = self.result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Calculator
|
2
|
+
module Model
|
3
|
+
class Command
|
4
|
+
class Operation < Command
|
5
|
+
def execute
|
6
|
+
if number1 && number2 && operation && !last_command.is_a?(Equals)
|
7
|
+
self.result = operation.calculate.to_s
|
8
|
+
self.number1 = self.result
|
9
|
+
else
|
10
|
+
self.result = last_result || '0'
|
11
|
+
self.operation = self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate
|
16
|
+
calculation_result = BigDecimal.new(number1.to_s).send(operation_method, BigDecimal.new(number2.to_s)).to_f
|
17
|
+
calculation_result.to_s.match(/\.0+$/) ? calculation_result.to_i : calculation_result
|
18
|
+
end
|
19
|
+
|
20
|
+
# Subclasses must implement to indicate operation method on number (e.g. :+ for addition)
|
21
|
+
def operation_method
|
22
|
+
raise 'Not implemented!'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Calculator
|
2
|
+
module Model
|
3
|
+
class Command
|
4
|
+
class Point < Command
|
5
|
+
keyword '.'
|
6
|
+
|
7
|
+
def execute
|
8
|
+
self.result = last_result.nil? || !last_command.is_a?(Number) ? '0.' : "#{last_result}."
|
9
|
+
if operation.nil? || last_command.is_a?(Equals)
|
10
|
+
self.number1 = self.result
|
11
|
+
self.number2 = nil
|
12
|
+
self.operation = nil
|
13
|
+
else
|
14
|
+
self.number2 = self.result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'command'
|
2
|
+
|
3
|
+
class Calculator
|
4
|
+
module Model
|
5
|
+
class Presenter
|
6
|
+
FORMATTER = {
|
7
|
+
nil => '0',
|
8
|
+
'NaN' => 'Not a number'
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_accessor :result
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
self.result = '0'
|
15
|
+
end
|
16
|
+
|
17
|
+
def press(button)
|
18
|
+
command = Command.for(button)
|
19
|
+
if command
|
20
|
+
new_result = command.result
|
21
|
+
self.result = FORMATTER[new_result] || new_result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def purge_command_history
|
26
|
+
Command.purge_command_history
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/samples/elaborate/login.rb
CHANGED
@@ -35,24 +35,19 @@ class LoginPresenter
|
|
35
35
|
|
36
36
|
def status=(status)
|
37
37
|
@status = status
|
38
|
-
|
39
|
-
notify_observers("logged_in")
|
40
|
-
notify_observers("logged_out")
|
41
38
|
end
|
42
39
|
|
43
40
|
def valid?
|
44
41
|
!@user_name.to_s.strip.empty? && !@password.to_s.strip.empty?
|
45
42
|
end
|
46
43
|
|
47
|
-
def logged_in
|
44
|
+
def logged_in?
|
48
45
|
self.status == "Logged In"
|
49
46
|
end
|
50
|
-
alias logged_in? logged_in
|
51
47
|
|
52
|
-
def logged_out
|
53
|
-
!self.logged_in
|
48
|
+
def logged_out?
|
49
|
+
!self.logged_in?
|
54
50
|
end
|
55
|
-
alias logged_out? logged_out
|
56
51
|
|
57
52
|
def login!
|
58
53
|
return unless valid?
|
@@ -77,13 +72,15 @@ class Login
|
|
77
72
|
body {
|
78
73
|
shell {
|
79
74
|
text "Login"
|
75
|
+
|
80
76
|
composite {
|
81
77
|
grid_layout 2, false #two columns with differing widths
|
82
78
|
|
83
79
|
label { text "Username:" } # goes in column 1
|
84
80
|
@user_name_text = text { # goes in column 2
|
85
81
|
text <=> [@presenter, :user_name]
|
86
|
-
enabled <= [@presenter, :logged_out]
|
82
|
+
enabled <= [@presenter, :logged_out?, computed_by: :status]
|
83
|
+
|
87
84
|
on_key_pressed { |event|
|
88
85
|
@password_text.set_focus if event.keyCode == swt(:cr)
|
89
86
|
}
|
@@ -92,7 +89,8 @@ class Login
|
|
92
89
|
label { text "Password:" }
|
93
90
|
@password_text = text(:password, :border) {
|
94
91
|
text <=> [@presenter, :password]
|
95
|
-
enabled <= [@presenter, :logged_out]
|
92
|
+
enabled <= [@presenter, :logged_out?, computed_by: :status]
|
93
|
+
|
96
94
|
on_key_pressed { |event|
|
97
95
|
@presenter.login! if event.keyCode == swt(:cr)
|
98
96
|
}
|
@@ -103,16 +101,20 @@ class Login
|
|
103
101
|
|
104
102
|
button {
|
105
103
|
text "Login"
|
106
|
-
enabled <= [@presenter, :logged_out]
|
104
|
+
enabled <= [@presenter, :logged_out?, computed_by: :status]
|
105
|
+
|
107
106
|
on_widget_selected { @presenter.login! }
|
108
107
|
on_key_pressed { |event|
|
109
|
-
|
108
|
+
if event.keyCode == swt(:cr)
|
109
|
+
@presenter.login!
|
110
|
+
end
|
110
111
|
}
|
111
112
|
}
|
112
113
|
|
113
114
|
button {
|
114
115
|
text "Logout"
|
115
|
-
enabled <= [@presenter, :logged_in]
|
116
|
+
enabled <= [@presenter, :logged_in?, computed_by: :status]
|
117
|
+
|
116
118
|
on_widget_selected { @presenter.logout! }
|
117
119
|
on_key_pressed { |event|
|
118
120
|
if event.keyCode == swt(:cr)
|
data/samples/elaborate/tetris.rb
CHANGED
@@ -54,7 +54,7 @@ class Tetris
|
|
54
54
|
Display.app_name = 'Glimmer Tetris'
|
55
55
|
|
56
56
|
display {
|
57
|
-
|
57
|
+
on_swt_keydown { |key_event|
|
58
58
|
case key_event.keyCode
|
59
59
|
when swt(:arrow_down), 's'.bytes.first
|
60
60
|
game.down! if OS.mac?
|
@@ -82,7 +82,7 @@ class Tetris
|
|
82
82
|
|
83
83
|
# invoke game.down! on keyup with Windows/Linux since they seem to group-render similar events, preventing intermediate renders (causing invisiblity while holding keys)
|
84
84
|
if !OS.mac?
|
85
|
-
|
85
|
+
on_swt_keyup { |key_event|
|
86
86
|
case key_event.keyCode
|
87
87
|
when swt(:arrow_down), 's'.bytes.first
|
88
88
|
game.down!
|
@@ -91,11 +91,11 @@ class Tetris
|
|
91
91
|
end
|
92
92
|
|
93
93
|
# if running in app mode, set the Mac app about dialog (ignored in platforms)
|
94
|
-
|
94
|
+
on_about {
|
95
95
|
show_about_dialog
|
96
96
|
}
|
97
97
|
|
98
|
-
|
98
|
+
on_quit {
|
99
99
|
exit(0)
|
100
100
|
}
|
101
101
|
}
|
@@ -33,8 +33,6 @@ class Tetris
|
|
33
33
|
class Game
|
34
34
|
PLAYFIELD_WIDTH = 10
|
35
35
|
PLAYFIELD_HEIGHT = 20
|
36
|
-
# PLAYFIELD_WIDTH = 5
|
37
|
-
# PLAYFIELD_HEIGHT = 5
|
38
36
|
PREVIEW_PLAYFIELD_WIDTH = 4
|
39
37
|
PREVIEW_PLAYFIELD_HEIGHT = 2
|
40
38
|
SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
|
@@ -201,7 +199,6 @@ class Tetris
|
|
201
199
|
|
202
200
|
def delay
|
203
201
|
[1.1 - (level.to_i * 0.1), 0.001].max
|
204
|
-
# 99999
|
205
202
|
end
|
206
203
|
|
207
204
|
def beep
|
@@ -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
|