glimmer-dsl-libui 0.2.0 → 0.2.1

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.
@@ -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