glimmer-dsl-libui 0.2.21 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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