glimmer-dsl-libui 0.2.18 → 0.2.22

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.18
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.22
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -218,9 +218,10 @@ NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is fe
218
218
  Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
219
219
  - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
220
220
  - [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
221
+ - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
222
+ - [glimmer-dsl-gtk](https://github.com/AndyObtiva/glimmer-dsl-gtk): Ruby-GNOME Desktop Development GUI Library
221
223
  - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
222
224
  - [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
223
- - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
224
225
 
225
226
  ## Table of Contents
226
227
 
@@ -278,6 +279,8 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
278
279
  - [Custom Draw Text](#custom-draw-text)
279
280
  - [Method-Based Custom Keyword](#method-based-custom-keyword)
280
281
  - [Tetris](#tetris)
282
+ - [Tic Tac Toe](#tic-tac-toe)
283
+ - [Snake](#snake)
281
284
  - [Applications](#applications)
282
285
  - [Manga2PDF](#manga2pdf)
283
286
  - [Befunge98 GUI](#befunge98-gui)
@@ -370,7 +373,7 @@ gem install glimmer-dsl-libui
370
373
  Or install via Bundler `Gemfile`:
371
374
 
372
375
  ```ruby
373
- gem 'glimmer-dsl-libui', '~> 0.2.18'
376
+ gem 'glimmer-dsl-libui', '~> 0.2.22'
374
377
  ```
375
378
 
376
379
  Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
@@ -446,7 +449,7 @@ These are all the supported keywords. Note that some keywords do not represent c
446
449
  Keyword(Args) | Properties | Listeners
447
450
  ------------- | ---------- | ---------
448
451
  `about_menu_item` | None | `on_clicked`
449
- `area` | None | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`
452
+ `area` | `auto_draw_enabled` | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`
450
453
  `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)` | `x_center` (`Numeric`), `y_center` (`Numeric`), `radius` (`Numeric`), `start_angle` (`Numeric`), `sweep` (`Numeric`), `is_negative` (Boolean) | None
451
454
  `background_color_column(name as String)` | None | None
452
455
  `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
@@ -456,6 +459,7 @@ Keyword(Args) | Properties | Listeners
456
459
  `checkbox_column(name as String)` | `editable` (Boolean) | None
457
460
  `checkbox_text_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
458
461
  `checkbox_text_color_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
462
+ `check_menu_item(text as String)` | `checked` (Boolean) | `on_clicked`
459
463
  `combobox` | `items` (`Array` of `String`), `selected` (`Integer`) | `on_selected`
460
464
  `color_button` | `color` (Array of `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float`), `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float` | `on_changed`
461
465
  `date_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`, `mday` as `Integer`, `mon` as `Integer`, `year` as `Integer`, `wday` as `Integer`, `yday` as `Integer`, `dst` as Boolean) | `on_changed`
@@ -478,7 +482,7 @@ Keyword(Args) | Properties | Listeners
478
482
  `line(x as Numeric, y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`) | None
479
483
  `matrix(m11 = nil as Numeric, m12 = nil as Numeric, m21 = nil as Numeric, m22 = nil as Numeric, m31 = nil as Numeric, m32 = nil as Numeric)` | `m11` (`Numeric`), `m12` (`Numeric`), `m21` (`Numeric`), `m22` (`Numeric`), `m31` (`Numeric`), `m32` (`Numeric`) | None
480
484
  `menu(text as String)` | None | None
481
- `menu_item(text as String)` | `checked` (Boolean) | `on_clicked`
485
+ `menu_item(text as String)` | None | `on_clicked`
482
486
  `message_box` (alias for `msg_box`; see for arguments) | None | None
483
487
  `message_box_error` (alias for `msg_box_error`; see for arguments) | None | None
484
488
  `multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
@@ -497,6 +501,7 @@ Keyword(Args) | Properties | Listeners
497
501
  `radio_buttons` | `selected` (`Integer`) | `on_selected`
498
502
  `rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `width` (`Numeric`), `height` (`Numeric`) | None
499
503
  `search_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
504
+ `separator_menu_item` | None | None
500
505
  `slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
501
506
  `spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
502
507
  `square(x as Numeric, y as Numeric, length as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `length` (`Numeric`) | None
@@ -510,7 +515,7 @@ Keyword(Args) | Properties | Listeners
510
515
  `time_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`) | `on_changed`
511
516
  `vertical_box` | `padded` (Boolean) | None
512
517
  `vertical_separator` | None | None
513
- `window(title as String, width as Integer, height as Integer, has_menubar as Boolean)` | `borderless` (Boolean), `content_size` (width `Numeric`, height `Numeric`), `fullscreen` (Boolean), `margined` (Boolean), `title` (`String`) | `on_closing`, `on_content_size_changed`, `on_destroy`
518
+ `window(title as String, width as Integer, height as Integer, has_menubar as Boolean)` | `borderless` (Boolean), `content_size` (width `Numeric`, height `Numeric`), `fullscreen` (Boolean), `margined` (Boolean), `title` (`String`), `resizable` (Boolean) | `on_closing`, `on_content_size_changed`, `on_destroy`
514
519
 
515
520
  ### Common Control Properties
516
521
  - `enabled` (Boolean)
@@ -543,6 +548,35 @@ All operations that could normally be called on `LibUI` can also be called on `G
543
548
  - `Glimmer::LibUI::queue_main(&block)`: queues an operation to be run on the main event loop at the earliest opportunity possible
544
549
  - `Glimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, &block)`: calls block after time_in_seconds has elapsed, repeating indefinitely unless repeat is `false` or an `Integer` for finite number of repeats. Block can return `false` or `true` to override next repetition.
545
550
 
551
+ There are additional useful `Glimmer::LibUI` operations that are not found in `LibUI`, which mostly help if you would like to do advanced lower level [LibUI](https://github.com/kojix2/LibUI) programming:
552
+ - `Glimmer::LibUI::integer_to_boolean(int, allow_nil: true)`
553
+ - `Glimmer::LibUI::boolean_to_integer(int, allow_nil: true)`
554
+ - `Glimmer::LibUI::degrees_to_radians(degrees)`
555
+ - `Glimmer::LibUI::interpret_color(value)`: interprets a color in any form like `String`, `Symbol`, or hex into an rgb `Hash`
556
+ - `Glimmer::LibUI::hex_to_rgb(value)`: converts a hex color to an rgb `Hash`
557
+ - `Glimmer::LibUI::enum_names`: provides all possible enum names to use with `Glimmer::LibUI::enum_symbols(enum_name)`
558
+ - `Glimmer::LibUI::enum_symbols(enum_name)`: returns all possible values for an enum. `enum_name` can be:
559
+ - `:draw_brush_type`: `[:solid, :linear_gradient, :radial_gradient, :image]`
560
+ - `:draw_line_cap`: `[:flat, :round, :square]`
561
+ - `:draw_line_join`: `[:miter, :round, :bevel]`
562
+ - `:draw_fill_mode`: `[:winding, :alternate]`
563
+ - `:attribute_type`: attributes for attributed `string`s: `[:family, :size, weight, :italic, :stretch, :color, :background, :underline, :underline_color, :features]`
564
+ - `:text_weight`: `[:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]`
565
+ - `:text_italic`: `[:normal, :oblique, :italic]`
566
+ - `:text_stretch`: `[:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]`
567
+ - `:underline`: `[:none, :single, :double, :suggestion, :color_custom, :color_spelling, :color_grammar, :color_auxiliary]`
568
+ - `:underline_color`: `[:custom, :spelling, :grammar, :auxiliary]`
569
+ - `:draw_text_align`: `[:left, :center, :right]`
570
+ - `:modifier`: `[:ctrl, :alt, :shift, :super]`
571
+ - `:ext_key`: `[:escape, :insert, :delete, :home, :end, :page_up, :page_down, :up, :down, :left, :right, :f1, :f2, :f3, :f4, :f5, :f6, :f7, :f8, :f9, :f10, :f11, :f12, :n0, :n1, :n2, :n3, :n4, :n5, :n6, :n7, :n8, :n9, :n_dot, :n_enter, :n_add, :n_subtract, :n_multiply, :n_divide]`
572
+ - `:at`: for inserting `grid` controls: `[:leading, :top, :trailing, :bottom]`
573
+ - `:align`: `[:fill, :start, :center, :end]`
574
+ - `:table_value_type`: `[:string, :image, :int, :color]`
575
+ - `:table_model_column`: `[:never_editable, :always_editable]`
576
+ - `Glimmer::LibUI::enum_symbol_to_value(enum_name, enum_symbol, default_symbol: nil, default_index: 0)`
577
+ - `Glimmer::LibUI::enum_value_to_symbol(enum_name, enum_value)`
578
+ - `Glimmer::LibUI::x11_colors`: returns all [X11 colors](https://en.wikipedia.org/wiki/X11_color_names): `[:alice_blue, :antique_white, :aqua, :aquamarine, :azure, :beige, :bisque, :rebecca_purple, :becca_purple, :blanched_almond, :blue, :blue_violet, :brown, :burly_wood, :burlywood, :cadet_blue, :carnation, :cayenne, :chartreuse, :chocolate, :coral, :cornflower_blue, :cornsilk, :crimson, :cyan, :dark_blue, :dark_cyan, :dark_golden_rod, :dark_goldenrod, :dark_gray, :dark_grey, :dark_green, :dark_khaki, :dark_magenta, :dark_olive_green, :darkolive_green, :dark_orange, :dark_orchid, :dark_red, :dark_salmon, :darksalmon, :dark_sea_green, :dark_slate_blue, :dark_slate_gray, :dark_slate_grey, :dark_turquoise, :dark_violet, :darkorange, :deep_pink, :deep_sky_blue, :dim_gray, :dim_grey, :dodger_blue, :feldspar, :fire_brick, :firebrick, :floral_white, :forest_green, :fuchsia, :gainsboro, :ghost_white, :gold, :golden_rod, :goldenrod, :gray, :grey, :gray10, :grey10, :gray20, :grey20, :gray30, :grey30, :gray40, :grey40, :gray50, :grey50, :gray60, :grey60, :gray70, :grey70, :gray80, :grey80, :gray90, :grey90, :green, :green_yellow, :honey_dew, :honeydew, :hot_pink, :indian_red, :indigo, :ivory, :khaki, :lavender, :lavender_blush, :lawn_green, :lemon_chiffon, :light_blue, :light_coral, :light_cyan, :light_golden_rod_yellow, :light_goldenrod_yellow, :light_gray, :light_grey, :light_green, :light_pink, :light_salmon, :lightsalmon, :light_sea_green, :light_sky_blue, :light_slate_blue, :light_slate_gray, :light_slate_grey, :light_steel_blue, :lightsteel_blue, :light_yellow, :lime, :lime_green, :linen, :magenta, :maroon, :medium_aqua_marine, :medium_aquamarine, :medium_blue, :medium_orchid, :medium_purple, :medium_sea_green, :medium_slate_blue, :medium_spring_green, :medium_turquoise, :medium_violet_red, :midnight_blue, :mint_cream, :misty_rose, :moccasin, :navajo_white, :navy, :old_lace, :olive, :olive_drab, :olivedrab, :orange, :orange_red, :orchid, :pale_golden_rod, :pale_goldenrod, :pale_green, :pale_turquoise, :pale_violet_red, :papaya_whip, :peach_puff, :peachpuff, :peru, :pink, :plum, :powder_blue, :purple, :red, :rosy_brown, :royal_blue, :saddle_brown, :salmon, :sandy_brown, :sea_green, :sea_shell, :seashell, :sienna, :silver, :sky_blue, :slate_blue, :slate_gray, :slate_grey, :snow, :spring_green, :steel_blue, :tan, :teal, :thistle, :tomato, :turquoise, :violet, :violet_red, :wheat, :white_smoke, :yellow, :yellow_green, :metallic, :white, :black, :gray_scale, :grey_scale]`
579
+
546
580
  ### Extra Dialogs
547
581
 
548
582
  - `open_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected file (`String`) or `nil` if cancelled
@@ -871,7 +905,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
871
905
 
872
906
  `fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-number hex or 3-number hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
873
907
 
874
- Available [X11](https://en.wikipedia.org/wiki/X11_color_names) colors can be obtained through `Glimmer::LibUI.x11_colors` method.
908
+ Available [X11 colors](https://en.wikipedia.org/wiki/X11_color_names) can be obtained through `Glimmer::LibUI.x11_colors` method.
875
909
 
876
910
  Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wikipedia.org/wiki/X11_color_names) colors.
877
911
 
@@ -1088,8 +1122,8 @@ window('Method-Based Custom Keyword') {
1088
1122
 
1089
1123
  To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1090
1124
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1091
- - Check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
1092
- - Check out the [Go UI (Golang LibUI) documentation](https://pkg.go.dev/github.com/andlabs/ui) for an alternative well-documented [libui](https://github.com/andlabs/libui) reference.
1125
+ - Check out the [libui C Headers](https://github.com/andlabs/libui/blob/master/ui.h)
1126
+ - Check out the [Go UI (Golang LibUI) API Documentation](https://pkg.go.dev/github.com/andlabs/ui) for an alternative well-documented [libui](https://github.com/andlabs/libui) reference.
1093
1127
 
1094
1128
  ## Packaging
1095
1129
 
@@ -1189,7 +1223,7 @@ class MetaExample
1189
1223
 
1190
1224
  def run_example(example)
1191
1225
  Thread.new do
1192
- command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1226
+ command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1193
1227
  result = ''
1194
1228
  IO.popen(command) do |f|
1195
1229
  sleep(0.0001) # yield to main thread
@@ -6234,6 +6268,8 @@ window('Method-Based Custom Keyword') {
6234
6268
 
6235
6269
  ### Tetris
6236
6270
 
6271
+ Glimmer Tetris utilizes many small areas to represent Tetromino blocks because this ensures smaller redraws per tetromino block color change, thus achieving higher performance than redrawing one large area on every little change.
6272
+
6237
6273
  [examples/tetris.rb](examples/tetris.rb)
6238
6274
 
6239
6275
  Run with this command from the root of the project if you cloned the project:
@@ -6252,6 +6288,10 @@ Mac
6252
6288
 
6253
6289
  ![glimmer-dsl-libui-mac-tetris.png](images/glimmer-dsl-libui-mac-tetris.png)
6254
6290
 
6291
+ ![glimmer-dsl-libui-mac-tetris-game-over.png](images/glimmer-dsl-libui-mac-tetris-game-over.png)
6292
+
6293
+ ![glimmer-dsl-libui-mac-tetris-high-scores.png](images/glimmer-dsl-libui-mac-tetris-high-scores.png)
6294
+
6255
6295
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6256
6296
 
6257
6297
  ```ruby
@@ -6266,40 +6306,55 @@ class Tetris
6266
6306
  BEVEL_CONSTANT = 20
6267
6307
  COLOR_GRAY = {r: 192, g: 192, b: 192}
6268
6308
 
6269
- attr_reader :game
6270
-
6271
6309
  def initialize
6272
6310
  @game = Model::Game.new
6273
- create_gui
6274
- register_observers
6275
6311
  end
6276
6312
 
6277
6313
  def launch
6314
+ create_gui
6315
+ register_observers
6278
6316
  @game.start!
6279
6317
  @main_window.show
6280
6318
  end
6281
6319
 
6282
6320
  def create_gui
6283
- @main_window = window('Glimmer Tetris', Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE) {
6284
- playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
6321
+ menu_bar
6322
+
6323
+ @main_window = window('Glimmer Tetris') {
6324
+ content_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE + 98
6325
+ resizable false
6326
+
6327
+ vertical_box {
6328
+ label { # filler
6329
+ stretchy false
6330
+ }
6331
+
6332
+ score_board(block_size: BLOCK_SIZE) {
6333
+ stretchy false
6334
+ }
6335
+
6336
+ @playfield_blocks = playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
6337
+ }
6285
6338
  }
6286
6339
  end
6287
6340
 
6288
6341
  def register_observers
6289
6342
  Glimmer::DataBinding::Observer.proc do |game_over|
6290
6343
  if game_over
6344
+ @pause_menu_item.enabled = false
6291
6345
  show_game_over_dialog
6292
6346
  else
6347
+ @pause_menu_item.enabled = true
6293
6348
  start_moving_tetrominos_down
6294
6349
  end
6295
6350
  end.observe(@game, :game_over)
6296
6351
 
6297
6352
  Model::Game::PLAYFIELD_HEIGHT.times do |row|
6298
- Model::Game::PLAYFIELD_HEIGHT.times do |column|
6353
+ Model::Game::PLAYFIELD_WIDTH.times do |column|
6299
6354
  Glimmer::DataBinding::Observer.proc do |new_color|
6300
6355
  Glimmer::LibUI.queue_main do
6301
6356
  color = Glimmer::LibUI.interpret_color(new_color)
6302
- block = @blocks[row][column]
6357
+ block = @playfield_blocks[row][column]
6303
6358
  block[:background_square].fill = color
6304
6359
  block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
6305
6360
  block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
@@ -6310,27 +6365,135 @@ class Tetris
6310
6365
  end.observe(@game.playfield[row][column], :color)
6311
6366
  end
6312
6367
  end
6368
+
6369
+ Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
6370
+ Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
6371
+ Glimmer::DataBinding::Observer.proc do |new_color|
6372
+ Glimmer::LibUI.queue_main do
6373
+ color = Glimmer::LibUI.interpret_color(new_color)
6374
+ block = @preview_playfield_blocks[row][column]
6375
+ block[:background_square].fill = color
6376
+ block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
6377
+ block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
6378
+ block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
6379
+ block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
6380
+ block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
6381
+ end
6382
+ end.observe(@game.preview_playfield[row][column], :color)
6383
+ end
6384
+ end
6385
+
6386
+ Glimmer::DataBinding::Observer.proc do |new_score|
6387
+ Glimmer::LibUI.queue_main do
6388
+ @score_label.text = new_score.to_s
6389
+ end
6390
+ end.observe(@game, :score)
6391
+
6392
+ Glimmer::DataBinding::Observer.proc do |new_lines|
6393
+ Glimmer::LibUI.queue_main do
6394
+ @lines_label.text = new_lines.to_s
6395
+ end
6396
+ end.observe(@game, :lines)
6397
+
6398
+ Glimmer::DataBinding::Observer.proc do |new_level|
6399
+ Glimmer::LibUI.queue_main do
6400
+ @level_label.text = new_level.to_s
6401
+ end
6402
+ end.observe(@game, :level)
6313
6403
  end
6314
6404
 
6315
- def playfield(playfield_width: , playfield_height: , block_size: )
6316
- @blocks = []
6405
+ def menu_bar
6406
+ menu('Game') {
6407
+ @pause_menu_item = check_menu_item('Pause') {
6408
+ enabled false
6409
+
6410
+ on_clicked do
6411
+ @game.paused = @pause_menu_item.checked?
6412
+ end
6413
+ }
6414
+ menu_item('Restart') {
6415
+ on_clicked do
6416
+ @game.restart!
6417
+ end
6418
+ }
6419
+ separator_menu_item
6420
+ menu_item('Exit') {
6421
+ on_clicked do
6422
+ exit(0)
6423
+ end
6424
+ }
6425
+ quit_menu_item if OS.mac?
6426
+ }
6427
+
6428
+ menu('View') {
6429
+ menu_item('Show High Scores') {
6430
+ on_clicked do
6431
+ show_high_scores
6432
+ end
6433
+ }
6434
+ menu_item('Clear High Scores') {
6435
+ on_clicked {
6436
+ @game.clear_high_scores!
6437
+ }
6438
+ }
6439
+ }
6440
+
6441
+ menu('Options') {
6442
+ radio_menu_item('Instant Down on Up Arrow') {
6443
+ on_clicked do
6444
+ @game.instant_down_on_up = true
6445
+ end
6446
+ }
6447
+ radio_menu_item('Rotate Right on Up Arrow') {
6448
+ on_clicked do
6449
+ @game.rotate_right_on_up = true
6450
+ end
6451
+ }
6452
+ radio_menu_item('Rotate Left on Up Arrow') {
6453
+ on_clicked do
6454
+ @game.rotate_left_on_up = true
6455
+ end
6456
+ }
6457
+ }
6458
+
6459
+ menu('Help') {
6460
+ if OS.mac?
6461
+ about_menu_item {
6462
+ on_clicked do
6463
+ show_about_dialog
6464
+ end
6465
+ }
6466
+ end
6467
+ menu_item('About') {
6468
+ on_clicked do
6469
+ show_about_dialog
6470
+ end
6471
+ }
6472
+ }
6473
+ end
6474
+
6475
+ def playfield(playfield_width: , playfield_height: , block_size: , &extra_content)
6476
+ blocks = []
6317
6477
  vertical_box {
6318
6478
  padded false
6319
6479
 
6320
6480
  playfield_height.times.map do |row|
6321
- @blocks << []
6481
+ blocks << []
6322
6482
  horizontal_box {
6323
6483
  padded false
6324
6484
 
6325
6485
  playfield_width.times.map do |column|
6326
- @blocks.last << block(row: row, column: column, block_size: block_size)
6486
+ blocks.last << block(row: row, column: column, block_size: block_size)
6327
6487
  end
6328
6488
  }
6329
6489
  end
6490
+
6491
+ extra_content&.call
6330
6492
  }
6493
+ blocks
6331
6494
  end
6332
6495
 
6333
- def block(row: , column: , block_size: )
6496
+ def block(row: , column: , block_size: , &extra_content)
6334
6497
  block = {}
6335
6498
  bevel_pixel_size = 0.16 * block_size.to_f
6336
6499
  color = Glimmer::LibUI.interpret_color(Model::Block::COLOR_CLEAR)
@@ -6369,32 +6532,89 @@ class Tetris
6369
6532
  on_key_down do |key_event|
6370
6533
  case key_event
6371
6534
  in ext_key: :down
6372
- game.down!
6535
+ @game.down!
6536
+ in key: ' '
6537
+ @game.down!(instant: true)
6373
6538
  in ext_key: :up
6374
- case game.up_arrow_action
6539
+ case @game.up_arrow_action
6375
6540
  when :instant_down
6376
- game.down!(instant: true)
6541
+ @game.down!(instant: true)
6377
6542
  when :rotate_right
6378
- game.rotate!(:right)
6543
+ @game.rotate!(:right)
6379
6544
  when :rotate_left
6380
- game.rotate!(:left)
6545
+ @game.rotate!(:left)
6381
6546
  end
6382
6547
  in ext_key: :left
6383
- game.left!
6548
+ @game.left!
6384
6549
  in ext_key: :right
6385
- game.right!
6550
+ @game.right!
6386
6551
  in modifier: :shift
6387
- game.rotate!(:right)
6552
+ @game.rotate!(:right)
6388
6553
  in modifier: :control
6389
- game.rotate!(:left)
6554
+ @game.rotate!(:left)
6390
6555
  else
6391
6556
  # Do Nothing
6392
6557
  end
6393
6558
  end
6559
+
6560
+ extra_content&.call
6394
6561
  }
6395
6562
  block
6396
6563
  end
6397
6564
 
6565
+ def score_board(block_size: , &extra_content)
6566
+ vertical_box {
6567
+ horizontal_box {
6568
+ label # filler
6569
+ @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
6570
+ label # filler
6571
+ }
6572
+
6573
+ horizontal_box {
6574
+ label # filler
6575
+ grid {
6576
+ stretchy false
6577
+
6578
+ label('Score') {
6579
+ left 0
6580
+ top 0
6581
+ halign :fill
6582
+ }
6583
+ @score_label = label {
6584
+ left 0
6585
+ top 1
6586
+ halign :center
6587
+ }
6588
+
6589
+ label('Lines') {
6590
+ left 1
6591
+ top 0
6592
+ halign :fill
6593
+ }
6594
+ @lines_label = label {
6595
+ left 1
6596
+ top 1
6597
+ halign :center
6598
+ }
6599
+
6600
+ label('Level') {
6601
+ left 2
6602
+ top 0
6603
+ halign :fill
6604
+ }
6605
+ @level_label = label {
6606
+ left 2
6607
+ top 1
6608
+ halign :center
6609
+ }
6610
+ }
6611
+ label # filler
6612
+ }
6613
+
6614
+ extra_content&.call
6615
+ }
6616
+ end
6617
+
6398
6618
  def start_moving_tetrominos_down
6399
6619
  Glimmer::LibUI.timer(@game.delay) do
6400
6620
  @game.down! if !@game.game_over? && !@game.paused?
@@ -6403,15 +6623,269 @@ class Tetris
6403
6623
 
6404
6624
  def show_game_over_dialog
6405
6625
  Glimmer::LibUI.queue_main do
6406
- msg_box('Game Over', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
6626
+ msg_box('Game Over!', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
6407
6627
  @game.restart!
6408
6628
  end
6409
6629
  end
6630
+
6631
+ def show_high_scores
6632
+ Glimmer::LibUI.queue_main do
6633
+ if @game.high_scores.empty?
6634
+ high_scores_string = "No games have been scored yet."
6635
+ else
6636
+ high_scores_string = @game.high_scores.map do |high_score|
6637
+ "#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}"
6638
+ end.join("\n")
6639
+ end
6640
+ msg_box('High Scores', high_scores_string)
6641
+ end
6642
+ end
6643
+
6644
+ def show_about_dialog
6645
+ Glimmer::LibUI.queue_main do
6646
+ msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021 Andy Maleh')
6647
+ end
6648
+ end
6410
6649
  end
6411
6650
 
6412
6651
  Tetris.new.launch
6413
6652
  ```
6414
6653
 
6654
+ ### Tic Tac Toe
6655
+
6656
+ [examples/tic_tac_toe.rb](examples/tic_tac_toe.rb)
6657
+
6658
+ Run with this command from the root of the project if you cloned the project:
6659
+
6660
+ ```
6661
+ ruby -r './lib/glimmer-dsl-libui' examples/tic_tac_toe.rb
6662
+ ```
6663
+
6664
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6665
+
6666
+ ```
6667
+ ruby -r glimmer-dsl-libui -e "require 'examples/tic_tac_toe'"
6668
+ ```
6669
+
6670
+ Mac
6671
+
6672
+ ![glimmer-dsl-libui-mac-tic-tac-toe.png](images/glimmer-dsl-libui-mac-tic-tac-toe.png)
6673
+
6674
+ ![glimmer-dsl-libui-mac-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-mac-tic-tac-toe-player-o-wins.png)
6675
+
6676
+ ![glimmer-dsl-libui-mac-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-mac-tic-tac-toe-player-x-wins.png)
6677
+
6678
+ ![glimmer-dsl-libui-mac-tic-tac-toe-draw.png](images/glimmer-dsl-libui-mac-tic-tac-toe-draw.png)
6679
+
6680
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6681
+
6682
+ ```ruby
6683
+ require 'glimmer-dsl-libui'
6684
+
6685
+ require_relative "tic_tac_toe/board"
6686
+
6687
+ class TicTacToe
6688
+ include Glimmer
6689
+
6690
+ def initialize
6691
+ @tic_tac_toe_board = Board.new
6692
+ end
6693
+
6694
+ def launch
6695
+ create_gui
6696
+ register_observers
6697
+ @main_window.show
6698
+ end
6699
+
6700
+ def register_observers
6701
+ Glimmer::DataBinding::Observer.proc do |game_status|
6702
+ display_win_message if game_status == Board::WIN
6703
+ display_draw_message if game_status == Board::DRAW
6704
+ end.observe(@tic_tac_toe_board, :game_status)
6705
+
6706
+ 3.times.map do |row|
6707
+ 3.times.map do |column|
6708
+ Glimmer::DataBinding::Observer.proc do |sign|
6709
+ @cells[row][column].string = sign
6710
+ end.observe(@tic_tac_toe_board[row + 1, column + 1], :sign) # board model is 1-based
6711
+ end
6712
+ end
6713
+ end
6714
+
6715
+ def create_gui
6716
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
6717
+ resizable false
6718
+
6719
+ @cells = []
6720
+ vertical_box {
6721
+ padded false
6722
+
6723
+ 3.times.map do |row|
6724
+ @cells << []
6725
+ horizontal_box {
6726
+ padded false
6727
+
6728
+ 3.times.map do |column|
6729
+ area {
6730
+ path {
6731
+ square(0, 0, 60)
6732
+
6733
+ stroke :black, thickness: 2
6734
+ }
6735
+ text(23, 19) {
6736
+ @cells[row] << string('') {
6737
+ font family: 'Arial', size: 20
6738
+ }
6739
+ }
6740
+ on_mouse_up do
6741
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
6742
+ end
6743
+ }
6744
+ end
6745
+ }
6746
+ end
6747
+ }
6748
+ }
6749
+ end
6750
+
6751
+ def display_win_message
6752
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
6753
+ end
6754
+
6755
+ def display_draw_message
6756
+ display_game_over_message("Draw!")
6757
+ end
6758
+
6759
+ def display_game_over_message(message_text)
6760
+ Glimmer::LibUI.queue_main do
6761
+ msg_box('Game Over', message_text)
6762
+ @tic_tac_toe_board.reset!
6763
+ end
6764
+ end
6765
+ end
6766
+
6767
+ TicTacToe.new.launch
6768
+ ```
6769
+
6770
+ ### Snake
6771
+
6772
+ Snake provides an example of building a desktop application [test-first](/spec/examples/snake/model/game_spec.rb) following the MVP ([Model](/examples/snake/model/game.rb) / [View](/examples/snake.rb) / [Presenter](/examples/snake/presenter/grid.rb)) architectural pattern.
6773
+
6774
+ [examples/snake.rb](examples/snake.rb)
6775
+
6776
+ Run with this command from the root of the project if you cloned the project:
6777
+
6778
+ ```
6779
+ ruby -r './lib/glimmer-dsl-libui' examples/snake.rb
6780
+ ```
6781
+
6782
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6783
+
6784
+ ```
6785
+ ruby -r glimmer-dsl-libui -e "require 'examples/snake'"
6786
+ ```
6787
+
6788
+ Mac
6789
+
6790
+ ![glimmer-dsl-libui-mac-snake.png](images/glimmer-dsl-libui-mac-snake.png)
6791
+
6792
+ ![glimmer-dsl-libui-mac-snake-game-over.png](images/glimmer-dsl-libui-mac-snake-game-over.png)
6793
+
6794
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6795
+
6796
+ ```ruby
6797
+ require 'glimmer-dsl-libui'
6798
+ require 'glimmer/data_binding/observer'
6799
+
6800
+ require_relative 'snake/presenter/grid'
6801
+
6802
+ class Snake
6803
+ CELL_SIZE = 15
6804
+ SNAKE_MOVE_DELAY = 0.1
6805
+ include Glimmer
6806
+
6807
+ def initialize
6808
+ @game = Model::Game.new
6809
+ @grid = Presenter::Grid.new(@game)
6810
+ @game.start
6811
+ create_gui
6812
+ register_observers
6813
+ end
6814
+
6815
+ def launch
6816
+ @main_window.show
6817
+ end
6818
+
6819
+ def register_observers
6820
+ @game.height.times do |row|
6821
+ @game.width.times do |column|
6822
+ Glimmer::DataBinding::Observer.proc do |new_color|
6823
+ @cell_grid[row][column].fill = new_color
6824
+ end.observe(@grid.cells[row][column], :color)
6825
+ end
6826
+ end
6827
+
6828
+ Glimmer::DataBinding::Observer.proc do |game_over|
6829
+ Glimmer::LibUI.queue_main do
6830
+ if game_over
6831
+ msg_box('Game Over!', "Score: #{@game.score}")
6832
+ @game.start
6833
+ end
6834
+ end
6835
+ end.observe(@game, :over)
6836
+
6837
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
6838
+ unless @game.over?
6839
+ @game.snake.move
6840
+ @main_window.title = "Glimmer Snake (Score: #{@game.score})"
6841
+ end
6842
+ end
6843
+ end
6844
+
6845
+ def create_gui
6846
+ @cell_grid = []
6847
+ @main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
6848
+ resizable false
6849
+
6850
+ vertical_box {
6851
+ padded false
6852
+
6853
+ @game.height.times do |row|
6854
+ @cell_grid << []
6855
+ horizontal_box {
6856
+ padded false
6857
+
6858
+ @game.width.times do |column|
6859
+ area {
6860
+ @cell_grid.last << path {
6861
+ square(0, 0, CELL_SIZE)
6862
+
6863
+ fill Presenter::Cell::COLOR_CLEAR
6864
+ }
6865
+
6866
+ on_key_up do |area_key_event|
6867
+ orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
6868
+ case orientation_and_key
6869
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
6870
+ @game.snake.turn_right
6871
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
6872
+ @game.snake.turn_left
6873
+ else
6874
+ # No Op
6875
+ end
6876
+ end
6877
+ }
6878
+ end
6879
+ }
6880
+ end
6881
+ }
6882
+ }
6883
+ end
6884
+ end
6885
+
6886
+ Snake.new.launch
6887
+ ```
6888
+
6415
6889
  ## Applications
6416
6890
 
6417
6891
  Here are some applications built with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui)
@@ -6442,9 +6916,10 @@ https://github.com/iraamaro/i3off-gtk-ruby
6442
6916
 
6443
6917
  ## Resources
6444
6918
 
6445
- - [libui C Library](https://github.com/andlabs/libui)
6446
- - [LibUI Ruby Bindings](https://github.com/kojix2/LibUI)
6447
6919
  - [Code Master Blog](https://andymaleh.blogspot.com/search/label/LibUI)
6920
+ - [LibUI Ruby Bindings](https://github.com/kojix2/LibUI)
6921
+ - [libui C Library](https://github.com/andlabs/libui)
6922
+ - [Go UI (Golang LibUI) API Documentation](https://pkg.go.dev/github.com/andlabs/ui)
6448
6923
 
6449
6924
  ## Help
6450
6925