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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +201 -51
- data/VERSION +1 -1
- data/examples/class_based_custom_controls.rb +113 -0
- data/examples/{method_based_custom_keyword.rb → method_based_custom_controls.rb} +4 -4
- data/examples/{method_based_custom_keyword2.rb → method_based_custom_controls2.rb} +4 -4
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/custom_control_expression.rb +58 -0
- data/lib/glimmer/dsl/libui/dsl.rb +1 -0
- data/lib/glimmer/dsl/libui/property_expression.rb +2 -1
- data/lib/glimmer/libui/custom_control.rb +249 -0
- data/lib/glimmer/proc_tracker.rb +39 -0
- metadata +24 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48926d5aab22eec664c80bc9ebe5a94a841e4b6173cca343ad9876fb33924b03
|
4
|
+
data.tar.gz: 890dba3f171225448aad354ec767819faaf3cb59e48764adc63b0f35bcbc0fe3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
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
|
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.
|
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 `
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
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/
|
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/
|
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/
|
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(
|
8629
|
+
def address_form(address_model)
|
8628
8630
|
form {
|
8629
|
-
form_field(
|
8630
|
-
form_field(
|
8631
|
-
form_field(
|
8632
|
-
form_field(
|
8633
|
-
form_field(
|
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
|
8648
|
+
def address_view(address_model)
|
8647
8649
|
vertical_box {
|
8648
|
-
|
8649
|
-
label_pair(
|
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
|
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
|
-
|
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
|
-
|
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(
|
8728
|
+
def address_form(address_model)
|
8727
8729
|
form {
|
8728
|
-
form_field(
|
8729
|
-
form_field(
|
8730
|
-
form_field(
|
8731
|
-
form_field(
|
8732
|
-
form_field(
|
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
|
8750
|
+
def address_view(address_model)
|
8749
8751
|
vertical_box {
|
8750
|
-
|
8751
|
-
label_pair(
|
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
|
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
|
-
|
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
|
-
|
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.
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
94
|
+
address_view(address2)
|
95
95
|
}
|
96
96
|
}
|
97
97
|
}.show
|
data/glimmer-dsl-libui.gemspec
CHANGED
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
|
@@ -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.
|
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-
|
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.
|
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.
|
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/
|
318
|
-
- examples/
|
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
|