glimmer-dsl-libui 0.4.5 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +388 -57
  4. data/VERSION +1 -1
  5. data/examples/button_counter.rb +2 -1
  6. data/examples/color_button.rb +18 -13
  7. data/examples/color_button2.rb +14 -0
  8. data/examples/date_time_picker.rb +19 -14
  9. data/examples/date_time_picker2.rb +20 -0
  10. data/examples/font_button.rb +17 -12
  11. data/examples/font_button2.rb +18 -0
  12. data/examples/histogram.rb +1 -6
  13. data/examples/meta_example.rb +17 -6
  14. data/examples/midi_player.rb +5 -6
  15. data/examples/midi_player2.rb +83 -0
  16. data/examples/midi_player3.rb +84 -0
  17. data/examples/snake.rb +19 -10
  18. data/examples/tetris.rb +15 -18
  19. data/glimmer-dsl-libui.gemspec +0 -0
  20. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +4 -0
  21. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +4 -0
  22. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +17 -2
  23. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +4 -0
  24. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +4 -0
  25. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -2
  26. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +8 -0
  27. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +4 -0
  28. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +16 -4
  29. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -2
  30. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +19 -0
  31. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +37 -0
  32. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -2
  33. data/lib/glimmer/libui/control_proxy.rb +2 -2
  34. data/lib/glimmer/libui/data_bindable.rb +27 -2
  35. metadata +8 -2
@@ -0,0 +1,14 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ include Glimmer
4
+
5
+ window('color button', 240) {
6
+ color_button { |cb|
7
+ color :blue
8
+
9
+ on_changed do
10
+ rgba = cb.color
11
+ p rgba
12
+ end
13
+ }
14
+ }.show
@@ -2,19 +2,24 @@
2
2
 
3
3
  require 'glimmer-dsl-libui'
4
4
 
5
- include Glimmer
6
-
7
- window('Date Time Pickers', 300, 200) {
8
- vertical_box {
9
- date_time_picker { |dtp|
10
- on_changed do
11
- time = dtp.time
12
- p time
13
- end
14
- }
15
- }
5
+ class DateTimePicker
6
+ include Glimmer
7
+
8
+ attr_accessor :picked_time
16
9
 
17
- on_closing do
18
- puts 'Bye Bye'
10
+ def launch
11
+ window('Date Time Pickers', 300, 200) {
12
+ vertical_box {
13
+ date_time_picker {
14
+ time <=> [self, :picked_time, after_write: ->(time) { p time }]
15
+ }
16
+ }
17
+
18
+ on_closing do
19
+ puts 'Bye Bye'
20
+ end
21
+ }.show
19
22
  end
20
- }.show
23
+ end
24
+
25
+ DateTimePicker.new.launch
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Date Time Pickers', 300, 200) {
8
+ vertical_box {
9
+ date_time_picker { |dtp|
10
+ on_changed do
11
+ time = dtp.time
12
+ p time
13
+ end
14
+ }
15
+ }
16
+
17
+ on_closing do
18
+ puts 'Bye Bye'
19
+ end
20
+ }.show
@@ -2,17 +2,22 @@
2
2
 
3
3
  require 'glimmer-dsl-libui'
4
4
 
5
- include Glimmer
6
-
7
- window('hello world', 300, 200) {
8
- font_button { |fb|
9
- on_changed do
10
- font_descriptor = fb.font
11
- p font_descriptor
12
- end
13
- }
5
+ class FontButton
6
+ include Glimmer
7
+
8
+ attr_accessor :font_descriptor
14
9
 
15
- on_closing do
16
- puts 'Bye Bye'
10
+ def launch
11
+ window('hello world', 300, 200) {
12
+ font_button {
13
+ font <=> [self, :font_descriptor, after_write: -> { p font_descriptor }]
14
+ }
15
+
16
+ on_closing do
17
+ puts 'Bye Bye'
18
+ end
19
+ }.show
17
20
  end
18
- }.show
21
+ end
22
+
23
+ FontButton.new.launch
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('hello world', 300, 200) {
8
+ font_button { |fb|
9
+ on_changed do
10
+ font_descriptor = fb.font
11
+ p font_descriptor
12
+ end
13
+ }
14
+
15
+ on_closing do
16
+ puts 'Bye Bye'
17
+ end
18
+ }.show
@@ -71,12 +71,7 @@ class Histogram
71
71
 
72
72
  color_button { |cb|
73
73
  stretchy false
74
- color COLOR_BLUE
75
-
76
- on_changed do
77
- @histogram_color = cb.color
78
- @area.queue_redraw_all
79
- end
74
+ color <=> [self, :histogram_color, after_write: -> { @area.queue_redraw_all }]
80
75
  }
81
76
  }
82
77
 
@@ -24,18 +24,28 @@ class MetaExample
24
24
  @examples
25
25
  end
26
26
 
27
+ def basic_examples
28
+ examples.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
29
+ end
30
+
31
+ def advanced_examples
32
+ examples - basic_examples
33
+ end
34
+
27
35
  def examples_with_versions
28
- examples.map do |example|
29
- version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
30
- end
36
+ append_versions(examples)
31
37
  end
32
38
 
33
39
  def basic_examples_with_versions
34
- examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
40
+ append_versions(basic_examples)
35
41
  end
36
42
 
37
43
  def advanced_examples_with_versions
38
- examples_with_versions - basic_examples_with_versions
44
+ append_versions(advanced_examples)
45
+ end
46
+
47
+ def append_versions(examples)
48
+ examples.map { |example| version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example }
39
49
  end
40
50
 
41
51
  def file_path_for(example)
@@ -72,7 +82,7 @@ class MetaExample
72
82
  end
73
83
 
74
84
  def launch
75
- window('Meta-Example', 700, 500) {
85
+ window('Meta-Example', 1000, 500) {
76
86
  margined true
77
87
 
78
88
  horizontal_box {
@@ -107,6 +117,7 @@ class MetaExample
107
117
  @advanced_example_radio_buttons = radio_buttons {
108
118
  stretchy false
109
119
  items advanced_examples_with_versions
120
+ selected -1
110
121
 
111
122
  on_selected do
112
123
  @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
@@ -6,6 +6,8 @@ class TinyMidiPlayer
6
6
  include Glimmer
7
7
 
8
8
  VERSION = '0.0.1'
9
+
10
+ attr_accessor :selected_file
9
11
 
10
12
  def initialize
11
13
  @pid = nil
@@ -68,13 +70,10 @@ class TinyMidiPlayer
68
70
  }
69
71
  }
70
72
 
71
- combobox { |c|
73
+ combobox {
72
74
  items @midi_files.map { |path| File.basename(path) }
73
-
74
- on_selected do
75
- @selected_file = @midi_files[c.selected]
76
- play_midi if @th&.alive?
77
- end
75
+ # data-bind selected item (String) to self.selected_file with on-read/on-write converters and after_write operation
76
+ selected_item <=> [self, :selected_file, on_read: ->(f) {File.basename(f.to_s)}, on_write: ->(f) {File.join(@music_directory, f)}, after_write: -> { play_midi if @th&.alive? }]
78
77
  }
79
78
  }
80
79
  }.show
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ class TinyMidiPlayer
6
+ include Glimmer
7
+
8
+ VERSION = '0.0.1'
9
+
10
+ attr_accessor :selected_file
11
+
12
+ def initialize
13
+ @pid = nil
14
+ @music_directory = File.expand_path('../sounds', __dir__)
15
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
16
+ .sort_by { |path| File.basename(path) }
17
+ at_exit { stop_midi }
18
+ create_gui
19
+ end
20
+
21
+ def stop_midi
22
+ if @pid
23
+ Process.kill(:SIGKILL, @pid) if @th.alive?
24
+ @pid = nil
25
+ end
26
+ end
27
+
28
+ def play_midi
29
+ stop_midi
30
+ if @pid.nil? && @selected_file
31
+ begin
32
+ @pid = spawn "timidity #{@selected_file}"
33
+ @th = Process.detach @pid
34
+ rescue Errno::ENOENT
35
+ warn 'Timidty++ not found. Please install Timidity++.'
36
+ warn 'https://sourceforge.net/projects/timidity/'
37
+ end
38
+ end
39
+ end
40
+
41
+ def show_version
42
+ msg_box('Tiny Midi Player',
43
+ "Written in Ruby\n" \
44
+ "https://github.com/kojix2/libui\n" \
45
+ "Version #{VERSION}")
46
+ end
47
+
48
+ def create_gui
49
+ menu('Help') {
50
+ menu_item('Version') {
51
+ on_clicked do
52
+ show_version
53
+ end
54
+ }
55
+ }
56
+ window('Tiny Midi Player', 200, 50) {
57
+ horizontal_box {
58
+ vertical_box {
59
+ stretchy false
60
+
61
+ button('▶') {
62
+ on_clicked do
63
+ play_midi
64
+ end
65
+ }
66
+ button('■') {
67
+ on_clicked do
68
+ stop_midi
69
+ end
70
+ }
71
+ }
72
+
73
+ combobox { |c|
74
+ items @midi_files.map { |path| File.basename(path) }
75
+ # data-bind selected index (Integer) to self.selected_file with on-read/on-write converters and after_write operation
76
+ selected <=> [self, :selected_file, on_read: ->(f) {@midi_files.index(f)}, on_write: ->(i) {@midi_files[i]}, after_write: -> { play_midi if @th&.alive? }]
77
+ }
78
+ }
79
+ }.show
80
+ end
81
+ end
82
+
83
+ TinyMidiPlayer.new
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ class TinyMidiPlayer
6
+ include Glimmer
7
+
8
+ VERSION = '0.0.1'
9
+
10
+ def initialize
11
+ @pid = nil
12
+ @music_directory = File.expand_path('../sounds', __dir__)
13
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
14
+ .sort_by { |path| File.basename(path) }
15
+ at_exit { stop_midi }
16
+ create_gui
17
+ end
18
+
19
+ def stop_midi
20
+ if @pid
21
+ Process.kill(:SIGKILL, @pid) if @th.alive?
22
+ @pid = nil
23
+ end
24
+ end
25
+
26
+ def play_midi
27
+ stop_midi
28
+ if @pid.nil? && @selected_file
29
+ begin
30
+ @pid = spawn "timidity #{@selected_file}"
31
+ @th = Process.detach @pid
32
+ rescue Errno::ENOENT
33
+ warn 'Timidty++ not found. Please install Timidity++.'
34
+ warn 'https://sourceforge.net/projects/timidity/'
35
+ end
36
+ end
37
+ end
38
+
39
+ def show_version
40
+ msg_box('Tiny Midi Player',
41
+ "Written in Ruby\n" \
42
+ "https://github.com/kojix2/libui\n" \
43
+ "Version #{VERSION}")
44
+ end
45
+
46
+ def create_gui
47
+ menu('Help') {
48
+ menu_item('Version') {
49
+ on_clicked do
50
+ show_version
51
+ end
52
+ }
53
+ }
54
+ window('Tiny Midi Player', 200, 50) {
55
+ horizontal_box {
56
+ vertical_box {
57
+ stretchy false
58
+
59
+ button('▶') {
60
+ on_clicked do
61
+ play_midi
62
+ end
63
+ }
64
+ button('■') {
65
+ on_clicked do
66
+ stop_midi
67
+ end
68
+ }
69
+ }
70
+
71
+ combobox { |c|
72
+ items @midi_files.map { |path| File.basename(path) }
73
+
74
+ on_selected do
75
+ @selected_file = @midi_files[c.selected]
76
+ play_midi if @th&.alive?
77
+ end
78
+ }
79
+ }
80
+ }.show
81
+ end
82
+ end
83
+
84
+ TinyMidiPlayer.new
data/examples/snake.rb CHANGED
@@ -12,6 +12,7 @@ class Snake
12
12
  @game = Model::Game.new
13
13
  @grid = Presenter::Grid.new(@game)
14
14
  @game.start
15
+ @keypress_queue = []
15
16
  create_gui
16
17
  register_observers
17
18
  end
@@ -31,7 +32,23 @@ class Snake
31
32
  end
32
33
 
33
34
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
34
- @game.snake.move unless @game.over?
35
+ unless @game.over?
36
+ process_queued_keypress
37
+ @game.snake.move
38
+ end
39
+ end
40
+ end
41
+
42
+ def process_queued_keypress
43
+ # key press queue ensures one turn per snake move to avoid a double-turn resulting in instant death (due to snake illogically going back against itself)
44
+ key = @keypress_queue.shift
45
+ case [@game.snake.head.orientation, key]
46
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
47
+ @game.snake.turn_right
48
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
49
+ @game.snake.turn_left
50
+ else
51
+ # No Op
35
52
  end
36
53
  end
37
54
 
@@ -56,15 +73,7 @@ class Snake
56
73
  }
57
74
 
58
75
  on_key_up do |area_key_event|
59
- orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
60
- case orientation_and_key
61
- in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
62
- @game.snake.turn_right
63
- in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
64
- @game.snake.turn_left
65
- else
66
- # No Op
67
- end
76
+ @keypress_queue << area_key_event[:ext_key]
68
77
  end
69
78
  }
70
79
  end
data/examples/tetris.rb CHANGED
@@ -109,22 +109,23 @@ class Tetris
109
109
  menu('Game') {
110
110
  @pause_menu_item = check_menu_item('Pause') {
111
111
  enabled false
112
-
113
- on_clicked do
114
- @game.paused = @pause_menu_item.checked?
115
- end
112
+ checked <=> [@game, :paused]
116
113
  }
114
+
117
115
  menu_item('Restart') {
118
116
  on_clicked do
119
117
  @game.restart!
120
118
  end
121
119
  }
120
+
122
121
  separator_menu_item
122
+
123
123
  menu_item('Exit') {
124
124
  on_clicked do
125
125
  exit(0)
126
126
  end
127
127
  }
128
+
128
129
  quit_menu_item if OS.mac?
129
130
  }
130
131
 
@@ -134,6 +135,7 @@ class Tetris
134
135
  show_high_scores
135
136
  end
136
137
  }
138
+
137
139
  menu_item('Clear High Scores') {
138
140
  on_clicked {
139
141
  @game.clear_high_scores!
@@ -142,22 +144,16 @@ class Tetris
142
144
  }
143
145
 
144
146
  menu('Options') {
145
- radio_menu_item('Instant Down on Up Arrow') {
146
- on_clicked do
147
- @game.instant_down_on_up = true
148
- end
147
+ radio_menu_item('Instant Down on Up Arrow') { |r|
148
+ checked <=> [@game, :instant_down_on_up]
149
149
  }
150
- radio_menu_item('Rotate Right on Up Arrow') {
151
- on_clicked do
152
- @game.rotate_right_on_up = true
153
- end
150
+
151
+ radio_menu_item('Rotate Right on Up Arrow') { |r|
152
+ checked <=> [@game, :rotate_right_on_up]
154
153
  }
155
- radio_menu_item('Rotate Left on Up Arrow') {
156
- checked true
157
-
158
- on_clicked do
159
- @game.rotate_left_on_up = true
160
- end
154
+
155
+ radio_menu_item('Rotate Left on Up Arrow') { |r|
156
+ checked <=> [@game, :rotate_left_on_up]
161
157
  }
162
158
  }
163
159
 
@@ -169,6 +165,7 @@ class Tetris
169
165
  end
170
166
  }
171
167
  end
168
+
172
169
  menu_item('About') {
173
170
  on_clicked do
174
171
  show_about_dialog
Binary file
@@ -29,6 +29,10 @@ module Glimmer
29
29
  # Follows the Proxy Design Pattern
30
30
  class CheckboxProxy < ControlProxy
31
31
  DEFAULT_TEXT = ''
32
+
33
+ def data_bind_write(property, model_binding)
34
+ handle_listener('on_toggled') { model_binding.call(checked) } if property == 'checked'
35
+ end
32
36
 
33
37
  private
34
38
 
@@ -113,6 +113,10 @@ module Glimmer
113
113
  Fiddle.free @alpha unless @alpha.nil?
114
114
  super
115
115
  end
116
+
117
+ def data_bind_write(property, model_binding)
118
+ handle_listener('on_changed') { model_binding.call(color) } if property == 'color'
119
+ end
116
120
  end
117
121
  end
118
122
  end
@@ -54,8 +54,23 @@ module Glimmer
54
54
  alias set_items items
55
55
  alias items= items
56
56
 
57
- def selected_item
58
- items[selected]
57
+ def selected_item(value = nil)
58
+ if value.nil?
59
+ items[selected]
60
+ else
61
+ self.selected = items.index(value)
62
+ end
63
+ end
64
+ alias set_selected_item selected_item
65
+ alias selected_item= selected_item
66
+
67
+ def data_bind_write(property, model_binding)
68
+ case property
69
+ when 'selected'
70
+ handle_listener('on_selected') { model_binding.call(selected) }
71
+ when 'selected_item'
72
+ handle_listener('on_selected') { model_binding.call(selected_item) }
73
+ end
59
74
  end
60
75
  end
61
76
  end
@@ -64,6 +64,10 @@ module Glimmer
64
64
  Fiddle.free @time unless @time.nil?
65
65
  super
66
66
  end
67
+
68
+ def data_bind_write(property, model_binding)
69
+ handle_listener('on_changed') { model_binding.call(time) } if property == 'time'
70
+ end
67
71
  end
68
72
  end
69
73
  end
@@ -39,6 +39,10 @@ module Glimmer
39
39
  end
40
40
  alias set_items items
41
41
  alias items= items
42
+
43
+ def data_bind_write(property, model_binding)
44
+ handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class EntryProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
34
33
  end
35
34
 
@@ -64,6 +64,14 @@ module Glimmer
64
64
  ::LibUI.free_font_button_font(@font_descriptor) unless @font_descriptor.nil?
65
65
  super
66
66
  end
67
+
68
+ def data_bind_read(property, model_binding)
69
+ # No Op
70
+ end
71
+
72
+ def data_bind_write(property, model_binding)
73
+ handle_listener('on_changed') { model_binding.call(font) } if property == 'font'
74
+ end
67
75
  end
68
76
  end
69
77
  end
@@ -29,6 +29,10 @@ module Glimmer
29
29
  #
30
30
  # Follows the Proxy Design Pattern
31
31
  class CheckMenuItemProxy < MenuItemProxy
32
+ def data_bind_write(property, model_binding)
33
+ handle_listener('on_clicked') { model_binding.call(checked) } if property == 'checked'
34
+ end
35
+
32
36
  private
33
37
 
34
38
  def build_control
@@ -29,15 +29,23 @@ module Glimmer
29
29
  #
30
30
  # Follows the Proxy Design Pattern
31
31
  class RadioMenuItemProxy < MenuItemProxy
32
+ def initialize(keyword, parent, args, &block)
33
+ @last_checked = nil
34
+ super
35
+ end
36
+
32
37
  def checked(value = nil)
33
- if !value.nil?
34
- if Glimmer::LibUI.integer_to_boolean(value) != checked?
35
- super
38
+ if value.nil?
39
+ super()
40
+ else
41
+ super
42
+ if Glimmer::LibUI.integer_to_boolean(value, allow_nil: false) != Glimmer::LibUI.integer_to_boolean(@last_checked, allow_nil: false)
36
43
  if Glimmer::LibUI.integer_to_boolean(value)
37
44
  (@parent_proxy.children - [self]).select {|c| c.is_a?(MenuItemProxy)}.each do |menu_item|
38
45
  menu_item.checked = false
39
46
  end
40
47
  end
48
+ @last_checked = checked
41
49
  end
42
50
  end
43
51
  end
@@ -48,7 +56,7 @@ module Glimmer
48
56
  def handle_listener(listener_name, &listener)
49
57
  if listener_name.to_s == 'on_clicked'
50
58
  radio_listener = Proc.new do
51
- self.checked = true if !checked?
59
+ self.checked = true
52
60
  listener.call(self)
53
61
  end
54
62
  super(listener_name, &radio_listener)
@@ -57,6 +65,10 @@ module Glimmer
57
65
  end
58
66
  end
59
67
 
68
+ def data_bind_write(property, model_binding)
69
+ handle_listener('on_clicked') { model_binding.call(checked) } if property == 'checked'
70
+ end
71
+
60
72
  private
61
73
 
62
74
  def build_control
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class MultilineEntryProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
34
33
  end
35
34