glimmer-dsl-libui 0.5.5 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 186c49fc28b9767289f398563e727a564aec12ccadee63ef1ef8b8337b186c34
4
- data.tar.gz: 45b1ea94ca10b26f52146270c40cd4cf43567ff8142aecda6b1f46c528418882
3
+ metadata.gz: 48926d5aab22eec664c80bc9ebe5a94a841e4b6173cca343ad9876fb33924b03
4
+ data.tar.gz: 890dba3f171225448aad354ec767819faaf3cb59e48764adc63b0f35bcbc0fe3
5
5
  SHA512:
6
- metadata.gz: 253f2335dc985e7e25481f27fb78d78b9ce2a77b1c86cbd513c1e3d289d6204155e72512cbb72994329d905e4906bdf7905e3e070487d19c22605092ce249a70
7
- data.tar.gz: 33633220d46d61a1428c9e0cfad06ea6a0706a44bc537006802f4f24210bbb415a04c7332c4f7fd020e1fb68adce28edf81946ea88eb4652a44fce847725c5fe
6
+ metadata.gz: 94650e60ccb1ed496dd682f937e4a91a3cffad37cfc3da17c462fd2db7349e06af9572c425f1b90d8a3a522c09eddd2b7eed6b4982c31a77fff97322aff6bbed
7
+ data.tar.gz: fc41c1e9882b42155729547ee2d83e43a40a64ae56c2523ff880cd2f818357fb42328a72b79810ccfb75fc7356ac70910e1d4202249488cb4f8aef9d3a8034fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.5.6
4
+
5
+ - Upgrade to glimmer 2.7.1 and document its support for keyed data-binding
6
+ - Support class-based custom controls
7
+ - examples/class_based_custom_controls.rb example
8
+ - Rename examples/method_based_custom_keyword.rb example to examples/method_based_custom_controls.rb
9
+
3
10
  ## 0.5.5
4
11
 
5
12
  - Upgrade to libui 0.0.15 (with official Mac ARM64 support)
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.5.5
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.5.6
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)
@@ -19,7 +19,7 @@ The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/gl
19
19
  - [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy
20
20
  - [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details
21
21
  - Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI
22
- - [Custom Keyword](#custom-keywords) support
22
+ - [Custom Control](#custom-keywords) support
23
23
  - [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models
24
24
  - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
25
25
  - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
@@ -419,7 +419,8 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
419
419
  - [Grid](#grid)
420
420
  - [Histogram](#histogram)
421
421
  - [Login](#login)
422
- - [Method-Based Custom Keyword](#method-based-custom-keyword)
422
+ - [Method-Based Custom Controls](#method-based-custom-controls)
423
+ - [Class-Based Custom Controls](#class-based-custom-controls)
423
424
  - [Area-Based Custom Controls](#area-based-custom-controls)
424
425
  - [Midi Player](#midi-player)
425
426
  - [Snake](#snake)
@@ -454,7 +455,7 @@ The Glimmer GUI DSL follows these simple concepts in mapping from [LibUI](https:
454
455
 
455
456
  **Keyword(args)**: [LibUI](https://github.com/kojix2/LibUI) controls may be declared by lower-case underscored name (aka keyword from list of [supported keywords](#supported-keywords)) (e.g. `window` or `button`). Behind the scenes, they are represented by keyword methods that map to corresponding `LibUI.new_keyword` methods receiving args (e.g. `window('hello world', 300, 200, true)`).
456
457
 
457
- **Content Block** (Properties/Listeners/Controls): Any keyword may be optionally followed by a Ruby curly-brace multi-line content block containing properties (attributes), listeners, and/or nested controls.
458
+ **Content Block** (Properties/Listeners/Controls): Any keyword may be optionally followed by a Ruby curly-brace multi-line content block containing properties (attributes), listeners, and/or nested controls.
458
459
 
459
460
  Example:
460
461
 
@@ -464,7 +465,7 @@ window {
464
465
 
465
466
  on_closing do # listener (always has a do; end block to signify logic)
466
467
  puts 'Bye'
467
- end
468
+ end
468
469
 
469
470
  button('greet') { # nested control
470
471
  on_clicked do
@@ -474,13 +475,13 @@ window {
474
475
  }
475
476
  ```
476
477
 
477
- Content block optionally receives one arg representing the controll
478
+ Content block optionally receives one arg representing the controll
478
479
 
479
480
  Example:
480
481
 
481
482
  ```ruby
482
- button('greet') { |b|
483
- on_clicked do
483
+ button('greet') { |b|
484
+ on_clicked do
484
485
  puts b.text
485
486
  end
486
487
  }
@@ -488,7 +489,7 @@ button('greet') { |b|
488
489
 
489
490
  **Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title "hello world"` inside `group`). Behind the scenes, properties correspond to `LibUI.control_set_property` methods.
490
491
 
491
- **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (always followed by a `do; end` style block to signify logic).
492
+ **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (always followed by a `do; end` style block to signify logic).
492
493
 
493
494
  Example:
494
495
 
@@ -500,11 +501,11 @@ button('click') {
500
501
  }
501
502
  ```
502
503
 
503
- Optionally, the listener block can receive an arg representing the control.
504
+ Optionally, the listener block can receive an arg representing the control.
504
505
 
505
506
  ```ruby
506
507
  button('click') {
507
- on_clicked do |btn|
508
+ on_clicked do |btn|
508
509
  puts btn.text
509
510
  end
510
511
  }
@@ -567,7 +568,7 @@ window('hello world', 300, 200) {
567
568
 
568
569
  ## Usage
569
570
 
570
- Install [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly:
571
+ Install [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly into a [maintained Ruby version](https://www.ruby-lang.org/en/downloads/):
571
572
 
572
573
  ```
573
574
  gem install glimmer-dsl-libui
@@ -576,7 +577,7 @@ gem install glimmer-dsl-libui
576
577
  Or install via Bundler `Gemfile`:
577
578
 
578
579
  ```ruby
579
- gem 'glimmer-dsl-libui', '~> 0.5.5'
580
+ gem 'glimmer-dsl-libui', '~> 0.5.6'
580
581
  ```
581
582
 
582
583
  Test that installation worked by running the [Meta-Example](#examples):
@@ -1598,15 +1599,15 @@ SpinnerExample.new.launch
1598
1599
 
1599
1600
  Custom keywords can be defined to represent custom controls (components) that provide new features or act as composites of [existing controls](#supported-keywords) that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
1600
1601
 
1601
- For example, you can define a custom `address` control as an aggregate of multiple `label` controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
1602
+ For example, you can define a custom `address_view` control as an aggregate of multiple `label` controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
1602
1603
 
1603
- To define custom keywords, simply define a method representing the custom control you want (e.g. `address`) with any arguments needed (e.g. `address(address_model)`).
1604
+ There are two ways to define custom keywords:
1605
+ - Method-Based: simply define a method representing the custom control you want (e.g. `address_view`) with any arguments needed (e.g. `address(address_model)`).
1606
+ - Class-Based: define a class matching the camelcased name of the custom control by convention (e.g. the `address_view` custom control keyword would have a class called `AddressView`) and `include Glimmer::LibUI::CustomControl`. Classes add the benefit of being able to distribute the custom controls into separate files and reuse externally from multiple places or share via Ruby gems.
1604
1607
 
1605
- To make custom keywords externally reusable, you can define in modules and simply include the modules in the view classes that need them.
1608
+ It is OK to use the terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. `cylinder`), custom attributed strings (e.g. `alternating_color_string`), and custom transforms (`isometric_transform`).
1606
1609
 
1607
- It is OK to use terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. `cylinder`), custom attributed strings (e.g. `alternating_color_string`), and custom transforms (`isometric_transform`).
1608
-
1609
- Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1610
+ Example that defines `form_field`, `address_form`, `label_pair`, and `address_view` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1610
1611
 
1611
1612
  ```ruby
1612
1613
  require 'glimmer-dsl-libui'
@@ -1643,7 +1644,7 @@ def label_pair(model, attribute, value)
1643
1644
  }
1644
1645
  end
1645
1646
 
1646
- def address(address_model)
1647
+ def address_view(address_model)
1647
1648
  vertical_box {
1648
1649
  address_model.each_pair do |attribute, value|
1649
1650
  label_pair(address_model, attribute, value)
@@ -1673,7 +1674,7 @@ window('Method-Based Custom Keyword') {
1673
1674
  stretchy false
1674
1675
  }
1675
1676
 
1676
- address(address1)
1677
+ address_view(address1)
1677
1678
  }
1678
1679
 
1679
1680
  vertical_separator {
@@ -1695,7 +1696,7 @@ window('Method-Based Custom Keyword') {
1695
1696
  stretchy false
1696
1697
  }
1697
1698
 
1698
- address(address2)
1699
+ address_view(address2)
1699
1700
  }
1700
1701
  }
1701
1702
  }.show
@@ -1978,6 +1979,7 @@ Data-bound model attribute can be:
1978
1979
  - **Direct:** `Symbol` representing attribute reader/writer (e.g. `[person, :name`])
1979
1980
  - **Nested:** `String` representing nested attribute path (e.g. `[company, 'address.street']`). That results in "nested data-binding"
1980
1981
  - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
1982
+ - **Keyed:** `String` containing hash attribute key (e.g. `[customer, 'addresses[:main].street']`). That results in "keyed data-binding"
1981
1983
 
1982
1984
  Data-binding options include:
1983
1985
  - `before_read {|value| ...}`: performs an operation before reading data from Model to update View.
@@ -8580,26 +8582,26 @@ window('Login') {
8580
8582
  }.show
8581
8583
  ```
8582
8584
 
8583
- #### Method-Based Custom Keyword
8585
+ #### Method-Based Custom Controls
8584
8586
 
8585
8587
  [Custom keywords](#custom-keywords) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
8586
8588
 
8587
- This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom control keywords.
8589
+ This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
8588
8590
 
8589
8591
  The custom keywords are defined via methods (thus are "method-based").
8590
8592
 
8591
- [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
8593
+ [examples/method_based_custom_controls.rb](examples/method_based_custom_controls.rb)
8592
8594
 
8593
8595
  Run with this command from the root of the project if you cloned the project:
8594
8596
 
8595
8597
  ```
8596
- ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_keyword.rb
8598
+ ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_controls.rb
8597
8599
  ```
8598
8600
 
8599
8601
  Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
8600
8602
 
8601
8603
  ```
8602
- ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_keyword'"
8604
+ ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_controls'"
8603
8605
  ```
8604
8606
 
8605
8607
  Mac | Windows | Linux
@@ -8624,13 +8626,13 @@ def form_field(model, attribute)
8624
8626
  }
8625
8627
  end
8626
8628
 
8627
- def address_form(address)
8629
+ def address_form(address_model)
8628
8630
  form {
8629
- form_field(address, :street)
8630
- form_field(address, :p_o_box)
8631
- form_field(address, :city)
8632
- form_field(address, :state)
8633
- form_field(address, :zip_code)
8631
+ form_field(address_model, :street)
8632
+ form_field(address_model, :p_o_box)
8633
+ form_field(address_model, :city)
8634
+ form_field(address_model, :state)
8635
+ form_field(address_model, :zip_code)
8634
8636
  }
8635
8637
  end
8636
8638
 
@@ -8643,10 +8645,10 @@ def label_pair(model, attribute, value)
8643
8645
  }
8644
8646
  end
8645
8647
 
8646
- def address(address)
8648
+ def address_view(address_model)
8647
8649
  vertical_box {
8648
- address.each_pair do |attribute, value|
8649
- label_pair(address, attribute, value)
8650
+ address_model.each_pair do |attribute, value|
8651
+ label_pair(address_model, attribute, value)
8650
8652
  end
8651
8653
  }
8652
8654
  end
@@ -8654,7 +8656,7 @@ end
8654
8656
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
8655
8657
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
8656
8658
 
8657
- window('Method-Based Custom Keyword') {
8659
+ window('Method-Based Custom Controls') {
8658
8660
  margined true
8659
8661
 
8660
8662
  horizontal_box {
@@ -8673,7 +8675,7 @@ window('Method-Based Custom Keyword') {
8673
8675
  stretchy false
8674
8676
  }
8675
8677
 
8676
- address(address1)
8678
+ address_view(address1)
8677
8679
  }
8678
8680
 
8679
8681
  vertical_separator {
@@ -8695,7 +8697,7 @@ window('Method-Based Custom Keyword') {
8695
8697
  stretchy false
8696
8698
  }
8697
8699
 
8698
- address(address2)
8700
+ address_view(address2)
8699
8701
  }
8700
8702
  }
8701
8703
  }.show
@@ -8723,13 +8725,13 @@ def form_field(model, property)
8723
8725
  }
8724
8726
  end
8725
8727
 
8726
- def address_form(address)
8728
+ def address_form(address_model)
8727
8729
  form {
8728
- form_field(address, :street)
8729
- form_field(address, :p_o_box)
8730
- form_field(address, :city)
8731
- form_field(address, :state)
8732
- form_field(address, :zip_code)
8730
+ form_field(address_model, :street)
8731
+ form_field(address_model, :p_o_box)
8732
+ form_field(address_model, :city)
8733
+ form_field(address_model, :state)
8734
+ form_field(address_model, :zip_code)
8733
8735
  }
8734
8736
  end
8735
8737
 
@@ -8745,10 +8747,10 @@ def label_pair(model, attribute, value)
8745
8747
  end
8746
8748
  end
8747
8749
 
8748
- def address(address)
8750
+ def address_view(address_model)
8749
8751
  vertical_box {
8750
- address.each_pair do |attribute, value|
8751
- label_pair(address, attribute, value)
8752
+ address_model.each_pair do |attribute, value|
8753
+ label_pair(address_model, attribute, value)
8752
8754
  end
8753
8755
  }
8754
8756
  end
@@ -8756,7 +8758,7 @@ end
8756
8758
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
8757
8759
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
8758
8760
 
8759
- window('Method-Based Custom Keyword') {
8761
+ window('Method-Based Custom Controls') {
8760
8762
  margined true
8761
8763
 
8762
8764
  horizontal_box {
@@ -8775,7 +8777,7 @@ window('Method-Based Custom Keyword') {
8775
8777
  stretchy false
8776
8778
  }
8777
8779
 
8778
- address(address1)
8780
+ address_view(address1)
8779
8781
  }
8780
8782
 
8781
8783
  vertical_separator {
@@ -8797,7 +8799,151 @@ window('Method-Based Custom Keyword') {
8797
8799
  stretchy false
8798
8800
  }
8799
8801
 
8800
- address(address2)
8802
+ address_view(address2)
8803
+ }
8804
+ }
8805
+ }.show
8806
+ ```
8807
+
8808
+ #### Class-Based Custom Controls
8809
+
8810
+ [Custom keywords](#custom-keywords) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
8811
+
8812
+ This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
8813
+
8814
+ The custom keywords are defined via classes that include `Glimmer::LibUI::CustomControl` (thus are "class-based"), thus enabling offloading each custom control into its own file when needed for better code organization.
8815
+
8816
+ [examples/class_based_custom_controls.rb](examples/class_based_custom_controls.rb)
8817
+
8818
+ Run with this command from the root of the project if you cloned the project:
8819
+
8820
+ ```
8821
+ ruby -r './lib/glimmer-dsl-libui' examples/class_based_custom_controls.rb
8822
+ ```
8823
+
8824
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
8825
+
8826
+ ```
8827
+ ruby -r glimmer-dsl-libui -e "require 'examples/class_based_custom_controls'"
8828
+ ```
8829
+
8830
+ Mac | Windows | Linux
8831
+ ----|---------|------
8832
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png) | ![glimmer-dsl-libui-windows-method-based-custom-keyword.png](images/glimmer-dsl-libui-windows-method-based-custom-keyword.png) | ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
8833
+
8834
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8835
+
8836
+ ```ruby
8837
+ require 'glimmer-dsl-libui'
8838
+ require 'facets'
8839
+
8840
+ include Glimmer
8841
+
8842
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
8843
+
8844
+ class FormField
8845
+ include Glimmer::LibUI::CustomControl
8846
+
8847
+ options :model, :attribute
8848
+
8849
+ body {
8850
+ entry { |e|
8851
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
8852
+ text <=> [model, attribute]
8853
+ }
8854
+ }
8855
+ end
8856
+
8857
+ class AddressForm
8858
+ include Glimmer::LibUI::CustomControl
8859
+
8860
+ options :address
8861
+
8862
+ body {
8863
+ form {
8864
+ form_field(model: address, attribute: :street)
8865
+ form_field(model: address, attribute: :p_o_box)
8866
+ form_field(model: address, attribute: :city)
8867
+ form_field(model: address, attribute: :state)
8868
+ form_field(model: address, attribute: :zip_code)
8869
+ }
8870
+ }
8871
+ end
8872
+
8873
+ class LabelPair
8874
+ include Glimmer::LibUI::CustomControl
8875
+
8876
+ options :model, :attribute, :value
8877
+
8878
+ body {
8879
+ horizontal_box {
8880
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
8881
+ label(value.to_s) {
8882
+ text <= [model, attribute]
8883
+ }
8884
+ }
8885
+ }
8886
+ end
8887
+
8888
+ class AddressView
8889
+ include Glimmer::LibUI::CustomControl
8890
+
8891
+ options :address
8892
+
8893
+ body {
8894
+ vertical_box {
8895
+ address.each_pair do |attribute, value|
8896
+ label_pair(model: address, attribute: attribute, value: value)
8897
+ end
8898
+ }
8899
+ }
8900
+ end
8901
+
8902
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
8903
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
8904
+
8905
+ window('Class-Based Custom Keyword') {
8906
+ margined true
8907
+
8908
+ horizontal_box {
8909
+ vertical_box {
8910
+ label('Address 1') {
8911
+ stretchy false
8912
+ }
8913
+
8914
+ address_form(address: address1)
8915
+
8916
+ horizontal_separator {
8917
+ stretchy false
8918
+ }
8919
+
8920
+ label('Address 1 (Saved)') {
8921
+ stretchy false
8922
+ }
8923
+
8924
+ address_view(address: address1)
8925
+ }
8926
+
8927
+ vertical_separator {
8928
+ stretchy false
8929
+ }
8930
+
8931
+ vertical_box {
8932
+ label('Address 2') {
8933
+ stretchy false
8934
+ }
8935
+
8936
+ address_form(address: address2)
8937
+
8938
+ horizontal_separator {
8939
+ stretchy false
8940
+ }
8941
+
8942
+ label('Address 2 (Saved)') {
8943
+ stretchy false
8944
+ }
8945
+
8946
+ address_view(address: address2)
8801
8947
  }
8802
8948
  }
8803
8949
  }.show
@@ -9514,6 +9660,8 @@ Snake provides an example of building a desktop application [test-first](/spec/e
9514
9660
 
9515
9661
  Use arrows to move and spacebar to pause/resume.
9516
9662
 
9663
+ Note that Snake relies on the new [Ruby Pattern Matching feature](https://docs.ruby-lang.org/en/3.0/doc/syntax/pattern_matching_rdoc.html) available starting in Ruby 2.7 experimentally and in Ruby 3.0 officially.
9664
+
9517
9665
  [examples/snake.rb](examples/snake.rb)
9518
9666
 
9519
9667
  Run with this command from the root of the project if you cloned the project:
@@ -9738,6 +9886,8 @@ Snake.new.launch
9738
9886
 
9739
9887
  Glimmer Tetris utilizes many small areas to represent Tetromino blocks because this ensures smaller redraws per tetromino block color change, thus achieving higher performance than redrawing one large area on every little change.
9740
9888
 
9889
+ Note that Tetris relies on the new [Ruby Pattern Matching feature](https://docs.ruby-lang.org/en/3.0/doc/syntax/pattern_matching_rdoc.html) available starting in Ruby 2.7 experimentally and in Ruby 3.0 officially.
9890
+
9741
9891
  [examples/tetris.rb](examples/tetris.rb)
9742
9892
 
9743
9893
  Run with this command from the root of the project if you cloned the project:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.5
1
+ 0.5.6
@@ -0,0 +1,113 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'facets'
3
+
4
+ include Glimmer
5
+
6
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
7
+
8
+ class FormField
9
+ include Glimmer::LibUI::CustomControl
10
+
11
+ options :model, :attribute
12
+
13
+ body {
14
+ entry { |e|
15
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
16
+ text <=> [model, attribute]
17
+ }
18
+ }
19
+ end
20
+
21
+ class AddressForm
22
+ include Glimmer::LibUI::CustomControl
23
+
24
+ options :address
25
+
26
+ body {
27
+ form {
28
+ form_field(model: address, attribute: :street)
29
+ form_field(model: address, attribute: :p_o_box)
30
+ form_field(model: address, attribute: :city)
31
+ form_field(model: address, attribute: :state)
32
+ form_field(model: address, attribute: :zip_code)
33
+ }
34
+ }
35
+ end
36
+
37
+ class LabelPair
38
+ include Glimmer::LibUI::CustomControl
39
+
40
+ options :model, :attribute, :value
41
+
42
+ body {
43
+ horizontal_box {
44
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
45
+ label(value.to_s) {
46
+ text <= [model, attribute]
47
+ }
48
+ }
49
+ }
50
+ end
51
+
52
+ class AddressView
53
+ include Glimmer::LibUI::CustomControl
54
+
55
+ options :address
56
+
57
+ body {
58
+ vertical_box {
59
+ address.each_pair do |attribute, value|
60
+ label_pair(model: address, attribute: attribute, value: value)
61
+ end
62
+ }
63
+ }
64
+ end
65
+
66
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
67
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
68
+
69
+ window('Class-Based Custom Keyword') {
70
+ margined true
71
+
72
+ horizontal_box {
73
+ vertical_box {
74
+ label('Address 1') {
75
+ stretchy false
76
+ }
77
+
78
+ address_form(address: address1)
79
+
80
+ horizontal_separator {
81
+ stretchy false
82
+ }
83
+
84
+ label('Address 1 (Saved)') {
85
+ stretchy false
86
+ }
87
+
88
+ address_view(address: address1)
89
+ }
90
+
91
+ vertical_separator {
92
+ stretchy false
93
+ }
94
+
95
+ vertical_box {
96
+ label('Address 2') {
97
+ stretchy false
98
+ }
99
+
100
+ address_form(address: address2)
101
+
102
+ horizontal_separator {
103
+ stretchy false
104
+ }
105
+
106
+ label('Address 2 (Saved)') {
107
+ stretchy false
108
+ }
109
+
110
+ address_view(address: address2)
111
+ }
112
+ }
113
+ }.show
@@ -32,7 +32,7 @@ def label_pair(model, attribute, value)
32
32
  }
33
33
  end
34
34
 
35
- def address(address_model)
35
+ def address_view(address_model)
36
36
  vertical_box {
37
37
  address_model.each_pair do |attribute, value|
38
38
  label_pair(address_model, attribute, value)
@@ -43,7 +43,7 @@ end
43
43
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
44
44
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
45
45
 
46
- window('Method-Based Custom Keyword') {
46
+ window('Method-Based Custom Controls') {
47
47
  margined true
48
48
 
49
49
  horizontal_box {
@@ -62,7 +62,7 @@ window('Method-Based Custom Keyword') {
62
62
  stretchy false
63
63
  }
64
64
 
65
- address(address1)
65
+ address_view(address1)
66
66
  }
67
67
 
68
68
  vertical_separator {
@@ -84,7 +84,7 @@ window('Method-Based Custom Keyword') {
84
84
  stretchy false
85
85
  }
86
86
 
87
- address(address2)
87
+ address_view(address2)
88
88
  }
89
89
  }
90
90
  }.show
@@ -39,7 +39,7 @@ def label_pair(model, attribute, value)
39
39
  end
40
40
  end
41
41
 
42
- def address(address_model)
42
+ def address_view(address_model)
43
43
  vertical_box {
44
44
  address_model.each_pair do |attribute, value|
45
45
  label_pair(address_model, attribute, value)
@@ -50,7 +50,7 @@ end
50
50
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
51
51
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
52
52
 
53
- window('Method-Based Custom Keyword') {
53
+ window('Method-Based Custom Controls') {
54
54
  margined true
55
55
 
56
56
  horizontal_box {
@@ -69,7 +69,7 @@ window('Method-Based Custom Keyword') {
69
69
  stretchy false
70
70
  }
71
71
 
72
- address(address1)
72
+ address_view(address1)
73
73
  }
74
74
 
75
75
  vertical_separator {
@@ -91,7 +91,7 @@ window('Method-Based Custom Keyword') {
91
91
  stretchy false
92
92
  }
93
93
 
94
- address(address2)
94
+ address_view(address2)
95
95
  }
96
96
  }
97
97
  }.show
Binary file
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer'
23
+ require 'glimmer/dsl/expression'
24
+ require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/dsl/top_level_expression'
26
+ require 'glimmer/libui/custom_control'
27
+
28
+ module Glimmer
29
+ module DSL
30
+ module Libui
31
+ class CustomControlExpression < Expression
32
+ # TODO Consider making custom controls automatically generate static expressions
33
+ include ParentExpression
34
+ include TopLevelExpression
35
+
36
+ def can_interpret?(parent, keyword, *args, &block)
37
+ LibUI::CustomControl.for(keyword)
38
+ end
39
+
40
+ def interpret(parent, keyword, *args, &block)
41
+ options = args.last.is_a?(Hash) ? args.pop : {}
42
+ LibUI::CustomControl.for(keyword).new(keyword, parent, args, options, &block)
43
+ end
44
+
45
+ def add_content(custom_control, keyword, *args, &block)
46
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
47
+ # TODO consider avoiding source_location
48
+ if block.source_location == custom_control.content&.__getobj__&.source_location
49
+ custom_control.content.call(custom_control) unless custom_control.content.called?
50
+ else
51
+ super
52
+ end
53
+ custom_control.post_add_content if options[:post_add_content]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -43,6 +43,7 @@ module Glimmer
43
43
  string
44
44
  operation
45
45
  control
46
+ custom_control
46
47
  shape
47
48
  ]
48
49
  )
@@ -32,7 +32,8 @@ module Glimmer
32
32
  (
33
33
  parent.is_a?(Glimmer::LibUI::ControlProxy) or
34
34
  parent.is_a?(Glimmer::LibUI::Shape) or
35
- parent.is_a?(Glimmer::LibUI::AttributedString)
35
+ parent.is_a?(Glimmer::LibUI::AttributedString) or
36
+ parent.is_a?(Glimmer::LibUI::CustomControl)
36
37
  ) and
37
38
  block.nil? and
38
39
  parent.respond_to?("#{keyword}=", *args)
@@ -0,0 +1,249 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'super_module'
23
+ require 'glimmer'
24
+ require 'glimmer/proc_tracker'
25
+ require 'glimmer/data_binding/observer'
26
+ require 'glimmer/data_binding/observable_model'
27
+
28
+ module Glimmer
29
+ module LibUI
30
+ module CustomControl
31
+ include SuperModule
32
+ include DataBinding::ObservableModel
33
+
34
+ super_module_included do |klass|
35
+ # TODO clear memoization of WidgetProxy.libui_class_for for a keyword if a custom control was defined with that keyword
36
+ klass.include(Glimmer)
37
+ Glimmer::LibUI::CustomControl.add_custom_control_namespaces_for(klass)
38
+ end
39
+
40
+ class << self
41
+ def for(keyword)
42
+ unless flyweight_custom_control_classes.keys.include?(keyword)
43
+ begin
44
+ extracted_namespaces = keyword.
45
+ to_s.
46
+ split(/__/).map do |namespace|
47
+ namespace.camelcase(:upper)
48
+ end
49
+ custom_control_namespaces.each do |base|
50
+ extracted_namespaces.reduce(base) do |result, namespace|
51
+ if !result.constants.include?(namespace)
52
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
53
+ end
54
+ begin
55
+ flyweight_custom_control_classes[keyword] = constant = result.const_get(namespace)
56
+ return constant if constant.ancestors.include?(Glimmer::LibUI::CustomControl)
57
+ flyweight_custom_control_classes[keyword] = constant
58
+ rescue => e
59
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
60
+ flyweight_custom_control_classes[keyword] = result
61
+ end
62
+ end
63
+ end
64
+ raise "#{keyword} has no custom control class!"
65
+ rescue => e
66
+ Glimmer::Config.logger.debug {e.message}
67
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
68
+ flyweight_custom_control_classes[keyword] = nil
69
+ end
70
+ end
71
+ flyweight_custom_control_classes[keyword]
72
+ end
73
+
74
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
75
+ def flyweight_custom_control_classes
76
+ @flyweight_custom_control_classes ||= {}
77
+ end
78
+
79
+ # Returns keyword to use for this custom control
80
+ def keyword
81
+ self.name.underscore.gsub('::', '__')
82
+ end
83
+
84
+ # Returns shortcut keyword to use for this custom control (keyword minus namespace)
85
+ def shortcut_keyword
86
+ self.name.underscore.gsub('::', '__').split('__').last
87
+ end
88
+
89
+ def add_custom_control_namespaces_for(klass)
90
+ Glimmer::LibUI::CustomControl.namespaces_for_class(klass).drop(1).each do |namespace|
91
+ Glimmer::LibUI::CustomControl.custom_control_namespaces << namespace
92
+ end
93
+ end
94
+
95
+ def namespaces_for_class(m)
96
+ return [m] if m.name.nil?
97
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
98
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
99
+ output += [output.last.const_get(namespace_constant)]
100
+ end[1..-1].uniq.reverse
101
+ end
102
+
103
+ def custom_control_namespaces
104
+ @custom_control_namespaces ||= reset_custom_control_namespaces
105
+ end
106
+
107
+ def reset_custom_control_namespaces
108
+ @custom_control_namespaces = Set[Object, Glimmer::LibUI]
109
+ end
110
+
111
+ # Allows defining convenience option accessors for an array of option names
112
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
113
+ # where they return the instance values `options[:color1]` and `options[:color2]`
114
+ # respectively.
115
+ # Can be called multiple times to set more options additively.
116
+ # When passed no arguments, it returns list of all option names captured so far
117
+ def options(*new_options)
118
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
119
+ if new_options.empty?
120
+ @options ||= {} # maps options to defaults
121
+ else
122
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
123
+ @options = options.merge(new_options)
124
+ def_option_attr_accessors(new_options)
125
+ end
126
+ end
127
+
128
+ def option(new_option, default: nil)
129
+ new_option = new_option.to_s.to_sym
130
+ new_options = {new_option => default}
131
+ @options = options.merge(new_options)
132
+ def_option_attr_accessors(new_options)
133
+ end
134
+
135
+ def def_option_attr_accessors(new_options)
136
+ new_options.each do |option, default|
137
+ class_eval <<-end_eval, __FILE__, __LINE__
138
+ def #{option}
139
+ options[:#{option}]
140
+ end
141
+
142
+ def #{option}=(option_value)
143
+ self.options[:#{option}] = option_value
144
+ end
145
+ end_eval
146
+ end
147
+ end
148
+
149
+ def before_body(&block)
150
+ @before_body_block = block
151
+ end
152
+
153
+ def body(&block)
154
+ @body_block = block
155
+ end
156
+
157
+ def after_body(&block)
158
+ @after_body_block = block
159
+ end
160
+ end
161
+
162
+ attr_reader :body_root, :libui, :parent, :parent_proxy, :args, :keyword, :content, :options
163
+
164
+ def initialize(keyword, parent, args, options, &content)
165
+ @parent_proxy = @parent = parent
166
+ options ||= {}
167
+ @options = self.class.options.merge(options)
168
+ @content = ProcTracker.new(content) if content
169
+ execute_hook('before_body')
170
+ body_block = self.class.instance_variable_get("@body_block")
171
+ raise Glimmer::Error, 'Invalid custom control for having no body! Please define body block!' if body_block.nil?
172
+ @body_root = instance_exec(&body_block)
173
+ raise Glimmer::Error, 'Invalid custom control for having an empty body! Please fill body block!' if @body_root.nil?
174
+ @libui = @body_root.libui
175
+ execute_hook('after_body')
176
+ # TODO deregister all observer_registrations on destroy of the control once that listener is supported
177
+ # (on_destroy) unless it is the last window closing, in which case exit faster
178
+ post_add_content if content.nil?
179
+ end
180
+
181
+ # Subclasses may override to perform post initialization work on an added child
182
+ def post_initialize_child(child)
183
+ # No Op by default
184
+ end
185
+
186
+ def post_add_content
187
+ # No Op by default
188
+ end
189
+
190
+ def observer_registrations
191
+ @observer_registrations ||= []
192
+ end
193
+
194
+ def can_handle_listener?(listener)
195
+ body_root&.can_handle_listener?(listener.to_s)
196
+ end
197
+
198
+ def handle_listener(listener, &block)
199
+ body_root.handle_listener(listener.to_s, &block)
200
+ end
201
+
202
+ # This method ensures it has an instance method not coming from Glimmer DSL
203
+ def has_instance_method?(method_name)
204
+ respond_to?(method_name) and
205
+ !@body_root.respond_to_libui?(method_name) and
206
+ (method(method_name) rescue nil) and
207
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
208
+ !method(method_name)&.source_location&.first&.include?('glimmer/libui/control_proxy.rb')
209
+ end
210
+
211
+ # Returns content block if used as an attribute reader (no args)
212
+ # Otherwise, if a block is passed, it adds it as content to this custom control
213
+ def content(&block)
214
+ if block_given?
215
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::CustomControlExpression.new, self.class.keyword, &block)
216
+ else
217
+ @content
218
+ end
219
+ end
220
+
221
+ def method_missing(method_name, *args, &block)
222
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
223
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
224
+ if block && can_handle_listener?(method_name)
225
+ handle_listener(method_name, &block)
226
+ else
227
+ @body_root.send(method_name, *args, &block)
228
+ end
229
+ end
230
+
231
+ def respond_to?(method_name, *args, &block)
232
+ super or
233
+ can_handle_listener?(method_name) or
234
+ @body_root.respond_to?(method_name, *args, &block)
235
+ end
236
+
237
+ private
238
+
239
+ def execute_hook(hook_name)
240
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
241
+ return if hook_block.nil?
242
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
243
+ singleton_class.define_method(temp_method_name, &hook_block)
244
+ send(temp_method_name)
245
+ singleton_class.send(:remove_method, temp_method_name)
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2007-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'delegate'
23
+
24
+ module Glimmer
25
+ class ProcTracker < DelegateClass(Proc)
26
+ def initialize(proc)
27
+ super(proc)
28
+ end
29
+
30
+ def call(*args)
31
+ __getobj__.call(*args)
32
+ @called = true
33
+ end
34
+
35
+ def called?
36
+ !!@called
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-12 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.6.0
19
+ version: 2.7.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.6.0
26
+ version: 2.7.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: perfect-shape
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: super_module
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.4.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.4.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: os
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -281,6 +295,7 @@ files:
281
295
  - examples/basic_window.rb
282
296
  - examples/basic_window2.rb
283
297
  - examples/button_counter.rb
298
+ - examples/class_based_custom_controls.rb
284
299
  - examples/color_button.rb
285
300
  - examples/color_button2.rb
286
301
  - examples/color_the_circles.rb
@@ -314,8 +329,8 @@ files:
314
329
  - examples/login4.rb
315
330
  - examples/login5.rb
316
331
  - examples/meta_example.rb
317
- - examples/method_based_custom_keyword.rb
318
- - examples/method_based_custom_keyword2.rb
332
+ - examples/method_based_custom_controls.rb
333
+ - examples/method_based_custom_controls2.rb
319
334
  - examples/midi_player.rb
320
335
  - examples/midi_player2.rb
321
336
  - examples/midi_player3.rb
@@ -345,6 +360,7 @@ files:
345
360
  - lib/glimmer-dsl-libui.rb
346
361
  - lib/glimmer/dsl/libui/bind_expression.rb
347
362
  - lib/glimmer/dsl/libui/control_expression.rb
363
+ - lib/glimmer/dsl/libui/custom_control_expression.rb
348
364
  - lib/glimmer/dsl/libui/data_binding_expression.rb
349
365
  - lib/glimmer/dsl/libui/dsl.rb
350
366
  - lib/glimmer/dsl/libui/file_expression.rb
@@ -426,6 +442,7 @@ files:
426
442
  - lib/glimmer/libui/control_proxy/transformable.rb
427
443
  - lib/glimmer/libui/control_proxy/triple_column.rb
428
444
  - lib/glimmer/libui/control_proxy/window_proxy.rb
445
+ - lib/glimmer/libui/custom_control.rb
429
446
  - lib/glimmer/libui/data_bindable.rb
430
447
  - lib/glimmer/libui/image_path_renderer.rb
431
448
  - lib/glimmer/libui/parent.rb
@@ -440,6 +457,7 @@ files:
440
457
  - lib/glimmer/libui/shape/polyline.rb
441
458
  - lib/glimmer/libui/shape/rectangle.rb
442
459
  - lib/glimmer/libui/shape/square.rb
460
+ - lib/glimmer/proc_tracker.rb
443
461
  - sounds/AlanWalker-Faded.mid
444
462
  - sounds/AlanWalker-SingMeToSleep.mid
445
463
  - sounds/CalvinHarris-Blame.mid