glimmer-dsl-libui 0.4.4 → 0.4.5

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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.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.5
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -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.5'
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.
@@ -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)
@@ -1367,6 +1386,7 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1367
1386
  - `multiline_entry` `text` property
1368
1387
  - `non_wrapping_multiline_entry` `text` property
1369
1388
  - `search_entry` `text` property
1389
+ - `spinbox` `value` property
1370
1390
 
1371
1391
  Example of bidirectional data-binding:
1372
1392
 
@@ -1435,7 +1455,9 @@ entry {
1435
1455
  }
1436
1456
  ```
1437
1457
 
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)
1458
+ Data-binding gotchas:
1459
+ - Never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
1460
+ - Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so you cannot hook into the listener directly anymore as that would negate data-binding. Instead, you can add an `after_write: ->(val) {}` option to perform something on trigger of the control listener instead.
1439
1461
 
1440
1462
  Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1441
1463
 
@@ -1880,17 +1902,23 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
1880
1902
  ```ruby
1881
1903
  require 'glimmer-dsl-libui'
1882
1904
  require 'facets'
1905
+ require 'fileutils'
1883
1906
 
1884
1907
  class MetaExample
1885
1908
  include Glimmer
1886
1909
 
1910
+ ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
1911
+
1912
+ attr_accessor :code_text
1913
+
1887
1914
  def initialize
1888
- @selected_example_index = 0
1915
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
1916
+ @code_text = File.read(file_path_for(selected_example))
1889
1917
  end
1890
1918
 
1891
1919
  def examples
1892
1920
  if @examples.nil?
1893
- example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
1921
+ example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
1894
1922
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
1895
1923
  example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
1896
1924
  @examples = example_file_names.map { |f| f.underscore.titlecase }
@@ -1904,12 +1932,20 @@ class MetaExample
1904
1932
  end
1905
1933
  end
1906
1934
 
1935
+ def basic_examples_with_versions
1936
+ examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
1937
+ end
1938
+
1939
+ def advanced_examples_with_versions
1940
+ examples_with_versions - basic_examples_with_versions
1941
+ end
1942
+
1907
1943
  def file_path_for(example)
1908
1944
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
1909
1945
  end
1910
1946
 
1911
1947
  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
1948
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
1913
1949
  end
1914
1950
 
1915
1951
  def glimmer_dsl_libui_file
@@ -1945,17 +1981,47 @@ class MetaExample
1945
1981
  vertical_box {
1946
1982
  stretchy false
1947
1983
 
1948
- @example_radio_buttons = radio_buttons {
1984
+ tab {
1949
1985
  stretchy false
1950
- items examples_with_versions
1951
- selected @selected_example_index
1952
1986
 
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
1987
+ tab_item('Basic') {
1988
+ vertical_box {
1989
+ @basic_example_radio_buttons = radio_buttons {
1990
+ stretchy false
1991
+ items basic_examples_with_versions
1992
+ selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
1993
+
1994
+ on_selected do
1995
+ @selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
1996
+ example = selected_example
1997
+ self.code_text = File.read(file_path_for(example))
1998
+ @version_spinbox.value = 1
1999
+ end
2000
+ }
2001
+
2002
+ label # filler
2003
+ label # filler
2004
+ }
2005
+ }
2006
+
2007
+ tab_item('Advanced') {
2008
+ vertical_box {
2009
+ @advanced_example_radio_buttons = radio_buttons {
2010
+ stretchy false
2011
+ items advanced_examples_with_versions
2012
+
2013
+ on_selected do
2014
+ @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
2015
+ example = selected_example
2016
+ self.code_text = File.read(file_path_for(example))
2017
+ @version_spinbox.value = 1
2018
+ end
2019
+ }
2020
+
2021
+ label # filler
2022
+ label # filler
2023
+ }
2024
+ }
1959
2025
  }
1960
2026
 
1961
2027
  horizontal_box {
@@ -1973,7 +2039,7 @@ class MetaExample
1973
2039
  else
1974
2040
  version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1975
2041
  example = "#{selected_example}#{version_number}"
1976
- @code_entry.text = File.read(file_path_for(example))
2042
+ self.code_text = File.read(file_path_for(example))
1977
2043
  end
1978
2044
  end
1979
2045
  }
@@ -1985,9 +2051,15 @@ class MetaExample
1985
2051
  button('Launch') {
1986
2052
  on_clicked do
1987
2053
  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)
2054
+ parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
2055
+ FileUtils.mkdir_p(parent_dir)
2056
+ example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
2057
+ File.write(example_file, code_text)
2058
+ example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
2059
+ FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
2060
+ FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
2061
+ FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
2062
+ run_example(example_file)
1991
2063
  rescue => e
1992
2064
  puts e.full_message
1993
2065
  puts 'Unable to write code changes! Running original example...'
@@ -1997,14 +2069,14 @@ class MetaExample
1997
2069
  }
1998
2070
  button('Reset') {
1999
2071
  on_clicked do
2000
- @code_entry.text = File.read(file_path_for(selected_example))
2072
+ self.code_text = File.read(file_path_for(selected_example))
2001
2073
  end
2002
2074
  }
2003
2075
  }
2004
2076
  }
2005
2077
 
2006
2078
  @code_entry = non_wrapping_multiline_entry {
2007
- text File.read(file_path_for(selected_example))
2079
+ text <=> [self, :code_text]
2008
2080
  }
2009
2081
  }
2010
2082
  }.show
@@ -5751,7 +5823,96 @@ Mac | Windows | Linux
5751
5823
  ----|---------|------
5752
5824
  ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-dynamic-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-dynamic-area-updated.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-dynamic-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-dynamic-area-updated.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-dynamic-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-dynamic-area-updated.png)
5753
5825
 
5754
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5826
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
5827
+
5828
+ ```ruby
5829
+ require 'glimmer-dsl-libui'
5830
+
5831
+ class DynamicArea
5832
+ include Glimmer
5833
+
5834
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
5835
+
5836
+ def initialize
5837
+ @rectangle_x = 25
5838
+ @rectangle_y = 25
5839
+ @rectangle_width = 150
5840
+ @rectangle_height = 150
5841
+ @rectangle_red = 102
5842
+ @rectangle_green = 102
5843
+ @rectangle_blue = 204
5844
+ @rectangle_alpha = 100
5845
+ end
5846
+
5847
+ def launch
5848
+ window('Dynamic Area', 240, 600) {
5849
+ margined true
5850
+
5851
+ vertical_box {
5852
+ label('Rectangle Properties') {
5853
+ stretchy false
5854
+ }
5855
+
5856
+ form {
5857
+ stretchy false
5858
+
5859
+ spinbox(0, 1000) {
5860
+ label 'x'
5861
+ value <=> [self, :rectangle_x, after_write: -> {@area.queue_redraw_all}]
5862
+ }
5863
+
5864
+ spinbox(0, 1000) {
5865
+ label 'y'
5866
+ value <=> [self, :rectangle_y, after_write: -> {@area.queue_redraw_all}]
5867
+ }
5868
+
5869
+ spinbox(0, 1000) {
5870
+ label 'width'
5871
+ value <=> [self, :rectangle_width, after_write: -> {@area.queue_redraw_all}]
5872
+ }
5873
+
5874
+ spinbox(0, 1000) {
5875
+ label 'height'
5876
+ value <=> [self, :rectangle_height, after_write: -> {@area.queue_redraw_all}]
5877
+ }
5878
+
5879
+ spinbox(0, 255) {
5880
+ label 'red'
5881
+ value <=> [self, :rectangle_red, after_write: -> {@area.queue_redraw_all}]
5882
+ }
5883
+
5884
+ spinbox(0, 255) {
5885
+ label 'green'
5886
+ value <=> [self, :rectangle_green, after_write: -> {@area.queue_redraw_all}]
5887
+ }
5888
+
5889
+ spinbox(0, 255) {
5890
+ label 'blue'
5891
+ value <=> [self, :rectangle_blue, after_write: -> {@area.queue_redraw_all}]
5892
+ }
5893
+
5894
+ spinbox(0, 100) {
5895
+ label 'alpha'
5896
+ value <=> [self, :rectangle_alpha, after_write: -> {@area.queue_redraw_all}]
5897
+ }
5898
+ }
5899
+
5900
+ @area = area {
5901
+ on_draw do |area_draw_params|
5902
+ rectangle(rectangle_x, rectangle_y, rectangle_width, rectangle_height) { # a dynamic path is added semi-declaratively inside on_draw block
5903
+ fill r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0
5904
+ }
5905
+ end
5906
+ }
5907
+ }
5908
+ }.show
5909
+ end
5910
+ end
5911
+
5912
+ DynamicArea.new.launch
5913
+ ```
5914
+
5915
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5755
5916
 
5756
5917
  ```ruby
5757
5918
  require 'glimmer-dsl-libui'
@@ -5853,7 +6014,102 @@ window('Dynamic Area', 240, 600) {
5853
6014
  }.show
5854
6015
  ```
5855
6016
 
5856
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (declarative stable `path` approach):
6017
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (declarative stable `path` approach with [data-binding](#data-binding)):
6018
+
6019
+ ```ruby
6020
+ require 'glimmer-dsl-libui'
6021
+
6022
+ class DynamicArea
6023
+ include Glimmer
6024
+
6025
+ attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
6026
+
6027
+ def initialize
6028
+ @rectangle_x = 25
6029
+ @rectangle_y = 25
6030
+ @rectangle_width = 150
6031
+ @rectangle_height = 150
6032
+ @rectangle_red = 102
6033
+ @rectangle_green = 102
6034
+ @rectangle_blue = 204
6035
+ @rectangle_alpha = 100
6036
+ end
6037
+
6038
+ def rectangle_fill
6039
+ { r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0 }
6040
+ end
6041
+
6042
+ def launch
6043
+ window('Dynamic Area', 240, 600) {
6044
+ margined true
6045
+
6046
+ vertical_box {
6047
+ label('Rectangle Properties') {
6048
+ stretchy false
6049
+ }
6050
+
6051
+ form {
6052
+ stretchy false
6053
+
6054
+ @x_spinbox = spinbox(0, 1000) {
6055
+ label 'x'
6056
+ value <=> [self, :rectangle_x]
6057
+ }
6058
+
6059
+ @y_spinbox = spinbox(0, 1000) {
6060
+ label 'y'
6061
+ value <=> [self, :rectangle_y]
6062
+ }
6063
+
6064
+ @width_spinbox = spinbox(0, 1000) {
6065
+ label 'width'
6066
+ value <=> [self, :rectangle_width]
6067
+ }
6068
+
6069
+ @height_spinbox = spinbox(0, 1000) {
6070
+ label 'height'
6071
+ value <=> [self, :rectangle_height]
6072
+ }
6073
+
6074
+ @red_spinbox = spinbox(0, 255) {
6075
+ label 'red'
6076
+ value <=> [self, :rectangle_red]
6077
+ }
6078
+
6079
+ @green_spinbox = spinbox(0, 255) {
6080
+ label 'green'
6081
+ value <=> [self, :rectangle_green]
6082
+ }
6083
+
6084
+ @blue_spinbox = spinbox(0, 255) {
6085
+ label 'blue'
6086
+ value <=> [self, :rectangle_blue]
6087
+ }
6088
+
6089
+ @alpha_spinbox = spinbox(0, 100) {
6090
+ label 'alpha'
6091
+ value <=> [self, :rectangle_alpha]
6092
+ }
6093
+ }
6094
+
6095
+ area {
6096
+ @rectangle = rectangle { # stable implicit path shape
6097
+ x <= [self, :rectangle_x]
6098
+ y <= [self, :rectangle_y]
6099
+ width <= [self, :rectangle_width]
6100
+ height <= [self, :rectangle_height]
6101
+ fill <= [self, :rectangle_fill, computed_by: [:rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha]]
6102
+ }
6103
+ }
6104
+ }
6105
+ }.show
6106
+ end
6107
+ end
6108
+
6109
+ DynamicArea.new.launch
6110
+ ```
6111
+
6112
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (declarative stable `path` approach without [data-binding](#data-binding)):
5857
6113
 
5858
6114
  ```ruby
5859
6115
  require 'glimmer-dsl-libui'
@@ -5945,7 +6201,7 @@ window('Dynamic Area', 240, 600) {
5945
6201
  }
5946
6202
 
5947
6203
  area {
5948
- @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable path
6204
+ @rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable implicit path shape
5949
6205
  fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
5950
6206
  }
5951
6207
  }
@@ -6643,7 +6899,126 @@ UI.main
6643
6899
  UI.quit
6644
6900
  ```
6645
6901
 
6646
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6902
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6903
+
6904
+ ```ruby
6905
+ # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
6906
+
6907
+ require 'glimmer-dsl-libui'
6908
+
6909
+ class Histogram
6910
+ include Glimmer
6911
+
6912
+ X_OFF_LEFT = 20
6913
+ Y_OFF_TOP = 20
6914
+ X_OFF_RIGHT = 20
6915
+ Y_OFF_BOTTOM = 20
6916
+ POINT_RADIUS = 5
6917
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
6918
+
6919
+ attr_accessor :datapoints, :histogram_color
6920
+
6921
+ def initialize
6922
+ @datapoints = 10.times.map {Random.new.rand(90)}
6923
+ @histogram_color = COLOR_BLUE
6924
+ end
6925
+
6926
+ def graph_size(area_width, area_height)
6927
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
6928
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
6929
+ [graph_width, graph_height]
6930
+ end
6931
+
6932
+ def point_locations(width, height)
6933
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
6934
+ yincr = height / 100.0
6935
+
6936
+ @datapoints.each_with_index.map do |value, i|
6937
+ val = 100 - value
6938
+ [xincr * i, yincr * val]
6939
+ end
6940
+ end
6941
+
6942
+ # method-based custom control representing a graph path
6943
+ def graph_path(width, height, should_extend, &block)
6944
+ locations = point_locations(width, height).flatten
6945
+ path {
6946
+ if should_extend
6947
+ polygon(locations + [width, height, 0, height])
6948
+ else
6949
+ polyline(locations)
6950
+ end
6951
+
6952
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
6953
+ transform {
6954
+ translate X_OFF_LEFT, Y_OFF_TOP
6955
+ }
6956
+
6957
+ block.call
6958
+ }
6959
+ end
6960
+
6961
+ def launch
6962
+ window('histogram example', 640, 480) {
6963
+ margined true
6964
+
6965
+ horizontal_box {
6966
+ vertical_box {
6967
+ stretchy false
6968
+
6969
+ 10.times do |i|
6970
+ spinbox(0, 100) { |sb|
6971
+ stretchy false
6972
+ value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
6973
+ }
6974
+ end
6975
+
6976
+ color_button { |cb|
6977
+ stretchy false
6978
+ color COLOR_BLUE
6979
+
6980
+ on_changed do
6981
+ @histogram_color = cb.color
6982
+ @area.queue_redraw_all
6983
+ end
6984
+ }
6985
+ }
6986
+
6987
+ @area = area {
6988
+ on_draw do |area_draw_params|
6989
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6990
+ fill 0xFFFFFF
6991
+ }
6992
+
6993
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6994
+
6995
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
6996
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
6997
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6998
+
6999
+ stroke 0x000000, thickness: 2, miter_limit: 10
7000
+ }
7001
+
7002
+ # now create the fill for the graph below the graph line
7003
+ graph_path(graph_width, graph_height, true) {
7004
+ fill @histogram_color.merge(a: 0.5)
7005
+ }
7006
+
7007
+ # now draw the histogram line
7008
+ graph_path(graph_width, graph_height, false) {
7009
+ stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
7010
+ }
7011
+ end
7012
+ }
7013
+ }
7014
+ }.show
7015
+ end
7016
+ end
7017
+
7018
+ Histogram.new.launch
7019
+ ```
7020
+
7021
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6647
7022
 
6648
7023
  ```ruby
6649
7024
  # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
@@ -6657,9 +7032,10 @@ Y_OFF_TOP = 20
6657
7032
  X_OFF_RIGHT = 20
6658
7033
  Y_OFF_BOTTOM = 20
6659
7034
  POINT_RADIUS = 5
6660
- COLOR_BLUE = 0x1E90FF
7035
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
6661
7036
 
6662
7037
  @datapoints = 10.times.map {Random.new.rand(90)}
7038
+ @color = COLOR_BLUE
6663
7039
 
6664
7040
  def graph_size(area_width, area_height)
6665
7041
  graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
@@ -6715,11 +7091,12 @@ window('histogram example', 640, 480) {
6715
7091
  }
6716
7092
  end
6717
7093
 
6718
- @color_button = color_button {
7094
+ color_button { |cb|
6719
7095
  stretchy false
6720
7096
  color COLOR_BLUE
6721
7097
 
6722
7098
  on_changed do
7099
+ @color = cb.color
6723
7100
  @area.queue_redraw_all
6724
7101
  end
6725
7102
  }
@@ -6727,31 +7104,27 @@ window('histogram example', 640, 480) {
6727
7104
 
6728
7105
  @area = area {
6729
7106
  on_draw do |area_draw_params|
6730
- path {
6731
- rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
6732
-
7107
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
6733
7108
  fill 0xFFFFFF
6734
7109
  }
6735
7110
 
6736
7111
  graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
6737
7112
 
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
- }
7113
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
7114
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
7115
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
6743
7116
 
6744
7117
  stroke 0x000000, thickness: 2, miter_limit: 10
6745
7118
  }
6746
7119
 
6747
7120
  # now create the fill for the graph below the graph line
6748
7121
  graph_path(graph_width, graph_height, true) {
6749
- fill @color_button.color.merge(a: 0.5)
7122
+ fill @color.merge(a: 0.5)
6750
7123
  }
6751
7124
 
6752
7125
  # now draw the histogram line
6753
7126
  graph_path(graph_width, graph_height, false) {
6754
- stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
7127
+ stroke @color.merge(thickness: 2, miter_limit: 10)
6755
7128
  }
6756
7129
  end
6757
7130
  }
@@ -8140,7 +8513,140 @@ Mac | Windows | Linux
8140
8513
  ----|---------|------
8141
8514
  ![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png) ![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png) | ![glimmer-dsl-libui-windows-timer.png](images/glimmer-dsl-libui-windows-timer.png) ![glimmer-dsl-libui-windows-timer-in-progress.png](images/glimmer-dsl-libui-windows-timer-in-progress.png) | ![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png) ![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
8142
8515
 
8143
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8516
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8517
+
8518
+ ```ruby
8519
+ require 'glimmer-dsl-libui'
8520
+
8521
+ class Timer
8522
+ include Glimmer
8523
+
8524
+ SECOND_MAX = 59
8525
+ MINUTE_MAX = 59
8526
+ HOUR_MAX = 23
8527
+
8528
+ attr_accessor :hour, :min, :sec, :started, :played
8529
+
8530
+ def initialize
8531
+ @pid = nil
8532
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
8533
+ @hour = @min = @sec = 0
8534
+ at_exit { stop_alarm }
8535
+ setup_timer
8536
+ create_gui
8537
+ end
8538
+
8539
+ def stop_alarm
8540
+ if @pid
8541
+ Process.kill(:SIGKILL, @pid) if @th.alive?
8542
+ @pid = nil
8543
+ end
8544
+ end
8545
+
8546
+ def play_alarm
8547
+ stop_alarm
8548
+ if @pid.nil?
8549
+ begin
8550
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
8551
+ @th = Process.detach @pid
8552
+ rescue Errno::ENOENT
8553
+ warn 'Timidty++ not found. Please install Timidity++.'
8554
+ warn 'https://sourceforge.net/projects/timidity/'
8555
+ end
8556
+ end
8557
+ end
8558
+
8559
+ def setup_timer
8560
+ unless @setup_timer
8561
+ Glimmer::LibUI.timer(1) do
8562
+ if @started
8563
+ seconds = @sec
8564
+ minutes = @min
8565
+ hours = @hour
8566
+ if seconds > 0
8567
+ self.sec = seconds -= 1
8568
+ end
8569
+ if seconds == 0
8570
+ if minutes > 0
8571
+ self.min = minutes -= 1
8572
+ self.sec = seconds = SECOND_MAX
8573
+ end
8574
+ if minutes == 0
8575
+ if hours > 0
8576
+ self.hour = hours -= 1
8577
+ self.min = minutes = MINUTE_MAX
8578
+ self.sec = seconds = SECOND_MAX
8579
+ end
8580
+ if hours == 0 && minutes == 0 && seconds == 0
8581
+ self.started = false
8582
+ unless @played
8583
+ play_alarm
8584
+ msg_box('Alarm', 'Countdown Is Finished!')
8585
+ self.played = true
8586
+ end
8587
+ end
8588
+ end
8589
+ end
8590
+ end
8591
+ end
8592
+ @setup_timer = true
8593
+ end
8594
+ end
8595
+
8596
+ def create_gui
8597
+ window('Timer') {
8598
+ margined true
8599
+
8600
+ group('Countdown') {
8601
+ vertical_box {
8602
+ horizontal_box {
8603
+ spinbox(0, HOUR_MAX) {
8604
+ stretchy false
8605
+ value <=> [self, :hour]
8606
+ }
8607
+ label(':') {
8608
+ stretchy false
8609
+ }
8610
+ spinbox(0, MINUTE_MAX) {
8611
+ stretchy false
8612
+ value <=> [self, :min]
8613
+ }
8614
+ label(':') {
8615
+ stretchy false
8616
+ }
8617
+ spinbox(0, SECOND_MAX) {
8618
+ stretchy false
8619
+ value <=> [self, :sec]
8620
+ }
8621
+ }
8622
+ horizontal_box {
8623
+ button('Start') {
8624
+ enabled <= [self, :started, on_read: :!]
8625
+
8626
+ on_clicked do
8627
+ self.started = true
8628
+ self.played = false
8629
+ end
8630
+ }
8631
+
8632
+ button('Stop') {
8633
+ enabled <= [self, :started]
8634
+
8635
+ on_clicked do
8636
+ self.started = false
8637
+ end
8638
+ }
8639
+ }
8640
+ }
8641
+ }
8642
+ }.show
8643
+ end
8644
+ end
8645
+
8646
+ Timer.new
8647
+ ```
8648
+
8649
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8144
8650
 
8145
8651
  ```ruby
8146
8652
  require 'glimmer-dsl-libui'