glimmer-dsl-libui 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -42,11 +42,17 @@ window('Area Gallery', 400, 400) {
42
42
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
43
43
  }
44
44
  path { # a dynamic path is added semi-declaratively inside on_draw block
45
- arc(200, 200, 90, 0, 360, false)
46
-
45
+ circle(200, 200, 90)
46
+
47
47
  fill r: 202, g: 102, b: 204, a: 0.5
48
48
  stroke r: 0, g: 0, b: 0, thickness: 2
49
49
  }
50
+ path { # a dynamic path is added semi-declaratively inside on_draw block
51
+ arc(400, 220, 180, 90, 90, false)
52
+
53
+ fill r: 204, g: 102, b: 204, a: 0.5
54
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
55
+ }
50
56
  end
51
57
 
52
58
  on_mouse_event do |area_mouse_event|
@@ -96,18 +96,28 @@ window('Area Gallery', 400, 400) {
96
96
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
97
97
  }
98
98
  path { # a dynamic path is added semi-declaratively inside on_draw block
99
- arc {
99
+ circle {
100
100
  x_center 200
101
101
  y_center 200
102
102
  radius 90
103
- start_angle 0
104
- sweep 360
105
- is_negative false
106
103
  }
107
-
104
+
108
105
  fill r: 202, g: 102, b: 204, a: 0.5
109
106
  stroke r: 0, g: 0, b: 0, thickness: 2
110
107
  }
108
+ path { # a dynamic path is added semi-declaratively inside on_draw block
109
+ arc {
110
+ x_center 400
111
+ y_center 220
112
+ radius 180
113
+ start_angle 90
114
+ sweep 90
115
+ is_negative false
116
+ }
117
+
118
+ fill r: 204, g: 102, b: 204, a: 0.5
119
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
120
+ }
111
121
  end
112
122
 
113
123
  on_mouse_event do |area_mouse_event|
@@ -0,0 +1,223 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class ColorTheCircles
4
+ include Glimmer
5
+
6
+ WINDOW_WIDTH = 800
7
+ WINDOW_HEIGHT = 600
8
+ CIRCLE_MIN_RADIUS = 15
9
+ CIRCLE_MAX_RADIUS = 50
10
+ MARGIN_WIDTH = 55
11
+ MARGIN_HEIGHT = 155
12
+ TIME_MAX_EASY = 4
13
+ TIME_MAX_MEDIUM = 3
14
+ TIME_MAX_HARD = 2
15
+ TIME_MAX_INSANE = 1
16
+
17
+ attr_accessor :score
18
+
19
+ def initialize
20
+ @circles_data = []
21
+ @score = 0
22
+ @time_max = TIME_MAX_HARD
23
+ @game_over = false
24
+ register_observers
25
+ setup_circle_factory
26
+ end
27
+
28
+ def register_observers
29
+ observer = Glimmer::DataBinding::Observer.proc do |new_score|
30
+ @score_label.text = new_score.to_s
31
+ if new_score == -20
32
+ @game_over = true
33
+ msg_box('You Lost!', 'Sorry! Your score reached -20')
34
+ restart_game
35
+ elsif new_score == 0
36
+ @game_over = true
37
+ msg_box('You Won!', 'Congratulations! Your score reached 0')
38
+ restart_game
39
+ end
40
+ end
41
+ observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
42
+ end
43
+
44
+ def setup_circle_factory
45
+ consumer = Proc.new do
46
+ unless @game_over
47
+ if @circles_data.empty?
48
+ # start with 3 circles to make more challenging
49
+ add_circle until @circles_data.size > 3
50
+ else
51
+ add_circle
52
+ end
53
+ end
54
+ delay = rand * @time_max
55
+ Glimmer::LibUI.timer(delay, repeat: false, &consumer)
56
+ end
57
+ Glimmer::LibUI.queue_main(&consumer)
58
+ end
59
+
60
+ def add_circle
61
+ circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
62
+ circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
63
+ circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
64
+ stroke_color = Glimmer::LibUI.x11_colors.sample
65
+ @circles_data << {
66
+ args: [circle_x_center, circle_y_center, circle_radius],
67
+ fill: nil,
68
+ stroke: stroke_color
69
+ }
70
+ @area.queue_redraw_all
71
+ self.score -= 1 # notifies score observers automatically of change
72
+ end
73
+
74
+ def restart_game
75
+ @score = 0 # update variable directly to avoid notifying observers
76
+ @circles_data.clear
77
+ @game_over = false
78
+ end
79
+
80
+ def color_circle(x, y)
81
+ clicked_circle_data = @circles_data.find do |circle_data|
82
+ circle_data[:fill].nil? && circle_data[:circle].include?(x, y)
83
+ end
84
+ if clicked_circle_data
85
+ clicked_circle_data[:fill] = clicked_circle_data[:stroke]
86
+ push_colored_circle_behind_uncolored_circles(clicked_circle_data)
87
+ @area.queue_redraw_all
88
+ self.score += 1 # notifies score observers automatically of change
89
+ end
90
+ end
91
+
92
+ def push_colored_circle_behind_uncolored_circles(colored_circle_data)
93
+ removed_colored_circle_data = @circles_data.delete(colored_circle_data)
94
+ last_colored_circle_data = @circles_data.select {|cd| cd[:fill]}.last
95
+ last_colored_circle_data_index = @circles_data.index(last_colored_circle_data) || -1
96
+ @circles_data.insert(last_colored_circle_data_index + 1, removed_colored_circle_data)
97
+ end
98
+
99
+ def launch
100
+ menu('Actions') {
101
+ menu_item('Restart') {
102
+ on_clicked do
103
+ restart_game
104
+ end
105
+ }
106
+
107
+ quit_menu_item
108
+ }
109
+
110
+ menu('Difficulty') {
111
+ radio_menu_item('Easy') {
112
+ on_clicked do
113
+ @time_max = TIME_MAX_EASY
114
+ end
115
+ }
116
+
117
+ radio_menu_item('Medium') {
118
+ on_clicked do
119
+ @time_max = TIME_MAX_MEDIUM
120
+ end
121
+ }
122
+
123
+ radio_menu_item('Hard') {
124
+ checked true
125
+
126
+ on_clicked do
127
+ @time_max = TIME_MAX_HARD
128
+ end
129
+ }
130
+
131
+ radio_menu_item('Insane') {
132
+ on_clicked do
133
+ @time_max = TIME_MAX_INSANE
134
+ end
135
+ }
136
+ }
137
+
138
+ menu('Help') {
139
+ menu_item('Instructions') {
140
+ on_clicked do
141
+ msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
142
+ end
143
+ }
144
+ }
145
+
146
+ window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
147
+ margined true
148
+
149
+ grid {
150
+ button('Restart') {
151
+ left 0
152
+ top 0
153
+ halign :center
154
+
155
+ on_clicked do
156
+ restart_game
157
+ end
158
+ }
159
+
160
+ label('Score goes down as circles are added. If it reaches -20, you lose!') {
161
+ left 0
162
+ top 1
163
+ halign :center
164
+ }
165
+
166
+ label('Click circles to color and score! Once score reaches 0, you win!') {
167
+ left 0
168
+ top 2
169
+ halign :center
170
+ }
171
+
172
+ horizontal_box {
173
+ left 0
174
+ top 3
175
+ halign :center
176
+
177
+ label('Score:') {
178
+ stretchy false
179
+ }
180
+
181
+ @score_label = label(@score.to_s) {
182
+ stretchy false
183
+ }
184
+ }
185
+
186
+ vertical_box {
187
+ left 0
188
+ top 4
189
+ hexpand true
190
+ vexpand true
191
+ halign :fill
192
+ valign :fill
193
+
194
+ @area = area {
195
+ on_draw do |area_draw_params|
196
+ path {
197
+ rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
198
+
199
+ fill :white
200
+ }
201
+
202
+ @circles_data.each do |circle_data|
203
+ path {
204
+ circle_data[:circle] = circle(*circle_data[:args])
205
+
206
+ fill circle_data[:fill]
207
+ stroke circle_data[:stroke]
208
+ }
209
+ end
210
+ end
211
+
212
+ on_mouse_down do |area_mouse_event|
213
+ color_circle(area_mouse_event[:x], area_mouse_event[:y])
214
+ end
215
+ }
216
+ }
217
+
218
+ }
219
+ }.show
220
+ end
221
+ end
222
+
223
+ ColorTheCircles.new.launch
@@ -48,7 +48,7 @@ class TinyMidiPlayer
48
48
  end
49
49
 
50
50
  def create_gui
51
- menu('Help') { |m|
51
+ menu('Help') {
52
52
  menu_item('Version') {
53
53
  on_clicked do
54
54
  show_version
data/examples/timer.rb ADDED
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ class Timer
6
+ include Glimmer
7
+
8
+ SECOND_MAX = 59
9
+ MINUTE_MAX = 59
10
+ HOUR_MAX = 23
11
+
12
+ def initialize
13
+ @pid = nil
14
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
15
+ at_exit { stop_alarm }
16
+ setup_timer
17
+ create_gui
18
+ end
19
+
20
+ def stop_alarm
21
+ if @pid
22
+ if @th.alive?
23
+ Process.kill(:SIGKILL, @pid)
24
+ @pid = nil
25
+ else
26
+ @pid = nil
27
+ end
28
+ end
29
+ end
30
+
31
+ def play_alarm
32
+ stop_alarm
33
+ if @pid.nil?
34
+ begin
35
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
36
+ @th = Process.detach @pid
37
+ rescue Errno::ENOENT
38
+ warn 'Timidty++ not found. Please install Timidity++.'
39
+ warn 'https://sourceforge.net/projects/timidity/'
40
+ end
41
+ end
42
+ end
43
+
44
+ def setup_timer
45
+ unless @setup_timer
46
+ Glimmer::LibUI.timer(1) do
47
+ if @started
48
+ seconds = @sec_spinbox.value
49
+ minutes = @min_spinbox.value
50
+ hours = @hour_spinbox.value
51
+ if seconds > 0
52
+ @sec_spinbox.value = seconds -= 1
53
+ end
54
+ if seconds == 0
55
+ if minutes > 0
56
+ @min_spinbox.value = minutes -= 1
57
+ @sec_spinbox.value = seconds = SECOND_MAX
58
+ end
59
+ if minutes == 0
60
+ if hours > 0
61
+ @hour_spinbox.value = hours -= 1
62
+ @min_spinbox.value = minutes = MINUTE_MAX
63
+ @sec_spinbox.value = seconds = SECOND_MAX
64
+ end
65
+ if hours == 0 && minutes == 0 && seconds == 0
66
+ @start_button.enabled = true
67
+ @stop_button.enabled = false
68
+ @started = false
69
+ unless @played
70
+ play_alarm
71
+ msg_box('Alarm', 'Countdown Is Finished!')
72
+ @played = true
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ @setup_timer = true
80
+ end
81
+ end
82
+
83
+ def create_gui
84
+ window('Timer') {
85
+ margined true
86
+
87
+ group('Countdown') {
88
+ vertical_box {
89
+ horizontal_box {
90
+ @hour_spinbox = spinbox(0, HOUR_MAX) {
91
+ stretchy false
92
+ value 0
93
+ }
94
+ label(':') {
95
+ stretchy false
96
+ }
97
+ @min_spinbox = spinbox(0, MINUTE_MAX) {
98
+ stretchy false
99
+ value 0
100
+ }
101
+ label(':') {
102
+ stretchy false
103
+ }
104
+ @sec_spinbox = spinbox(0, SECOND_MAX) {
105
+ stretchy false
106
+ value 0
107
+ }
108
+ }
109
+ horizontal_box {
110
+ @start_button = button('Start') {
111
+ on_clicked do
112
+ @start_button.enabled = false
113
+ @stop_button.enabled = true
114
+ @started = true
115
+ @played = false
116
+ end
117
+ }
118
+
119
+ @stop_button = button('Stop') {
120
+ enabled false
121
+
122
+ on_clicked do
123
+ @start_button.enabled = true
124
+ @stop_button.enabled = false
125
+ @started = false
126
+ end
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }.show
132
+ end
133
+ end
134
+
135
+ Timer.new
Binary file
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 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/libui/control_proxy/menu_item_proxy'
23
+
24
+ module Glimmer
25
+ module LibUI
26
+ class ControlProxy
27
+ class MenuItemProxy < ControlProxy
28
+ # Proxy for LibUI radio menu item object
29
+ #
30
+ # Follows the Proxy Design Pattern
31
+ class RadioMenuItemProxy < MenuItemProxy
32
+ def checked(value = nil)
33
+ if !value.nil?
34
+ if Glimmer::LibUI.integer_to_boolean(value) != checked?
35
+ super
36
+ if Glimmer::LibUI.integer_to_boolean(value)
37
+ (@parent_proxy.children - [self]).select {|c| c.is_a?(MenuItemProxy)}.each do |menu_item|
38
+ menu_item.checked = false
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ alias checked? checked
45
+ alias set_checked checked
46
+ alias checked= checked
47
+
48
+ def handle_listener(listener_name, &listener)
49
+ if listener_name.to_s == 'on_clicked'
50
+ radio_listener = Proc.new do
51
+ self.checked = true if !checked?
52
+ listener.call(self)
53
+ end
54
+ super(listener_name, &radio_listener)
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def build_control
63
+ @libui = @parent_proxy.append_check_item(*@args)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -20,6 +20,7 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'glimmer/libui/control_proxy'
23
+ require 'glimmer/libui/parent'
23
24
 
24
25
  module Glimmer
25
26
  module LibUI
@@ -28,6 +29,8 @@ module Glimmer
28
29
  #
29
30
  # Follows the Proxy Design Pattern
30
31
  class MenuProxy < ControlProxy
32
+ include Parent
33
+
31
34
  DEFAULT_TEXT = ''
32
35
 
33
36
  private
@@ -223,7 +223,13 @@ module Glimmer
223
223
  elsif ::LibUI.respond_to?("#{libui_api_keyword}_set_#{method_name.to_s.sub(/=$/, '')}") && !args.empty?
224
224
  property = method_name.to_s.sub(/=$/, '')
225
225
  args[0] = Glimmer::LibUI.boolean_to_integer(args.first) if BOOLEAN_PROPERTIES.include?(property) && (args.first.is_a?(TrueClass) || args.first.is_a?(FalseClass))
226
- ::LibUI.send("#{libui_api_keyword}_set_#{property}", @libui, *args)
226
+ if property.to_s == 'checked'
227
+ current_value = Glimmer::LibUI.integer_to_boolean(::LibUI.send("#{libui_api_keyword}_checked", @libui))
228
+ new_value = Glimmer::LibUI.integer_to_boolean(args[0])
229
+ ::LibUI.send("#{libui_api_keyword}_set_#{property}", @libui, *args) if new_value != current_value
230
+ else
231
+ ::LibUI.send("#{libui_api_keyword}_set_#{property}", @libui, *args)
232
+ end
227
233
  elsif ::LibUI.respond_to?("#{libui_api_keyword}_#{method_name}") && !args.empty?
228
234
  ::LibUI.send("#{libui_api_keyword}_#{method_name}", @libui, *args)
229
235
  elsif ::LibUI.respond_to?("control_#{method_name.to_s.sub(/\?$/, '')}") && args.empty?
@@ -29,11 +29,14 @@ module Glimmer
29
29
  parameter_defaults 0, 0, 0, 0, 360, false
30
30
 
31
31
  def draw(area_draw_params)
32
- @args[5] ||= Glimmer::LibUI.boolean_to_integer(@args[5], allow_nil: false)
32
+ arc_args = @args.dup
33
+ arc_args[3] = Glimmer::LibUI.degrees_to_radians(arc_args[3])
34
+ arc_args[4] = Glimmer::LibUI.degrees_to_radians(arc_args[4])
35
+ arc_args[5] = Glimmer::LibUI.boolean_to_integer(arc_args[5], allow_nil: false)
33
36
  if parent.is_a?(Figure) && parent.x.nil? && parent.y.nil?
34
- ::LibUI.draw_path_new_figure_with_arc(path_proxy.libui, *@args)
37
+ ::LibUI.draw_path_new_figure_with_arc(path_proxy.libui, *arc_args)
35
38
  else
36
- ::LibUI.draw_path_arc_to(path_proxy.libui, *@args)
39
+ ::LibUI.draw_path_arc_to(path_proxy.libui, *arc_args)
37
40
  end
38
41
  super
39
42
  end
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 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/libui/shape'
23
+
24
+ module Glimmer
25
+ module LibUI
26
+ class Shape
27
+ class Circle < Shape
28
+ parameters :x_center, :y_center, :radius
29
+ parameter_defaults 0, 0, 0
30
+
31
+ def draw(area_draw_params)
32
+ arc_args = @args.dup
33
+ arc_args[3] = 0
34
+ arc_args[4] = Math::PI * 2.0
35
+ arc_args[5] = 0
36
+ if parent.is_a?(Figure) && parent.x.nil? && parent.y.nil?
37
+ ::LibUI.draw_path_new_figure_with_arc(path_proxy.libui, *arc_args)
38
+ else
39
+ ::LibUI.draw_path_arc_to(path_proxy.libui, *arc_args)
40
+ end
41
+ super
42
+ end
43
+
44
+ def include?(x, y)
45
+ (x - x_center)**2 + (y - y_center)**2 < radius**2
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -32,6 +32,10 @@ module Glimmer
32
32
  ::LibUI.draw_path_add_rectangle(path_proxy.libui, *@args)
33
33
  super
34
34
  end
35
+
36
+ def include?(x, y)
37
+ x.between?(self.x, self.x + width) && y.between?(self.y, self.y + height)
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -32,6 +32,10 @@ module Glimmer
32
32
  ::LibUI.draw_path_add_rectangle(path_proxy.libui, *@args, length)
33
33
  super
34
34
  end
35
+
36
+ def include?(x, y)
37
+ x.between?(self.x, self.x + length) && y.between?(self.y, self.y + length)
38
+ end
35
39
  end
36
40
  end
37
41
  end