glimmer-dsl-libui 0.4.6 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +601 -323
  4. data/VERSION +1 -1
  5. data/examples/button_counter.rb +2 -1
  6. data/examples/cpu_percentage.rb +36 -0
  7. data/examples/date_time_picker.rb +19 -14
  8. data/examples/date_time_picker2.rb +20 -0
  9. data/examples/meta_example.rb +17 -6
  10. data/examples/midi_player.rb +5 -6
  11. data/examples/midi_player2.rb +83 -0
  12. data/examples/midi_player3.rb +84 -0
  13. data/examples/snake.rb +19 -10
  14. data/examples/snake2.rb +97 -0
  15. data/examples/tetris.rb +15 -18
  16. data/examples/tic_tac_toe.rb +1 -0
  17. data/examples/tic_tac_toe2.rb +84 -0
  18. data/glimmer-dsl-libui.gemspec +0 -0
  19. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +4 -0
  20. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -2
  21. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +17 -2
  22. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +4 -0
  23. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +4 -0
  24. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -2
  25. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +5 -1
  26. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +4 -0
  27. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +16 -4
  28. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -2
  29. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +19 -0
  30. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -2
  31. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -2
  32. data/lib/glimmer/libui/control_proxy.rb +2 -2
  33. data/lib/glimmer/libui/data_bindable.rb +27 -2
  34. metadata +8 -2
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.6
1
+ 0.4.10
@@ -13,7 +13,8 @@ class ButtonCounter
13
13
  window('Hello, Button!', 190, 20) {
14
14
  vertical_box {
15
15
  button {
16
- text <= [self, :count, on_read: ->(count) {"Count: #{count}"}] # data-bind button text to self count, converting to string on read.
16
+ # data-bind button text to self count, converting to string on read.
17
+ text <= [self, :count, on_read: ->(count) {"Count: #{count}"}]
17
18
 
18
19
  on_clicked do
19
20
  self.count += 1
@@ -0,0 +1,36 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'bigdecimal'
3
+
4
+ include Glimmer
5
+
6
+ data = [
7
+ ['CPU', '0%', 0],
8
+ ]
9
+
10
+ Glimmer::LibUI.timer(1) do
11
+ cpu_percentage_value = nil
12
+ if OS.windows?
13
+ cpu_percentage_raw_value = `wmic cpu get loadpercentage`
14
+ cpu_percentage_value = cpu_percentage_raw_value.split("\n")[2].to_i
15
+ elsif OS.mac?
16
+ cpu_percentage_value = `ps -A -o %cpu | awk '{s+=$1} END {print s}'`.to_i
17
+ elsif OS.linux?
18
+ stats = `top -n 1`
19
+ idle_percentage = stats.split("\n")[2].match(/ni,.* (.*) .*id/)[1]
20
+ cpu_percentage_value = (BigDecimal(100) - BigDecimal(idle_percentage)).to_i
21
+ end
22
+ data[0][1] = "#{cpu_percentage_value}%"
23
+ data[0][2] = cpu_percentage_value
24
+ end
25
+
26
+ window('CPU Percentage', 400, 200) {
27
+ vertical_box {
28
+ table {
29
+ text_column('Name')
30
+ text_column('Value')
31
+ progress_bar_column('Percentage')
32
+
33
+ cell_rows data # implicit data-binding
34
+ }
35
+ }
36
+ }.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
@@ -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
@@ -0,0 +1,97 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ require_relative 'snake/presenter/grid'
4
+
5
+ class Snake
6
+ include Glimmer
7
+
8
+ CELL_SIZE = 15
9
+ SNAKE_MOVE_DELAY = 0.1
10
+
11
+ def initialize
12
+ @game = Model::Game.new
13
+ @grid = Presenter::Grid.new(@game)
14
+ @game.start
15
+ @keypress_queue = []
16
+ create_gui
17
+ register_observers
18
+ end
19
+
20
+ def launch
21
+ @main_window.show
22
+ end
23
+
24
+ def register_observers
25
+ @game.height.times do |row|
26
+ @game.width.times do |column|
27
+ observe(@grid.cells[row][column], :color) do |new_color|
28
+ @cell_grid[row][column].fill = new_color
29
+ end
30
+ end
31
+ end
32
+
33
+ observe(@game, :over) do |game_over|
34
+ Glimmer::LibUI.queue_main do
35
+ if game_over
36
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
37
+ @game.start
38
+ end
39
+ end
40
+ end
41
+
42
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
43
+ unless @game.over?
44
+ process_queued_keypress
45
+ @game.snake.move
46
+ end
47
+ end
48
+ end
49
+
50
+ def process_queued_keypress
51
+ # 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)
52
+ key = @keypress_queue.shift
53
+ case [@game.snake.head.orientation, key]
54
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
55
+ @game.snake.turn_right
56
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
57
+ @game.snake.turn_left
58
+ else
59
+ # No Op
60
+ end
61
+ end
62
+
63
+ def create_gui
64
+ @cell_grid = []
65
+ @main_window = window {
66
+ # data-bind window title to game score, converting it to a title string on read from the model
67
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
68
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
69
+ resizable false
70
+
71
+ vertical_box {
72
+ padded false
73
+
74
+ @game.height.times do |row|
75
+ @cell_grid << []
76
+ horizontal_box {
77
+ padded false
78
+
79
+ @game.width.times do |column|
80
+ area {
81
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
82
+ fill Presenter::Cell::COLOR_CLEAR
83
+ }
84
+
85
+ on_key_up do |area_key_event|
86
+ @keypress_queue << area_key_event[:ext_key]
87
+ end
88
+ }
89
+ end
90
+ }
91
+ end
92
+ }
93
+ }
94
+ end
95
+ end
96
+
97
+ Snake.new.launch
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
@@ -41,6 +41,7 @@ class TicTacToe
41
41
  text(23, 19) {
42
42
  string {
43
43
  font family: 'Arial', size: OS.mac? ? 20 : 16
44
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
44
45
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
45
46
  }
46
47
  }
@@ -0,0 +1,84 @@
1
+
2
+ require 'glimmer-dsl-libui'
3
+
4
+ require_relative "tic_tac_toe/board"
5
+
6
+ class TicTacToe
7
+ include Glimmer
8
+
9
+ def initialize
10
+ @tic_tac_toe_board = Board.new
11
+ end
12
+
13
+ def launch
14
+ create_gui
15
+ register_observers
16
+ @main_window.show
17
+ end
18
+
19
+ def register_observers
20
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
21
+ display_win_message if game_status == Board::WIN
22
+ display_draw_message if game_status == Board::DRAW
23
+ end
24
+
25
+ 3.times.map do |row|
26
+ 3.times.map do |column|
27
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
28
+ @cells[row][column].string = sign
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_gui
35
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
36
+ resizable false
37
+
38
+ @cells = []
39
+ vertical_box {
40
+ padded false
41
+
42
+ 3.times.map do |row|
43
+ @cells << []
44
+ horizontal_box {
45
+ padded false
46
+
47
+ 3.times.map do |column|
48
+ area {
49
+ square(0, 0, 60) {
50
+ stroke :black, thickness: 2
51
+ }
52
+ text(23, 19) {
53
+ @cells[row] << string('') {
54
+ font family: 'Arial', size: OS.mac? ? 20 : 16
55
+ }
56
+ }
57
+ on_mouse_up do
58
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
59
+ end
60
+ }
61
+ end
62
+ }
63
+ end
64
+ }
65
+ }
66
+ end
67
+
68
+ def display_win_message
69
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
70
+ end
71
+
72
+ def display_draw_message
73
+ display_game_over_message("Draw!")
74
+ end
75
+
76
+ def display_game_over_message(message_text)
77
+ Glimmer::LibUI.queue_main do
78
+ msg_box('Game Over', message_text)
79
+ @tic_tac_toe_board.reset!
80
+ end
81
+ end
82
+ end
83
+
84
+ TicTacToe.new.launch
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
 
@@ -114,8 +114,7 @@ module Glimmer
114
114
  super
115
115
  end
116
116
 
117
- def data_bind(property, model_binding)
118
- super
117
+ def data_bind_write(property, model_binding)
119
118
  handle_listener('on_changed') { model_binding.call(color) } if property == 'color'
120
119
  end
121
120
  end