glimmer-dsl-libui 0.4.18 → 0.4.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +448 -74
  5. data/VERSION +1 -1
  6. data/bin/girb +0 -0
  7. data/bin/girb_runner.rb +1 -1
  8. data/examples/area_based_custom_controls.rb +282 -0
  9. data/examples/method_based_custom_keyword.rb +9 -9
  10. data/examples/method_based_custom_keyword2.rb +9 -9
  11. data/examples/snake/model/game.rb +18 -1
  12. data/examples/snake.rb +6 -2
  13. data/examples/snake2.rb +6 -2
  14. data/examples/tetris/model/block.rb +1 -1
  15. data/examples/tetris/model/game.rb +26 -24
  16. data/examples/tetris/model/past_game.rb +1 -1
  17. data/examples/tetris/model/tetromino.rb +1 -1
  18. data/examples/tetris.rb +51 -20
  19. data/examples/tic_tac_toe/board.rb +1 -1
  20. data/examples/tic_tac_toe/cell.rb +1 -1
  21. data/glimmer-dsl-libui.gemspec +0 -0
  22. data/lib/glimmer/dsl/libui/bind_expression.rb +1 -1
  23. data/lib/glimmer/dsl/libui/control_expression.rb +1 -1
  24. data/lib/glimmer/dsl/libui/data_binding_expression.rb +1 -1
  25. data/lib/glimmer/dsl/libui/dsl.rb +1 -1
  26. data/lib/glimmer/dsl/libui/file_expression.rb +1 -1
  27. data/lib/glimmer/dsl/libui/listener_expression.rb +1 -1
  28. data/lib/glimmer/dsl/libui/observe_expression.rb +2 -2
  29. data/lib/glimmer/dsl/libui/open_file_expression.rb +1 -1
  30. data/lib/glimmer/dsl/libui/operation_expression.rb +1 -1
  31. data/lib/glimmer/dsl/libui/property_expression.rb +1 -1
  32. data/lib/glimmer/dsl/libui/save_file_expression.rb +1 -1
  33. data/lib/glimmer/dsl/libui/shape_expression.rb +1 -1
  34. data/lib/glimmer/dsl/libui/shine_data_binding_expression.rb +1 -1
  35. data/lib/glimmer/dsl/libui/string_expression.rb +1 -1
  36. data/lib/glimmer/dsl/libui/tab_item_expression.rb +1 -1
  37. data/lib/glimmer/fiddle_consumer.rb +1 -1
  38. data/lib/glimmer/libui/attributed_string.rb +1 -1
  39. data/lib/glimmer/libui/control_proxy/area_proxy/scrolling_area_proxy.rb +1 -1
  40. data/lib/glimmer/libui/control_proxy/area_proxy.rb +1 -1
  41. data/lib/glimmer/libui/control_proxy/box/horizontal_box_proxy.rb +1 -1
  42. data/lib/glimmer/libui/control_proxy/box/vertical_box_proxy.rb +1 -1
  43. data/lib/glimmer/libui/control_proxy/box.rb +2 -1
  44. data/lib/glimmer/libui/control_proxy/button_proxy.rb +1 -1
  45. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +1 -1
  46. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -1
  47. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +1 -1
  48. data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +1 -1
  49. data/lib/glimmer/libui/control_proxy/column/checkbox_column_proxy.rb +1 -1
  50. data/lib/glimmer/libui/control_proxy/column/checkbox_text_color_column_proxy.rb +1 -1
  51. data/lib/glimmer/libui/control_proxy/column/checkbox_text_column_proxy.rb +1 -1
  52. data/lib/glimmer/libui/control_proxy/column/image_column_proxy.rb +1 -1
  53. data/lib/glimmer/libui/control_proxy/column/image_text_color_column_proxy.rb +1 -1
  54. data/lib/glimmer/libui/control_proxy/column/image_text_column_proxy.rb +1 -1
  55. data/lib/glimmer/libui/control_proxy/column/progress_bar_column_proxy.rb +1 -1
  56. data/lib/glimmer/libui/control_proxy/column/text_color_column_proxy.rb +1 -1
  57. data/lib/glimmer/libui/control_proxy/column/text_column_proxy.rb +1 -1
  58. data/lib/glimmer/libui/control_proxy/column.rb +1 -1
  59. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +1 -1
  60. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy/date_picker_proxy.rb +1 -1
  61. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy/time_picker_proxy.rb +1 -1
  62. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +1 -1
  63. data/lib/glimmer/libui/control_proxy/dual_column.rb +1 -1
  64. data/lib/glimmer/libui/control_proxy/editable_column.rb +1 -1
  65. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +1 -1
  66. data/lib/glimmer/libui/control_proxy/enableable_column.rb +1 -1
  67. data/lib/glimmer/libui/control_proxy/entry_proxy/password_entry_proxy.rb +1 -1
  68. data/lib/glimmer/libui/control_proxy/entry_proxy/search_entry_proxy.rb +1 -1
  69. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -1
  70. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +1 -1
  71. data/lib/glimmer/libui/control_proxy/form_proxy.rb +2 -1
  72. data/lib/glimmer/libui/control_proxy/grid_proxy.rb +1 -1
  73. data/lib/glimmer/libui/control_proxy/group_proxy.rb +1 -1
  74. data/lib/glimmer/libui/control_proxy/image_part_proxy.rb +1 -1
  75. data/lib/glimmer/libui/control_proxy/image_proxy.rb +1 -1
  76. data/lib/glimmer/libui/control_proxy/label_proxy.rb +1 -1
  77. data/lib/glimmer/libui/control_proxy/matrix_proxy.rb +1 -1
  78. data/lib/glimmer/libui/control_proxy/menu_item_proxy/about_menu_item_proxy.rb +1 -1
  79. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +1 -1
  80. data/lib/glimmer/libui/control_proxy/menu_item_proxy/preferences_menu_item_proxy.rb +1 -1
  81. data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +1 -1
  82. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +1 -1
  83. data/lib/glimmer/libui/control_proxy/menu_item_proxy/separator_menu_item_proxy.rb +1 -1
  84. data/lib/glimmer/libui/control_proxy/menu_item_proxy.rb +1 -1
  85. data/lib/glimmer/libui/control_proxy/menu_proxy.rb +1 -1
  86. data/lib/glimmer/libui/control_proxy/message_box/msg_box_error_proxy.rb +1 -1
  87. data/lib/glimmer/libui/control_proxy/message_box/msg_box_proxy.rb +1 -1
  88. data/lib/glimmer/libui/control_proxy/message_box.rb +1 -1
  89. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy/non_wrapping_multiline_entry_proxy.rb +1 -1
  90. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -1
  91. data/lib/glimmer/libui/control_proxy/open_type_features_proxy.rb +1 -1
  92. data/lib/glimmer/libui/control_proxy/open_type_tag_proxy.rb +1 -1
  93. data/lib/glimmer/libui/control_proxy/path_proxy.rb +1 -1
  94. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +1 -1
  95. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -1
  96. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -1
  97. data/lib/glimmer/libui/control_proxy/tab_item_proxy.rb +1 -1
  98. data/lib/glimmer/libui/control_proxy/table_proxy.rb +1 -1
  99. data/lib/glimmer/libui/control_proxy/text_proxy.rb +1 -1
  100. data/lib/glimmer/libui/control_proxy/transformable.rb +1 -1
  101. data/lib/glimmer/libui/control_proxy/triple_column.rb +1 -1
  102. data/lib/glimmer/libui/control_proxy/window_proxy.rb +1 -1
  103. data/lib/glimmer/libui/control_proxy.rb +2 -1
  104. data/lib/glimmer/libui/data_bindable.rb +1 -1
  105. data/lib/glimmer/libui/parent.rb +1 -1
  106. data/lib/glimmer/libui/shape/arc.rb +1 -1
  107. data/lib/glimmer/libui/shape/bezier.rb +21 -4
  108. data/lib/glimmer/libui/shape/circle.rb +1 -1
  109. data/lib/glimmer/libui/shape/figure.rb +1 -1
  110. data/lib/glimmer/libui/shape/line.rb +24 -4
  111. data/lib/glimmer/libui/shape/polybezier.rb +1 -1
  112. data/lib/glimmer/libui/shape/polygon.rb +1 -1
  113. data/lib/glimmer/libui/shape/polyline.rb +1 -1
  114. data/lib/glimmer/libui/shape/rectangle.rb +1 -1
  115. data/lib/glimmer/libui/shape/square.rb +1 -1
  116. data/lib/glimmer/libui/shape.rb +1 -1
  117. data/lib/glimmer/libui.rb +1 -1
  118. data/lib/glimmer-dsl-libui.rb +1 -1
  119. metadata +8 -6
data/README.md CHANGED
@@ -1,9 +1,11 @@
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.4.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.4.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)
5
5
 
6
- [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/kojix2/LibUI) is a prerequisite-free Ruby desktop development GUI (Graphical User Interface) library. No need to pre-install any prerequisites. Just install the [gem](https://rubygems.org/gems/glimmer-dsl-libui) and have platform-independent native GUI that just works!
6
+ [**(Fukuoka Ruby Award Competition 2022 Special Award Winner)**](http://www.digitalfukuoka.jp/topics/187?locale=ja)
7
+
8
+ [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/kojix2/LibUI) is a prerequisite-free [MRI Ruby](https://www.ruby-lang.org) desktop development GUI (Graphical User Interface) library. No need to pre-install any prerequisites. Just install the [gem](https://rubygems.org/gems/glimmer-dsl-libui) and have platform-independent native GUI that just works!
7
9
 
8
10
  Mac | Windows | Linux
9
11
  ----|---------|------
@@ -22,6 +24,8 @@ The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/gl
22
24
  - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
23
25
  - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
24
26
 
27
+ Note that currently, [LibUI](https://github.com/kojix2/LibUI) only includes x86_64 binaries out of the box, but there are plans to include ARM64/AARCH64 binaries too in the future.
28
+
25
29
  Hello, World!
26
30
 
27
31
  ```ruby
@@ -320,19 +324,27 @@ Mac | Windows | Linux
320
324
 
321
325
  [Check Out Many More Examples Over Here!](#examples)
322
326
 
323
- Mac | Windows | Linux
324
- ----|---------|------
325
- ![glimmer-dsl-libui-mac-tetris.png](images/glimmer-dsl-libui-mac-tetris.png) | ![glimmer-dsl-libui-windows-tetris.png](images/glimmer-dsl-libui-windows-tetris.png) | ![glimmer-dsl-libui-linux-tetris.png](images/glimmer-dsl-libui-linux-tetris.png)
327
+ ![glimmer-dsl-libui-mac-snake.gif](images/glimmer-dsl-libui-mac-snake.gif)
328
+
329
+ ![glimmer-dsl-libui-mac-color-the-circles.gif](images/glimmer-dsl-libui-mac-color-the-circles.gif)
330
+
331
+ ![glimmer-dsl-libui-mac-tetris.gif](images/glimmer-dsl-libui-mac-tetris.gif)
326
332
 
327
- NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is feature-complete and in beta mode (though the C [libui](https://github.com/andlabs/libui) is still mid-alpha). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.
333
+ NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is 100% feature-complete and in beta mode (though the C [libui](https://github.com/andlabs/libui) is still mid-alpha). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.
328
334
 
329
- Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
330
- - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
331
- - [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
332
- - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
333
- - [glimmer-dsl-gtk](https://github.com/AndyObtiva/glimmer-dsl-gtk): Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)
334
- - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
335
- - [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
335
+ **[Glimmer](https://rubygems.org/gems/glimmer) DSL Comparison Table:**
336
+ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
337
+ ----|-----------|---------|------------------|------|------|--------
338
+ [Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)](https://github.com/AndyObtiva/glimmer-dsl-swt) | Mac / Windows / Linux | Yes | Yes (Canvas Shape DSL) | Very Mature / Scaffolding / Native Executable Packaging / Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint | Java / JRuby
339
+ [Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)](https://github.com/AndyObtiva/glimmer-dsl-opal) | All Web Browsers | No | Yes (Canvas Shape DSL) | Simpler than All JavaScript Technologies / Auto-Webify Desktop Apps | Setup Process / Only Rails 5 Support for Now | Rails
340
+ [Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-libui) | Mac / Windows / Linux | Yes | Yes (Area API) | Fast Startup Time / Light Memory Footprint | LibUI is an Incomplete Mid-Alpha Only | None Other Than MRI Ruby
341
+ [Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-tk) | Mac / Windows / Linux | Some Native-Themed Widgets (Not Truly Native) | Yes (Canvas) | Fast Startup Time / Light Memory Footprint | Widgets Do Not Look Truly Native, Espcially on Linux | ActiveTcl / MRI Ruby
342
+ [Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-gtk) | Mac / Windows / Linux | Only on Linux | Yes (Cairo) | Complete Access to GNOME Features on Linux (Forte) | Not Native on Mac and Windows | None Other Than MRI Ruby on Linux / Brew Packages on Mac / MSYS & MING Toolchains on Windows / MRI Ruby
343
+ [Glimmer DSL for FX (FOX Toolkit Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-fx) | Mac (requires XQuartz) / Windows / Linux | No | Yes (Canvas) | No Prerequisites on Windows (Forte Since Binaries Are Included Out of The Box) | Widgets Do Not Look Native / Mac Usage Obtrusively Starts XQuartz | None Other Than MRI Ruby on Windows / XQuarts on Mac / MRI Ruby
344
+ [Glimmer DSL for JFX (JRuby JavaFX Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-jfx) | Mac / Windows / Linux | No | Yes (javafx.scene.shape and javafx.scene.canvas) | Rich in Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby / JavaFX SDK
345
+ [Glimmer DSL for Swing (JRuby Swing Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-swing) | Mac / Windows / Linux | No | Yes (Java2D) | Very Mature | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby
346
+ [Glimmer DSL for XML (& HTML)](https://github.com/AndyObtiva/glimmer-dsl-xml) | All Web Browsers | No | Yes (SVG) | Programmable / Lighter-weight Than Actual XML | XML Elements Are Sometimes Not Well-Named (Many Types of Input) | None
347
+ [Glimmer DSL for CSS](https://github.com/AndyObtiva/glimmer-dsl-css) | All Web Browsers | No | Yes | Programmable | CSS Is Over-Engineered / Too Many Features To Learn | None
336
348
 
337
349
  ## Table of Contents
338
350
 
@@ -409,6 +421,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
409
421
  - [Histogram](#histogram)
410
422
  - [Login](#login)
411
423
  - [Method-Based Custom Keyword](#method-based-custom-keyword)
424
+ - [Area-Based Custom Controls](#area-based-custom-controls)
412
425
  - [Midi Player](#midi-player)
413
426
  - [Snake](#snake)
414
427
  - [Tetris](#tetris)
@@ -506,7 +519,7 @@ gem install glimmer-dsl-libui
506
519
  Or install via Bundler `Gemfile`:
507
520
 
508
521
  ```ruby
509
- gem 'glimmer-dsl-libui', '~> 0.4.18'
522
+ gem 'glimmer-dsl-libui', '~> 0.4.22'
510
523
  ```
511
524
 
512
525
  Test that installation worked by running the [Meta-Example](#examples):
@@ -576,13 +589,14 @@ w.set_title 'aloha'
576
589
  puts w.title # => aloha
577
590
  ```
578
591
 
579
- Controls are wrapped as Ruby proxy objects, having a `#libui` method to obtain the wrapped [LibUI](https://github.com/kojix2/LibUI) Fiddle pointer object. Ruby proxy objects rely on composition (via [Proxy Design Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed.
592
+ Controls are wrapped as Ruby proxy objects, having a `#libui` method to obtain the wrapped [LibUI](https://github.com/kojix2/LibUI) Fiddle pointer object. Ruby proxy objects rely on composition (via [Proxy Design Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed. That said, you can invoke any [LibUI operation](#libui-operations) on the Glimmer proxy object directly and it gets proxied automatically to the wrapped Fiddle pointer object (e.g. `window_proxy.title` gets proxied to `LibUI.window_title(window_proxy.libui).to_s` automatically), so you rarely have to refer to the wrapped `#libui` Fiddle pointer object directly.
580
593
 
581
594
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
582
595
 
583
596
  ```ruby
584
597
  w = window('hello world') # => #<Glimmer::LibUI::WindowProxy:0x00007fde4ea39fb0
585
598
  w.libui # => #<Fiddle::Pointer:0x00007fde53997980 ptr=0x00007fde51352a60 size=0 free=0x0000000000000000>
599
+ w.title == LibUI.window_title(w.libui).to_s # => true
586
600
  ```
587
601
 
588
602
  ### Supported Keywords
@@ -595,7 +609,7 @@ Keyword(Args) | Properties | Listeners
595
609
  `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)`
596
610
  `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
597
611
  `background_color_column` | None | None
598
- `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
612
+ `bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
599
613
  `button(text as String)` | `text` (`String`) | `on_clicked`
600
614
  `button_column(name as String)` | `enabled` (Boolean) | None
601
615
  `checkbox(text as String)` | `checked` (Boolean), `text` (`String`) | `on_toggled`
@@ -622,7 +636,7 @@ Keyword(Args) | Properties | Listeners
622
636
  `image_text_column(name as String)` | None | None
623
637
  `image_text_color_column(name as String)` | None | None
624
638
  `label(text as String)` | `text` (`String`) | None
625
- `line(x as Numeric, y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`) | None
639
+ `line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
626
640
  `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
627
641
  `menu(text as String)` | None | None
628
642
  `menu_item(text as String)` | None | `on_clicked`
@@ -958,8 +972,8 @@ Available `path` shapes (that can be nested explicitly under `path` or implicitl
958
972
  - `square(x as Numeric, y as Numeric, length as Numeric)`
959
973
  - `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)`
960
974
  - `circle(x_center as Numeric, y_center as Numeric, radius as Numeric)`
961
- - `line(x as Numeric, y as Numeric)`: must be placed in a figure (check `polyline`/`polygon` alternatives that do not require a `figure`)
962
- - `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)`: must be placed in a figure (check `polybezier` alternative that does not require a `figure`)
975
+ - `line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)`: must be placed in a `figure` if only `x`/`y` are specified or have `end_x`/`end_y` otherwise if outside of `figure` (check `polyline`/`polygon` alternatives that do not require a `figure`)
976
+ - `bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)`: must be placed in a `figure` if `x`/`y` are not specified or have `x`/`y` as start point otherwise if outside of `figure` (check `polybezier` alternative that does not require a `figure`)
963
977
  - `polygon(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for a closed figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]
964
978
  - `polyline(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for an open figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]
965
979
  - `polybezier(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for an open figure of beziers; can receive points as [[start_x1, start_y1], [c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2], [c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3], ...] or [start_x1, start_y1, c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2, c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3, ...]
@@ -1365,8 +1379,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1365
1379
  - All boolean property readers return `true` or `false` in Ruby instead of the [libui](https://github.com/andlabs/libui) original `0` or `1` in C.
1366
1380
  - All boolean property writers accept `true`/`false` in addition to `1`/`0` in Ruby
1367
1381
  - All string property readers return a `String` object in Ruby instead of the [libui](https://github.com/andlabs/libui) Fiddle pointer object.
1368
- - Automatically allocate font descriptors upon instantiating `font_button` controls and free them when destorying `font_button` controls
1369
- - Automatically allocate color value pointers upon instantiating `color_button` controls and free them when destorying `color_button` controls
1382
+ - Automatically allocate font descriptors upon instantiating `font_button` controls and free them when destroying `font_button` controls
1383
+ - Automatically allocate color value pointers upon instantiating `color_button` controls and free them when destroying `color_button` controls
1370
1384
  - On the Mac, if no `menu` items were added, an automatic `quit_menu_item` is added to enable quitting with CTRL+Q
1371
1385
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1372
1386
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
@@ -1384,6 +1398,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1384
1398
  - `scrolling_area` `#scroll_to` 3rd and 4th arguments (`width` and `height`) default to main window width and height if not specified.
1385
1399
  - `area` paths are specified declaratively with shapes/figures underneath (e.g. `rectangle`), and `area` draw listener is automatically generated
1386
1400
  - `area` path shapes can be added directly under `area` without declaring `path` explicitly as a convenient shorthand
1401
+ - `line` and `bezier` automatically start a new figure if placed outside of `figure`
1387
1402
  - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
1388
1403
  - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
1389
1404
  - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
@@ -1395,7 +1410,15 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1395
1410
 
1396
1411
  ### Custom Keywords
1397
1412
 
1398
- To define custom keywords, simply define a method representing the custom control you want. To make reusable, you can define in modules and simply include the modules in the view classes that need them.
1413
+ Custom keywords can be defined to represent custom controls (components) that provide new features or act as composites of [existing controls](#supported-keywords) that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
1414
+
1415
+ For example, you can define a custom `address` control as an aggregate of multiple `label` controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
1416
+
1417
+ To define custom keywords, simply define a method representing the custom control you want (e.g. `address`) with any arguments needed (e.g. `address(address_model)`).
1418
+
1419
+ To make custom keywords externally reusable, you can define in modules and simply include the modules in the view classes that need them.
1420
+
1421
+ It is OK to use terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. `cylinder`), custom attributed strings (e.g. `alternating_color_string`), and custom transforms (`isometric_transform`).
1399
1422
 
1400
1423
  Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1401
1424
 
@@ -1407,44 +1430,37 @@ include Glimmer
1407
1430
 
1408
1431
  Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
1409
1432
 
1410
- def form_field(model, property)
1411
- property = property.to_s
1433
+ def form_field(model, attribute)
1434
+ attribute = attribute.to_s
1412
1435
  entry { |e|
1413
- label property.underscore.split('_').map(&:capitalize).join(' ')
1414
- text model.send(property).to_s
1415
-
1416
- on_changed do
1417
- model.send("#{property}=", e.text)
1418
- end
1436
+ label attribute.underscore.split('_').map(&:capitalize).join(' ')
1437
+ text <=> [model, attribute]
1419
1438
  }
1420
1439
  end
1421
1440
 
1422
- def address_form(address)
1441
+ def address_form(address_model)
1423
1442
  form {
1424
- form_field(address, :street)
1425
- form_field(address, :p_o_box)
1426
- form_field(address, :city)
1427
- form_field(address, :state)
1428
- form_field(address, :zip_code)
1443
+ form_field(address_model, :street)
1444
+ form_field(address_model, :p_o_box)
1445
+ form_field(address_model, :city)
1446
+ form_field(address_model, :state)
1447
+ form_field(address_model, :zip_code)
1429
1448
  }
1430
1449
  end
1431
1450
 
1432
1451
  def label_pair(model, attribute, value)
1433
- name_label = nil
1434
- value_label = nil
1435
1452
  horizontal_box {
1436
- name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
1437
- value_label = label(value.to_s)
1453
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
1454
+ label(value.to_s) {
1455
+ text <= [model, attribute]
1456
+ }
1438
1457
  }
1439
- Glimmer::DataBinding::Observer.proc do
1440
- value_label.text = model.send(attribute)
1441
- end.observe(model, attribute)
1442
1458
  end
1443
1459
 
1444
- def address(address)
1460
+ def address(address_model)
1445
1461
  vertical_box {
1446
- address.each_pair do |attribute, value|
1447
- label_pair(address, attribute, value)
1462
+ address_model.each_pair do |attribute, value|
1463
+ label_pair(address_model, attribute, value)
1448
1464
  end
1449
1465
  }
1450
1466
  end
@@ -1501,6 +1517,12 @@ window('Method-Based Custom Keyword') {
1501
1517
 
1502
1518
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1503
1519
 
1520
+ The [`area`](#area-api) control can be utilized to build non-native custom controls from scratch by leveraging vector graphics, formattable text, keyboard events, and mouse events. This is demonstrated in the [Area-Based Custom Controls](#area-based-custom-controls) example.
1521
+
1522
+ Defining custom keywords enables unlimited extension of the [Glimmer GUI DSL](#glimmer-gui-dsl). The sky is the limit on what can be done with custom keywords as a result. You can compose new visual vocabulary to build applications in any domain from higher concepts rather than [mere standard controls](#supported-keywords). For example, in a traffic signaling app, you could define `street`, `light_signal`, `traffic_sign`, and `car` as custom keywords and build your application from these concepts directly, saving enormous time and achieving much higher productivity.
1523
+
1524
+ Learn more from custom keyword usage in [Method-Based Custom Keyword](#method-based-custom-keyword), [Area-Based Custom Controls](#area-based-custom-controls), [Basic Scrolling Area](#basic-scrolling-area), [Histogram](#histogram), and [Tetris](#tetris) examples.
1525
+
1504
1526
  ### Observer Pattern
1505
1527
 
1506
1528
  The [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) (a.k.a. Observer Pattern) is fundamental to building GUIs (Graphical User Interfaces) following the [MVC (Model View Controller) Architectural Pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) or any of its variations like [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter). In the original Smalltalk-MVC, the View observes the Model for changes and updates itself accordingly.
@@ -1772,9 +1794,9 @@ Data-bound model attribute can be:
1772
1794
  - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
1773
1795
 
1774
1796
  Data-binding options include:
1775
- - `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
1797
+ - `before_read {|value| ...}`: performs an operation before reading data from Model to update View.
1776
1798
  - `on_read {|value| ...}`: converts value read from Model to update the View.
1777
- - `after_read {|converted_value| ...}`: performs an operation after read from Model and updating the View.
1799
+ - `after_read {|converted_value| ...}`: performs an operation after read from Model to update View.
1778
1800
  - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
1779
1801
  - `on_write {|value| ...}`: converts value read from View to update the Model.
1780
1802
  - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
@@ -1805,8 +1827,8 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1805
1827
  - `table` `checkbox_text_column` checkbox editing only works on Linux (not Mac or Windows) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
1806
1828
  - `text` `align` property seems not to work on the Mac ([libui](https://github.com/andlabs/libui) has an [issue](https://github.com/andlabs/libui/pull/407) about it)
1807
1829
  - `text` `string` `background` does not work on Windows due to an [issue in libui](https://github.com/andlabs/libui/issues/347).
1808
- - `table` controls on Windows intentionally get an extra empty row at the end because if any row were to be deleted for the first time, double-deletion happens due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
1809
1830
  - `table` `progress_bar` column on Windows cannot be updated with a positive value if it started initially with `-1` (it ignores update to avoid crashing due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
1831
+ - `radio_buttons` on Linux has an issue where it always selects the first item even if you did not set its `selected` value or set it to `-1` (meaning unselected). It works correctly on Mac and Windows.
1810
1832
  - It seems that [libui](https://github.com/andlabs/libui) does not support nesting multiple `area` controls under a `grid` as only the first one shows up in that scenario. To workaround that limitation, use a `vertical_box` with nested `horizontal_box`s instead to include multiple `area`s in a GUI.
1811
1833
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1812
1834
  - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
@@ -8370,6 +8392,12 @@ window('Login') {
8370
8392
 
8371
8393
  #### Method-Based Custom Keyword
8372
8394
 
8395
+ [Custom keywords](#custom-keywords) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
8396
+
8397
+ This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom control keywords.
8398
+
8399
+ The custom keywords are defined via methods (thus are "method-based").
8400
+
8373
8401
  [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
8374
8402
 
8375
8403
  Run with this command from the root of the project if you cloned the project:
@@ -8585,6 +8613,317 @@ window('Method-Based Custom Keyword') {
8585
8613
  }.show
8586
8614
  ```
8587
8615
 
8616
+ #### Area-Based Custom Controls
8617
+
8618
+ [Custom keywords](#custom-keywords) can be defined for graphical custom controls (components) built completely from scratch as vector-graphics on top of the [`area`](#area-api) control while leveraging keyboard and mouse listeners.
8619
+
8620
+ This example defines `text_label` and `push_button` as [`area`](#area-api)-based graphical custom controls that can have width, height, font, fill, stroke, border, and custom text location.
8621
+
8622
+ [examples/area_based_custom_controls.rb](examples/area_based_custom_controls.rb)
8623
+
8624
+ Run with this command from the root of the project if you cloned the project:
8625
+
8626
+ ```
8627
+ ruby -r './lib/glimmer-dsl-libui' examples/area_based_custom_controls.rb
8628
+ ```
8629
+
8630
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
8631
+
8632
+ ```
8633
+ ruby -r glimmer-dsl-libui -e "require 'examples/area_based_custom_controls'"
8634
+ ```
8635
+
8636
+ Mac | Windows | Linux
8637
+ ----|---------|------
8638
+ ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-push-button-clicked.png) | ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-push-button-clicked.png) | ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-push-button-clicked.png)
8639
+
8640
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8641
+
8642
+ ```ruby
8643
+ require 'glimmer-dsl-libui'
8644
+
8645
+ class AreaBasedCustomControls
8646
+ include Glimmer
8647
+
8648
+ attr_accessor :label_width, :label_height, :label_font_descriptor,
8649
+ :label_text_color, :label_background_fill, :label_border_stroke,
8650
+ :label_text_x, :label_text_y,
8651
+ :button_width, :button_height, :button_font_descriptor,
8652
+ :button_text_color, :button_background_fill, :button_border_stroke,
8653
+ :button_text_x, :button_text_y
8654
+
8655
+ def initialize
8656
+ self.label_width = 335
8657
+ self.label_height = 50
8658
+ self.label_font_descriptor = {family: OS.linux? ? 'Monospace Bold Italic' : 'Courier New', size: 16, weight: :bold, italic: :italic}
8659
+ self.label_text_color = :red
8660
+ self.label_background_fill = :yellow
8661
+ self.label_border_stroke = :limegreen
8662
+
8663
+ self.button_width = 150
8664
+ self.button_height = 50
8665
+ self.button_font_descriptor = {family: OS.linux? ? 'Monospace Bold Italic' : 'Courier New', size: 36, weight: OS.linux? ? :normal : :bold, italic: :italic}
8666
+ self.button_text_color = :green
8667
+ self.button_background_fill = :yellow
8668
+ self.button_border_stroke = :limegreen
8669
+ end
8670
+
8671
+ def rebuild_text_label
8672
+ @text_label.destroy
8673
+ @text_label_vertical_box.content { # re-open vertical box content and shove in a new label
8674
+ @text_label = text_label('This is a text label.',
8675
+ width: label_width, height: label_height, font_descriptor: label_font_descriptor,
8676
+ background_fill: label_background_fill, text_color: label_text_color, border_stroke: label_border_stroke,
8677
+ text_x: label_text_x, text_y: label_text_y)
8678
+ }
8679
+ end
8680
+
8681
+ def rebuild_push_button
8682
+ @push_button.destroy
8683
+ @push_button_vertical_box.content { # re-open vertical box content and shove in a new button
8684
+ @push_button = push_button('Push',
8685
+ width: button_width, height: button_height, font_descriptor: button_font_descriptor,
8686
+ background_fill: button_background_fill, text_color: button_text_color, border_stroke: button_border_stroke,
8687
+ text_x: button_text_x, text_y: button_text_y) {
8688
+ on_mouse_up do
8689
+ message_box('Button Pushed', 'Thank you for pushing the button!')
8690
+ end
8691
+ }
8692
+ }
8693
+ end
8694
+
8695
+ def launch
8696
+ window('Area-Based Custom Controls', 385, 385) { |w|
8697
+ margined true
8698
+
8699
+ tab {
8700
+ tab_item('Text Label') {
8701
+ @text_label_vertical_box = vertical_box {
8702
+ vertical_box {
8703
+ text_label('Text Label Form:', width: 385, height: 30, background_fill: OS.windows? ? :white : {a: 0}, border_stroke: OS.windows? ? :white : {a: 0}, font_descriptor: {size: 16, weight: :bold}, text_x: 0, text_y: OS.windows? ? 0 : 5)
8704
+
8705
+ horizontal_box {
8706
+ label('Width')
8707
+ spinbox(1, 1000) {
8708
+ value <=> [self, :label_width, after_write: method(:rebuild_text_label)]
8709
+ }
8710
+ }
8711
+
8712
+ horizontal_box {
8713
+ label('Height')
8714
+ spinbox(1, 1000) {
8715
+ value <=> [self, :label_height, after_write: method(:rebuild_text_label)]
8716
+ }
8717
+ }
8718
+
8719
+ horizontal_box {
8720
+ label('Font')
8721
+ font_button {
8722
+ font <=> [self, :label_font_descriptor, after_write: method(:rebuild_text_label)]
8723
+ }
8724
+ }
8725
+
8726
+ horizontal_box {
8727
+ label('Text Color')
8728
+ color_button {
8729
+ color <=> [self, :label_text_color, after_write: method(:rebuild_text_label)]
8730
+ }
8731
+ }
8732
+
8733
+ horizontal_box {
8734
+ label('Background Color')
8735
+ color_button {
8736
+ color <=> [self, :label_background_fill, after_write: method(:rebuild_text_label)]
8737
+ }
8738
+ }
8739
+
8740
+ horizontal_box {
8741
+ label('Border Color')
8742
+ color_button {
8743
+ color <=> [self, :label_border_stroke, after_write: method(:rebuild_text_label)]
8744
+ }
8745
+ }
8746
+
8747
+ horizontal_box {
8748
+ label('Text X (0=centered)')
8749
+ spinbox(0, 1000) {
8750
+ value <=> [self, :label_text_x, on_read: ->(x) {x.nil? ? 0 : x}, on_write: ->(x) {x == 0 ? nil : x}, after_write: method(:rebuild_text_label)]
8751
+ }
8752
+ }
8753
+
8754
+ horizontal_box {
8755
+ label('Text Y (0=centered)')
8756
+ spinbox(0, 1000) {
8757
+ value <=> [self, :label_text_y, on_read: ->(y) {y.nil? ? 0 : y}, on_write: ->(y) {y == 0 ? nil : y}, after_write: method(:rebuild_text_label)]
8758
+ }
8759
+ }
8760
+ }
8761
+
8762
+ @text_label = text_label('This is a text label.',
8763
+ width: label_width, height: label_height, font_descriptor: label_font_descriptor,
8764
+ background_fill: label_background_fill, text_color: label_text_color, border_stroke: label_border_stroke,
8765
+ text_x: label_text_x, text_y: label_text_y)
8766
+ }
8767
+ }
8768
+
8769
+ tab_item('Push Button') {
8770
+ @push_button_vertical_box = vertical_box {
8771
+ vertical_box {
8772
+ text_label('Push Button Form:', width: 385, height: 30, background_fill: OS.windows? ? :white : {a: 0}, border_stroke: OS.windows? ? :white : {a: 0}, font_descriptor: {size: 16, weight: :bold}, text_x: 0, text_y: OS.windows? ? 0 : 5)
8773
+
8774
+ horizontal_box {
8775
+ label('Width')
8776
+ spinbox(1, 1000) {
8777
+ value <=> [self, :button_width, after_write: method(:rebuild_push_button)]
8778
+ }
8779
+ }
8780
+
8781
+ horizontal_box {
8782
+ label('Height')
8783
+ spinbox(1, 1000) {
8784
+ value <=> [self, :button_height, after_write: method(:rebuild_push_button)]
8785
+ }
8786
+ }
8787
+
8788
+ horizontal_box {
8789
+ label('Font')
8790
+ font_button {
8791
+ font <=> [self, :button_font_descriptor, after_write: method(:rebuild_push_button)]
8792
+ }
8793
+ }
8794
+
8795
+ horizontal_box {
8796
+ label('Text Color')
8797
+ color_button {
8798
+ color <=> [self, :button_text_color, after_write: method(:rebuild_push_button)]
8799
+ }
8800
+ }
8801
+
8802
+ horizontal_box {
8803
+ label('Background Color')
8804
+ color_button {
8805
+ color <=> [self, :button_background_fill, after_write: method(:rebuild_push_button)]
8806
+ }
8807
+ }
8808
+
8809
+ horizontal_box {
8810
+ label('Border Color')
8811
+ color_button {
8812
+ color <=> [self, :button_border_stroke, after_write: method(:rebuild_push_button)]
8813
+ }
8814
+ }
8815
+
8816
+ horizontal_box {
8817
+ label('Text X (0=centered)')
8818
+ spinbox(0, 1000) {
8819
+ value <=> [self, :button_text_x, on_read: ->(x) {x.nil? ? 0 : x}, on_write: ->(x) {x == 0 ? nil : x}, after_write: method(:rebuild_push_button)]
8820
+ }
8821
+ }
8822
+
8823
+ horizontal_box {
8824
+ label('Text Y (0=centered)')
8825
+ spinbox(0, 1000) {
8826
+ value <=> [self, :button_text_y, on_read: ->(y) {y.nil? ? 0 : y}, on_write: ->(y) {y == 0 ? nil : y}, after_write: method(:rebuild_push_button)]
8827
+ }
8828
+ }
8829
+ }
8830
+
8831
+ @push_button = push_button('Push',
8832
+ width: button_width, height: button_height, font_descriptor: button_font_descriptor,
8833
+ background_fill: button_background_fill, text_color: button_text_color, border_stroke: button_border_stroke,
8834
+ text_x: button_text_x, text_y: button_text_y) {
8835
+ on_mouse_up do
8836
+ message_box('Button Pushed', 'Thank you for pushing the button!')
8837
+ end
8838
+ }
8839
+ }
8840
+ }
8841
+ }
8842
+ }.show
8843
+ end
8844
+
8845
+ # text label (area-based custom control) built with vector graphics on top of area.
8846
+ #
8847
+ # background_fill is transparent by default.
8848
+ # background_fill can accept a single color or gradient stops just as per `fill` property in README.
8849
+ # border_stroke is transparent by default.
8850
+ # border_stroke can accept thickness and dashes in addition to color just as per `stroke` property in README.
8851
+ # text_x and text_y are the offset of the label text in relation to its top-left corner.
8852
+ # When text_x, text_y are left nil, the text is automatically centered in the label area.
8853
+ # Sometimes, the centering calculation is not perfect due to using a custom font, so
8854
+ # in that case, pass in text_x, and text_y manually.
8855
+ def text_label(label_text,
8856
+ width: 80, height: 30, font_descriptor: {},
8857
+ background_fill: {a: 0}, text_color: :black, border_stroke: {a: 0},
8858
+ text_x: nil, text_y: nil,
8859
+ &content)
8860
+ area { |the_area|
8861
+ rectangle(1, 1, width, height) {
8862
+ fill background_fill
8863
+ }
8864
+ rectangle(1, 1, width, height) {
8865
+ stroke border_stroke
8866
+ }
8867
+
8868
+ text_height = (font_descriptor[:size] || 12) * (OS.mac? ? 0.75 : 1.35)
8869
+ text_width = (text_height * label_text.size) * (OS.mac? ? 0.75 : 0.60)
8870
+ text_x ||= (width - text_width) / 2.0
8871
+ text_y ||= (height - 4 - text_height) / 2.0
8872
+ text(text_x, text_y, width) {
8873
+ string(label_text) {
8874
+ color text_color
8875
+ font font_descriptor
8876
+ }
8877
+ }
8878
+
8879
+ content&.call(the_area)
8880
+ }
8881
+ end
8882
+
8883
+ # push button (area-based custom control) built with vector graphics on top of area.
8884
+ #
8885
+ # background_fill is white by default.
8886
+ # background_fill can accept a single color or gradient stops just as per `fill` property in README.
8887
+ # border_stroke is black by default.
8888
+ # border_stroke can accept thickness and dashes in addition to color just as per `stroke` property in README.
8889
+ # text_x and text_y are the offset of the button text in relation to its top-left corner.
8890
+ # When text_x, text_y are left nil, the text is automatically centered in the button area.
8891
+ # Sometimes, the centering calculation is not perfect due to using a custom font, so
8892
+ # in that case, pass in text_x, and text_y manually.
8893
+ #
8894
+ # reuses the text_label custom control
8895
+ def push_button(button_text,
8896
+ width: 80, height: 30, font_descriptor: {},
8897
+ background_fill: :white, text_color: :black, border_stroke: {r: 201, g: 201, b: 201},
8898
+ text_x: nil, text_y: nil,
8899
+ &content)
8900
+ text_label(button_text,
8901
+ width: width, height: height, font_descriptor: font_descriptor,
8902
+ background_fill: background_fill, text_color: text_color, border_stroke: border_stroke,
8903
+ text_x: text_x, text_y: text_y) { |the_area|
8904
+
8905
+ # dig into the_area content and grab elements to modify in mouse listeners below
8906
+ background_rectangle = the_area.children[0]
8907
+ button_string = the_area.children[2].children[0]
8908
+
8909
+ on_mouse_down do
8910
+ background_rectangle.fill = {x0: 0, y0: 0, x1: 0, y1: height, stops: [{pos: 0, r: 72, g: 146, b: 247}, {pos: 1, r: 12, g: 85, b: 214}]}
8911
+ button_string.color = :white
8912
+ end
8913
+
8914
+ on_mouse_up do
8915
+ background_rectangle.fill = background_fill
8916
+ button_string.color = text_color
8917
+ end
8918
+
8919
+ content&.call(the_area)
8920
+ }
8921
+ end
8922
+ end
8923
+
8924
+ AreaBasedCustomControls.new.launch
8925
+ ```
8926
+
8588
8927
  #### Midi Player
8589
8928
 
8590
8929
  To run this example, install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
@@ -8983,6 +9322,8 @@ TinyMidiPlayer.new
8983
9322
 
8984
9323
  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.
8985
9324
 
9325
+ Use arrows to move and spacebar to pause/resume.
9326
+
8986
9327
  [examples/snake.rb](examples/snake.rb)
8987
9328
 
8988
9329
  Run with this command from the root of the project if you cloned the project:
@@ -9038,7 +9379,7 @@ class Snake
9038
9379
  end
9039
9380
 
9040
9381
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
9041
- unless @game.over?
9382
+ unless @game.paused? || @game.over?
9042
9383
  process_queued_keypress
9043
9384
  @game.snake.move
9044
9385
  end
@@ -9079,7 +9420,11 @@ class Snake
9079
9420
  }
9080
9421
 
9081
9422
  on_key_up do |area_key_event|
9082
- @keypress_queue << area_key_event[:ext_key]
9423
+ if area_key_event[:key] == ' '
9424
+ @game.toggle_pause
9425
+ else
9426
+ @keypress_queue << area_key_event[:ext_key]
9427
+ end
9083
9428
  end
9084
9429
  }
9085
9430
  end
@@ -9138,7 +9483,7 @@ class Snake
9138
9483
  end
9139
9484
 
9140
9485
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
9141
- unless @game.over?
9486
+ unless @game.paused? || @game.over?
9142
9487
  process_queued_keypress
9143
9488
  @game.snake.move
9144
9489
  end
@@ -9181,7 +9526,11 @@ class Snake
9181
9526
  }
9182
9527
 
9183
9528
  on_key_up do |area_key_event|
9184
- @keypress_queue << area_key_event[:ext_key]
9529
+ if area_key_event[:key] == ' '
9530
+ @game.toggle_pause
9531
+ else
9532
+ @keypress_queue << area_key_event[:ext_key]
9533
+ end
9185
9534
  end
9186
9535
  }
9187
9536
  end
@@ -9215,7 +9564,7 @@ ruby -r glimmer-dsl-libui -e "require 'examples/tetris'"
9215
9564
 
9216
9565
  Mac | Windows | Linux
9217
9566
  ----|---------|------
9218
- ![glimmer-dsl-libui-mac-tetris.png](images/glimmer-dsl-libui-mac-tetris.png) ![glimmer-dsl-libui-mac-tetris-game-over.png](images/glimmer-dsl-libui-mac-tetris-game-over.png) ![glimmer-dsl-libui-mac-tetris-high-scores.png](images/glimmer-dsl-libui-mac-tetris-high-scores.png) | ![glimmer-dsl-libui-windows-tetris.png](images/glimmer-dsl-libui-windows-tetris.png) ![glimmer-dsl-libui-windows-tetris-game-over.png](images/glimmer-dsl-libui-windows-tetris-game-over.png) ![glimmer-dsl-libui-windows-tetris-high-scores.png](images/glimmer-dsl-libui-windows-tetris-high-scores.png) | ![glimmer-dsl-libui-linux-tetris.png](images/glimmer-dsl-libui-linux-tetris.png) ![glimmer-dsl-libui-linux-tetris-game-over.png](images/glimmer-dsl-libui-linux-tetris-game-over.png) ![glimmer-dsl-libui-linux-tetris-high-scores.png](images/glimmer-dsl-libui-linux-tetris-high-scores.png)
9567
+ ![glimmer-dsl-libui-mac-tetris.png](images/glimmer-dsl-libui-mac-tetris.png) | ![glimmer-dsl-libui-windows-tetris.png](images/glimmer-dsl-libui-windows-tetris.png) | ![glimmer-dsl-libui-linux-tetris.png](images/glimmer-dsl-libui-linux-tetris.png)
9219
9568
 
9220
9569
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
9221
9570
 
@@ -9293,18 +9642,31 @@ class Tetris
9293
9642
 
9294
9643
  Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
9295
9644
  Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
9296
- observe(@game.preview_playfield[row][column], :color) do |new_color|
9645
+ preview_updater = proc do
9297
9646
  Glimmer::LibUI.queue_main do
9647
+ new_color = @game.preview_playfield[row][column].color
9298
9648
  color = Glimmer::LibUI.interpret_color(new_color)
9299
9649
  block = @preview_playfield_blocks[row][column]
9300
- block[:background_square].fill = color
9301
- block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
9302
- block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9303
- block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9304
- block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9305
- block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
9650
+ if @game.show_preview_tetromino?
9651
+ block[:background_square].fill = color
9652
+ block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
9653
+ block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9654
+ block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9655
+ block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
9656
+ block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
9657
+ else
9658
+ transparent_color = {r: 255, g: 255, b: 255, a: 0}
9659
+ block[:background_square].fill = transparent_color
9660
+ block[:top_bevel_edge].fill = transparent_color
9661
+ block[:right_bevel_edge].fill = transparent_color
9662
+ block[:bottom_bevel_edge].fill = transparent_color
9663
+ block[:left_bevel_edge].fill = transparent_color
9664
+ block[:border_square].stroke = transparent_color
9665
+ end
9306
9666
  end
9307
9667
  end
9668
+ observe(@game.preview_playfield[row][column], :color, &preview_updater)
9669
+ observe(@game, :show_preview_tetromino, &preview_updater)
9308
9670
  end
9309
9671
  end
9310
9672
 
@@ -9352,6 +9714,12 @@ class Tetris
9352
9714
  }
9353
9715
 
9354
9716
  menu('View') {
9717
+ check_menu_item('Show Next Block Preview') {
9718
+ checked <=> [@game, :show_preview_tetromino]
9719
+ }
9720
+
9721
+ separator_menu_item
9722
+
9355
9723
  menu_item('Show High Scores') {
9356
9724
  on_clicked do
9357
9725
  show_high_scores
@@ -9363,18 +9731,20 @@ class Tetris
9363
9731
  @game.clear_high_scores!
9364
9732
  }
9365
9733
  }
9734
+
9735
+ separator_menu_item
9366
9736
  }
9367
9737
 
9368
9738
  menu('Options') {
9369
- radio_menu_item('Instant Down on Up Arrow') { |r|
9739
+ radio_menu_item('Instant Down on Up Arrow') {
9370
9740
  checked <=> [@game, :instant_down_on_up]
9371
9741
  }
9372
9742
 
9373
- radio_menu_item('Rotate Right on Up Arrow') { |r|
9743
+ radio_menu_item('Rotate Right on Up Arrow') {
9374
9744
  checked <=> [@game, :rotate_right_on_up]
9375
9745
  }
9376
9746
 
9377
- radio_menu_item('Rotate Left on Up Arrow') { |r|
9747
+ radio_menu_item('Rotate Left on Up Arrow') {
9378
9748
  checked <=> [@game, :rotate_left_on_up]
9379
9749
  }
9380
9750
  }
@@ -9421,7 +9791,7 @@ class Tetris
9421
9791
  block = {}
9422
9792
  bevel_pixel_size = 0.16 * block_size.to_f
9423
9793
  color = Glimmer::LibUI.interpret_color(Model::Block::COLOR_CLEAR)
9424
- area {
9794
+ block[:area] = area {
9425
9795
  block[:background_square] = square(0, 0, block_size) {
9426
9796
  fill color
9427
9797
  }
@@ -9497,12 +9867,6 @@ class Tetris
9497
9867
 
9498
9868
  def score_board(block_size: , &extra_content)
9499
9869
  vertical_box {
9500
- horizontal_box {
9501
- label # filler
9502
- @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
9503
- label # filler
9504
- }
9505
-
9506
9870
  horizontal_box {
9507
9871
  label # filler
9508
9872
  grid {
@@ -9543,6 +9907,12 @@ class Tetris
9543
9907
  }
9544
9908
  label # filler
9545
9909
  }
9910
+
9911
+ horizontal_box {
9912
+ label # filler
9913
+ @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
9914
+ label # filler
9915
+ }
9546
9916
 
9547
9917
  extra_content&.call
9548
9918
  }
@@ -9582,7 +9952,7 @@ class Tetris
9582
9952
 
9583
9953
  def show_about_dialog
9584
9954
  Glimmer::LibUI.queue_main do
9585
- msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021 Andy Maleh')
9955
+ msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021-2022 Andy Maleh')
9586
9956
  end
9587
9957
  end
9588
9958
  end
@@ -10092,6 +10462,10 @@ https://github.com/AndyObtiva/befunge98/tree/gui
10092
10462
 
10093
10463
  https://github.com/iraamaro/i3off-gtk-ruby
10094
10464
 
10465
+ ### Chess
10466
+
10467
+ https://github.com/mikeweber/chess
10468
+
10095
10469
  ## Process
10096
10470
 
10097
10471
  [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
@@ -10107,7 +10481,7 @@ https://github.com/iraamaro/i3off-gtk-ruby
10107
10481
 
10108
10482
  ### Issues
10109
10483
 
10110
- If you encounter [issues](https://github.com/AndyObtiva/glimmer-dsl-libui/issues) that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use [libui](https://github.com/andlabs/libui) than what is possible with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), you may submit an [issue](https://github.com/AndyObtiva/glimmer-dsl-libui/issues/new) or [pull request](https://github.com/AndyObtiva/glimmer-dsl-libui/compare) on [GitHub](https://github.com). In the meantime, you may try older gem versions of [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) till you find one that works until issues are resolved.
10484
+ If you encounter [issues](https://github.com/AndyObtiva/glimmer-dsl-libui/issues) that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use [libui](https://github.com/andlabs/libui) than what is possible with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), you may submit an [issue](https://github.com/AndyObtiva/glimmer-dsl-libui/issues/new) or [pull request](https://github.com/AndyObtiva/glimmer-dsl-libui/compare) on [GitHub](https://github.com). In the meantime, you may try older gem versions of [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) till you find one that works.
10111
10485
 
10112
10486
  ### Chat
10113
10487
 
@@ -10151,7 +10525,7 @@ Note that the latest development sometimes takes place in the [development](http
10151
10525
 
10152
10526
  [MIT](LICENSE.txt)
10153
10527
 
10154
- Copyright (c) 2021 Andy Maleh
10528
+ Copyright (c) 2021-2022 Andy Maleh
10155
10529
 
10156
10530
  --
10157
10531