glimmer-dsl-libui 0.4.3 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/README.md +926 -153
  4. data/VERSION +1 -1
  5. data/examples/color_button.rb +18 -13
  6. data/examples/color_button2.rb +14 -0
  7. data/examples/date_time_picker.rb +19 -14
  8. data/examples/date_time_picker2.rb +20 -0
  9. data/examples/dynamic_area.rb +77 -90
  10. data/examples/dynamic_area2.rb +14 -12
  11. data/examples/dynamic_area3.rb +90 -0
  12. data/examples/dynamic_area4.rb +95 -0
  13. data/examples/font_button.rb +17 -12
  14. data/examples/font_button2.rb +18 -0
  15. data/examples/form_table.rb +0 -2
  16. data/examples/form_table2.rb +0 -2
  17. data/examples/histogram.rb +93 -91
  18. data/examples/histogram2.rb +109 -0
  19. data/examples/midi_player.rb +4 -5
  20. data/examples/midi_player2.rb +83 -0
  21. data/examples/midi_player3.rb +84 -0
  22. data/examples/timer.rb +28 -31
  23. data/examples/timer2.rb +129 -0
  24. data/glimmer-dsl-libui.gemspec +0 -0
  25. data/lib/glimmer/libui/attributed_string.rb +3 -0
  26. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +5 -0
  27. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +18 -2
  28. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +5 -0
  29. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +5 -0
  30. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +4 -0
  31. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +38 -0
  32. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +38 -0
  33. data/lib/glimmer/libui/control_proxy.rb +4 -12
  34. data/lib/glimmer/libui/data_bindable.rb +39 -0
  35. data/lib/glimmer/libui/shape.rb +2 -0
  36. metadata +14 -2
data/README.md CHANGED
@@ -1,9 +1,9 @@
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.3
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.7
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 library. No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
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!
7
7
 
8
8
  Mac | Windows | Linux
9
9
  ----|---------|------
@@ -373,7 +373,7 @@ gem install glimmer-dsl-libui
373
373
  Or install via Bundler `Gemfile`:
374
374
 
375
375
  ```ruby
376
- gem 'glimmer-dsl-libui', '~> 0.4.3'
376
+ gem 'glimmer-dsl-libui', '~> 0.4.7'
377
377
  ```
378
378
 
379
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.
@@ -460,7 +460,7 @@ Keyword(Args) | Properties | Listeners
460
460
  `checkbox_text_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
461
461
  `checkbox_text_color_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
462
462
  `check_menu_item(text as String)` | `checked` (Boolean) | `on_clicked`
463
- `combobox` | `items` (`Array` of `String`), `selected` (`Integer`) | `on_selected`
463
+ `combobox` | `items` (`Array` of `String`), `selected` (`Integer`), `selected_item` (`String`) | `on_selected`
464
464
  `color_button` | `color` (Array of `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float`), `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float` | `on_changed`
465
465
  `date_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`, `mday` as `Integer`, `mon` as `Integer`, `year` as `Integer`, `wday` as `Integer`, `yday` as `Integer`, `dst` as Boolean) | `on_changed`
466
466
  `date_time_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`, `mday` as `Integer`, `mon` as `Integer`, `year` as `Integer`, `wday` as `Integer`, `yday` as `Integer`, `dst` as Boolean) | `on_changed`
@@ -620,93 +620,112 @@ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
620
620
  ```ruby
621
621
  require 'glimmer-dsl-libui'
622
622
 
623
- include Glimmer
624
-
625
- data = [
626
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
627
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
628
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
629
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
630
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
631
- ]
632
-
633
- window('Contacts', 600, 600) { |w|
634
- margined true
623
+ class FormTable
624
+ include Glimmer
635
625
 
636
- vertical_box {
637
- form {
638
- stretchy false
639
-
640
- @name_entry = entry {
641
- label 'Name'
642
- }
643
- @email_entry = entry {
644
- label 'Email'
645
- }
646
- @phone_entry = entry {
647
- label 'Phone'
648
- }
649
- @city_entry = entry {
650
- label 'City'
651
- }
652
- @state_entry = entry {
653
- label 'State'
654
- }
655
- }
656
-
657
- button('Save Contact') {
658
- stretchy false
659
-
660
- on_clicked do
661
- new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
662
- if new_row.include?('')
663
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
664
- else
665
- data << new_row # automatically inserts a row into the table due to implicit data-binding
666
- @unfiltered_data = data.dup
667
- @name_entry.text = ''
668
- @email_entry.text = ''
669
- @phone_entry.text = ''
670
- @city_entry.text = ''
671
- @state_entry.text = ''
672
- end
673
- end
674
- }
675
-
676
- search_entry { |se|
677
- 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
678
641
 
679
- on_changed do
680
- filter_value = se.text
681
- @unfiltered_data ||= data.dup
682
- # Unfilter first to remove any previous filters
683
- data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
684
- # Now, apply filter if entered
685
- unless filter_value.empty?
686
- data.filter! do |row_data| # affects table indirectly through implicit data-binding
687
- row_data.any? do |cell|
688
- 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 = ''
689
687
  end
690
688
  end
691
- end
692
- end
693
- }
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')
694
716
 
695
- table {
696
- text_column('Name')
697
- text_column('Email')
698
- text_column('Phone')
699
- text_column('City')
700
- 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
701
727
 
702
- cell_rows data # implicit data-binding
703
-
704
- on_changed do |row, type, row_data|
705
- puts "Row #{row} #{type}: #{row_data}"
706
- end
707
- }
708
- }
709
- }.show
728
+ FormTable.new.launch
710
729
  ```
711
730
 
712
731
  ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
@@ -877,7 +896,7 @@ Given that it is very new and is not a [libui](https://github.com/andlabs/libui)
877
896
  - Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
878
897
  - Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image.
879
898
 
880
- 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).
881
900
 
882
901
  Setting a [`transform` `matrix`](#area-transform-matrix) is supported under `image` just like it is under `path` and `text` inside `area`.
883
902
 
@@ -1033,6 +1052,8 @@ window('Basic Image', 96, 96) {
1033
1052
 
1034
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.
1035
1054
 
1055
+ ![linux table image](images/glimmer-dsl-libui-linux-basic-table-image.png)
1056
+
1036
1057
  Check out [examples/basic_image.rb](#basic-image) (all versions) for examples of using `image` Glimmer custom control.
1037
1058
 
1038
1059
  #### Colors
@@ -1352,11 +1373,28 @@ See examples of the `observe` keyword at [Color The Circles](#color-the-circles)
1352
1373
 
1353
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.
1354
1375
 
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.
1377
+
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.
1379
+
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
+
1355
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):
1356
- - `entry` `text` property
1357
- - `multiline_entry` `text` property
1358
- - `non_wrapping_multiline_entry` `text` property
1359
- - `search_entry` `text` property
1385
+ - `color_button`: `color`
1386
+ - `combobox`: `selected`, `selected_item`
1387
+ - `date_picker`: `time`
1388
+ - `date_time_picker`: `time`
1389
+ - `editable_combobox`: `text`
1390
+ - `entry`: `text`
1391
+ - `font_button`: `font`
1392
+ - `multiline_entry`: `text`
1393
+ - `non_wrapping_multiline_entry`: `text`
1394
+ - `search_entry`: `text`
1395
+ - `slider`: `value`
1396
+ - `spinbox`: `value`
1397
+ - `time_picker`: `time`
1360
1398
 
1361
1399
  Example of bidirectional data-binding:
1362
1400
 
@@ -1372,11 +1410,11 @@ Another example of bidirectional data-binding with an option:
1372
1410
 
1373
1411
  ```ruby
1374
1412
  entry {
1375
- text <=> [self, :entry_text, after_write: ->(text) {puts text}]
1413
+ text <=> [self, :entered_text, after_write: ->(text) {puts text}]
1376
1414
  }
1377
1415
  ```
1378
1416
 
1379
- That is data-binding `entry_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1417
+ That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1380
1418
 
1381
1419
  [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).
1382
1420
 
@@ -1400,19 +1438,16 @@ window {
1400
1438
 
1401
1439
  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`.
1402
1440
 
1403
- The data-binding API:
1404
- - Bidirectional (two-way) data-binding to a Model attribute accessor: `view_property <=> [model, attribute, *read_or_write_options]`
1405
- - Unidirectional (one-way) data-binding to a Model attribute reader: `view_property <= [model, attribute, *read_only_options]`
1441
+ To summarize the data-binding API:
1442
+ - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1443
+ - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
1406
1444
 
1407
1445
  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.
1408
1446
 
1409
- 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.
1410
-
1411
- 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.
1412
-
1413
- 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).
1414
-
1415
- ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1447
+ Data-bound model attribute can be:
1448
+ - **Direct:** `Symbol` representing attribute reader/writer (e.g. `[person, :name`])
1449
+ - **Nested:** `String` representing nested attribute path (e.g. `[company, 'address.street']`). That results in "nested data-binding"
1450
+ - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
1416
1451
 
1417
1452
  Data-binding options include:
1418
1453
  - `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
@@ -1421,6 +1456,7 @@ Data-binding options include:
1421
1456
  - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
1422
1457
  - `on_write {|value| ...}`: converts value read from View to update the Model.
1423
1458
  - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
1459
+ - `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)). That is known as "computed data-binding".
1424
1460
 
1425
1461
  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.
1426
1462
 
@@ -1432,9 +1468,11 @@ entry {
1432
1468
  }
1433
1469
  ```
1434
1470
 
1435
- Gotcha: 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)
1471
+ Data-binding gotchas:
1472
+ - 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)
1473
+ - 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.
1436
1474
 
1437
- Learn more from data-binding usage in [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Login](#login), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1475
+ 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.
1438
1476
 
1439
1477
  ### API Gotchas
1440
1478
 
@@ -1850,7 +1888,7 @@ Example:
1850
1888
 
1851
1889
  ## Examples
1852
1890
 
1853
- The following examples include reimplementions of the examples in the [LibUI](https://github.com/kojix2/LibUI) project utilizing the [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) as well as brand new examples.
1891
+ The following examples include reimplementions of the examples in the [LibUI](https://github.com/kojix2/LibUI) project utilizing the [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) (with and without [data-binding](#data-binding)) as well as brand new examples.
1854
1892
 
1855
1893
  To browse all examples, simply launch the [Meta-Example](examples/meta_example.rb), which lists all examples and displays each example's code when selected. It also enables code editing to facilitate experimentation and learning.
1856
1894
 
@@ -1877,17 +1915,23 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
1877
1915
  ```ruby
1878
1916
  require 'glimmer-dsl-libui'
1879
1917
  require 'facets'
1918
+ require 'fileutils'
1880
1919
 
1881
1920
  class MetaExample
1882
1921
  include Glimmer
1883
1922
 
1923
+ ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
1924
+
1925
+ attr_accessor :code_text
1926
+
1884
1927
  def initialize
1885
- @selected_example_index = 0
1928
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
1929
+ @code_text = File.read(file_path_for(selected_example))
1886
1930
  end
1887
1931
 
1888
1932
  def examples
1889
1933
  if @examples.nil?
1890
- example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
1934
+ example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
1891
1935
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
1892
1936
  example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
1893
1937
  @examples = example_file_names.map { |f| f.underscore.titlecase }
@@ -1901,12 +1945,20 @@ class MetaExample
1901
1945
  end
1902
1946
  end
1903
1947
 
1948
+ def basic_examples_with_versions
1949
+ examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
1950
+ end
1951
+
1952
+ def advanced_examples_with_versions
1953
+ examples_with_versions - basic_examples_with_versions
1954
+ end
1955
+
1904
1956
  def file_path_for(example)
1905
1957
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
1906
1958
  end
1907
1959
 
1908
1960
  def version_count_for(example)
1909
- Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1961
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
1910
1962
  end
1911
1963
 
1912
1964
  def glimmer_dsl_libui_file
@@ -1942,17 +1994,47 @@ class MetaExample
1942
1994
  vertical_box {
1943
1995
  stretchy false
1944
1996
 
1945
- @example_radio_buttons = radio_buttons {
1997
+ tab {
1946
1998
  stretchy false
1947
- items examples_with_versions
1948
- selected @selected_example_index
1949
1999
 
1950
- on_selected do
1951
- @selected_example_index = @example_radio_buttons.selected
1952
- example = selected_example
1953
- @code_entry.text = File.read(file_path_for(example))
1954
- @version_spinbox.value = 1
1955
- end
2000
+ tab_item('Basic') {
2001
+ vertical_box {
2002
+ @basic_example_radio_buttons = radio_buttons {
2003
+ stretchy false
2004
+ items basic_examples_with_versions
2005
+ selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
2006
+
2007
+ on_selected do
2008
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
2009
+ example = selected_example
2010
+ self.code_text = File.read(file_path_for(example))
2011
+ @version_spinbox.value = 1
2012
+ end
2013
+ }
2014
+
2015
+ label # filler
2016
+ label # filler
2017
+ }
2018
+ }
2019
+
2020
+ tab_item('Advanced') {
2021
+ vertical_box {
2022
+ @advanced_example_radio_buttons = radio_buttons {
2023
+ stretchy false
2024
+ items advanced_examples_with_versions
2025
+
2026
+ on_selected do
2027
+ @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
2028
+ example = selected_example
2029
+ self.code_text = File.read(file_path_for(example))
2030
+ @version_spinbox.value = 1
2031
+ end
2032
+ }
2033
+
2034
+ label # filler
2035
+ label # filler
2036
+ }
2037
+ }
1956
2038
  }
1957
2039
 
1958
2040
  horizontal_box {
@@ -1970,7 +2052,7 @@ class MetaExample
1970
2052
  else
1971
2053
  version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1972
2054
  example = "#{selected_example}#{version_number}"
1973
- @code_entry.text = File.read(file_path_for(example))
2055
+ self.code_text = File.read(file_path_for(example))
1974
2056
  end
1975
2057
  end
1976
2058
  }
@@ -1982,9 +2064,15 @@ class MetaExample
1982
2064
  button('Launch') {
1983
2065
  on_clicked do
1984
2066
  begin
1985
- meta_example_file = File.join(Dir.home, '.meta_example.rb')
1986
- File.write(meta_example_file, @code_entry.text)
1987
- run_example(meta_example_file)
2067
+ parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
2068
+ FileUtils.mkdir_p(parent_dir)
2069
+ example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
2070
+ File.write(example_file, code_text)
2071
+ example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
2072
+ FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
2073
+ FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
2074
+ FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
2075
+ run_example(example_file)
1988
2076
  rescue => e
1989
2077
  puts e.full_message
1990
2078
  puts 'Unable to write code changes! Running original example...'
@@ -1994,14 +2082,14 @@ class MetaExample
1994
2082
  }
1995
2083
  button('Reset') {
1996
2084
  on_clicked do
1997
- @code_entry.text = File.read(file_path_for(selected_example))
2085
+ self.code_text = File.read(file_path_for(selected_example))
1998
2086
  end
1999
2087
  }
2000
2088
  }
2001
2089
  }
2002
2090
 
2003
2091
  @code_entry = non_wrapping_multiline_entry {
2004
- text File.read(file_path_for(selected_example))
2092
+ text <=> [self, :code_text]
2005
2093
  }
2006
2094
  }
2007
2095
  }.show
@@ -2415,7 +2503,33 @@ UI.quit
2415
2503
 
2416
2504
  ```
2417
2505
 
2418
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2506
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2507
+
2508
+ ```ruby
2509
+ require 'glimmer-dsl-libui'
2510
+
2511
+ class FontButton
2512
+ include Glimmer
2513
+
2514
+ attr_accessor :font_descriptor
2515
+
2516
+ def launch
2517
+ window('hello world', 300, 200) {
2518
+ font_button {
2519
+ font <=> [self, :font_descriptor, after_write: -> { p font_descriptor }]
2520
+ }
2521
+
2522
+ on_closing do
2523
+ puts 'Bye Bye'
2524
+ end
2525
+ }.show
2526
+ end
2527
+ end
2528
+
2529
+ FontButton.new.launch
2530
+ ```
2531
+
2532
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2419
2533
 
2420
2534
  ```ruby
2421
2535
  require 'glimmer-dsl-libui'
@@ -2456,18 +2570,44 @@ Mac | Windows | Linux
2456
2570
  ----|---------|------
2457
2571
  ![glimmer-dsl-libui-mac-color-button.png](images/glimmer-dsl-libui-mac-color-button.png) ![glimmer-dsl-libui-mac-color-button-selection.png](images/glimmer-dsl-libui-mac-color-button-selection.png) | ![glimmer-dsl-libui-windows-color-button.png](images/glimmer-dsl-libui-windows-color-button.png) ![glimmer-dsl-libui-windows-color-button-selection.png](images/glimmer-dsl-libui-windows-color-button-selection.png) | ![glimmer-dsl-libui-linux-color-button.png](images/glimmer-dsl-libui-linux-color-button.png) ![glimmer-dsl-libui-linux-color-button-selection.png](images/glimmer-dsl-libui-linux-color-button-selection.png)
2458
2572
 
2459
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2573
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2460
2574
 
2461
2575
  ```ruby
2462
2576
  require 'glimmer-dsl-libui'
2463
2577
 
2464
- include Glimmer
2578
+ class ColorButton
2579
+ include Glimmer
2580
+
2581
+ attr_accessor :selected_color
2582
+
2583
+ def initialize
2584
+ @selected_color = :blue
2585
+ end
2586
+
2587
+ def launch
2588
+ window('color button', 240) {
2589
+ color_button {
2590
+ color <=> [self, :selected_color, after_write: ->(color) {p color}]
2591
+ }
2592
+ }.show
2593
+ end
2594
+ end
2465
2595
 
2466
- window('color button', 240) {
2467
- color_button { |cb|
2468
- color :blue
2469
-
2470
- on_changed do
2596
+ ColorButton.new.launch
2597
+ ```
2598
+
2599
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2600
+
2601
+ ```ruby
2602
+ require 'glimmer-dsl-libui'
2603
+
2604
+ include Glimmer
2605
+
2606
+ window('color button', 240) {
2607
+ color_button { |cb|
2608
+ color :blue
2609
+
2610
+ on_changed do
2471
2611
  rgba = cb.color
2472
2612
  p rgba
2473
2613
  end
@@ -2538,7 +2678,35 @@ UI.main
2538
2678
  UI.quit
2539
2679
  ```
2540
2680
 
2541
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2681
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2682
+
2683
+ ```ruby
2684
+ require 'glimmer-dsl-libui'
2685
+
2686
+ class DateTimePicker
2687
+ include Glimmer
2688
+
2689
+ attr_accessor :picked_time
2690
+
2691
+ def launch
2692
+ window('Date Time Pickers', 300, 200) {
2693
+ vertical_box {
2694
+ date_time_picker {
2695
+ time <=> [self, :picked_time, after_write: ->(time) { p time }]
2696
+ }
2697
+ }
2698
+
2699
+ on_closing do
2700
+ puts 'Bye Bye'
2701
+ end
2702
+ }.show
2703
+ end
2704
+ end
2705
+
2706
+ DateTimePicker.new.launch
2707
+ ```
2708
+
2709
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2542
2710
 
2543
2711
  ```ruby
2544
2712
  require 'glimmer-dsl-libui'
@@ -5748,7 +5916,96 @@ Mac | Windows | Linux
5748
5916
  ----|---------|------
5749
5917
  ![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)
5750
5918
 
5751
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5919
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
5920
+
5921
+ ```ruby
5922
+ require 'glimmer-dsl-libui'
5923
+
5924
+ class DynamicArea
5925
+ include Glimmer
5926
+
5927
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
5928
+
5929
+ def initialize
5930
+ @rectangle_x = 25
5931
+ @rectangle_y = 25
5932
+ @rectangle_width = 150
5933
+ @rectangle_height = 150
5934
+ @rectangle_red = 102
5935
+ @rectangle_green = 102
5936
+ @rectangle_blue = 204
5937
+ @rectangle_alpha = 100
5938
+ end
5939
+
5940
+ def launch
5941
+ window('Dynamic Area', 240, 600) {
5942
+ margined true
5943
+
5944
+ vertical_box {
5945
+ label('Rectangle Properties') {
5946
+ stretchy false
5947
+ }
5948
+
5949
+ form {
5950
+ stretchy false
5951
+
5952
+ spinbox(0, 1000) {
5953
+ label 'x'
5954
+ value <=> [self, :rectangle_x, after_write: -> {@area.queue_redraw_all}]
5955
+ }
5956
+
5957
+ spinbox(0, 1000) {
5958
+ label 'y'
5959
+ value <=> [self, :rectangle_y, after_write: -> {@area.queue_redraw_all}]
5960
+ }
5961
+
5962
+ spinbox(0, 1000) {
5963
+ label 'width'
5964
+ value <=> [self, :rectangle_width, after_write: -> {@area.queue_redraw_all}]
5965
+ }
5966
+
5967
+ spinbox(0, 1000) {
5968
+ label 'height'
5969
+ value <=> [self, :rectangle_height, after_write: -> {@area.queue_redraw_all}]
5970
+ }
5971
+
5972
+ spinbox(0, 255) {
5973
+ label 'red'
5974
+ value <=> [self, :rectangle_red, after_write: -> {@area.queue_redraw_all}]
5975
+ }
5976
+
5977
+ spinbox(0, 255) {
5978
+ label 'green'
5979
+ value <=> [self, :rectangle_green, after_write: -> {@area.queue_redraw_all}]
5980
+ }
5981
+
5982
+ spinbox(0, 255) {
5983
+ label 'blue'
5984
+ value <=> [self, :rectangle_blue, after_write: -> {@area.queue_redraw_all}]
5985
+ }
5986
+
5987
+ spinbox(0, 100) {
5988
+ label 'alpha'
5989
+ value <=> [self, :rectangle_alpha, after_write: -> {@area.queue_redraw_all}]
5990
+ }
5991
+ }
5992
+
5993
+ @area = area {
5994
+ on_draw do |area_draw_params|
5995
+ rectangle(rectangle_x, rectangle_y, rectangle_width, rectangle_height) { # a dynamic path is added semi-declaratively inside on_draw block
5996
+ fill r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0
5997
+ }
5998
+ end
5999
+ }
6000
+ }
6001
+ }.show
6002
+ end
6003
+ end
6004
+
6005
+ DynamicArea.new.launch
6006
+ ```
6007
+
6008
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5752
6009
 
5753
6010
  ```ruby
5754
6011
  require 'glimmer-dsl-libui'
@@ -5850,7 +6107,102 @@ window('Dynamic Area', 240, 600) {
5850
6107
  }.show
5851
6108
  ```
5852
6109
 
5853
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (declarative stable `path` approach):
6110
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (declarative stable `path` approach with [data-binding](#data-binding)):
6111
+
6112
+ ```ruby
6113
+ require 'glimmer-dsl-libui'
6114
+
6115
+ class DynamicArea
6116
+ include Glimmer
6117
+
6118
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
6119
+
6120
+ def initialize
6121
+ @rectangle_x = 25
6122
+ @rectangle_y = 25
6123
+ @rectangle_width = 150
6124
+ @rectangle_height = 150
6125
+ @rectangle_red = 102
6126
+ @rectangle_green = 102
6127
+ @rectangle_blue = 204
6128
+ @rectangle_alpha = 100
6129
+ end
6130
+
6131
+ def rectangle_fill
6132
+ { r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0 }
6133
+ end
6134
+
6135
+ def launch
6136
+ window('Dynamic Area', 240, 600) {
6137
+ margined true
6138
+
6139
+ vertical_box {
6140
+ label('Rectangle Properties') {
6141
+ stretchy false
6142
+ }
6143
+
6144
+ form {
6145
+ stretchy false
6146
+
6147
+ @x_spinbox = spinbox(0, 1000) {
6148
+ label 'x'
6149
+ value <=> [self, :rectangle_x]
6150
+ }
6151
+
6152
+ @y_spinbox = spinbox(0, 1000) {
6153
+ label 'y'
6154
+ value <=> [self, :rectangle_y]
6155
+ }
6156
+
6157
+ @width_spinbox = spinbox(0, 1000) {
6158
+ label 'width'
6159
+ value <=> [self, :rectangle_width]
6160
+ }
6161
+
6162
+ @height_spinbox = spinbox(0, 1000) {
6163
+ label 'height'
6164
+ value <=> [self, :rectangle_height]
6165
+ }
6166
+
6167
+ @red_spinbox = spinbox(0, 255) {
6168
+ label 'red'
6169
+ value <=> [self, :rectangle_red]
6170
+ }
6171
+
6172
+ @green_spinbox = spinbox(0, 255) {
6173
+ label 'green'
6174
+ value <=> [self, :rectangle_green]
6175
+ }
6176
+
6177
+ @blue_spinbox = spinbox(0, 255) {
6178
+ label 'blue'
6179
+ value <=> [self, :rectangle_blue]
6180
+ }
6181
+
6182
+ @alpha_spinbox = spinbox(0, 100) {
6183
+ label 'alpha'
6184
+ value <=> [self, :rectangle_alpha]
6185
+ }
6186
+ }
6187
+
6188
+ area {
6189
+ @rectangle = rectangle { # stable implicit path shape
6190
+ x <= [self, :rectangle_x]
6191
+ y <= [self, :rectangle_y]
6192
+ width <= [self, :rectangle_width]
6193
+ height <= [self, :rectangle_height]
6194
+ fill <= [self, :rectangle_fill, computed_by: [:rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha]]
6195
+ }
6196
+ }
6197
+ }
6198
+ }.show
6199
+ end
6200
+ end
6201
+
6202
+ DynamicArea.new.launch
6203
+ ```
6204
+
6205
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (declarative stable `path` approach without [data-binding](#data-binding)):
5854
6206
 
5855
6207
  ```ruby
5856
6208
  require 'glimmer-dsl-libui'
@@ -5942,7 +6294,7 @@ window('Dynamic Area', 240, 600) {
5942
6294
  }
5943
6295
 
5944
6296
  area {
5945
- @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable path
6297
+ @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable implicit path shape
5946
6298
  fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
5947
6299
  }
5948
6300
  }
@@ -6640,7 +6992,121 @@ UI.main
6640
6992
  UI.quit
6641
6993
  ```
6642
6994
 
6643
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6995
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6996
+
6997
+ ```ruby
6998
+ # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
6999
+
7000
+ require 'glimmer-dsl-libui'
7001
+
7002
+ class Histogram
7003
+ include Glimmer
7004
+
7005
+ X_OFF_LEFT = 20
7006
+ Y_OFF_TOP = 20
7007
+ X_OFF_RIGHT = 20
7008
+ Y_OFF_BOTTOM = 20
7009
+ POINT_RADIUS = 5
7010
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
7011
+
7012
+ attr_accessor :datapoints, :histogram_color
7013
+
7014
+ def initialize
7015
+ @datapoints = 10.times.map {Random.new.rand(90)}
7016
+ @histogram_color = COLOR_BLUE
7017
+ end
7018
+
7019
+ def graph_size(area_width, area_height)
7020
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
7021
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
7022
+ [graph_width, graph_height]
7023
+ end
7024
+
7025
+ def point_locations(width, height)
7026
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
7027
+ yincr = height / 100.0
7028
+
7029
+ @datapoints.each_with_index.map do |value, i|
7030
+ val = 100 - value
7031
+ [xincr * i, yincr * val]
7032
+ end
7033
+ end
7034
+
7035
+ # method-based custom control representing a graph path
7036
+ def graph_path(width, height, should_extend, &block)
7037
+ locations = point_locations(width, height).flatten
7038
+ path {
7039
+ if should_extend
7040
+ polygon(locations + [width, height, 0, height])
7041
+ else
7042
+ polyline(locations)
7043
+ end
7044
+
7045
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
7046
+ transform {
7047
+ translate X_OFF_LEFT, Y_OFF_TOP
7048
+ }
7049
+
7050
+ block.call
7051
+ }
7052
+ end
7053
+
7054
+ def launch
7055
+ window('histogram example', 640, 480) {
7056
+ margined true
7057
+
7058
+ horizontal_box {
7059
+ vertical_box {
7060
+ stretchy false
7061
+
7062
+ 10.times do |i|
7063
+ spinbox(0, 100) { |sb|
7064
+ stretchy false
7065
+ value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
7066
+ }
7067
+ end
7068
+
7069
+ color_button { |cb|
7070
+ stretchy false
7071
+ color <=> [self, :histogram_color, after_write: -> { @area.queue_redraw_all }]
7072
+ }
7073
+ }
7074
+
7075
+ @area = area {
7076
+ on_draw do |area_draw_params|
7077
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
7078
+ fill 0xFFFFFF
7079
+ }
7080
+
7081
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
7082
+
7083
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7084
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7085
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
7086
+
7087
+ stroke 0x000000, thickness: 2, miter_limit: 10
7088
+ }
7089
+
7090
+ # now create the fill for the graph below the graph line
7091
+ graph_path(graph_width, graph_height, true) {
7092
+ fill @histogram_color.merge(a: 0.5)
7093
+ }
7094
+
7095
+ # now draw the histogram line
7096
+ graph_path(graph_width, graph_height, false) {
7097
+ stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
7098
+ }
7099
+ end
7100
+ }
7101
+ }
7102
+ }.show
7103
+ end
7104
+ end
7105
+
7106
+ Histogram.new.launch
7107
+ ```
7108
+
7109
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6644
7110
 
6645
7111
  ```ruby
6646
7112
  # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
@@ -6654,9 +7120,10 @@ Y_OFF_TOP = 20
6654
7120
  X_OFF_RIGHT = 20
6655
7121
  Y_OFF_BOTTOM = 20
6656
7122
  POINT_RADIUS = 5
6657
- COLOR_BLUE = 0x1E90FF
7123
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
6658
7124
 
6659
7125
  @datapoints = 10.times.map {Random.new.rand(90)}
7126
+ @color = COLOR_BLUE
6660
7127
 
6661
7128
  def graph_size(area_width, area_height)
6662
7129
  graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
@@ -6712,11 +7179,12 @@ window('histogram example', 640, 480) {
6712
7179
  }
6713
7180
  end
6714
7181
 
6715
- @color_button = color_button {
7182
+ color_button { |cb|
6716
7183
  stretchy false
6717
7184
  color COLOR_BLUE
6718
7185
 
6719
7186
  on_changed do
7187
+ @color = cb.color
6720
7188
  @area.queue_redraw_all
6721
7189
  end
6722
7190
  }
@@ -6724,31 +7192,27 @@ window('histogram example', 640, 480) {
6724
7192
 
6725
7193
  @area = area {
6726
7194
  on_draw do |area_draw_params|
6727
- path {
6728
- rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
6729
-
7195
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6730
7196
  fill 0xFFFFFF
6731
7197
  }
6732
7198
 
6733
7199
  graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6734
7200
 
6735
- path {
6736
- figure(X_OFF_LEFT, Y_OFF_TOP) {
6737
- line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
6738
- line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6739
- }
7201
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7202
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7203
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6740
7204
 
6741
7205
  stroke 0x000000, thickness: 2, miter_limit: 10
6742
7206
  }
6743
7207
 
6744
7208
  # now create the fill for the graph below the graph line
6745
7209
  graph_path(graph_width, graph_height, true) {
6746
- fill @color_button.color.merge(a: 0.5)
7210
+ fill @color.merge(a: 0.5)
6747
7211
  }
6748
7212
 
6749
7213
  # now draw the histogram line
6750
7214
  graph_path(graph_width, graph_height, false) {
6751
- stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
7215
+ stroke @color.merge(thickness: 2, miter_limit: 10)
6752
7216
  }
6753
7217
  end
6754
7218
  }
@@ -6892,7 +7356,7 @@ end
6892
7356
  Login.new.launch
6893
7357
  ```
6894
7358
 
6895
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7359
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (with [data-binding](#data-binding)):
6896
7360
 
6897
7361
  ```ruby
6898
7362
  require 'glimmer-dsl-libui'
@@ -6962,7 +7426,7 @@ end
6962
7426
  Login.new.launch
6963
7427
  ```
6964
7428
 
6965
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7429
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with [data-binding](#data-binding)):
6966
7430
 
6967
7431
  ```ruby
6968
7432
  require 'glimmer-dsl-libui'
@@ -7426,7 +7890,181 @@ end
7426
7890
  TinyMidiPlayer.new
7427
7891
  ```
7428
7892
 
7429
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7893
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7894
+
7895
+ ```ruby
7896
+ # frozen_string_literal: true
7897
+
7898
+ require 'glimmer-dsl-libui'
7899
+
7900
+ class TinyMidiPlayer
7901
+ include Glimmer
7902
+
7903
+ VERSION = '0.0.1'
7904
+
7905
+ attr_accessor :selected_file
7906
+
7907
+ def initialize
7908
+ @pid = nil
7909
+ @music_directory = File.expand_path('../sounds', __dir__)
7910
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
7911
+ .sort_by { |path| File.basename(path) }
7912
+ at_exit { stop_midi }
7913
+ create_gui
7914
+ end
7915
+
7916
+ def stop_midi
7917
+ if @pid
7918
+ Process.kill(:SIGKILL, @pid) if @th.alive?
7919
+ @pid = nil
7920
+ end
7921
+ end
7922
+
7923
+ def play_midi
7924
+ stop_midi
7925
+ if @pid.nil? && @selected_file
7926
+ begin
7927
+ @pid = spawn "timidity #{@selected_file}"
7928
+ @th = Process.detach @pid
7929
+ rescue Errno::ENOENT
7930
+ warn 'Timidty++ not found. Please install Timidity++.'
7931
+ warn 'https://sourceforge.net/projects/timidity/'
7932
+ end
7933
+ end
7934
+ end
7935
+
7936
+ def show_version
7937
+ msg_box('Tiny Midi Player',
7938
+ "Written in Ruby\n" \
7939
+ "https://github.com/kojix2/libui\n" \
7940
+ "Version #{VERSION}")
7941
+ end
7942
+
7943
+ def create_gui
7944
+ menu('Help') {
7945
+ menu_item('Version') {
7946
+ on_clicked do
7947
+ show_version
7948
+ end
7949
+ }
7950
+ }
7951
+ window('Tiny Midi Player', 200, 50) {
7952
+ horizontal_box {
7953
+ vertical_box {
7954
+ stretchy false
7955
+
7956
+ button('▶') {
7957
+ on_clicked do
7958
+ play_midi
7959
+ end
7960
+ }
7961
+ button('■') {
7962
+ on_clicked do
7963
+ stop_midi
7964
+ end
7965
+ }
7966
+ }
7967
+
7968
+ combobox { |c|
7969
+ items @midi_files.map { |path| File.basename(path) }
7970
+ # data-bind selected item (String) to self.selected_file with on-read/on-write converters and after_write operation
7971
+ selected_item <=> [self, :selected_file, on_read: ->(f) {File.basename(f.to_s)}, on_write: ->(f) {File.join(@music_directory, f)}, after_write: -> { play_midi if @th&.alive? }]
7972
+ }
7973
+ }
7974
+ }.show
7975
+ end
7976
+ end
7977
+
7978
+ TinyMidiPlayer.new
7979
+ ```
7980
+
7981
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
7982
+
7983
+ ```ruby
7984
+ require 'glimmer-dsl-libui'
7985
+
7986
+ class TinyMidiPlayer
7987
+ include Glimmer
7988
+
7989
+ VERSION = '0.0.1'
7990
+
7991
+ attr_accessor :selected_file
7992
+
7993
+ def initialize
7994
+ @pid = nil
7995
+ @music_directory = File.expand_path('../sounds', __dir__)
7996
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
7997
+ .sort_by { |path| File.basename(path) }
7998
+ at_exit { stop_midi }
7999
+ create_gui
8000
+ end
8001
+
8002
+ def stop_midi
8003
+ if @pid
8004
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8005
+ @pid = nil
8006
+ end
8007
+ end
8008
+
8009
+ def play_midi
8010
+ stop_midi
8011
+ if @pid.nil? && @selected_file
8012
+ begin
8013
+ @pid = spawn "timidity #{@selected_file}"
8014
+ @th = Process.detach @pid
8015
+ rescue Errno::ENOENT
8016
+ warn 'Timidty++ not found. Please install Timidity++.'
8017
+ warn 'https://sourceforge.net/projects/timidity/'
8018
+ end
8019
+ end
8020
+ end
8021
+
8022
+ def show_version
8023
+ msg_box('Tiny Midi Player',
8024
+ "Written in Ruby\n" \
8025
+ "https://github.com/kojix2/libui\n" \
8026
+ "Version #{VERSION}")
8027
+ end
8028
+
8029
+ def create_gui
8030
+ menu('Help') {
8031
+ menu_item('Version') {
8032
+ on_clicked do
8033
+ show_version
8034
+ end
8035
+ }
8036
+ }
8037
+ window('Tiny Midi Player', 200, 50) {
8038
+ horizontal_box {
8039
+ vertical_box {
8040
+ stretchy false
8041
+
8042
+ button('▶') {
8043
+ on_clicked do
8044
+ play_midi
8045
+ end
8046
+ }
8047
+ button('■') {
8048
+ on_clicked do
8049
+ stop_midi
8050
+ end
8051
+ }
8052
+ }
8053
+
8054
+ combobox { |c|
8055
+ items @midi_files.map { |path| File.basename(path) }
8056
+ # data-bind selected index (Integer) to self.selected_file with on-read/on-write converters and after_write operation
8057
+ selected <=> [self, :selected_file, on_read: ->(f) {@midi_files.index(f)}, on_write: ->(i) {@midi_files[i]}, after_write: -> { play_midi if @th&.alive? }]
8058
+ }
8059
+ }
8060
+ }.show
8061
+ end
8062
+ end
8063
+
8064
+ TinyMidiPlayer.new
8065
+ ```
8066
+
8067
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (without [data-binding](#data-binding)):
7430
8068
 
7431
8069
  ```ruby
7432
8070
  require 'glimmer-dsl-libui'
@@ -8137,7 +8775,140 @@ Mac | Windows | Linux
8137
8775
  ----|---------|------
8138
8776
  ![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)
8139
8777
 
8140
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8778
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8779
+
8780
+ ```ruby
8781
+ require 'glimmer-dsl-libui'
8782
+
8783
+ class Timer
8784
+ include Glimmer
8785
+
8786
+ SECOND_MAX = 59
8787
+ MINUTE_MAX = 59
8788
+ HOUR_MAX = 23
8789
+
8790
+ attr_accessor :hour, :min, :sec, :started, :played
8791
+
8792
+ def initialize
8793
+ @pid = nil
8794
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
8795
+ @hour = @min = @sec = 0
8796
+ at_exit { stop_alarm }
8797
+ setup_timer
8798
+ create_gui
8799
+ end
8800
+
8801
+ def stop_alarm
8802
+ if @pid
8803
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8804
+ @pid = nil
8805
+ end
8806
+ end
8807
+
8808
+ def play_alarm
8809
+ stop_alarm
8810
+ if @pid.nil?
8811
+ begin
8812
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
8813
+ @th = Process.detach @pid
8814
+ rescue Errno::ENOENT
8815
+ warn 'Timidty++ not found. Please install Timidity++.'
8816
+ warn 'https://sourceforge.net/projects/timidity/'
8817
+ end
8818
+ end
8819
+ end
8820
+
8821
+ def setup_timer
8822
+ unless @setup_timer
8823
+ Glimmer::LibUI.timer(1) do
8824
+ if @started
8825
+ seconds = @sec
8826
+ minutes = @min
8827
+ hours = @hour
8828
+ if seconds > 0
8829
+ self.sec = seconds -= 1
8830
+ end
8831
+ if seconds == 0
8832
+ if minutes > 0
8833
+ self.min = minutes -= 1
8834
+ self.sec = seconds = SECOND_MAX
8835
+ end
8836
+ if minutes == 0
8837
+ if hours > 0
8838
+ self.hour = hours -= 1
8839
+ self.min = minutes = MINUTE_MAX
8840
+ self.sec = seconds = SECOND_MAX
8841
+ end
8842
+ if hours == 0 && minutes == 0 && seconds == 0
8843
+ self.started = false
8844
+ unless @played
8845
+ play_alarm
8846
+ msg_box('Alarm', 'Countdown Is Finished!')
8847
+ self.played = true
8848
+ end
8849
+ end
8850
+ end
8851
+ end
8852
+ end
8853
+ end
8854
+ @setup_timer = true
8855
+ end
8856
+ end
8857
+
8858
+ def create_gui
8859
+ window('Timer') {
8860
+ margined true
8861
+
8862
+ group('Countdown') {
8863
+ vertical_box {
8864
+ horizontal_box {
8865
+ spinbox(0, HOUR_MAX) {
8866
+ stretchy false
8867
+ value <=> [self, :hour]
8868
+ }
8869
+ label(':') {
8870
+ stretchy false
8871
+ }
8872
+ spinbox(0, MINUTE_MAX) {
8873
+ stretchy false
8874
+ value <=> [self, :min]
8875
+ }
8876
+ label(':') {
8877
+ stretchy false
8878
+ }
8879
+ spinbox(0, SECOND_MAX) {
8880
+ stretchy false
8881
+ value <=> [self, :sec]
8882
+ }
8883
+ }
8884
+ horizontal_box {
8885
+ button('Start') {
8886
+ enabled <= [self, :started, on_read: :!]
8887
+
8888
+ on_clicked do
8889
+ self.started = true
8890
+ self.played = false
8891
+ end
8892
+ }
8893
+
8894
+ button('Stop') {
8895
+ enabled <= [self, :started]
8896
+
8897
+ on_clicked do
8898
+ self.started = false
8899
+ end
8900
+ }
8901
+ }
8902
+ }
8903
+ }
8904
+ }.show
8905
+ end
8906
+ end
8907
+
8908
+ Timer.new
8909
+ ```
8910
+
8911
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8141
8912
 
8142
8913
  ```ruby
8143
8914
  require 'glimmer-dsl-libui'
@@ -8310,7 +9081,7 @@ https://github.com/iraamaro/i3off-gtk-ruby
8310
9081
 
8311
9082
  ### Issues
8312
9083
 
8313
- 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).
9084
+ 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.
8314
9085
 
8315
9086
  ### Chat
8316
9087
 
@@ -8342,6 +9113,8 @@ These features have been planned or suggested. You might see them in a future ve
8342
9113
  is fine, but please isolate to its own commit so I can cherry-pick
8343
9114
  around it.
8344
9115
 
9116
+ Note that the latest development sometimes takes place in [development](https://github.com/AndyObtiva/glimmer-dsl-libui/tree/development) branch (which is deleted once merged back to [master](https://github.com/AndyObtiva/glimmer-dsl-libui)).
9117
+
8345
9118
  ## Contributors
8346
9119
 
8347
9120
  * [Andy Maleh](https://github.com/AndyObtiva) (Founder)