glimmer-dsl-libui 0.2.21 → 0.3.0

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.21
1
+ 0.3.0
@@ -49,7 +49,7 @@ window('Area Gallery', 400, 400) {
49
49
  }
50
50
  text(161, 40, 100) { # x, y, width
51
51
  string('Area Gallery') {
52
- font family: 'Arial', size: 14
52
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
53
53
  color :black
54
54
  }
55
55
  }
@@ -149,7 +149,7 @@ window('Area Gallery', 400, 400) {
149
149
  width 100
150
150
 
151
151
  string {
152
- font family: 'Arial', size: 14
152
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
153
153
  color :black
154
154
 
155
155
  'Area Gallery'
@@ -50,7 +50,7 @@ window('Area Gallery', 400, 400) {
50
50
  }
51
51
  text(161, 40, 100) { # x, y, width
52
52
  string('Area Gallery') {
53
- font family: 'Arial', size: 14
53
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
54
54
  color :black
55
55
  }
56
56
  }
@@ -150,7 +150,7 @@ window('Area Gallery', 400, 400) {
150
150
  width 100
151
151
 
152
152
  string {
153
- font family: 'Arial', size: 14
153
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
154
154
  color :black
155
155
 
156
156
  'Area Gallery'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
10
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
11
+ # be used in exceptional circumstances where an image control is really needed.
12
+ #
13
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
14
+ # image pixel rendered. Check basic_image2.rb for a faster alternative using on_draw manually.
15
+ #
16
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
17
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
18
+ }
19
+ }.show
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ on_draw do |area_draw_params|
10
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
11
+ end
12
+ }
13
+ }.show
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
10
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
11
+ # be used in exceptional circumstances where an image control is really needed.
12
+ #
13
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
14
+ # image pixel rendered. Check basic_image4.rb for a faster alternative using on_draw manually.
15
+ #
16
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
17
+ image {
18
+ file File.expand_path('../icons/glimmer.png', __dir__)
19
+ width 96
20
+ height 96
21
+ }
22
+ }
23
+ }.show
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ on_draw do |area_draw_params|
10
+ image {
11
+ file File.expand_path('../icons/glimmer.png', __dir__)
12
+ width 96
13
+ height 96
14
+ }
15
+ end
16
+ }
17
+ }.show
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is the manual way of rendering an image unto an area control.
4
+ # It could come in handy in special situations.
5
+ # Otherwise, it is recommended to simply utilize the `image` control that
6
+ # can be nested under area or area on_draw listener to automate all this work.
7
+
8
+ require 'glimmer-dsl-libui'
9
+ require 'chunky_png'
10
+
11
+ include Glimmer
12
+
13
+ puts 'Parsing image...'; $stdout.flush
14
+
15
+ f = File.open(File.expand_path('../icons/glimmer.png', __dir__))
16
+ canvas = ChunkyPNG::Canvas.from_io(f)
17
+ f.close
18
+ canvas.resample_nearest_neighbor!(96, 96)
19
+ data = canvas.to_rgba_stream
20
+ width = canvas.width
21
+ height = canvas.height
22
+ puts "Image width: #{width}"
23
+ puts "Image height: #{height}"
24
+
25
+ puts 'Parsing colors...'; $stdout.flush
26
+
27
+ color_maps = height.times.map do |y|
28
+ width.times.map do |x|
29
+ r = data[(y*width + x)*4].ord
30
+ g = data[(y*width + x)*4 + 1].ord
31
+ b = data[(y*width + x)*4 + 2].ord
32
+ a = data[(y*width + x)*4 + 3].ord
33
+ {x: x, y: y, color: {r: r, g: g, b: b, a: a}}
34
+ end
35
+ end.flatten
36
+ puts "#{color_maps.size} pixels to render..."; $stdout.flush
37
+
38
+ puts 'Parsing shapes...'; $stdout.flush
39
+
40
+ shape_maps = []
41
+ original_color_maps = color_maps.dup
42
+ indexed_original_color_maps = Hash[original_color_maps.each_with_index.to_a]
43
+ color_maps.each do |color_map|
44
+ index = indexed_original_color_maps[color_map]
45
+ @rectangle_start_x ||= color_map[:x]
46
+ @rectangle_width ||= 1
47
+ if color_map[:x] < width - 1 && color_map[:color] == original_color_maps[index + 1][:color]
48
+ @rectangle_width += 1
49
+ else
50
+ if color_map[:x] > 0 && color_map[:color] == original_color_maps[index - 1][:color]
51
+ shape_maps << {x: @rectangle_start_x, y: color_map[:y], width: @rectangle_width, height: 1, color: color_map[:color]}
52
+ else
53
+ shape_maps << {x: color_map[:x], y: color_map[:y], width: 1, height: 1, color: color_map[:color]}
54
+ end
55
+ @rectangle_width = 1
56
+ @rectangle_start_x = color_map[:x] == width - 1 ? 0 : color_map[:x] + 1
57
+ end
58
+ end
59
+ puts "#{shape_maps.size} shapes to render..."; $stdout.flush
60
+
61
+ puts 'Rendering image...'; $stdout.flush
62
+
63
+ window('Basic Image', 96, 96) {
64
+ area {
65
+ on_draw do |area_draw_params|
66
+ shape_maps.each do |shape_map|
67
+ path {
68
+ rectangle(shape_map[:x], shape_map[:y], shape_map[:width], shape_map[:height])
69
+
70
+ fill shape_map[:color]
71
+ }
72
+ end
73
+ end
74
+ }
75
+ }.show
@@ -43,7 +43,7 @@ class MetaExample
43
43
 
44
44
  def run_example(example)
45
45
  Thread.new do
46
- command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
46
+ command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
47
47
  result = ''
48
48
  IO.popen(command) do |f|
49
49
  sleep(0.0001) # yield to main thread
@@ -0,0 +1,33 @@
1
+ class Snake
2
+ module Model
3
+ class Apple
4
+ attr_reader :game
5
+ attr_accessor :row, :column
6
+
7
+ def initialize(game)
8
+ @game = game
9
+ end
10
+
11
+ # generates a new location from scratch or via dependency injection of what cell is (for testing purposes)
12
+ def generate(initial_row: nil, initial_column: nil)
13
+ if initial_row && initial_column
14
+ self.row, self.column = initial_row, initial_column
15
+ else
16
+ self.row, self.column = @game.height.times.zip(@game.width.times).reject do |row, column|
17
+ @game.snake.vertebrae.map {|v| [v.row, v.column]}.include?([row, column])
18
+ end.sample
19
+ end
20
+ end
21
+
22
+ def remove
23
+ self.row = nil
24
+ self.column = nil
25
+ end
26
+
27
+ # inspect is overridden to prevent printing very long stack traces
28
+ def inspect
29
+ "#{super[0, 120]}... >"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ require 'fileutils'
2
+
3
+ require_relative 'snake'
4
+ require_relative 'apple'
5
+
6
+ class Snake
7
+ module Model
8
+ class Game
9
+ WIDTH_DEFAULT = 40
10
+ HEIGHT_DEFAULT = 40
11
+ FILE_HIGH_SCORE = File.expand_path(File.join(Dir.home, '.glimmer-snake'))
12
+
13
+ attr_reader :width, :height
14
+ attr_accessor :snake, :apple, :over, :score, :high_score
15
+ alias over? over
16
+
17
+ def initialize(width = WIDTH_DEFAULT, height = HEIGHT_DEFAULT)
18
+ @width = width
19
+ @height = height
20
+ @snake = Snake.new(self)
21
+ @apple = Apple.new(self)
22
+ FileUtils.touch(FILE_HIGH_SCORE)
23
+ @high_score = File.read(FILE_HIGH_SCORE).to_i rescue 0
24
+ end
25
+
26
+ def score=(new_score)
27
+ @score = new_score
28
+ self.high_score = @score if @score > @high_score
29
+ end
30
+
31
+ def high_score=(new_high_score)
32
+ @high_score = new_high_score
33
+ File.write(FILE_HIGH_SCORE, @high_score.to_s)
34
+ rescue => e
35
+ puts e.full_message
36
+ end
37
+
38
+ def start
39
+ self.over = false
40
+ self.score = 0
41
+ self.snake.generate
42
+ self.apple.generate
43
+ end
44
+
45
+ # inspect is overridden to prevent printing very long stack traces
46
+ def inspect
47
+ "#{super[0, 75]}... >"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,95 @@
1
+ require_relative 'vertebra'
2
+
3
+ class Snake
4
+ module Model
5
+ class Snake
6
+ SCORE_EAT_APPLE = 50
7
+ RIGHT_TURN_MAP = {
8
+ north: :east,
9
+ east: :south,
10
+ south: :west,
11
+ west: :north
12
+ }
13
+ LEFT_TURN_MAP = RIGHT_TURN_MAP.invert
14
+
15
+ attr_accessor :collided
16
+ alias collided? collided
17
+
18
+ attr_reader :game
19
+ # vertebrae and joins are ordered from tail to head
20
+ attr_accessor :vertebrae
21
+
22
+ def initialize(game)
23
+ @game = game
24
+ end
25
+
26
+ # generates a new snake location and orientation from scratch or via dependency injection of what head_cell and orientation are (for testing purposes)
27
+ def generate(initial_row: nil, initial_column: nil, initial_orientation: nil)
28
+ self.collided = false
29
+ initial_vertebra = Vertebra.new(snake: self, row: initial_row, column: initial_column, orientation: initial_orientation)
30
+ self.vertebrae = [initial_vertebra]
31
+ end
32
+
33
+ def length
34
+ @vertebrae.length
35
+ end
36
+
37
+ def head
38
+ @vertebrae.last
39
+ end
40
+
41
+ def tail
42
+ @vertebrae.first
43
+ end
44
+
45
+ def remove
46
+ self.vertebrae.clear
47
+ self.joins.clear
48
+ end
49
+
50
+ def move
51
+ @old_tail = tail.dup
52
+ @new_head = head.dup
53
+ case @new_head.orientation
54
+ when :east
55
+ @new_head.column = (@new_head.column + 1) % @game.width
56
+ when :west
57
+ @new_head.column = (@new_head.column - 1) % @game.width
58
+ when :south
59
+ @new_head.row = (@new_head.row + 1) % @game.height
60
+ when :north
61
+ @new_head.row = (@new_head.row - 1) % @game.height
62
+ end
63
+ if @vertebrae.map {|v| [v.row, v.column]}.include?([@new_head.row, @new_head.column])
64
+ self.collided = true
65
+ @game.over = true
66
+ else
67
+ @vertebrae.append(@new_head)
68
+ @vertebrae.delete(tail)
69
+ if head.row == @game.apple.row && head.column == @game.apple.column
70
+ grow
71
+ @game.apple.generate
72
+ end
73
+ end
74
+ end
75
+
76
+ def turn_right
77
+ head.orientation = RIGHT_TURN_MAP[head.orientation]
78
+ end
79
+
80
+ def turn_left
81
+ head.orientation = LEFT_TURN_MAP[head.orientation]
82
+ end
83
+
84
+ def grow
85
+ @game.score += SCORE_EAT_APPLE
86
+ @vertebrae.prepend(@old_tail)
87
+ end
88
+
89
+ # inspect is overridden to prevent printing very long stack traces
90
+ def inspect
91
+ "#{super[0, 150]}... >"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ class Snake
2
+ module Model
3
+ class Vertebra
4
+ ORIENTATIONS = %i[north east south west]
5
+ # orientation is needed for snake occuppied cells (but not apple cells)
6
+ attr_reader :snake
7
+ attr_accessor :row, :column, :orientation
8
+
9
+ def initialize(snake: , row: , column: , orientation: )
10
+ @row = row || rand(snake.game.height)
11
+ @column = column || rand(snake.game.width)
12
+ @orientation = orientation || ORIENTATIONS.sample
13
+ @snake = snake
14
+ end
15
+
16
+ # inspect is overridden to prevent printing very long stack traces
17
+ def inspect
18
+ "#{super[0, 150]}... >"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ class Snake
2
+ module Presenter
3
+ class Cell
4
+ COLOR_CLEAR = :white
5
+ COLOR_SNAKE = :green
6
+ COLOR_APPLE = :red
7
+
8
+ attr_reader :row, :column, :grid
9
+ attr_accessor :color
10
+
11
+ def initialize(grid: ,row: ,column: )
12
+ @row = row
13
+ @column = column
14
+ @grid = grid
15
+ end
16
+
17
+ def clear
18
+ self.color = COLOR_CLEAR unless color == COLOR_CLEAR
19
+ end
20
+
21
+ # inspect is overridden to prevent printing very long stack traces
22
+ def inspect
23
+ "#{super[0, 150]}... >"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ require 'glimmer/data_binding/observer'
2
+ require_relative '../model/game'
3
+ require_relative 'cell'
4
+
5
+ class Snake
6
+ module Presenter
7
+ class Grid
8
+ attr_reader :game, :cells
9
+
10
+ def initialize(game = Model::Game.new)
11
+ @game = game
12
+ @cells = @game.height.times.map do |row|
13
+ @game.width.times.map do |column|
14
+ Cell.new(grid: self, row: row, column: column)
15
+ end
16
+ end
17
+ Glimmer::DataBinding::Observer.proc do |new_vertebrae|
18
+ occupied_snake_positions = @game.snake.vertebrae.map {|v| [v.row, v.column]}
19
+ @cells.each_with_index do |row_cells, row|
20
+ row_cells.each_with_index do |cell, column|
21
+ if [@game.apple.row, @game.apple.column] == [row, column]
22
+ cell.color = Cell::COLOR_APPLE
23
+ elsif occupied_snake_positions.include?([row, column])
24
+ cell.color = Cell::COLOR_SNAKE
25
+ else
26
+ cell.clear
27
+ end
28
+ end
29
+ end
30
+ end.observe(@game.snake, :vertebrae)
31
+ end
32
+
33
+ def clear
34
+ @cells.each do |row_cells|
35
+ row_cells.each do |cell|
36
+ cell.clear
37
+ end
38
+ end
39
+ end
40
+
41
+ # inspect is overridden to prevent printing very long stack traces
42
+ def inspect
43
+ "#{super[0, 75]}... >"
44
+ end
45
+ end
46
+ end
47
+ end
data/examples/snake.rb ADDED
@@ -0,0 +1,90 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'glimmer/data_binding/observer'
3
+
4
+ require_relative 'snake/presenter/grid'
5
+
6
+ class Snake
7
+ CELL_SIZE = 15
8
+ SNAKE_MOVE_DELAY = 0.1
9
+ include Glimmer
10
+
11
+ def initialize
12
+ @game = Model::Game.new
13
+ @grid = Presenter::Grid.new(@game)
14
+ @game.start
15
+ create_gui
16
+ register_observers
17
+ end
18
+
19
+ def launch
20
+ @main_window.show
21
+ end
22
+
23
+ def register_observers
24
+ @game.height.times do |row|
25
+ @game.width.times do |column|
26
+ Glimmer::DataBinding::Observer.proc do |new_color|
27
+ @cell_grid[row][column].fill = new_color
28
+ end.observe(@grid.cells[row][column], :color)
29
+ end
30
+ end
31
+
32
+ Glimmer::DataBinding::Observer.proc do |game_over|
33
+ Glimmer::LibUI.queue_main do
34
+ if game_over
35
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
36
+ @game.start
37
+ end
38
+ end
39
+ end.observe(@game, :over)
40
+
41
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
42
+ unless @game.over?
43
+ @game.snake.move
44
+ @main_window.title = "Glimmer Snake (Score: #{@game.score} | High Score: #{@game.high_score})"
45
+ end
46
+ end
47
+ end
48
+
49
+ def create_gui
50
+ @cell_grid = []
51
+ @main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
52
+ resizable false
53
+
54
+ vertical_box {
55
+ padded false
56
+
57
+ @game.height.times do |row|
58
+ @cell_grid << []
59
+ horizontal_box {
60
+ padded false
61
+
62
+ @game.width.times do |column|
63
+ area {
64
+ @cell_grid.last << path {
65
+ square(0, 0, CELL_SIZE)
66
+
67
+ fill Presenter::Cell::COLOR_CLEAR
68
+ }
69
+
70
+ on_key_up do |area_key_event|
71
+ orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
72
+ case orientation_and_key
73
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
74
+ @game.snake.turn_right
75
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
76
+ @game.snake.turn_left
77
+ else
78
+ # No Op
79
+ end
80
+ end
81
+ }
82
+ end
83
+ }
84
+ end
85
+ }
86
+ }
87
+ end
88
+ end
89
+
90
+ Snake.new.launch
@@ -113,7 +113,7 @@ class Tetris
113
113
  end
114
114
 
115
115
  def tetris_dir
116
- @tetris_dir ||= File.join(File.expand_path('~'), '.glimmer-tetris')
116
+ @tetris_dir ||= File.join(Dir.home, '.glimmer-tetris')
117
117
  end
118
118
 
119
119
  def tetris_high_score_file
data/examples/tetris.rb CHANGED
@@ -153,6 +153,8 @@ class Tetris
153
153
  end
154
154
  }
155
155
  radio_menu_item('Rotate Left on Up Arrow') {
156
+ checked true
157
+
156
158
  on_clicked do
157
159
  @game.rotate_left_on_up = true
158
160
  end
@@ -235,7 +237,19 @@ class Tetris
235
237
  on_key_down do |key_event|
236
238
  case key_event
237
239
  in ext_key: :down
238
- @game.down!
240
+ if OS.windows?
241
+ # rate limit downs in Windows as they go too fast when key is held
242
+ @queued_downs ||= 0
243
+ if @queued_downs < 2
244
+ @queued_downs += 1
245
+ Glimmer::LibUI.timer(0.01, repeat: false) do
246
+ @game.down! if @queued_downs < 2
247
+ @queued_downs -= 1
248
+ end
249
+ end
250
+ else
251
+ @game.down!
252
+ end
239
253
  in key: ' '
240
254
  @game.down!(instant: true)
241
255
  in ext_key: :up
@@ -319,8 +333,11 @@ class Tetris
319
333
  end
320
334
 
321
335
  def start_moving_tetrominos_down
322
- Glimmer::LibUI.timer(@game.delay) do
323
- @game.down! if !@game.game_over? && !@game.paused?
336
+ unless @tetrominos_start_moving_down
337
+ @tetrominos_start_moving_down = true
338
+ Glimmer::LibUI.timer(@game.delay) do
339
+ @game.down! if !@game.game_over? && !@game.paused?
340
+ end
324
341
  end
325
342
  end
326
343
 
@@ -333,6 +350,8 @@ class Tetris
333
350
 
334
351
  def show_high_scores
335
352
  Glimmer::LibUI.queue_main do
353
+ game_paused = !!@game.paused
354
+ @game.paused = true
336
355
  if @game.high_scores.empty?
337
356
  high_scores_string = "No games have been scored yet."
338
357
  else
@@ -341,6 +360,7 @@ class Tetris
341
360
  end.join("\n")
342
361
  end
343
362
  msg_box('High Scores', high_scores_string)
363
+ @game.paused = game_paused
344
364
  end
345
365
  end
346
366
 
@@ -52,7 +52,7 @@ class TicTacToe
52
52
  }
53
53
  text(23, 19) {
54
54
  @cells[row] << string('') {
55
- font family: 'Arial', size: 20
55
+ font family: 'Arial', size: OS.mac? ? 20 : 16
56
56
  }
57
57
  }
58
58
  on_mouse_up do
Binary file
@@ -40,7 +40,7 @@ module Glimmer
40
40
 
41
41
  def add_content(parent, keyword, *args, &block)
42
42
  super
43
- parent.post_add_content
43
+ parent&.post_add_content
44
44
  end
45
45
  end
46
46
  end
@@ -34,7 +34,6 @@ module Glimmer
34
34
  @args = args
35
35
  @block = block
36
36
  @enabled = true
37
- @children = []
38
37
  post_add_content if @block.nil?
39
38
  end
40
39