glimmer-dsl-libui 0.4.4 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +932 -160
  4. data/VERSION +1 -1
  5. data/examples/button_counter.rb +2 -1
  6. data/examples/color_button.rb +18 -13
  7. data/examples/color_button2.rb +14 -0
  8. data/examples/date_time_picker.rb +19 -14
  9. data/examples/date_time_picker2.rb +20 -0
  10. data/examples/dynamic_area.rb +77 -90
  11. data/examples/dynamic_area2.rb +14 -12
  12. data/examples/dynamic_area3.rb +90 -0
  13. data/examples/dynamic_area4.rb +95 -0
  14. data/examples/font_button.rb +17 -12
  15. data/examples/font_button2.rb +18 -0
  16. data/examples/form_table.rb +0 -2
  17. data/examples/form_table2.rb +0 -2
  18. data/examples/histogram.rb +93 -91
  19. data/examples/histogram2.rb +109 -0
  20. data/examples/meta_example.rb +17 -6
  21. data/examples/midi_player.rb +5 -6
  22. data/examples/midi_player2.rb +83 -0
  23. data/examples/midi_player3.rb +84 -0
  24. data/examples/tetris.rb +15 -18
  25. data/examples/timer.rb +28 -31
  26. data/examples/timer2.rb +129 -0
  27. data/glimmer-dsl-libui.gemspec +0 -0
  28. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +5 -0
  29. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +5 -0
  30. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +18 -2
  31. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +5 -0
  32. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +5 -0
  33. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +4 -0
  34. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +5 -0
  35. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +17 -4
  36. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +20 -0
  37. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +38 -0
  38. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +38 -0
  39. data/lib/glimmer/libui/control_proxy.rb +2 -2
  40. metadata +13 -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.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.8
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.4'
376
+ gem 'glimmer-dsl-libui', '~> 0.4.8'
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)
@@ -1363,10 +1382,23 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1363
1382
  ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1364
1383
 
1365
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):
1366
- - `entry` `text` property
1367
- - `multiline_entry` `text` property
1368
- - `non_wrapping_multiline_entry` `text` property
1369
- - `search_entry` `text` property
1385
+ - `checkbox`: `checked`
1386
+ - `check_menu_item`: `checked`
1387
+ - `color_button`: `color`
1388
+ - `combobox`: `selected`, `selected_item`
1389
+ - `date_picker`: `time`
1390
+ - `date_time_picker`: `time`
1391
+ - `editable_combobox`: `text`
1392
+ - `entry`: `text`
1393
+ - `font_button`: `font`
1394
+ - `multiline_entry`: `text`
1395
+ - `non_wrapping_multiline_entry`: `text`
1396
+ - `radio_buttons`: `selected`
1397
+ - `radio_menu_item`: `checked`
1398
+ - `search_entry`: `text`
1399
+ - `slider`: `value`
1400
+ - `spinbox`: `value`
1401
+ - `time_picker`: `time`
1370
1402
 
1371
1403
  Example of bidirectional data-binding:
1372
1404
 
@@ -1416,6 +1448,11 @@ To summarize the data-binding API:
1416
1448
 
1417
1449
  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.
1418
1450
 
1451
+ Data-bound model attribute can be:
1452
+ - **Direct:** `Symbol` representing attribute reader/writer (e.g. `[person, :name`])
1453
+ - **Nested:** `String` representing nested attribute path (e.g. `[company, 'address.street']`). That results in "nested data-binding"
1454
+ - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
1455
+
1419
1456
  Data-binding options include:
1420
1457
  - `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
1421
1458
  - `on_read {|value| ...}`: converts value read from Model to update the View.
@@ -1423,7 +1460,7 @@ Data-binding options include:
1423
1460
  - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
1424
1461
  - `on_write {|value| ...}`: converts value read from View to update the Model.
1425
1462
  - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
1426
- - `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))
1463
+ - `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".
1427
1464
 
1428
1465
  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.
1429
1466
 
@@ -1435,7 +1472,9 @@ entry {
1435
1472
  }
1436
1473
  ```
1437
1474
 
1438
- 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)
1475
+ Data-binding gotchas:
1476
+ - 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)
1477
+ - 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.
1439
1478
 
1440
1479
  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.
1441
1480
 
@@ -1853,7 +1892,7 @@ Example:
1853
1892
 
1854
1893
  ## Examples
1855
1894
 
1856
- 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.
1895
+ 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.
1857
1896
 
1858
1897
  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.
1859
1898
 
@@ -1880,17 +1919,23 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
1880
1919
  ```ruby
1881
1920
  require 'glimmer-dsl-libui'
1882
1921
  require 'facets'
1922
+ require 'fileutils'
1883
1923
 
1884
1924
  class MetaExample
1885
1925
  include Glimmer
1886
1926
 
1927
+ ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
1928
+
1929
+ attr_accessor :code_text
1930
+
1887
1931
  def initialize
1888
- @selected_example_index = 0
1932
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
1933
+ @code_text = File.read(file_path_for(selected_example))
1889
1934
  end
1890
1935
 
1891
1936
  def examples
1892
1937
  if @examples.nil?
1893
- example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
1938
+ example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
1894
1939
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
1895
1940
  example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
1896
1941
  @examples = example_file_names.map { |f| f.underscore.titlecase }
@@ -1904,12 +1949,20 @@ class MetaExample
1904
1949
  end
1905
1950
  end
1906
1951
 
1952
+ def basic_examples_with_versions
1953
+ examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
1954
+ end
1955
+
1956
+ def advanced_examples_with_versions
1957
+ examples_with_versions - basic_examples_with_versions
1958
+ end
1959
+
1907
1960
  def file_path_for(example)
1908
1961
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
1909
1962
  end
1910
1963
 
1911
1964
  def version_count_for(example)
1912
- Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1965
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
1913
1966
  end
1914
1967
 
1915
1968
  def glimmer_dsl_libui_file
@@ -1945,17 +1998,47 @@ class MetaExample
1945
1998
  vertical_box {
1946
1999
  stretchy false
1947
2000
 
1948
- @example_radio_buttons = radio_buttons {
2001
+ tab {
1949
2002
  stretchy false
1950
- items examples_with_versions
1951
- selected @selected_example_index
1952
2003
 
1953
- on_selected do
1954
- @selected_example_index = @example_radio_buttons.selected
1955
- example = selected_example
1956
- @code_entry.text = File.read(file_path_for(example))
1957
- @version_spinbox.value = 1
1958
- end
2004
+ tab_item('Basic') {
2005
+ vertical_box {
2006
+ @basic_example_radio_buttons = radio_buttons {
2007
+ stretchy false
2008
+ items basic_examples_with_versions
2009
+ selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
2010
+
2011
+ on_selected do
2012
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
2013
+ example = selected_example
2014
+ self.code_text = File.read(file_path_for(example))
2015
+ @version_spinbox.value = 1
2016
+ end
2017
+ }
2018
+
2019
+ label # filler
2020
+ label # filler
2021
+ }
2022
+ }
2023
+
2024
+ tab_item('Advanced') {
2025
+ vertical_box {
2026
+ @advanced_example_radio_buttons = radio_buttons {
2027
+ stretchy false
2028
+ items advanced_examples_with_versions
2029
+
2030
+ on_selected do
2031
+ @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
2032
+ example = selected_example
2033
+ self.code_text = File.read(file_path_for(example))
2034
+ @version_spinbox.value = 1
2035
+ end
2036
+ }
2037
+
2038
+ label # filler
2039
+ label # filler
2040
+ }
2041
+ }
1959
2042
  }
1960
2043
 
1961
2044
  horizontal_box {
@@ -1973,7 +2056,7 @@ class MetaExample
1973
2056
  else
1974
2057
  version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1975
2058
  example = "#{selected_example}#{version_number}"
1976
- @code_entry.text = File.read(file_path_for(example))
2059
+ self.code_text = File.read(file_path_for(example))
1977
2060
  end
1978
2061
  end
1979
2062
  }
@@ -1985,9 +2068,15 @@ class MetaExample
1985
2068
  button('Launch') {
1986
2069
  on_clicked do
1987
2070
  begin
1988
- meta_example_file = File.join(Dir.home, '.meta_example.rb')
1989
- File.write(meta_example_file, @code_entry.text)
1990
- run_example(meta_example_file)
2071
+ parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
2072
+ FileUtils.mkdir_p(parent_dir)
2073
+ example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
2074
+ File.write(example_file, code_text)
2075
+ example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
2076
+ FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
2077
+ FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
2078
+ FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
2079
+ run_example(example_file)
1991
2080
  rescue => e
1992
2081
  puts e.full_message
1993
2082
  puts 'Unable to write code changes! Running original example...'
@@ -1997,14 +2086,14 @@ class MetaExample
1997
2086
  }
1998
2087
  button('Reset') {
1999
2088
  on_clicked do
2000
- @code_entry.text = File.read(file_path_for(selected_example))
2089
+ self.code_text = File.read(file_path_for(selected_example))
2001
2090
  end
2002
2091
  }
2003
2092
  }
2004
2093
  }
2005
2094
 
2006
2095
  @code_entry = non_wrapping_multiline_entry {
2007
- text File.read(file_path_for(selected_example))
2096
+ text <=> [self, :code_text]
2008
2097
  }
2009
2098
  }
2010
2099
  }.show
@@ -2418,7 +2507,33 @@ UI.quit
2418
2507
 
2419
2508
  ```
2420
2509
 
2421
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2510
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2511
+
2512
+ ```ruby
2513
+ require 'glimmer-dsl-libui'
2514
+
2515
+ class FontButton
2516
+ include Glimmer
2517
+
2518
+ attr_accessor :font_descriptor
2519
+
2520
+ def launch
2521
+ window('hello world', 300, 200) {
2522
+ font_button {
2523
+ font <=> [self, :font_descriptor, after_write: -> { p font_descriptor }]
2524
+ }
2525
+
2526
+ on_closing do
2527
+ puts 'Bye Bye'
2528
+ end
2529
+ }.show
2530
+ end
2531
+ end
2532
+
2533
+ FontButton.new.launch
2534
+ ```
2535
+
2536
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2422
2537
 
2423
2538
  ```ruby
2424
2539
  require 'glimmer-dsl-libui'
@@ -2459,7 +2574,33 @@ Mac | Windows | Linux
2459
2574
  ----|---------|------
2460
2575
  ![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)
2461
2576
 
2462
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2577
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2578
+
2579
+ ```ruby
2580
+ require 'glimmer-dsl-libui'
2581
+
2582
+ class ColorButton
2583
+ include Glimmer
2584
+
2585
+ attr_accessor :selected_color
2586
+
2587
+ def initialize
2588
+ @selected_color = :blue
2589
+ end
2590
+
2591
+ def launch
2592
+ window('color button', 240) {
2593
+ color_button {
2594
+ color <=> [self, :selected_color, after_write: ->(color) {p color}]
2595
+ }
2596
+ }.show
2597
+ end
2598
+ end
2599
+
2600
+ ColorButton.new.launch
2601
+ ```
2602
+
2603
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2463
2604
 
2464
2605
  ```ruby
2465
2606
  require 'glimmer-dsl-libui'
@@ -2541,20 +2682,48 @@ UI.main
2541
2682
  UI.quit
2542
2683
  ```
2543
2684
 
2544
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2685
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2545
2686
 
2546
2687
  ```ruby
2547
2688
  require 'glimmer-dsl-libui'
2548
2689
 
2549
- include Glimmer
2550
-
2551
- window('Date Time Pickers', 300, 200) {
2552
- vertical_box {
2553
- date_time_picker { |dtp|
2554
- on_changed do
2555
- time = dtp.time
2556
- p time
2557
- end
2690
+ class DateTimePicker
2691
+ include Glimmer
2692
+
2693
+ attr_accessor :picked_time
2694
+
2695
+ def launch
2696
+ window('Date Time Pickers', 300, 200) {
2697
+ vertical_box {
2698
+ date_time_picker {
2699
+ time <=> [self, :picked_time, after_write: ->(time) { p time }]
2700
+ }
2701
+ }
2702
+
2703
+ on_closing do
2704
+ puts 'Bye Bye'
2705
+ end
2706
+ }.show
2707
+ end
2708
+ end
2709
+
2710
+ DateTimePicker.new.launch
2711
+ ```
2712
+
2713
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2714
+
2715
+ ```ruby
2716
+ require 'glimmer-dsl-libui'
2717
+
2718
+ include Glimmer
2719
+
2720
+ window('Date Time Pickers', 300, 200) {
2721
+ vertical_box {
2722
+ date_time_picker { |dtp|
2723
+ on_changed do
2724
+ time = dtp.time
2725
+ p time
2726
+ end
2558
2727
  }
2559
2728
  }
2560
2729
 
@@ -4878,7 +5047,8 @@ class ButtonCounter
4878
5047
  def launch
4879
5048
  window('Hello, Button!') {
4880
5049
  button {
4881
- text <= [self, :count, on_read: ->(count) {"Count: #{count}"}] # data-bind button text to self count, converting to string on read.
5050
+ # data-bind button text to self count, converting to string on read.
5051
+ text <= [self, :count, on_read: ->(count) {"Count: #{count}"}]
4882
5052
 
4883
5053
  on_clicked do
4884
5054
  self.count += 1
@@ -5751,7 +5921,96 @@ Mac | Windows | Linux
5751
5921
  ----|---------|------
5752
5922
  ![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)
5753
5923
 
5754
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5924
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
5925
+
5926
+ ```ruby
5927
+ require 'glimmer-dsl-libui'
5928
+
5929
+ class DynamicArea
5930
+ include Glimmer
5931
+
5932
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
5933
+
5934
+ def initialize
5935
+ @rectangle_x = 25
5936
+ @rectangle_y = 25
5937
+ @rectangle_width = 150
5938
+ @rectangle_height = 150
5939
+ @rectangle_red = 102
5940
+ @rectangle_green = 102
5941
+ @rectangle_blue = 204
5942
+ @rectangle_alpha = 100
5943
+ end
5944
+
5945
+ def launch
5946
+ window('Dynamic Area', 240, 600) {
5947
+ margined true
5948
+
5949
+ vertical_box {
5950
+ label('Rectangle Properties') {
5951
+ stretchy false
5952
+ }
5953
+
5954
+ form {
5955
+ stretchy false
5956
+
5957
+ spinbox(0, 1000) {
5958
+ label 'x'
5959
+ value <=> [self, :rectangle_x, after_write: -> {@area.queue_redraw_all}]
5960
+ }
5961
+
5962
+ spinbox(0, 1000) {
5963
+ label 'y'
5964
+ value <=> [self, :rectangle_y, after_write: -> {@area.queue_redraw_all}]
5965
+ }
5966
+
5967
+ spinbox(0, 1000) {
5968
+ label 'width'
5969
+ value <=> [self, :rectangle_width, after_write: -> {@area.queue_redraw_all}]
5970
+ }
5971
+
5972
+ spinbox(0, 1000) {
5973
+ label 'height'
5974
+ value <=> [self, :rectangle_height, after_write: -> {@area.queue_redraw_all}]
5975
+ }
5976
+
5977
+ spinbox(0, 255) {
5978
+ label 'red'
5979
+ value <=> [self, :rectangle_red, after_write: -> {@area.queue_redraw_all}]
5980
+ }
5981
+
5982
+ spinbox(0, 255) {
5983
+ label 'green'
5984
+ value <=> [self, :rectangle_green, after_write: -> {@area.queue_redraw_all}]
5985
+ }
5986
+
5987
+ spinbox(0, 255) {
5988
+ label 'blue'
5989
+ value <=> [self, :rectangle_blue, after_write: -> {@area.queue_redraw_all}]
5990
+ }
5991
+
5992
+ spinbox(0, 100) {
5993
+ label 'alpha'
5994
+ value <=> [self, :rectangle_alpha, after_write: -> {@area.queue_redraw_all}]
5995
+ }
5996
+ }
5997
+
5998
+ @area = area {
5999
+ on_draw do |area_draw_params|
6000
+ rectangle(rectangle_x, rectangle_y, rectangle_width, rectangle_height) { # a dynamic path is added semi-declaratively inside on_draw block
6001
+ fill r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0
6002
+ }
6003
+ end
6004
+ }
6005
+ }
6006
+ }.show
6007
+ end
6008
+ end
6009
+
6010
+ DynamicArea.new.launch
6011
+ ```
6012
+
6013
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5755
6014
 
5756
6015
  ```ruby
5757
6016
  require 'glimmer-dsl-libui'
@@ -5853,7 +6112,102 @@ window('Dynamic Area', 240, 600) {
5853
6112
  }.show
5854
6113
  ```
5855
6114
 
5856
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (declarative stable `path` approach):
6115
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (declarative stable `path` approach with [data-binding](#data-binding)):
6116
+
6117
+ ```ruby
6118
+ require 'glimmer-dsl-libui'
6119
+
6120
+ class DynamicArea
6121
+ include Glimmer
6122
+
6123
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
6124
+
6125
+ def initialize
6126
+ @rectangle_x = 25
6127
+ @rectangle_y = 25
6128
+ @rectangle_width = 150
6129
+ @rectangle_height = 150
6130
+ @rectangle_red = 102
6131
+ @rectangle_green = 102
6132
+ @rectangle_blue = 204
6133
+ @rectangle_alpha = 100
6134
+ end
6135
+
6136
+ def rectangle_fill
6137
+ { r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0 }
6138
+ end
6139
+
6140
+ def launch
6141
+ window('Dynamic Area', 240, 600) {
6142
+ margined true
6143
+
6144
+ vertical_box {
6145
+ label('Rectangle Properties') {
6146
+ stretchy false
6147
+ }
6148
+
6149
+ form {
6150
+ stretchy false
6151
+
6152
+ @x_spinbox = spinbox(0, 1000) {
6153
+ label 'x'
6154
+ value <=> [self, :rectangle_x]
6155
+ }
6156
+
6157
+ @y_spinbox = spinbox(0, 1000) {
6158
+ label 'y'
6159
+ value <=> [self, :rectangle_y]
6160
+ }
6161
+
6162
+ @width_spinbox = spinbox(0, 1000) {
6163
+ label 'width'
6164
+ value <=> [self, :rectangle_width]
6165
+ }
6166
+
6167
+ @height_spinbox = spinbox(0, 1000) {
6168
+ label 'height'
6169
+ value <=> [self, :rectangle_height]
6170
+ }
6171
+
6172
+ @red_spinbox = spinbox(0, 255) {
6173
+ label 'red'
6174
+ value <=> [self, :rectangle_red]
6175
+ }
6176
+
6177
+ @green_spinbox = spinbox(0, 255) {
6178
+ label 'green'
6179
+ value <=> [self, :rectangle_green]
6180
+ }
6181
+
6182
+ @blue_spinbox = spinbox(0, 255) {
6183
+ label 'blue'
6184
+ value <=> [self, :rectangle_blue]
6185
+ }
6186
+
6187
+ @alpha_spinbox = spinbox(0, 100) {
6188
+ label 'alpha'
6189
+ value <=> [self, :rectangle_alpha]
6190
+ }
6191
+ }
6192
+
6193
+ area {
6194
+ @rectangle = rectangle { # stable implicit path shape
6195
+ x <= [self, :rectangle_x]
6196
+ y <= [self, :rectangle_y]
6197
+ width <= [self, :rectangle_width]
6198
+ height <= [self, :rectangle_height]
6199
+ fill <= [self, :rectangle_fill, computed_by: [:rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha]]
6200
+ }
6201
+ }
6202
+ }
6203
+ }.show
6204
+ end
6205
+ end
6206
+
6207
+ DynamicArea.new.launch
6208
+ ```
6209
+
6210
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (declarative stable `path` approach without [data-binding](#data-binding)):
5857
6211
 
5858
6212
  ```ruby
5859
6213
  require 'glimmer-dsl-libui'
@@ -5945,7 +6299,7 @@ window('Dynamic Area', 240, 600) {
5945
6299
  }
5946
6300
 
5947
6301
  area {
5948
- @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable path
6302
+ @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable implicit path shape
5949
6303
  fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
5950
6304
  }
5951
6305
  }
@@ -6643,7 +6997,121 @@ UI.main
6643
6997
  UI.quit
6644
6998
  ```
6645
6999
 
6646
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7000
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7001
+
7002
+ ```ruby
7003
+ # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
7004
+
7005
+ require 'glimmer-dsl-libui'
7006
+
7007
+ class Histogram
7008
+ include Glimmer
7009
+
7010
+ X_OFF_LEFT = 20
7011
+ Y_OFF_TOP = 20
7012
+ X_OFF_RIGHT = 20
7013
+ Y_OFF_BOTTOM = 20
7014
+ POINT_RADIUS = 5
7015
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
7016
+
7017
+ attr_accessor :datapoints, :histogram_color
7018
+
7019
+ def initialize
7020
+ @datapoints = 10.times.map {Random.new.rand(90)}
7021
+ @histogram_color = COLOR_BLUE
7022
+ end
7023
+
7024
+ def graph_size(area_width, area_height)
7025
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
7026
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
7027
+ [graph_width, graph_height]
7028
+ end
7029
+
7030
+ def point_locations(width, height)
7031
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
7032
+ yincr = height / 100.0
7033
+
7034
+ @datapoints.each_with_index.map do |value, i|
7035
+ val = 100 - value
7036
+ [xincr * i, yincr * val]
7037
+ end
7038
+ end
7039
+
7040
+ # method-based custom control representing a graph path
7041
+ def graph_path(width, height, should_extend, &block)
7042
+ locations = point_locations(width, height).flatten
7043
+ path {
7044
+ if should_extend
7045
+ polygon(locations + [width, height, 0, height])
7046
+ else
7047
+ polyline(locations)
7048
+ end
7049
+
7050
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
7051
+ transform {
7052
+ translate X_OFF_LEFT, Y_OFF_TOP
7053
+ }
7054
+
7055
+ block.call
7056
+ }
7057
+ end
7058
+
7059
+ def launch
7060
+ window('histogram example', 640, 480) {
7061
+ margined true
7062
+
7063
+ horizontal_box {
7064
+ vertical_box {
7065
+ stretchy false
7066
+
7067
+ 10.times do |i|
7068
+ spinbox(0, 100) { |sb|
7069
+ stretchy false
7070
+ value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
7071
+ }
7072
+ end
7073
+
7074
+ color_button { |cb|
7075
+ stretchy false
7076
+ color <=> [self, :histogram_color, after_write: -> { @area.queue_redraw_all }]
7077
+ }
7078
+ }
7079
+
7080
+ @area = area {
7081
+ on_draw do |area_draw_params|
7082
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
7083
+ fill 0xFFFFFF
7084
+ }
7085
+
7086
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
7087
+
7088
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7089
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7090
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
7091
+
7092
+ stroke 0x000000, thickness: 2, miter_limit: 10
7093
+ }
7094
+
7095
+ # now create the fill for the graph below the graph line
7096
+ graph_path(graph_width, graph_height, true) {
7097
+ fill @histogram_color.merge(a: 0.5)
7098
+ }
7099
+
7100
+ # now draw the histogram line
7101
+ graph_path(graph_width, graph_height, false) {
7102
+ stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
7103
+ }
7104
+ end
7105
+ }
7106
+ }
7107
+ }.show
7108
+ end
7109
+ end
7110
+
7111
+ Histogram.new.launch
7112
+ ```
7113
+
7114
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6647
7115
 
6648
7116
  ```ruby
6649
7117
  # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
@@ -6657,9 +7125,10 @@ Y_OFF_TOP = 20
6657
7125
  X_OFF_RIGHT = 20
6658
7126
  Y_OFF_BOTTOM = 20
6659
7127
  POINT_RADIUS = 5
6660
- COLOR_BLUE = 0x1E90FF
7128
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
6661
7129
 
6662
7130
  @datapoints = 10.times.map {Random.new.rand(90)}
7131
+ @color = COLOR_BLUE
6663
7132
 
6664
7133
  def graph_size(area_width, area_height)
6665
7134
  graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
@@ -6715,11 +7184,12 @@ window('histogram example', 640, 480) {
6715
7184
  }
6716
7185
  end
6717
7186
 
6718
- @color_button = color_button {
7187
+ color_button { |cb|
6719
7188
  stretchy false
6720
7189
  color COLOR_BLUE
6721
7190
 
6722
7191
  on_changed do
7192
+ @color = cb.color
6723
7193
  @area.queue_redraw_all
6724
7194
  end
6725
7195
  }
@@ -6727,31 +7197,27 @@ window('histogram example', 640, 480) {
6727
7197
 
6728
7198
  @area = area {
6729
7199
  on_draw do |area_draw_params|
6730
- path {
6731
- rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
6732
-
7200
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6733
7201
  fill 0xFFFFFF
6734
7202
  }
6735
7203
 
6736
7204
  graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6737
7205
 
6738
- path {
6739
- figure(X_OFF_LEFT, Y_OFF_TOP) {
6740
- line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
6741
- line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6742
- }
7206
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7207
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7208
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6743
7209
 
6744
7210
  stroke 0x000000, thickness: 2, miter_limit: 10
6745
7211
  }
6746
7212
 
6747
7213
  # now create the fill for the graph below the graph line
6748
7214
  graph_path(graph_width, graph_height, true) {
6749
- fill @color_button.color.merge(a: 0.5)
7215
+ fill @color.merge(a: 0.5)
6750
7216
  }
6751
7217
 
6752
7218
  # now draw the histogram line
6753
7219
  graph_path(graph_width, graph_height, false) {
6754
- stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
7220
+ stroke @color.merge(thickness: 2, miter_limit: 10)
6755
7221
  }
6756
7222
  end
6757
7223
  }
@@ -7429,7 +7895,181 @@ end
7429
7895
  TinyMidiPlayer.new
7430
7896
  ```
7431
7897
 
7432
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7898
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7899
+
7900
+ ```ruby
7901
+ # frozen_string_literal: true
7902
+
7903
+ require 'glimmer-dsl-libui'
7904
+
7905
+ class TinyMidiPlayer
7906
+ include Glimmer
7907
+
7908
+ VERSION = '0.0.1'
7909
+
7910
+ attr_accessor :selected_file
7911
+
7912
+ def initialize
7913
+ @pid = nil
7914
+ @music_directory = File.expand_path('../sounds', __dir__)
7915
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
7916
+ .sort_by { |path| File.basename(path) }
7917
+ at_exit { stop_midi }
7918
+ create_gui
7919
+ end
7920
+
7921
+ def stop_midi
7922
+ if @pid
7923
+ Process.kill(:SIGKILL, @pid) if @th.alive?
7924
+ @pid = nil
7925
+ end
7926
+ end
7927
+
7928
+ def play_midi
7929
+ stop_midi
7930
+ if @pid.nil? && @selected_file
7931
+ begin
7932
+ @pid = spawn "timidity #{@selected_file}"
7933
+ @th = Process.detach @pid
7934
+ rescue Errno::ENOENT
7935
+ warn 'Timidty++ not found. Please install Timidity++.'
7936
+ warn 'https://sourceforge.net/projects/timidity/'
7937
+ end
7938
+ end
7939
+ end
7940
+
7941
+ def show_version
7942
+ msg_box('Tiny Midi Player',
7943
+ "Written in Ruby\n" \
7944
+ "https://github.com/kojix2/libui\n" \
7945
+ "Version #{VERSION}")
7946
+ end
7947
+
7948
+ def create_gui
7949
+ menu('Help') {
7950
+ menu_item('Version') {
7951
+ on_clicked do
7952
+ show_version
7953
+ end
7954
+ }
7955
+ }
7956
+ window('Tiny Midi Player', 200, 50) {
7957
+ horizontal_box {
7958
+ vertical_box {
7959
+ stretchy false
7960
+
7961
+ button('▶') {
7962
+ on_clicked do
7963
+ play_midi
7964
+ end
7965
+ }
7966
+ button('■') {
7967
+ on_clicked do
7968
+ stop_midi
7969
+ end
7970
+ }
7971
+ }
7972
+
7973
+ combobox {
7974
+ items @midi_files.map { |path| File.basename(path) }
7975
+ # data-bind selected item (String) to self.selected_file with on-read/on-write converters and after_write operation
7976
+ 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? }]
7977
+ }
7978
+ }
7979
+ }.show
7980
+ end
7981
+ end
7982
+
7983
+ TinyMidiPlayer.new
7984
+ ```
7985
+
7986
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
7987
+
7988
+ ```ruby
7989
+ require 'glimmer-dsl-libui'
7990
+
7991
+ class TinyMidiPlayer
7992
+ include Glimmer
7993
+
7994
+ VERSION = '0.0.1'
7995
+
7996
+ attr_accessor :selected_file
7997
+
7998
+ def initialize
7999
+ @pid = nil
8000
+ @music_directory = File.expand_path('../sounds', __dir__)
8001
+ @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
8002
+ .sort_by { |path| File.basename(path) }
8003
+ at_exit { stop_midi }
8004
+ create_gui
8005
+ end
8006
+
8007
+ def stop_midi
8008
+ if @pid
8009
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8010
+ @pid = nil
8011
+ end
8012
+ end
8013
+
8014
+ def play_midi
8015
+ stop_midi
8016
+ if @pid.nil? && @selected_file
8017
+ begin
8018
+ @pid = spawn "timidity #{@selected_file}"
8019
+ @th = Process.detach @pid
8020
+ rescue Errno::ENOENT
8021
+ warn 'Timidty++ not found. Please install Timidity++.'
8022
+ warn 'https://sourceforge.net/projects/timidity/'
8023
+ end
8024
+ end
8025
+ end
8026
+
8027
+ def show_version
8028
+ msg_box('Tiny Midi Player',
8029
+ "Written in Ruby\n" \
8030
+ "https://github.com/kojix2/libui\n" \
8031
+ "Version #{VERSION}")
8032
+ end
8033
+
8034
+ def create_gui
8035
+ menu('Help') {
8036
+ menu_item('Version') {
8037
+ on_clicked do
8038
+ show_version
8039
+ end
8040
+ }
8041
+ }
8042
+ window('Tiny Midi Player', 200, 50) {
8043
+ horizontal_box {
8044
+ vertical_box {
8045
+ stretchy false
8046
+
8047
+ button('▶') {
8048
+ on_clicked do
8049
+ play_midi
8050
+ end
8051
+ }
8052
+ button('■') {
8053
+ on_clicked do
8054
+ stop_midi
8055
+ end
8056
+ }
8057
+ }
8058
+
8059
+ combobox {
8060
+ items @midi_files.map { |path| File.basename(path) }
8061
+ # data-bind selected index (Integer) to self.selected_file with on-read/on-write converters and after_write operation
8062
+ selected <=> [self, :selected_file, on_read: ->(f) {@midi_files.index(f)}, on_write: ->(i) {@midi_files[i]}, after_write: -> { play_midi if @th&.alive? }]
8063
+ }
8064
+ }
8065
+ }.show
8066
+ end
8067
+ end
8068
+
8069
+ TinyMidiPlayer.new
8070
+ ```
8071
+
8072
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (without [data-binding](#data-binding)):
7433
8073
 
7434
8074
  ```ruby
7435
8075
  require 'glimmer-dsl-libui'
@@ -7757,22 +8397,23 @@ class Tetris
7757
8397
  menu('Game') {
7758
8398
  @pause_menu_item = check_menu_item('Pause') {
7759
8399
  enabled false
7760
-
7761
- on_clicked do
7762
- @game.paused = @pause_menu_item.checked?
7763
- end
8400
+ checked <=> [@game, :paused]
7764
8401
  }
8402
+
7765
8403
  menu_item('Restart') {
7766
8404
  on_clicked do
7767
8405
  @game.restart!
7768
8406
  end
7769
8407
  }
8408
+
7770
8409
  separator_menu_item
8410
+
7771
8411
  menu_item('Exit') {
7772
8412
  on_clicked do
7773
8413
  exit(0)
7774
8414
  end
7775
8415
  }
8416
+
7776
8417
  quit_menu_item if OS.mac?
7777
8418
  }
7778
8419
 
@@ -7782,6 +8423,7 @@ class Tetris
7782
8423
  show_high_scores
7783
8424
  end
7784
8425
  }
8426
+
7785
8427
  menu_item('Clear High Scores') {
7786
8428
  on_clicked {
7787
8429
  @game.clear_high_scores!
@@ -7790,22 +8432,16 @@ class Tetris
7790
8432
  }
7791
8433
 
7792
8434
  menu('Options') {
7793
- radio_menu_item('Instant Down on Up Arrow') {
7794
- on_clicked do
7795
- @game.instant_down_on_up = true
7796
- end
8435
+ radio_menu_item('Instant Down on Up Arrow') { |r|
8436
+ checked <=> [@game, :instant_down_on_up]
7797
8437
  }
7798
- radio_menu_item('Rotate Right on Up Arrow') {
7799
- on_clicked do
7800
- @game.rotate_right_on_up = true
7801
- end
8438
+
8439
+ radio_menu_item('Rotate Right on Up Arrow') { |r|
8440
+ checked <=> [@game, :rotate_right_on_up]
7802
8441
  }
7803
- radio_menu_item('Rotate Left on Up Arrow') {
7804
- checked true
7805
-
7806
- on_clicked do
7807
- @game.rotate_left_on_up = true
7808
- end
8442
+
8443
+ radio_menu_item('Rotate Left on Up Arrow') { |r|
8444
+ checked <=> [@game, :rotate_left_on_up]
7809
8445
  }
7810
8446
  }
7811
8447
 
@@ -7817,6 +8453,7 @@ class Tetris
7817
8453
  end
7818
8454
  }
7819
8455
  end
8456
+
7820
8457
  menu_item('About') {
7821
8458
  on_clicked do
7822
8459
  show_about_dialog
@@ -8140,7 +8777,140 @@ Mac | Windows | Linux
8140
8777
  ----|---------|------
8141
8778
  ![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)
8142
8779
 
8143
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8780
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8781
+
8782
+ ```ruby
8783
+ require 'glimmer-dsl-libui'
8784
+
8785
+ class Timer
8786
+ include Glimmer
8787
+
8788
+ SECOND_MAX = 59
8789
+ MINUTE_MAX = 59
8790
+ HOUR_MAX = 23
8791
+
8792
+ attr_accessor :hour, :min, :sec, :started, :played
8793
+
8794
+ def initialize
8795
+ @pid = nil
8796
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
8797
+ @hour = @min = @sec = 0
8798
+ at_exit { stop_alarm }
8799
+ setup_timer
8800
+ create_gui
8801
+ end
8802
+
8803
+ def stop_alarm
8804
+ if @pid
8805
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8806
+ @pid = nil
8807
+ end
8808
+ end
8809
+
8810
+ def play_alarm
8811
+ stop_alarm
8812
+ if @pid.nil?
8813
+ begin
8814
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
8815
+ @th = Process.detach @pid
8816
+ rescue Errno::ENOENT
8817
+ warn 'Timidty++ not found. Please install Timidity++.'
8818
+ warn 'https://sourceforge.net/projects/timidity/'
8819
+ end
8820
+ end
8821
+ end
8822
+
8823
+ def setup_timer
8824
+ unless @setup_timer
8825
+ Glimmer::LibUI.timer(1) do
8826
+ if @started
8827
+ seconds = @sec
8828
+ minutes = @min
8829
+ hours = @hour
8830
+ if seconds > 0
8831
+ self.sec = seconds -= 1
8832
+ end
8833
+ if seconds == 0
8834
+ if minutes > 0
8835
+ self.min = minutes -= 1
8836
+ self.sec = seconds = SECOND_MAX
8837
+ end
8838
+ if minutes == 0
8839
+ if hours > 0
8840
+ self.hour = hours -= 1
8841
+ self.min = minutes = MINUTE_MAX
8842
+ self.sec = seconds = SECOND_MAX
8843
+ end
8844
+ if hours == 0 && minutes == 0 && seconds == 0
8845
+ self.started = false
8846
+ unless @played
8847
+ play_alarm
8848
+ msg_box('Alarm', 'Countdown Is Finished!')
8849
+ self.played = true
8850
+ end
8851
+ end
8852
+ end
8853
+ end
8854
+ end
8855
+ end
8856
+ @setup_timer = true
8857
+ end
8858
+ end
8859
+
8860
+ def create_gui
8861
+ window('Timer') {
8862
+ margined true
8863
+
8864
+ group('Countdown') {
8865
+ vertical_box {
8866
+ horizontal_box {
8867
+ spinbox(0, HOUR_MAX) {
8868
+ stretchy false
8869
+ value <=> [self, :hour]
8870
+ }
8871
+ label(':') {
8872
+ stretchy false
8873
+ }
8874
+ spinbox(0, MINUTE_MAX) {
8875
+ stretchy false
8876
+ value <=> [self, :min]
8877
+ }
8878
+ label(':') {
8879
+ stretchy false
8880
+ }
8881
+ spinbox(0, SECOND_MAX) {
8882
+ stretchy false
8883
+ value <=> [self, :sec]
8884
+ }
8885
+ }
8886
+ horizontal_box {
8887
+ button('Start') {
8888
+ enabled <= [self, :started, on_read: :!]
8889
+
8890
+ on_clicked do
8891
+ self.started = true
8892
+ self.played = false
8893
+ end
8894
+ }
8895
+
8896
+ button('Stop') {
8897
+ enabled <= [self, :started]
8898
+
8899
+ on_clicked do
8900
+ self.started = false
8901
+ end
8902
+ }
8903
+ }
8904
+ }
8905
+ }
8906
+ }.show
8907
+ end
8908
+ end
8909
+
8910
+ Timer.new
8911
+ ```
8912
+
8913
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8144
8914
 
8145
8915
  ```ruby
8146
8916
  require 'glimmer-dsl-libui'
@@ -8313,7 +9083,7 @@ https://github.com/iraamaro/i3off-gtk-ruby
8313
9083
 
8314
9084
  ### Issues
8315
9085
 
8316
- 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).
9086
+ 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.
8317
9087
 
8318
9088
  ### Chat
8319
9089
 
@@ -8345,6 +9115,8 @@ These features have been planned or suggested. You might see them in a future ve
8345
9115
  is fine, but please isolate to its own commit so I can cherry-pick
8346
9116
  around it.
8347
9117
 
9118
+ 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)).
9119
+
8348
9120
  ## Contributors
8349
9121
 
8350
9122
  * [Andy Maleh](https://github.com/AndyObtiva) (Founder)