glimmer-dsl-libui 0.4.1 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +1628 -204
  4. data/VERSION +1 -1
  5. data/examples/basic_entry.rb +27 -24
  6. data/examples/basic_entry2.rb +31 -0
  7. data/examples/button_counter.rb +27 -0
  8. data/examples/dynamic_area.rb +77 -90
  9. data/examples/dynamic_area2.rb +14 -12
  10. data/examples/dynamic_area3.rb +90 -0
  11. data/examples/dynamic_area4.rb +95 -0
  12. data/examples/form.rb +42 -30
  13. data/examples/form2.rb +37 -0
  14. data/examples/form_table.rb +100 -87
  15. data/examples/form_table2.rb +93 -0
  16. data/examples/histogram.rb +98 -91
  17. data/examples/histogram2.rb +109 -0
  18. data/examples/login.rb +45 -39
  19. data/examples/login2.rb +55 -0
  20. data/examples/login3.rb +65 -0
  21. data/examples/login4.rb +61 -0
  22. data/examples/login5.rb +43 -0
  23. data/examples/meta_example.rb +10 -7
  24. data/examples/method_based_custom_keyword.rb +8 -15
  25. data/examples/method_based_custom_keyword2.rb +97 -0
  26. data/examples/snake.rb +1 -1
  27. data/examples/timer.rb +28 -31
  28. data/examples/timer2.rb +129 -0
  29. data/glimmer-dsl-libui.gemspec +0 -0
  30. data/lib/glimmer/dsl/libui/data_binding_expression.rb +4 -6
  31. data/lib/glimmer/libui/attributed_string.rb +3 -0
  32. data/lib/glimmer/libui/control_proxy/area_proxy.rb +52 -46
  33. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +5 -0
  34. data/lib/glimmer/libui/control_proxy/image_proxy.rb +4 -5
  35. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +5 -0
  36. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +38 -0
  37. data/lib/glimmer/libui/control_proxy/table_proxy.rb +1 -1
  38. data/lib/glimmer/libui/control_proxy.rb +8 -1
  39. data/lib/glimmer/libui/data_bindable.rb +39 -0
  40. data/lib/glimmer/libui/shape.rb +7 -2
  41. metadata +17 -2
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.4.1
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.5
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)
@@ -14,11 +14,11 @@ Mac | Windows | Linux
14
14
  The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) as opposed to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) or [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk) is the fact that [SWT](https://www.eclipse.org/swt/) and [Tk](https://www.tcl.tk/) are more mature than mid-alpha [libui](https://github.com/andlabs/libui) as GUI toolkits. Still, if there is only a need to build a small simple application, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) could be a good convenient choice due to having zero prerequisites beyond the dependencies included in the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui). Also, just like [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk), its apps start instantly and have a small memory footprint. [LibUI](https://github.com/kojix2/LibUI) is a promising new GUI toolkit that might prove quite worthy in the future.
15
15
 
16
16
  [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) aims to provide a DSL similar to the [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) to enable more productive desktop development in Ruby with:
17
- - Declarative DSL syntax that visually maps to the GUI control hierarchy
18
- - Convention over configuration via smart defaults and automation of low-level details
19
- - Requiring the least amount of syntax possible to build GUI
20
- - Custom Keyword support
21
- - Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
17
+ - [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy
18
+ - [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details
19
+ - Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI
20
+ - [Custom Keyword](#custom-keywords) support
21
+ - [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models
22
22
  - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
23
23
  - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
24
24
 
@@ -264,6 +264,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
264
264
  - [Basic Draw Text](#basic-draw-text)
265
265
  - [Advanced Examples](#advanced-examples)
266
266
  - [Area Gallery](#area-gallery)
267
+ - [Button Counter](#button-counter)
267
268
  - [Color The Circles](#color-the-circles)
268
269
  - [Control Gallery](#control-gallery)
269
270
  - [Custom Draw Text](#custom-draw-text)
@@ -372,7 +373,7 @@ gem install glimmer-dsl-libui
372
373
  Or install via Bundler `Gemfile`:
373
374
 
374
375
  ```ruby
375
- gem 'glimmer-dsl-libui', '~> 0.4.1'
376
+ gem 'glimmer-dsl-libui', '~> 0.4.5'
376
377
  ```
377
378
 
378
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.
@@ -619,93 +620,112 @@ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
619
620
  ```ruby
620
621
  require 'glimmer-dsl-libui'
621
622
 
622
- include Glimmer
623
-
624
- data = [
625
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
626
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
627
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
628
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
629
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
630
- ]
631
-
632
- window('Contacts', 600, 600) { |w|
633
- margined true
623
+ class FormTable
624
+ include Glimmer
634
625
 
635
- vertical_box {
636
- form {
637
- stretchy false
638
-
639
- @name_entry = entry {
640
- label 'Name'
641
- }
642
- @email_entry = entry {
643
- label 'Email'
644
- }
645
- @phone_entry = entry {
646
- label 'Phone'
647
- }
648
- @city_entry = entry {
649
- label 'City'
650
- }
651
- @state_entry = entry {
652
- label 'State'
653
- }
654
- }
655
-
656
- button('Save Contact') {
657
- stretchy false
658
-
659
- on_clicked do
660
- new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
661
- if new_row.include?('')
662
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
663
- else
664
- data << new_row # automatically inserts a row into the table due to implicit data-binding
665
- @unfiltered_data = data.dup
666
- @name_entry.text = ''
667
- @email_entry.text = ''
668
- @phone_entry.text = ''
669
- @city_entry.text = ''
670
- @state_entry.text = ''
671
- end
672
- end
673
- }
674
-
675
- search_entry { |se|
676
- stretchy false
626
+ attr_accessor :name, :email, :phone, :city, :state, :filter_value
627
+
628
+ def initialize
629
+ @data = [
630
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
631
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
632
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
633
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
634
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
635
+ ]
636
+ end
637
+
638
+ def launch
639
+ window('Contacts', 600, 600) { |w|
640
+ margined true
677
641
 
678
- on_changed do
679
- filter_value = se.text
680
- @unfiltered_data ||= data.dup
681
- # Unfilter first to remove any previous filters
682
- data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
683
- # Now, apply filter if entered
684
- unless filter_value.empty?
685
- data.filter! do |row_data| # affects table indirectly through implicit data-binding
686
- row_data.any? do |cell|
687
- cell.to_s.downcase.include?(filter_value.downcase)
642
+ vertical_box {
643
+ form {
644
+ stretchy false
645
+
646
+ entry {
647
+ label 'Name'
648
+ text <=> [self, :name]
649
+ }
650
+
651
+ entry {
652
+ label 'Email'
653
+ text <=> [self, :email]
654
+ }
655
+
656
+ entry {
657
+ label 'Phone'
658
+ text <=> [self, :phone]
659
+ }
660
+
661
+ entry {
662
+ label 'City'
663
+ text <=> [self, :city]
664
+ }
665
+
666
+ entry {
667
+ label 'State'
668
+ text <=> [self, :state]
669
+ }
670
+ }
671
+
672
+ button('Save Contact') {
673
+ stretchy false
674
+
675
+ on_clicked do
676
+ new_row = [name, email, phone, city, state]
677
+ if new_row.include?('')
678
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
679
+ else
680
+ @data << new_row # automatically inserts a row into the table due to implicit data-binding
681
+ @unfiltered_data = @data.dup
682
+ self.name = '' # automatically clears name entry through explicit data-binding
683
+ self.email = ''
684
+ self.phone = ''
685
+ self.city = ''
686
+ self.state = ''
688
687
  end
689
688
  end
690
- end
691
- end
692
- }
689
+ }
690
+
691
+ search_entry {
692
+ stretchy false
693
+ text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
694
+ after_write: ->(filter_value) { # execute after write to self.filter_value
695
+ @unfiltered_data ||= @data.dup
696
+ # Unfilter first to remove any previous filters
697
+ @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
698
+ # Now, apply filter if entered
699
+ unless filter_value.empty?
700
+ @data.filter! do |row_data| # affects table indirectly through implicit data-binding
701
+ row_data.any? do |cell|
702
+ cell.to_s.downcase.include?(filter_value.downcase)
703
+ end
704
+ end
705
+ end
706
+ }
707
+ ]
708
+ }
709
+
710
+ table {
711
+ text_column('Name')
712
+ text_column('Email')
713
+ text_column('Phone')
714
+ text_column('City')
715
+ text_column('State')
693
716
 
694
- table {
695
- text_column('Name')
696
- text_column('Email')
697
- text_column('Phone')
698
- text_column('City')
699
- text_column('State')
717
+ cell_rows @data # implicit data-binding
718
+
719
+ on_changed do |row, type, row_data|
720
+ puts "Row #{row} #{type}: #{row_data}"
721
+ end
722
+ }
723
+ }
724
+ }.show
725
+ end
726
+ end
700
727
 
701
- cell_rows data # implicit data-binding
702
-
703
- on_changed do |row, type, row_data|
704
- puts "Row #{row} #{type}: #{row_data}"
705
- end
706
- }
707
- }
708
- }.show
728
+ FormTable.new.launch
709
729
  ```
710
730
 
711
731
  ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
@@ -778,9 +798,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
778
798
  - `scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)`: scrolls to `x`/`y` location with `width` and `height` viewport size.
779
799
  - `set_size(width as Numeric, height as Numeric)`: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
780
800
 
781
- Mac |
782
- ----|
783
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png)
801
+ Mac |Linux
802
+ ----|-----
803
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
784
804
 
785
805
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
786
806
 
@@ -876,7 +896,7 @@ Given that it is very new and is not a [libui](https://github.com/andlabs/libui)
876
896
  - Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
877
897
  - Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image.
878
898
 
879
- Currently, it is recommended to use `image` with very small `width` and `height` values only.
899
+ Currently, it is recommended to use `image` with very small `width` and `height` values only (e.g. 24x24).
880
900
 
881
901
  Setting a [`transform` `matrix`](#area-transform-matrix) is supported under `image` just like it is under `path` and `text` inside `area`.
882
902
 
@@ -1032,6 +1052,8 @@ window('Basic Image', 96, 96) {
1032
1052
 
1033
1053
  One final note is that in Linux, table images grow and shrink with the image size unlike on the Mac where table row heights are constant regardless of image sizes. As such, you may be able to repurpose a table with a single image column and a single row as an image control with more native libui rendering if you are only targeting Linux with your app.
1034
1054
 
1055
+ ![linux table image](images/glimmer-dsl-libui-linux-basic-table-image.png)
1056
+
1035
1057
  Check out [examples/basic_image.rb](#basic-image) (all versions) for examples of using `image` Glimmer custom control.
1036
1058
 
1037
1059
  #### Colors
@@ -1328,7 +1350,7 @@ The [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) (a
1328
1350
 
1329
1351
  ![MVC - Model View Controller](https://www.researchgate.net/profile/Danny-Weyns/publication/269303611/figure/fig2/AS:858133056462866@1581606272800/Smalltalk80-MVC-pattern-View-and-Controller-work-as-a-pair-allowing-the-user-to-interact.ppm)
1330
1352
 
1331
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) via the `observe(model, attribute=nil)` keyword, which can observe `Object` models with attributes, `Hash`es, and `Array`s. It automatically enhances objects as needed to support automatically notifying observers of changes via `observable#notify_observers(attribute)` method:
1353
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) via the `observe(model, attribute_or_key=nil)` keyword, which can observe `Object` models with attributes, `Hash`es with keys, and `Array`s. It automatically enhances objects as needed to support automatically notifying observers of changes via `observable#notify_observers(attribute_or_key = nil)` method:
1332
1354
  - `Object` becomes `Glimmer::DataBinding::ObservableModel`, which supports observing specified `Object` model attributes.
1333
1355
  - `Hash` becomes `Glimmer::DataBinding::ObservableHash`, which supports observing all `Hash` keys or a specific `Hash` key
1334
1356
  - `Array` becomes `Glimmer::DataBinding::ObservableArray`, which supports observing `Array` changes like those done with `push`, `<<`, `delete`, and `map!` methods (all mutation methods).
@@ -1349,13 +1371,46 @@ See examples of the `observe` keyword at [Color The Circles](#color-the-circles)
1349
1371
 
1350
1372
  ### Data-Binding
1351
1373
 
1352
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` symbol (indicating data is moving from the right side, which is the model, to the left side, which is the GUI view object).
1374
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports both bidirectional (two-way) data-binding and unidirectional (one-way) data-binding.
1353
1375
 
1354
- The data-binding API is more precisely: `view_property <= [model, attribute, *options]`
1376
+ Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes without writing many lines or pages of imperative code doing the same thing, increasing productivity immensely.
1355
1377
 
1356
- This is also known as the [Glimmer Shine](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax for data-binding, a [Glimmer](https://github.com/AndyObtiva/glimmer)-only unique innovation.
1378
+ Data-binding automatically takes advantage of the [Observer Pattern](#observer-pattern) behind the scenes and is very well suited to declaring View property data sources piecemeal. On the other hand, explicit use of the [Observer Pattern](#observer-pattern) is sometimes more suitable when needing to make multiple View updates upon a single Model attribute change.
1357
1379
 
1358
- Example:
1380
+ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) flavor of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) by observing both the View and a Presenter for changes and updating the opposite side upon encountering them. This enables writing more decoupled cleaner code that keeps View code and Model code disentangled and highly maintainable. For example, check out the Snake game presenters for [Grid](/examples/snake/presenter/grid.rb) and [Cell](/examples/snake/presenter/cell.rb), which act as proxies for the actual Snake game models [Snake](/examples/snake/model/snake.rb) and [Apple](/examples/snake/model/apple.rb), mediating synchronization of data between them and the [Snake View GUI](/examples/snake.rb).
1381
+
1382
+ ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1383
+
1384
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports bidirectional (two-way) data-binding of the following controls/properties via the `<=>` operator (indicating data is moving in both directions between View and Model):
1385
+ - `entry` `text` property
1386
+ - `multiline_entry` `text` property
1387
+ - `non_wrapping_multiline_entry` `text` property
1388
+ - `search_entry` `text` property
1389
+ - `spinbox` `value` property
1390
+
1391
+ Example of bidirectional data-binding:
1392
+
1393
+ ```ruby
1394
+ entry {
1395
+ text <=> [contract, :legal_text]
1396
+ }
1397
+ ```
1398
+
1399
+ That is data-binding a contract's legal text to an `entry` `text` property.
1400
+
1401
+ Another example of bidirectional data-binding with an option:
1402
+
1403
+ ```ruby
1404
+ entry {
1405
+ text <=> [self, :entered_text, after_write: ->(text) {puts text}]
1406
+ }
1407
+ ```
1408
+
1409
+ That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1410
+
1411
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` operator (indicating data is moving from the right side, which is the Model, to the left side, which is the GUI View object).
1412
+
1413
+ Example of unidirectional data-binding:
1359
1414
 
1360
1415
  ```ruby
1361
1416
  square(0, 0, CELL_SIZE) {
@@ -1365,7 +1420,7 @@ square(0, 0, CELL_SIZE) {
1365
1420
 
1366
1421
  That is data-binding a grid cell color to a `square` shape's `fill` property. That means if the `color` attribute of the grid cell is updated, the `fill` property of the `square` shape is automatically updated accordingly.
1367
1422
 
1368
- Another Example:
1423
+ Another Example of unidirectional data-binding with an option:
1369
1424
 
1370
1425
  ```ruby
1371
1426
  window {
@@ -1375,14 +1430,36 @@ window {
1375
1430
 
1376
1431
  That is data-binding the `window` `title` property to the `score` attribute of a `@game`, but converting on read from the Model to a `String`.
1377
1432
 
1378
- Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes instead of pages of imperative code doing the same thing.
1433
+ To summarize the data-binding API:
1434
+ - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1435
+ - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
1379
1436
 
1380
- Options include:
1437
+ This is also known as the [Glimmer Shine](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax for data-binding, a [Glimmer](https://github.com/AndyObtiva/glimmer)-only unique innovation that takes advantage of [Ruby](https://www.ruby-lang.org/en/)'s highly expressive syntax and malleable DSL support.
1438
+
1439
+ Data-binding options include:
1381
1440
  - `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
1382
1441
  - `on_read {|value| ...}`: converts value read from Model to update the View.
1383
1442
  - `after_read {|converted_value| ...}`: performs an operation after read from Model and updating the View.
1443
+ - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
1444
+ - `on_write {|value| ...}`: converts value read from View to update the Model.
1445
+ - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
1446
+ - `computed_by attribute` or `computed_by [attribute1, attribute2, ...]`: indicates model attribute is computed from specified attribute(s), thus updated when they are updated (see in [Login example version 2](/examples/login2.rb))
1447
+
1448
+ Note that with both `on_read` and `on_write` converters, you could pass a `Symbol` representing the name of a method on the value object to invoke.
1449
+
1450
+ Example:
1384
1451
 
1385
- Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1452
+ ```ruby
1453
+ entry {
1454
+ text <=> [product, :price, on_read: :to_s, on_write: :to_i]
1455
+ }
1456
+ ```
1457
+
1458
+ Data-binding gotchas:
1459
+ - Never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
1460
+ - Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so you cannot hook into the listener directly anymore as that would negate data-binding. Instead, you can add an `after_write: ->(val) {}` option to perform something on trigger of the control listener instead.
1461
+
1462
+ Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1386
1463
 
1387
1464
  ### API Gotchas
1388
1465
 
@@ -1398,6 +1475,270 @@ Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac
1398
1475
 
1399
1476
  ### Original API
1400
1477
 
1478
+ Here are all the lower-level [LibUI](https://github.com/kojix2/LibUI) API methods utilized by [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1479
+ - `alloc_control`
1480
+ - `area_begin_user_window_move`
1481
+ - `area_begin_user_window_resize`
1482
+ - `area_queue_redraw_all`
1483
+ - `area_scroll_to`
1484
+ - `area_set_size`
1485
+ - `attribute_color`
1486
+ - `attribute_family`
1487
+ - `attribute_features`
1488
+ - `attribute_get_type`
1489
+ - `attribute_italic`
1490
+ - `attribute_size`
1491
+ - `attribute_stretch`
1492
+ - `attribute_underline`
1493
+ - `attribute_underline_color`
1494
+ - `attribute_weight`
1495
+ - `attributed_string_append_unattributed`
1496
+ - `attributed_string_byte_index_to_grapheme`
1497
+ - `attributed_string_delete`
1498
+ - `attributed_string_for_each_attribute`
1499
+ - `attributed_string_grapheme_to_byte_index`
1500
+ - `attributed_string_insert_at_unattributed`
1501
+ - `attributed_string_len`
1502
+ - `attributed_string_num_graphemes`
1503
+ - `attributed_string_set_attribute`
1504
+ - `attributed_string_string`
1505
+ - `box_append`
1506
+ - `box_delete`
1507
+ - `box_padded`
1508
+ - `box_set_padded`
1509
+ - `button_on_clicked`
1510
+ - `button_set_text`
1511
+ - `button_text`
1512
+ - `checkbox_checked`
1513
+ - `checkbox_on_toggled`
1514
+ - `checkbox_set_checked`
1515
+ - `checkbox_set_text`
1516
+ - `checkbox_text`
1517
+ - `color_button_color`
1518
+ - `color_button_on_changed`
1519
+ - `color_button_set_color`
1520
+ - `combobox_append`
1521
+ - `combobox_on_selected`
1522
+ - `combobox_selected`
1523
+ - `combobox_set_selected`
1524
+ - `control_destroy`
1525
+ - `control_disable`
1526
+ - `control_enable`
1527
+ - `control_enabled`
1528
+ - `control_enabled_to_user`
1529
+ - `control_handle`
1530
+ - `control_hide`
1531
+ - `control_parent`
1532
+ - `control_set_parent`
1533
+ - `control_show`
1534
+ - `control_toplevel`
1535
+ - `control_verify_set_parent`
1536
+ - `control_visible`
1537
+ - `date_time_picker_on_changed`
1538
+ - `date_time_picker_set_time`
1539
+ - `date_time_picker_time`
1540
+ - `draw_clip`
1541
+ - `draw_fill`
1542
+ - `draw_free_path`
1543
+ - `draw_free_text_layout`
1544
+ - `draw_matrix_invert`
1545
+ - `draw_matrix_invertible`
1546
+ - `draw_matrix_multiply`
1547
+ - `draw_matrix_rotate`
1548
+ - `draw_matrix_scale`
1549
+ - `draw_matrix_set_identity`
1550
+ - `draw_matrix_skew`
1551
+ - `draw_matrix_transform_point`
1552
+ - `draw_matrix_transform_size`
1553
+ - `draw_matrix_translate`
1554
+ - `draw_new_path`
1555
+ - `draw_new_text_layout`
1556
+ - `draw_path_add_rectangle`
1557
+ - `draw_path_arc_to`
1558
+ - `draw_path_bezier_to`
1559
+ - `draw_path_close_figure`
1560
+ - `draw_path_end`
1561
+ - `draw_path_line_to`
1562
+ - `draw_path_new_figure`
1563
+ - `draw_path_new_figure_with_arc`
1564
+ - `draw_restore`
1565
+ - `draw_save`
1566
+ - `draw_stroke`
1567
+ - `draw_text`
1568
+ - `draw_text_layout_extents`
1569
+ - `draw_transform`
1570
+ - `editable_combobox_append`
1571
+ - `editable_combobox_on_changed`
1572
+ - `editable_combobox_set_text`
1573
+ - `editable_combobox_text`
1574
+ - `entry_on_changed`
1575
+ - `entry_read_only`
1576
+ - `entry_set_read_only`
1577
+ - `entry_set_text`
1578
+ - `entry_text`
1579
+ - `ffi_lib`
1580
+ - `ffi_lib=`
1581
+ - `font_button_font`
1582
+ - `font_button_on_changed`
1583
+ - `form_append`
1584
+ - `form_delete`
1585
+ - `form_padded`
1586
+ - `form_set_padded`
1587
+ - `free_attribute`
1588
+ - `free_attributed_string`
1589
+ - `free_control`
1590
+ - `free_font_button_font`
1591
+ - `free_image`
1592
+ - `free_init_error`
1593
+ - `free_open_type_features`
1594
+ - `free_table_model`
1595
+ - `free_table_value`
1596
+ - `free_text`
1597
+ - `grid_append`
1598
+ - `grid_insert_at`
1599
+ - `grid_padded`
1600
+ - `grid_set_padded`
1601
+ - `group_margined`
1602
+ - `group_set_child`
1603
+ - `group_set_margined`
1604
+ - `group_set_title`
1605
+ - `group_title`
1606
+ - `image_append`
1607
+ - `init`
1608
+ - `label_set_text`
1609
+ - `label_text`
1610
+ - `main`
1611
+ - `main_step`
1612
+ - `main_steps`
1613
+ - `menu_append_about_item`
1614
+ - `menu_append_check_item`
1615
+ - `menu_append_item`
1616
+ - `menu_append_preferences_item`
1617
+ - `menu_append_quit_item`
1618
+ - `menu_append_separator`
1619
+ - `menu_item_checked`
1620
+ - `menu_item_disable`
1621
+ - `menu_item_enable`
1622
+ - `menu_item_on_clicked`
1623
+ - `menu_item_set_checked`
1624
+ - `msg_box`
1625
+ - `msg_box_error`
1626
+ - `multiline_entry_append`
1627
+ - `multiline_entry_on_changed`
1628
+ - `multiline_entry_read_only`
1629
+ - `multiline_entry_set_read_only`
1630
+ - `multiline_entry_set_text`
1631
+ - `multiline_entry_text`
1632
+ - `new_area`
1633
+ - `new_attributed_string`
1634
+ - `new_background_attribute`
1635
+ - `new_button`
1636
+ - `new_checkbox`
1637
+ - `new_color_attribute`
1638
+ - `new_color_button`
1639
+ - `new_combobox`
1640
+ - `new_date_picker`
1641
+ - `new_date_time_picker`
1642
+ - `new_editable_combobox`
1643
+ - `new_entry`
1644
+ - `new_family_attribute`
1645
+ - `new_features_attribute`
1646
+ - `new_font_button`
1647
+ - `new_form`
1648
+ - `new_grid`
1649
+ - `new_group`
1650
+ - `new_horizontal_box`
1651
+ - `new_horizontal_separator`
1652
+ - `new_image`
1653
+ - `new_italic_attribute`
1654
+ - `new_label`
1655
+ - `new_menu`
1656
+ - `new_multiline_entry`
1657
+ - `new_non_wrapping_multiline_entry`
1658
+ - `new_open_type_features`
1659
+ - `new_password_entry`
1660
+ - `new_progress_bar`
1661
+ - `new_radio_buttons`
1662
+ - `new_scrolling_area`
1663
+ - `new_search_entry`
1664
+ - `new_size_attribute`
1665
+ - `new_slider`
1666
+ - `new_spinbox`
1667
+ - `new_stretch_attribute`
1668
+ - `new_tab`
1669
+ - `new_table`
1670
+ - `new_table_model`
1671
+ - `new_table_value_color`
1672
+ - `new_table_value_image`
1673
+ - `new_table_value_int`
1674
+ - `new_table_value_string`
1675
+ - `new_time_picker`
1676
+ - `new_underline_attribute`
1677
+ - `new_underline_color_attribute`
1678
+ - `new_vertical_box`
1679
+ - `new_vertical_separator`
1680
+ - `new_weight_attribute`
1681
+ - `new_window`
1682
+ - `on_should_quit`
1683
+ - `open_file`
1684
+ - `open_type_features_add`
1685
+ - `open_type_features_clone`
1686
+ - `open_type_features_for_each`
1687
+ - `open_type_features_get`
1688
+ - `open_type_features_remove`
1689
+ - `progress_bar_set_value`
1690
+ - `progress_bar_value`
1691
+ - `queue_main`
1692
+ - `quit`
1693
+ - `radio_buttons_append`
1694
+ - `radio_buttons_on_selected`
1695
+ - `radio_buttons_selected`
1696
+ - `radio_buttons_set_selected`
1697
+ - `save_file`
1698
+ - `slider_on_changed`
1699
+ - `slider_set_value`
1700
+ - `slider_value`
1701
+ - `spinbox_on_changed`
1702
+ - `spinbox_set_value`
1703
+ - `spinbox_value`
1704
+ - `tab_append`
1705
+ - `tab_delete`
1706
+ - `tab_insert_at`
1707
+ - `tab_margined`
1708
+ - `tab_num_pages`
1709
+ - `tab_set_margined`
1710
+ - `table_append_button_column`
1711
+ - `table_append_checkbox_column`
1712
+ - `table_append_checkbox_text_column`
1713
+ - `table_append_image_column`
1714
+ - `table_append_image_text_column`
1715
+ - `table_append_progress_bar_column`
1716
+ - `table_append_text_column`
1717
+ - `table_model_row_changed`
1718
+ - `table_model_row_deleted`
1719
+ - `table_model_row_inserted`
1720
+ - `table_value_color`
1721
+ - `table_value_get_type`
1722
+ - `table_value_image`
1723
+ - `table_value_int`
1724
+ - `table_value_string`
1725
+ - `timer`
1726
+ - `uninit`
1727
+ - `user_bug_cannot_set_parent_on_toplevel`
1728
+ - `window_borderless`
1729
+ - `window_content_size`
1730
+ - `window_fullscreen`
1731
+ - `window_margined`
1732
+ - `window_on_closing`
1733
+ - `window_on_content_size_changed`
1734
+ - `window_set_borderless`
1735
+ - `window_set_child`
1736
+ - `window_set_content_size`
1737
+ - `window_set_fullscreen`
1738
+ - `window_set_margined`
1739
+ - `window_set_title`
1740
+ - `window_title`
1741
+
1401
1742
  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):
1402
1743
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1403
1744
  - Check out the [libui C Headers](https://github.com/andlabs/libui/blob/master/ui.h)
@@ -1561,17 +1902,23 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
1561
1902
  ```ruby
1562
1903
  require 'glimmer-dsl-libui'
1563
1904
  require 'facets'
1905
+ require 'fileutils'
1564
1906
 
1565
1907
  class MetaExample
1566
1908
  include Glimmer
1567
1909
 
1910
+ ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
1911
+
1912
+ attr_accessor :code_text
1913
+
1568
1914
  def initialize
1569
- @selected_example_index = 0
1915
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
1916
+ @code_text = File.read(file_path_for(selected_example))
1570
1917
  end
1571
1918
 
1572
1919
  def examples
1573
1920
  if @examples.nil?
1574
- example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
1921
+ example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
1575
1922
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
1576
1923
  example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
1577
1924
  @examples = example_file_names.map { |f| f.underscore.titlecase }
@@ -1585,12 +1932,20 @@ class MetaExample
1585
1932
  end
1586
1933
  end
1587
1934
 
1935
+ def basic_examples_with_versions
1936
+ examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
1937
+ end
1938
+
1939
+ def advanced_examples_with_versions
1940
+ examples_with_versions - basic_examples_with_versions
1941
+ end
1942
+
1588
1943
  def file_path_for(example)
1589
1944
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
1590
1945
  end
1591
1946
 
1592
1947
  def version_count_for(example)
1593
- Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1948
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
1594
1949
  end
1595
1950
 
1596
1951
  def glimmer_dsl_libui_file
@@ -1626,17 +1981,47 @@ class MetaExample
1626
1981
  vertical_box {
1627
1982
  stretchy false
1628
1983
 
1629
- @example_radio_buttons = radio_buttons {
1984
+ tab {
1630
1985
  stretchy false
1631
- items examples_with_versions
1632
- selected @selected_example_index
1633
1986
 
1634
- on_selected do
1635
- @selected_example_index = @example_radio_buttons.selected
1636
- example = selected_example
1637
- @code_entry.text = File.read(file_path_for(example))
1638
- @version_spinbox.value = 1
1639
- end
1987
+ tab_item('Basic') {
1988
+ vertical_box {
1989
+ @basic_example_radio_buttons = radio_buttons {
1990
+ stretchy false
1991
+ items basic_examples_with_versions
1992
+ selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
1993
+
1994
+ on_selected do
1995
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
1996
+ example = selected_example
1997
+ self.code_text = File.read(file_path_for(example))
1998
+ @version_spinbox.value = 1
1999
+ end
2000
+ }
2001
+
2002
+ label # filler
2003
+ label # filler
2004
+ }
2005
+ }
2006
+
2007
+ tab_item('Advanced') {
2008
+ vertical_box {
2009
+ @advanced_example_radio_buttons = radio_buttons {
2010
+ stretchy false
2011
+ items advanced_examples_with_versions
2012
+
2013
+ on_selected do
2014
+ @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
2015
+ example = selected_example
2016
+ self.code_text = File.read(file_path_for(example))
2017
+ @version_spinbox.value = 1
2018
+ end
2019
+ }
2020
+
2021
+ label # filler
2022
+ label # filler
2023
+ }
2024
+ }
1640
2025
  }
1641
2026
 
1642
2027
  horizontal_box {
@@ -1654,7 +2039,7 @@ class MetaExample
1654
2039
  else
1655
2040
  version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1656
2041
  example = "#{selected_example}#{version_number}"
1657
- @code_entry.text = File.read(file_path_for(example))
2042
+ self.code_text = File.read(file_path_for(example))
1658
2043
  end
1659
2044
  end
1660
2045
  }
@@ -1666,9 +2051,15 @@ class MetaExample
1666
2051
  button('Launch') {
1667
2052
  on_clicked do
1668
2053
  begin
1669
- meta_example_file = File.join(Dir.home, '.meta_example.rb')
1670
- File.write(meta_example_file, @code_entry.text)
1671
- run_example(meta_example_file)
2054
+ parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
2055
+ FileUtils.mkdir_p(parent_dir)
2056
+ example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
2057
+ File.write(example_file, code_text)
2058
+ example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
2059
+ FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
2060
+ FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
2061
+ FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
2062
+ run_example(example_file)
1672
2063
  rescue => e
1673
2064
  puts e.full_message
1674
2065
  puts 'Unable to write code changes! Running original example...'
@@ -1678,14 +2069,14 @@ class MetaExample
1678
2069
  }
1679
2070
  button('Reset') {
1680
2071
  on_clicked do
1681
- @code_entry.text = File.read(file_path_for(selected_example))
2072
+ self.code_text = File.read(file_path_for(selected_example))
1682
2073
  end
1683
2074
  }
1684
2075
  }
1685
2076
  }
1686
2077
 
1687
2078
  @code_entry = non_wrapping_multiline_entry {
1688
- text File.read(file_path_for(selected_example))
2079
+ text <=> [self, :code_text]
1689
2080
  }
1690
2081
  }
1691
2082
  }.show
@@ -1904,7 +2295,44 @@ UI.main
1904
2295
  UI.quit
1905
2296
  ```
1906
2297
 
1907
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2298
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2299
+
2300
+ ```ruby
2301
+ require 'glimmer-dsl-libui'
2302
+
2303
+ class BasicEntry
2304
+ include Glimmer
2305
+
2306
+ attr_accessor :entry_text
2307
+
2308
+ def launch
2309
+ window('Basic Entry', 300, 50) {
2310
+ horizontal_box {
2311
+ entry {
2312
+ # stretchy true # Smart default option for appending to horizontal_box
2313
+ text <=> [self, :entry_text, after_write: ->(text) {puts text; $stdout.flush}] # bidirectional data-binding between text property and entry_text attribute, printing after write to model.
2314
+ }
2315
+
2316
+ button('Button') {
2317
+ stretchy false # stretchy property is available when control is nested under horizontal_box
2318
+
2319
+ on_clicked do
2320
+ msg_box('You entered', entry_text)
2321
+ end
2322
+ }
2323
+ }
2324
+
2325
+ on_closing do
2326
+ puts 'Bye Bye'
2327
+ end
2328
+ }.show
2329
+ end
2330
+ end
2331
+
2332
+ BasicEntry.new.launch
2333
+ ```
2334
+
2335
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
1908
2336
 
1909
2337
  ```ruby
1910
2338
  require 'glimmer-dsl-libui'
@@ -2228,7 +2656,59 @@ Mac | Windows | Linux
2228
2656
  ----|---------|------
2229
2657
  ![glimmer-dsl-libui-mac-form.png](images/glimmer-dsl-libui-mac-form.png) ![glimmer-dsl-libui-mac-form-msg-box.png](images/glimmer-dsl-libui-mac-form-msg-box.png) | ![glimmer-dsl-libui-windows-form.png](images/glimmer-dsl-libui-windows-form.png) ![glimmer-dsl-libui-windows-form-msg-box.png](images/glimmer-dsl-libui-windows-form-msg-box.png) | ![glimmer-dsl-libui-linux-form.png](images/glimmer-dsl-libui-linux-form.png) ![glimmer-dsl-libui-linux-form-msg-box.png](images/glimmer-dsl-libui-linux-form-msg-box.png)
2230
2658
 
2231
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2659
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2660
+
2661
+ ```ruby
2662
+ require 'glimmer-dsl-libui'
2663
+
2664
+ class Form
2665
+ include Glimmer
2666
+
2667
+ attr_accessor :first_name, :last_name, :phone, :email
2668
+
2669
+ def launch
2670
+ window('Form') {
2671
+ margined true
2672
+
2673
+ vertical_box {
2674
+ form {
2675
+ entry {
2676
+ label 'First Name' # label property is available when control is nested under form
2677
+ text <=> [self, :first_name] # bidirectional data-binding of entry text property to self first_name attribute
2678
+ }
2679
+
2680
+ entry {
2681
+ label 'Last Name' # label property is available when control is nested under form
2682
+ text <=> [self, :last_name]
2683
+ }
2684
+
2685
+ entry {
2686
+ label 'Phone' # label property is available when control is nested under form
2687
+ text <=> [self, :phone]
2688
+ }
2689
+
2690
+ entry {
2691
+ label 'Email' # label property is available when control is nested under form
2692
+ text <=> [self, :email]
2693
+ }
2694
+ }
2695
+
2696
+ button('Display Info') {
2697
+ stretchy false
2698
+
2699
+ on_clicked do
2700
+ msg_box('Info', "#{first_name} #{last_name} has phone #{phone} and email #{email}")
2701
+ end
2702
+ }
2703
+ }
2704
+ }.show
2705
+ end
2706
+ end
2707
+
2708
+ Form.new.launch
2709
+ ```
2710
+
2711
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2232
2712
 
2233
2713
  ```ruby
2234
2714
  require 'glimmer-dsl-libui'
@@ -3110,9 +3590,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3110
3590
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3111
3591
  ```
3112
3592
 
3113
- Mac |
3114
- ----|
3115
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png)
3593
+ Mac | Linux
3594
+ ----|------
3595
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3116
3596
 
3117
3597
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3118
3598
 
@@ -4433,52 +4913,102 @@ window('Area Gallery', 400, 400) {
4433
4913
  }.show
4434
4914
  ```
4435
4915
 
4436
- #### Color The Circles
4916
+ #### Button Counter
4437
4917
 
4438
- [examples/color_the_circles.rb](examples/color_the_circles.rb)
4918
+ [examples/button_counter.rb](examples/button_counter.rb)
4439
4919
 
4440
4920
  Run with this command from the root of the project if you cloned the project:
4441
4921
 
4442
4922
  ```
4443
- ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
4923
+ ruby -r './lib/glimmer-dsl-libui' examples/button_counter.rb
4444
4924
  ```
4445
4925
 
4446
4926
  Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4447
4927
 
4448
4928
  ```
4449
- ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
4929
+ ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
4450
4930
  ```
4451
4931
 
4452
- Mac | Windows | Linux
4453
- ----|---------|------
4454
- ![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png) ![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png) ![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png) | ![glimmer-dsl-libui-windows-color-the-circles.png](images/glimmer-dsl-libui-windows-color-the-circles.png) ![glimmer-dsl-libui-windows-color-the-circles-lost.png](images/glimmer-dsl-libui-windows-color-the-circles-lost.png) ![glimmer-dsl-libui-windows-color-the-circles-won.png](images/glimmer-dsl-libui-windows-color-the-circles-won.png) | ![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png) ![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png) ![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
4932
+ Mac | Linux
4933
+ ----|------
4934
+ ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
4455
4935
 
4456
4936
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4457
4937
 
4458
4938
  ```ruby
4459
4939
  require 'glimmer-dsl-libui'
4460
4940
 
4461
- class ColorTheCircles
4941
+ class ButtonCounter
4462
4942
  include Glimmer
4463
-
4464
- WINDOW_WIDTH = 800
4465
- WINDOW_HEIGHT = 600
4466
- SHAPE_MIN_SIZE = 15
4467
- SHAPE_MAX_SIZE = 75
4468
- MARGIN_WIDTH = 55
4469
- MARGIN_HEIGHT = 155
4470
- TIME_MAX_EASY = 4
4471
- TIME_MAX_MEDIUM = 3
4472
- TIME_MAX_HARD = 2
4473
- TIME_MAX_INSANE = 1
4474
-
4475
- attr_accessor :score
4476
-
4943
+
4944
+ attr_accessor :count
4945
+
4477
4946
  def initialize
4478
- @circles_data = []
4479
- @score = 0
4480
- @time_max = TIME_MAX_HARD
4481
- @game_over = false
4947
+ @count = 0
4948
+ end
4949
+
4950
+ def launch
4951
+ window('Hello, Button!') {
4952
+ button {
4953
+ text <= [self, :count, on_read: ->(count) {"Count: #{count}"}] # data-bind button text to self count, converting to string on read.
4954
+
4955
+ on_clicked do
4956
+ self.count += 1
4957
+ end
4958
+ }
4959
+ }.show
4960
+ end
4961
+ end
4962
+
4963
+ ButtonCounter.new.launch
4964
+ ```
4965
+
4966
+ #### Color The Circles
4967
+
4968
+ [examples/color_the_circles.rb](examples/color_the_circles.rb)
4969
+
4970
+ Run with this command from the root of the project if you cloned the project:
4971
+
4972
+ ```
4973
+ ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
4974
+ ```
4975
+
4976
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4977
+
4978
+ ```
4979
+ ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
4980
+ ```
4981
+
4982
+ Mac | Windows | Linux
4983
+ ----|---------|------
4984
+ ![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png) ![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png) ![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png) | ![glimmer-dsl-libui-windows-color-the-circles.png](images/glimmer-dsl-libui-windows-color-the-circles.png) ![glimmer-dsl-libui-windows-color-the-circles-lost.png](images/glimmer-dsl-libui-windows-color-the-circles-lost.png) ![glimmer-dsl-libui-windows-color-the-circles-won.png](images/glimmer-dsl-libui-windows-color-the-circles-won.png) | ![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png) ![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png) ![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
4985
+
4986
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4987
+
4988
+ ```ruby
4989
+ require 'glimmer-dsl-libui'
4990
+
4991
+ class ColorTheCircles
4992
+ include Glimmer
4993
+
4994
+ WINDOW_WIDTH = 800
4995
+ WINDOW_HEIGHT = 600
4996
+ SHAPE_MIN_SIZE = 15
4997
+ SHAPE_MAX_SIZE = 75
4998
+ MARGIN_WIDTH = 55
4999
+ MARGIN_HEIGHT = 155
5000
+ TIME_MAX_EASY = 4
5001
+ TIME_MAX_MEDIUM = 3
5002
+ TIME_MAX_HARD = 2
5003
+ TIME_MAX_INSANE = 1
5004
+
5005
+ attr_accessor :score
5006
+
5007
+ def initialize
5008
+ @circles_data = []
5009
+ @score = 0
5010
+ @time_max = TIME_MAX_HARD
5011
+ @game_over = false
4482
5012
  register_observers
4483
5013
  setup_circle_factory
4484
5014
  end
@@ -5293,7 +5823,96 @@ Mac | Windows | Linux
5293
5823
  ----|---------|------
5294
5824
  ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-dynamic-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-dynamic-area-updated.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-dynamic-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-dynamic-area-updated.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-dynamic-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-dynamic-area-updated.png)
5295
5825
 
5296
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5826
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
5827
+
5828
+ ```ruby
5829
+ require 'glimmer-dsl-libui'
5830
+
5831
+ class DynamicArea
5832
+ include Glimmer
5833
+
5834
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
5835
+
5836
+ def initialize
5837
+ @rectangle_x = 25
5838
+ @rectangle_y = 25
5839
+ @rectangle_width = 150
5840
+ @rectangle_height = 150
5841
+ @rectangle_red = 102
5842
+ @rectangle_green = 102
5843
+ @rectangle_blue = 204
5844
+ @rectangle_alpha = 100
5845
+ end
5846
+
5847
+ def launch
5848
+ window('Dynamic Area', 240, 600) {
5849
+ margined true
5850
+
5851
+ vertical_box {
5852
+ label('Rectangle Properties') {
5853
+ stretchy false
5854
+ }
5855
+
5856
+ form {
5857
+ stretchy false
5858
+
5859
+ spinbox(0, 1000) {
5860
+ label 'x'
5861
+ value <=> [self, :rectangle_x, after_write: -> {@area.queue_redraw_all}]
5862
+ }
5863
+
5864
+ spinbox(0, 1000) {
5865
+ label 'y'
5866
+ value <=> [self, :rectangle_y, after_write: -> {@area.queue_redraw_all}]
5867
+ }
5868
+
5869
+ spinbox(0, 1000) {
5870
+ label 'width'
5871
+ value <=> [self, :rectangle_width, after_write: -> {@area.queue_redraw_all}]
5872
+ }
5873
+
5874
+ spinbox(0, 1000) {
5875
+ label 'height'
5876
+ value <=> [self, :rectangle_height, after_write: -> {@area.queue_redraw_all}]
5877
+ }
5878
+
5879
+ spinbox(0, 255) {
5880
+ label 'red'
5881
+ value <=> [self, :rectangle_red, after_write: -> {@area.queue_redraw_all}]
5882
+ }
5883
+
5884
+ spinbox(0, 255) {
5885
+ label 'green'
5886
+ value <=> [self, :rectangle_green, after_write: -> {@area.queue_redraw_all}]
5887
+ }
5888
+
5889
+ spinbox(0, 255) {
5890
+ label 'blue'
5891
+ value <=> [self, :rectangle_blue, after_write: -> {@area.queue_redraw_all}]
5892
+ }
5893
+
5894
+ spinbox(0, 100) {
5895
+ label 'alpha'
5896
+ value <=> [self, :rectangle_alpha, after_write: -> {@area.queue_redraw_all}]
5897
+ }
5898
+ }
5899
+
5900
+ @area = area {
5901
+ on_draw do |area_draw_params|
5902
+ rectangle(rectangle_x, rectangle_y, rectangle_width, rectangle_height) { # a dynamic path is added semi-declaratively inside on_draw block
5903
+ fill r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0
5904
+ }
5905
+ end
5906
+ }
5907
+ }
5908
+ }.show
5909
+ end
5910
+ end
5911
+
5912
+ DynamicArea.new.launch
5913
+ ```
5914
+
5915
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5297
5916
 
5298
5917
  ```ruby
5299
5918
  require 'glimmer-dsl-libui'
@@ -5395,7 +6014,102 @@ window('Dynamic Area', 240, 600) {
5395
6014
  }.show
5396
6015
  ```
5397
6016
 
5398
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (declarative stable `path` approach):
6017
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (declarative stable `path` approach with [data-binding](#data-binding)):
6018
+
6019
+ ```ruby
6020
+ require 'glimmer-dsl-libui'
6021
+
6022
+ class DynamicArea
6023
+ include Glimmer
6024
+
6025
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
6026
+
6027
+ def initialize
6028
+ @rectangle_x = 25
6029
+ @rectangle_y = 25
6030
+ @rectangle_width = 150
6031
+ @rectangle_height = 150
6032
+ @rectangle_red = 102
6033
+ @rectangle_green = 102
6034
+ @rectangle_blue = 204
6035
+ @rectangle_alpha = 100
6036
+ end
6037
+
6038
+ def rectangle_fill
6039
+ { r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0 }
6040
+ end
6041
+
6042
+ def launch
6043
+ window('Dynamic Area', 240, 600) {
6044
+ margined true
6045
+
6046
+ vertical_box {
6047
+ label('Rectangle Properties') {
6048
+ stretchy false
6049
+ }
6050
+
6051
+ form {
6052
+ stretchy false
6053
+
6054
+ @x_spinbox = spinbox(0, 1000) {
6055
+ label 'x'
6056
+ value <=> [self, :rectangle_x]
6057
+ }
6058
+
6059
+ @y_spinbox = spinbox(0, 1000) {
6060
+ label 'y'
6061
+ value <=> [self, :rectangle_y]
6062
+ }
6063
+
6064
+ @width_spinbox = spinbox(0, 1000) {
6065
+ label 'width'
6066
+ value <=> [self, :rectangle_width]
6067
+ }
6068
+
6069
+ @height_spinbox = spinbox(0, 1000) {
6070
+ label 'height'
6071
+ value <=> [self, :rectangle_height]
6072
+ }
6073
+
6074
+ @red_spinbox = spinbox(0, 255) {
6075
+ label 'red'
6076
+ value <=> [self, :rectangle_red]
6077
+ }
6078
+
6079
+ @green_spinbox = spinbox(0, 255) {
6080
+ label 'green'
6081
+ value <=> [self, :rectangle_green]
6082
+ }
6083
+
6084
+ @blue_spinbox = spinbox(0, 255) {
6085
+ label 'blue'
6086
+ value <=> [self, :rectangle_blue]
6087
+ }
6088
+
6089
+ @alpha_spinbox = spinbox(0, 100) {
6090
+ label 'alpha'
6091
+ value <=> [self, :rectangle_alpha]
6092
+ }
6093
+ }
6094
+
6095
+ area {
6096
+ @rectangle = rectangle { # stable implicit path shape
6097
+ x <= [self, :rectangle_x]
6098
+ y <= [self, :rectangle_y]
6099
+ width <= [self, :rectangle_width]
6100
+ height <= [self, :rectangle_height]
6101
+ fill <= [self, :rectangle_fill, computed_by: [:rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha]]
6102
+ }
6103
+ }
6104
+ }
6105
+ }.show
6106
+ end
6107
+ end
6108
+
6109
+ DynamicArea.new.launch
6110
+ ```
6111
+
6112
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (declarative stable `path` approach without [data-binding](#data-binding)):
5399
6113
 
5400
6114
  ```ruby
5401
6115
  require 'glimmer-dsl-libui'
@@ -5487,7 +6201,7 @@ window('Dynamic Area', 240, 600) {
5487
6201
  }
5488
6202
 
5489
6203
  area {
5490
- @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable path
6204
+ @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable implicit path shape
5491
6205
  fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
5492
6206
  }
5493
6207
  }
@@ -5629,7 +6343,120 @@ Mac | Windows | Linux
5629
6343
  ----|---------|------
5630
6344
  ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) ![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png) ![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) ![glimmer-dsl-libui-windows-form-table-contact-entered.png](images/glimmer-dsl-libui-windows-form-table-contact-entered.png) ![glimmer-dsl-libui-windows-form-table-filtered.png](images/glimmer-dsl-libui-windows-form-table-filtered.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) ![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png) ![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
5631
6345
 
5632
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6346
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6347
+
6348
+ ```ruby
6349
+ require 'glimmer-dsl-libui'
6350
+
6351
+ class FormTable
6352
+ include Glimmer
6353
+
6354
+ attr_accessor :name, :email, :phone, :city, :state, :filter_value
6355
+
6356
+ def initialize
6357
+ @data = [
6358
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6359
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6360
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6361
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6362
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
6363
+ ]
6364
+ end
6365
+
6366
+ def launch
6367
+ window('Contacts', 600, 600) { |w|
6368
+ margined true
6369
+
6370
+ vertical_box {
6371
+ form {
6372
+ stretchy false
6373
+
6374
+ entry {
6375
+ label 'Name'
6376
+ text <=> [self, :name]
6377
+ }
6378
+
6379
+ entry {
6380
+ label 'Email'
6381
+ text <=> [self, :email]
6382
+ }
6383
+
6384
+ entry {
6385
+ label 'Phone'
6386
+ text <=> [self, :phone]
6387
+ }
6388
+
6389
+ entry {
6390
+ label 'City'
6391
+ text <=> [self, :city]
6392
+ }
6393
+
6394
+ entry {
6395
+ label 'State'
6396
+ text <=> [self, :state]
6397
+ }
6398
+ }
6399
+
6400
+ button('Save Contact') {
6401
+ stretchy false
6402
+
6403
+ on_clicked do
6404
+ new_row = [name, email, phone, city, state]
6405
+ if new_row.include?('')
6406
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6407
+ else
6408
+ @data << new_row # automatically inserts a row into the table due to implicit data-binding
6409
+ @unfiltered_data = @data.dup
6410
+ self.name = '' # automatically clears name entry through explicit data-binding
6411
+ self.email = ''
6412
+ self.phone = ''
6413
+ self.city = ''
6414
+ self.state = ''
6415
+ end
6416
+ end
6417
+ }
6418
+
6419
+ search_entry {
6420
+ stretchy false
6421
+ text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
6422
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6423
+ @unfiltered_data ||= @data.dup
6424
+ # Unfilter first to remove any previous filters
6425
+ @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6426
+ # Now, apply filter if entered
6427
+ unless filter_value.empty?
6428
+ @data.filter! do |row_data| # affects table indirectly through implicit data-binding
6429
+ row_data.any? do |cell|
6430
+ cell.to_s.downcase.include?(filter_value.downcase)
6431
+ end
6432
+ end
6433
+ end
6434
+ }
6435
+ ]
6436
+ }
6437
+
6438
+ table {
6439
+ text_column('Name')
6440
+ text_column('Email')
6441
+ text_column('Phone')
6442
+ text_column('City')
6443
+ text_column('State')
6444
+
6445
+ cell_rows @data # implicit data-binding
6446
+
6447
+ on_changed do |row, type, row_data|
6448
+ puts "Row #{row} #{type}: #{row_data}"
6449
+ end
6450
+ }
6451
+ }
6452
+ }.show
6453
+ end
6454
+ end
6455
+
6456
+ FormTable.new.launch
6457
+ ```
6458
+
6459
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5633
6460
 
5634
6461
  ```ruby
5635
6462
  require 'glimmer-dsl-libui'
@@ -6072,50 +6899,170 @@ UI.main
6072
6899
  UI.quit
6073
6900
  ```
6074
6901
 
6075
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6902
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6076
6903
 
6077
6904
  ```ruby
6078
6905
  # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
6079
6906
 
6080
6907
  require 'glimmer-dsl-libui'
6081
6908
 
6082
- include Glimmer
6083
-
6084
- X_OFF_LEFT = 20
6085
- Y_OFF_TOP = 20
6086
- X_OFF_RIGHT = 20
6087
- Y_OFF_BOTTOM = 20
6088
- POINT_RADIUS = 5
6089
- COLOR_BLUE = 0x1E90FF
6090
-
6091
- @datapoints = 10.times.map {Random.new.rand(90)}
6092
-
6093
- def graph_size(area_width, area_height)
6094
- graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
6095
- graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
6096
- [graph_width, graph_height]
6097
- end
6098
-
6099
- def point_locations(width, height)
6100
- xincr = width / 9.0 # 10 - 1 to make the last point be at the end
6101
- yincr = height / 100.0
6102
-
6103
- @datapoints.each_with_index.map do |value, i|
6104
- val = 100 - value
6105
- [xincr * i, yincr * val]
6909
+ class Histogram
6910
+ include Glimmer
6911
+
6912
+ X_OFF_LEFT = 20
6913
+ Y_OFF_TOP = 20
6914
+ X_OFF_RIGHT = 20
6915
+ Y_OFF_BOTTOM = 20
6916
+ POINT_RADIUS = 5
6917
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
6918
+
6919
+ attr_accessor :datapoints, :histogram_color
6920
+
6921
+ def initialize
6922
+ @datapoints = 10.times.map {Random.new.rand(90)}
6923
+ @histogram_color = COLOR_BLUE
6106
6924
  end
6107
- end
6108
-
6109
- # method-based custom control representing a graph path
6110
- def graph_path(width, height, should_extend, &block)
6111
- locations = point_locations(width, height).flatten
6112
- path {
6113
- if should_extend
6114
- polygon(locations + [width, height, 0, height])
6115
- else
6116
- polyline(locations)
6925
+
6926
+ def graph_size(area_width, area_height)
6927
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
6928
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
6929
+ [graph_width, graph_height]
6930
+ end
6931
+
6932
+ def point_locations(width, height)
6933
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
6934
+ yincr = height / 100.0
6935
+
6936
+ @datapoints.each_with_index.map do |value, i|
6937
+ val = 100 - value
6938
+ [xincr * i, yincr * val]
6117
6939
  end
6118
-
6940
+ end
6941
+
6942
+ # method-based custom control representing a graph path
6943
+ def graph_path(width, height, should_extend, &block)
6944
+ locations = point_locations(width, height).flatten
6945
+ path {
6946
+ if should_extend
6947
+ polygon(locations + [width, height, 0, height])
6948
+ else
6949
+ polyline(locations)
6950
+ end
6951
+
6952
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
6953
+ transform {
6954
+ translate X_OFF_LEFT, Y_OFF_TOP
6955
+ }
6956
+
6957
+ block.call
6958
+ }
6959
+ end
6960
+
6961
+ def launch
6962
+ window('histogram example', 640, 480) {
6963
+ margined true
6964
+
6965
+ horizontal_box {
6966
+ vertical_box {
6967
+ stretchy false
6968
+
6969
+ 10.times do |i|
6970
+ spinbox(0, 100) { |sb|
6971
+ stretchy false
6972
+ value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
6973
+ }
6974
+ end
6975
+
6976
+ color_button { |cb|
6977
+ stretchy false
6978
+ color COLOR_BLUE
6979
+
6980
+ on_changed do
6981
+ @histogram_color = cb.color
6982
+ @area.queue_redraw_all
6983
+ end
6984
+ }
6985
+ }
6986
+
6987
+ @area = area {
6988
+ on_draw do |area_draw_params|
6989
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6990
+ fill 0xFFFFFF
6991
+ }
6992
+
6993
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6994
+
6995
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
6996
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
6997
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6998
+
6999
+ stroke 0x000000, thickness: 2, miter_limit: 10
7000
+ }
7001
+
7002
+ # now create the fill for the graph below the graph line
7003
+ graph_path(graph_width, graph_height, true) {
7004
+ fill @histogram_color.merge(a: 0.5)
7005
+ }
7006
+
7007
+ # now draw the histogram line
7008
+ graph_path(graph_width, graph_height, false) {
7009
+ stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
7010
+ }
7011
+ end
7012
+ }
7013
+ }
7014
+ }.show
7015
+ end
7016
+ end
7017
+
7018
+ Histogram.new.launch
7019
+ ```
7020
+
7021
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
7022
+
7023
+ ```ruby
7024
+ # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
7025
+
7026
+ require 'glimmer-dsl-libui'
7027
+
7028
+ include Glimmer
7029
+
7030
+ X_OFF_LEFT = 20
7031
+ Y_OFF_TOP = 20
7032
+ X_OFF_RIGHT = 20
7033
+ Y_OFF_BOTTOM = 20
7034
+ POINT_RADIUS = 5
7035
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
7036
+
7037
+ @datapoints = 10.times.map {Random.new.rand(90)}
7038
+ @color = COLOR_BLUE
7039
+
7040
+ def graph_size(area_width, area_height)
7041
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
7042
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
7043
+ [graph_width, graph_height]
7044
+ end
7045
+
7046
+ def point_locations(width, height)
7047
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
7048
+ yincr = height / 100.0
7049
+
7050
+ @datapoints.each_with_index.map do |value, i|
7051
+ val = 100 - value
7052
+ [xincr * i, yincr * val]
7053
+ end
7054
+ end
7055
+
7056
+ # method-based custom control representing a graph path
7057
+ def graph_path(width, height, should_extend, &block)
7058
+ locations = point_locations(width, height).flatten
7059
+ path {
7060
+ if should_extend
7061
+ polygon(locations + [width, height, 0, height])
7062
+ else
7063
+ polyline(locations)
7064
+ end
7065
+
6119
7066
  # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
6120
7067
  transform {
6121
7068
  translate X_OFF_LEFT, Y_OFF_TOP
@@ -6144,11 +7091,12 @@ window('histogram example', 640, 480) {
6144
7091
  }
6145
7092
  end
6146
7093
 
6147
- @color_button = color_button {
7094
+ color_button { |cb|
6148
7095
  stretchy false
6149
7096
  color COLOR_BLUE
6150
7097
 
6151
7098
  on_changed do
7099
+ @color = cb.color
6152
7100
  @area.queue_redraw_all
6153
7101
  end
6154
7102
  }
@@ -6156,31 +7104,27 @@ window('histogram example', 640, 480) {
6156
7104
 
6157
7105
  @area = area {
6158
7106
  on_draw do |area_draw_params|
6159
- path {
6160
- rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
6161
-
7107
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6162
7108
  fill 0xFFFFFF
6163
7109
  }
6164
7110
 
6165
7111
  graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6166
7112
 
6167
- path {
6168
- figure(X_OFF_LEFT, Y_OFF_TOP) {
6169
- line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
6170
- line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6171
- }
7113
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7114
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7115
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6172
7116
 
6173
7117
  stroke 0x000000, thickness: 2, miter_limit: 10
6174
7118
  }
6175
7119
 
6176
7120
  # now create the fill for the graph below the graph line
6177
7121
  graph_path(graph_width, graph_height, true) {
6178
- fill @color_button.color.merge(a: 0.5)
7122
+ fill @color.merge(a: 0.5)
6179
7123
  }
6180
7124
 
6181
7125
  # now draw the histogram line
6182
7126
  graph_path(graph_width, graph_height, false) {
6183
- stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
7127
+ stroke @color.merge(thickness: 2, miter_limit: 10)
6184
7128
  }
6185
7129
  end
6186
7130
  }
@@ -6208,7 +7152,259 @@ Mac | Windows | Linux
6208
7152
  ----|---------|------
6209
7153
  ![glimmer-dsl-libui-mac-login.png](images/glimmer-dsl-libui-mac-login.png) ![glimmer-dsl-libui-mac-login-logged-in.png](images/glimmer-dsl-libui-mac-login-logged-in.png) | ![glimmer-dsl-libui-windows-login.png](images/glimmer-dsl-libui-windows-login.png) ![glimmer-dsl-libui-windows-login-logged-in.png](images/glimmer-dsl-libui-windows-login-logged-in.png) | ![glimmer-dsl-libui-linux-login.png](images/glimmer-dsl-libui-linux-login.png) ![glimmer-dsl-libui-linux-login-logged-in.png](images/glimmer-dsl-libui-linux-login-logged-in.png)
6210
7154
 
6211
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7155
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7156
+
7157
+ ```ruby
7158
+ require 'glimmer-dsl-libui'
7159
+
7160
+ class Login
7161
+ include Glimmer
7162
+
7163
+ attr_accessor :username, :password, :logged_in
7164
+
7165
+ def launch
7166
+ window('Login') {
7167
+ margined true
7168
+
7169
+ vertical_box {
7170
+ form {
7171
+ entry {
7172
+ label 'Username:'
7173
+ text <=> [self, :username]
7174
+ enabled <= [self, :logged_in, on_read: :!]
7175
+ }
7176
+
7177
+ password_entry {
7178
+ label 'Password:'
7179
+ text <=> [self, :password]
7180
+ enabled <= [self, :logged_in, on_read: :!]
7181
+ }
7182
+ }
7183
+
7184
+ horizontal_box {
7185
+ button('Login') {
7186
+ enabled <= [self, :logged_in, on_read: :!]
7187
+
7188
+ on_clicked do
7189
+ self.logged_in = true
7190
+ end
7191
+ }
7192
+
7193
+ button('Logout') {
7194
+ enabled <= [self, :logged_in]
7195
+
7196
+ on_clicked do
7197
+ self.logged_in = false
7198
+ self.username = ''
7199
+ self.password = ''
7200
+ end
7201
+ }
7202
+ }
7203
+ }
7204
+ }.show
7205
+ end
7206
+ end
7207
+
7208
+ Login.new.launch
7209
+ ```
7210
+
7211
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
7212
+
7213
+ ```ruby
7214
+ require 'glimmer-dsl-libui'
7215
+
7216
+ class Login
7217
+ include Glimmer
7218
+
7219
+ attr_accessor :username, :password, :logged_in
7220
+
7221
+ def logged_out
7222
+ !logged_in
7223
+ end
7224
+
7225
+ def launch
7226
+ window('Login') {
7227
+ margined true
7228
+
7229
+ vertical_box {
7230
+ form {
7231
+ entry {
7232
+ label 'Username:'
7233
+ text <=> [self, :username]
7234
+ enabled <= [self, :logged_out, computed_by: :logged_in] # computed_by option ensures being notified of changes to logged_in
7235
+ }
7236
+
7237
+ password_entry {
7238
+ label 'Password:'
7239
+ text <=> [self, :password]
7240
+ enabled <= [self, :logged_out, computed_by: :logged_in]
7241
+ }
7242
+ }
7243
+
7244
+ horizontal_box {
7245
+ button('Login') {
7246
+ enabled <= [self, :logged_out, computed_by: :logged_in]
7247
+
7248
+ on_clicked do
7249
+ self.logged_in = true
7250
+ end
7251
+ }
7252
+
7253
+ button('Logout') {
7254
+ enabled <= [self, :logged_in]
7255
+
7256
+ on_clicked do
7257
+ self.logged_in = false
7258
+ self.username = ''
7259
+ self.password = ''
7260
+ end
7261
+ }
7262
+ }
7263
+ }
7264
+ }.show
7265
+ end
7266
+ end
7267
+
7268
+ Login.new.launch
7269
+ ```
7270
+
7271
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (with [data-binding](#data-binding)):
7272
+
7273
+ ```ruby
7274
+ require 'glimmer-dsl-libui'
7275
+
7276
+ class Login
7277
+ include Glimmer
7278
+
7279
+ attr_accessor :username, :password
7280
+ attr_reader :logged_in
7281
+
7282
+ def logged_in=(value)
7283
+ @logged_in = value
7284
+ self.logged_out = !value # calling logged_out= method notifies logged_out observers
7285
+ end
7286
+
7287
+ def logged_out=(value)
7288
+ self.logged_in = !value unless logged_in == !value
7289
+ end
7290
+
7291
+ def logged_out
7292
+ !logged_in
7293
+ end
7294
+
7295
+ def launch
7296
+ window('Login') {
7297
+ margined true
7298
+
7299
+ vertical_box {
7300
+ form {
7301
+ entry {
7302
+ label 'Username:'
7303
+ text <=> [self, :username]
7304
+ enabled <= [self, :logged_out]
7305
+ }
7306
+
7307
+ password_entry {
7308
+ label 'Password:'
7309
+ text <=> [self, :password]
7310
+ enabled <= [self, :logged_out]
7311
+ }
7312
+ }
7313
+
7314
+ horizontal_box {
7315
+ button('Login') {
7316
+ enabled <= [self, :logged_out]
7317
+
7318
+ on_clicked do
7319
+ self.logged_in = true
7320
+ end
7321
+ }
7322
+
7323
+ button('Logout') {
7324
+ enabled <= [self, :logged_in]
7325
+
7326
+ on_clicked do
7327
+ self.logged_in = false
7328
+ self.username = ''
7329
+ self.password = ''
7330
+ end
7331
+ }
7332
+ }
7333
+ }
7334
+ }.show
7335
+ end
7336
+ end
7337
+
7338
+ Login.new.launch
7339
+ ```
7340
+
7341
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with [data-binding](#data-binding)):
7342
+
7343
+ ```ruby
7344
+ require 'glimmer-dsl-libui'
7345
+
7346
+ class Login
7347
+ include Glimmer
7348
+
7349
+ attr_accessor :username, :password
7350
+ attr_reader :logged_in
7351
+
7352
+ def logged_in=(value)
7353
+ @logged_in = value
7354
+ notify_observers(:logged_out) # manually notify observers of logged_out upon logged_in changes; this method comes automatically from enhancement as Glimmer::DataBinding::ObservableModel via data-binding
7355
+ end
7356
+
7357
+ def logged_out
7358
+ !logged_in
7359
+ end
7360
+
7361
+ def launch
7362
+ window('Login') {
7363
+ margined true
7364
+
7365
+ vertical_box {
7366
+ form {
7367
+ entry {
7368
+ label 'Username:'
7369
+ text <=> [self, :username]
7370
+ enabled <= [self, :logged_out]
7371
+ }
7372
+
7373
+ password_entry {
7374
+ label 'Password:'
7375
+ text <=> [self, :password]
7376
+ enabled <= [self, :logged_out]
7377
+ }
7378
+ }
7379
+
7380
+ horizontal_box {
7381
+ button('Login') {
7382
+ enabled <= [self, :logged_out]
7383
+
7384
+ on_clicked do
7385
+ self.logged_in = true
7386
+ end
7387
+ }
7388
+
7389
+ button('Logout') {
7390
+ enabled <= [self, :logged_in]
7391
+
7392
+ on_clicked do
7393
+ self.logged_in = false
7394
+ self.username = ''
7395
+ self.password = ''
7396
+ end
7397
+ }
7398
+ }
7399
+ }
7400
+ }.show
7401
+ end
7402
+ end
7403
+
7404
+ Login.new.launch
7405
+ ```
7406
+
7407
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (without [data-binding](#data-binding)):
6212
7408
 
6213
7409
  ```ruby
6214
7410
  require 'glimmer-dsl-libui'
@@ -6276,7 +7472,102 @@ Mac | Windows | Linux
6276
7472
  ----|---------|------
6277
7473
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png) | ![glimmer-dsl-libui-windows-method-based-custom-keyword.png](images/glimmer-dsl-libui-windows-method-based-custom-keyword.png) | ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
6278
7474
 
6279
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7475
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7476
+
7477
+ ```ruby
7478
+ require 'glimmer-dsl-libui'
7479
+ require 'facets'
7480
+
7481
+ include Glimmer
7482
+
7483
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
7484
+
7485
+ def form_field(model, attribute)
7486
+ attribute = attribute.to_s
7487
+ entry { |e|
7488
+ label attribute.underscore.split('_').map(&:capitalize).join(' ')
7489
+ text <=> [model, attribute]
7490
+ }
7491
+ end
7492
+
7493
+ def address_form(address)
7494
+ form {
7495
+ form_field(address, :street)
7496
+ form_field(address, :p_o_box)
7497
+ form_field(address, :city)
7498
+ form_field(address, :state)
7499
+ form_field(address, :zip_code)
7500
+ }
7501
+ end
7502
+
7503
+ def label_pair(model, attribute, value)
7504
+ horizontal_box {
7505
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
7506
+ label(value.to_s) {
7507
+ text <= [model, attribute]
7508
+ }
7509
+ }
7510
+ end
7511
+
7512
+ def address(address)
7513
+ vertical_box {
7514
+ address.each_pair do |attribute, value|
7515
+ label_pair(address, attribute, value)
7516
+ end
7517
+ }
7518
+ end
7519
+
7520
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
7521
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
7522
+
7523
+ window('Method-Based Custom Keyword') {
7524
+ margined true
7525
+
7526
+ horizontal_box {
7527
+ vertical_box {
7528
+ label('Address 1') {
7529
+ stretchy false
7530
+ }
7531
+
7532
+ address_form(address1)
7533
+
7534
+ horizontal_separator {
7535
+ stretchy false
7536
+ }
7537
+
7538
+ label('Address 1 (Saved)') {
7539
+ stretchy false
7540
+ }
7541
+
7542
+ address(address1)
7543
+ }
7544
+
7545
+ vertical_separator {
7546
+ stretchy false
7547
+ }
7548
+
7549
+ vertical_box {
7550
+ label('Address 2') {
7551
+ stretchy false
7552
+ }
7553
+
7554
+ address_form(address2)
7555
+
7556
+ horizontal_separator {
7557
+ stretchy false
7558
+ }
7559
+
7560
+ label('Address 2 (Saved)') {
7561
+ stretchy false
7562
+ }
7563
+
7564
+ address(address2)
7565
+ }
7566
+ }
7567
+ }.show
7568
+ ```
7569
+
7570
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6280
7571
 
6281
7572
  ```ruby
6282
7573
  require 'glimmer-dsl-libui'
@@ -7222,7 +8513,140 @@ Mac | Windows | Linux
7222
8513
  ----|---------|------
7223
8514
  ![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png) ![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png) | ![glimmer-dsl-libui-windows-timer.png](images/glimmer-dsl-libui-windows-timer.png) ![glimmer-dsl-libui-windows-timer-in-progress.png](images/glimmer-dsl-libui-windows-timer-in-progress.png) | ![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png) ![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
7224
8515
 
7225
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8516
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8517
+
8518
+ ```ruby
8519
+ require 'glimmer-dsl-libui'
8520
+
8521
+ class Timer
8522
+ include Glimmer
8523
+
8524
+ SECOND_MAX = 59
8525
+ MINUTE_MAX = 59
8526
+ HOUR_MAX = 23
8527
+
8528
+ attr_accessor :hour, :min, :sec, :started, :played
8529
+
8530
+ def initialize
8531
+ @pid = nil
8532
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
8533
+ @hour = @min = @sec = 0
8534
+ at_exit { stop_alarm }
8535
+ setup_timer
8536
+ create_gui
8537
+ end
8538
+
8539
+ def stop_alarm
8540
+ if @pid
8541
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8542
+ @pid = nil
8543
+ end
8544
+ end
8545
+
8546
+ def play_alarm
8547
+ stop_alarm
8548
+ if @pid.nil?
8549
+ begin
8550
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
8551
+ @th = Process.detach @pid
8552
+ rescue Errno::ENOENT
8553
+ warn 'Timidty++ not found. Please install Timidity++.'
8554
+ warn 'https://sourceforge.net/projects/timidity/'
8555
+ end
8556
+ end
8557
+ end
8558
+
8559
+ def setup_timer
8560
+ unless @setup_timer
8561
+ Glimmer::LibUI.timer(1) do
8562
+ if @started
8563
+ seconds = @sec
8564
+ minutes = @min
8565
+ hours = @hour
8566
+ if seconds > 0
8567
+ self.sec = seconds -= 1
8568
+ end
8569
+ if seconds == 0
8570
+ if minutes > 0
8571
+ self.min = minutes -= 1
8572
+ self.sec = seconds = SECOND_MAX
8573
+ end
8574
+ if minutes == 0
8575
+ if hours > 0
8576
+ self.hour = hours -= 1
8577
+ self.min = minutes = MINUTE_MAX
8578
+ self.sec = seconds = SECOND_MAX
8579
+ end
8580
+ if hours == 0 && minutes == 0 && seconds == 0
8581
+ self.started = false
8582
+ unless @played
8583
+ play_alarm
8584
+ msg_box('Alarm', 'Countdown Is Finished!')
8585
+ self.played = true
8586
+ end
8587
+ end
8588
+ end
8589
+ end
8590
+ end
8591
+ end
8592
+ @setup_timer = true
8593
+ end
8594
+ end
8595
+
8596
+ def create_gui
8597
+ window('Timer') {
8598
+ margined true
8599
+
8600
+ group('Countdown') {
8601
+ vertical_box {
8602
+ horizontal_box {
8603
+ spinbox(0, HOUR_MAX) {
8604
+ stretchy false
8605
+ value <=> [self, :hour]
8606
+ }
8607
+ label(':') {
8608
+ stretchy false
8609
+ }
8610
+ spinbox(0, MINUTE_MAX) {
8611
+ stretchy false
8612
+ value <=> [self, :min]
8613
+ }
8614
+ label(':') {
8615
+ stretchy false
8616
+ }
8617
+ spinbox(0, SECOND_MAX) {
8618
+ stretchy false
8619
+ value <=> [self, :sec]
8620
+ }
8621
+ }
8622
+ horizontal_box {
8623
+ button('Start') {
8624
+ enabled <= [self, :started, on_read: :!]
8625
+
8626
+ on_clicked do
8627
+ self.started = true
8628
+ self.played = false
8629
+ end
8630
+ }
8631
+
8632
+ button('Stop') {
8633
+ enabled <= [self, :started]
8634
+
8635
+ on_clicked do
8636
+ self.started = false
8637
+ end
8638
+ }
8639
+ }
8640
+ }
8641
+ }
8642
+ }.show
8643
+ end
8644
+ end
8645
+
8646
+ Timer.new
8647
+ ```
8648
+
8649
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
7226
8650
 
7227
8651
  ```ruby
7228
8652
  require 'glimmer-dsl-libui'