glimmer-dsl-libui 0.5.5 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +443 -55
- data/VERSION +1 -1
- data/examples/basic_code_area.rb +31 -0
- data/examples/class_based_custom_controls.rb +121 -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 +59 -0
- data/lib/glimmer/dsl/libui/dsl.rb +1 -0
- data/lib/glimmer/dsl/libui/property_expression.rb +2 -1
- data/lib/glimmer/libui/control_proxy/area_proxy.rb +1 -1
- data/lib/glimmer/libui/control_proxy.rb +1 -1
- data/lib/glimmer/libui/custom_control/code_area.rb +65 -0
- data/lib/glimmer/libui/custom_control.rb +255 -0
- data/lib/glimmer/libui/custom_window.rb +65 -0
- data/lib/glimmer/libui.rb +3 -5
- data/lib/glimmer/proc_tracker.rb +39 -0
- data/lib/glimmer-dsl-libui/ext/glimmer.rb +7 -0
- data/lib/glimmer-dsl-libui/ext/rouge/theme/glimmer.rb +29 -0
- data/lib/glimmer-dsl-libui.rb +1 -1
- metadata +50 -7
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.8
|
2
2
|
## Prerequisite-Free Ruby Desktop Development GUI Library
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
|
4
4
|
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -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.
|
@@ -405,6 +405,7 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
|
|
405
405
|
- [Basic Image](#basic-image)
|
406
406
|
- [Basic Transform](#basic-transform)
|
407
407
|
- [Basic Draw Text](#basic-draw-text)
|
408
|
+
- [Basic Code Area](#basic-code-area)
|
408
409
|
- [Advanced Examples](#advanced-examples)
|
409
410
|
- [Area Gallery](#area-gallery)
|
410
411
|
- [Button Counter](#button-counter)
|
@@ -419,7 +420,8 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
|
|
419
420
|
- [Grid](#grid)
|
420
421
|
- [Histogram](#histogram)
|
421
422
|
- [Login](#login)
|
422
|
-
- [Method-Based Custom
|
423
|
+
- [Method-Based Custom Controls](#method-based-custom-controls)
|
424
|
+
- [Class-Based Custom Controls](#class-based-custom-controls)
|
423
425
|
- [Area-Based Custom Controls](#area-based-custom-controls)
|
424
426
|
- [Midi Player](#midi-player)
|
425
427
|
- [Snake](#snake)
|
@@ -454,7 +456,7 @@ The Glimmer GUI DSL follows these simple concepts in mapping from [LibUI](https:
|
|
454
456
|
|
455
457
|
**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
458
|
|
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.
|
459
|
+
**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
460
|
|
459
461
|
Example:
|
460
462
|
|
@@ -464,7 +466,7 @@ window {
|
|
464
466
|
|
465
467
|
on_closing do # listener (always has a do; end block to signify logic)
|
466
468
|
puts 'Bye'
|
467
|
-
end
|
469
|
+
end
|
468
470
|
|
469
471
|
button('greet') { # nested control
|
470
472
|
on_clicked do
|
@@ -474,13 +476,13 @@ window {
|
|
474
476
|
}
|
475
477
|
```
|
476
478
|
|
477
|
-
Content block optionally receives one arg representing the controll
|
479
|
+
Content block optionally receives one arg representing the controll
|
478
480
|
|
479
481
|
Example:
|
480
482
|
|
481
483
|
```ruby
|
482
|
-
button('greet') { |b|
|
483
|
-
on_clicked do
|
484
|
+
button('greet') { |b|
|
485
|
+
on_clicked do
|
484
486
|
puts b.text
|
485
487
|
end
|
486
488
|
}
|
@@ -488,7 +490,7 @@ button('greet') { |b|
|
|
488
490
|
|
489
491
|
**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
492
|
|
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).
|
493
|
+
**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
494
|
|
493
495
|
Example:
|
494
496
|
|
@@ -500,11 +502,11 @@ button('click') {
|
|
500
502
|
}
|
501
503
|
```
|
502
504
|
|
503
|
-
Optionally, the listener block can receive an arg representing the control.
|
505
|
+
Optionally, the listener block can receive an arg representing the control.
|
504
506
|
|
505
507
|
```ruby
|
506
508
|
button('click') {
|
507
|
-
on_clicked do |btn|
|
509
|
+
on_clicked do |btn|
|
508
510
|
puts btn.text
|
509
511
|
end
|
510
512
|
}
|
@@ -567,7 +569,7 @@ window('hello world', 300, 200) {
|
|
567
569
|
|
568
570
|
## Usage
|
569
571
|
|
570
|
-
Install [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly:
|
572
|
+
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
573
|
|
572
574
|
```
|
573
575
|
gem install glimmer-dsl-libui
|
@@ -576,7 +578,7 @@ gem install glimmer-dsl-libui
|
|
576
578
|
Or install via Bundler `Gemfile`:
|
577
579
|
|
578
580
|
```ruby
|
579
|
-
gem 'glimmer-dsl-libui', '~> 0.5.
|
581
|
+
gem 'glimmer-dsl-libui', '~> 0.5.8'
|
580
582
|
```
|
581
583
|
|
582
584
|
Test that installation worked by running the [Meta-Example](#examples):
|
@@ -589,14 +591,40 @@ Mac | Windows | Linux
|
|
589
591
|
----|---------|------
|
590
592
|
![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | ![glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | ![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)
|
591
593
|
|
592
|
-
Now to use [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui), add `require 'glimmer-dsl-libui'` at the top
|
594
|
+
Now to use [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui), add `require 'glimmer-dsl-libui'` at the top.
|
593
595
|
|
594
|
-
|
596
|
+
Afterwards, `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
|
597
|
+
|
598
|
+
Alternatively, `include Glimmer::LibUI::Application` to conveniently declare the GUI `body` and run via the `::launch` method (`Glimmer::LibUI::Application` is an alias for `Glimmer::LibUI::CustomWindow` since that is what it represents).
|
599
|
+
|
600
|
+
Example including `Glimmer::LibUI::Application` (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
require 'glimmer-dsl-libui'
|
604
|
+
|
605
|
+
class SomeGlimmerApp
|
606
|
+
include Glimmer::LibUI::Application
|
607
|
+
|
608
|
+
body {
|
609
|
+
window('hello world', 300, 200) {
|
610
|
+
button('Button') {
|
611
|
+
on_clicked do
|
612
|
+
puts 'Button Clicked'
|
613
|
+
end
|
614
|
+
}
|
615
|
+
}
|
616
|
+
}
|
617
|
+
end
|
618
|
+
|
619
|
+
SomeGlimmerApp.launch
|
620
|
+
```
|
621
|
+
|
622
|
+
Example including `Glimmer` and manually implementing the `#launch` method (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
595
623
|
|
596
624
|
```ruby
|
597
625
|
require 'glimmer-dsl-libui'
|
598
626
|
|
599
|
-
class
|
627
|
+
class SomeGlimmerApp
|
600
628
|
include Glimmer
|
601
629
|
|
602
630
|
def launch
|
@@ -610,7 +638,23 @@ class Application
|
|
610
638
|
end
|
611
639
|
end
|
612
640
|
|
613
|
-
|
641
|
+
SomeGlimmerApp.new.launch
|
642
|
+
```
|
643
|
+
|
644
|
+
Example including `Glimmer` at the top-level scope just for some prototyping/demoing/testing (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
645
|
+
|
646
|
+
```ruby
|
647
|
+
require 'glimmer-dsl-libui'
|
648
|
+
|
649
|
+
include Glimmer
|
650
|
+
|
651
|
+
window('hello world', 300, 200) {
|
652
|
+
button('Button') {
|
653
|
+
on_clicked do
|
654
|
+
puts 'Button Clicked'
|
655
|
+
end
|
656
|
+
}
|
657
|
+
}.show
|
614
658
|
```
|
615
659
|
|
616
660
|
If you are new to [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), check out [Girb](#girb-glimmer-irb) and [Examples](#examples) to quickly learn through copy/paste. You may refer to the [API](#api) later on once you have gotten your feet wet with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) and need more detailed reference information.
|
@@ -674,6 +718,7 @@ Keyword(Args) | Properties | Listeners
|
|
674
718
|
`checkbox_text_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
|
675
719
|
`checkbox_text_color_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None
|
676
720
|
`check_menu_item(text as String)` | `checked` (Boolean) | `on_clicked`
|
721
|
+
`code_area` | `language` (String) (default: `'ruby'`), `theme` (String) (default: `'glimmer'`), `code` (String) | None
|
677
722
|
`combobox` | `items` (`Array` of `String`), `selected` (`Integer`), `selected_item` (`String`) | `on_selected`
|
678
723
|
`color_button` | `color` (Array of `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float`), `red` as `Float`, `green` as `Float`, `blue` as `Float`, `alpha` as `Float` | `on_changed`
|
679
724
|
`date_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`, `mday` as `Integer`, `mon` as `Integer`, `year` as `Integer`, `wday` as `Integer`, `yday` as `Integer`, `dst` as Boolean) | `on_changed`
|
@@ -1598,15 +1643,15 @@ SpinnerExample.new.launch
|
|
1598
1643
|
|
1599
1644
|
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
1645
|
|
1601
|
-
For example, you can define a custom `
|
1602
|
-
|
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)`).
|
1646
|
+
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.
|
1604
1647
|
|
1605
|
-
|
1648
|
+
There are two ways to define custom keywords:
|
1649
|
+
- 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)`).
|
1650
|
+
- 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.
|
1606
1651
|
|
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`).
|
1652
|
+
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`).
|
1608
1653
|
|
1609
|
-
Example that defines `form_field`, `address_form`, `label_pair`, and `
|
1654
|
+
Example that defines `form_field`, `address_form`, `label_pair`, and `address_view` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
1610
1655
|
|
1611
1656
|
```ruby
|
1612
1657
|
require 'glimmer-dsl-libui'
|
@@ -1643,7 +1688,7 @@ def label_pair(model, attribute, value)
|
|
1643
1688
|
}
|
1644
1689
|
end
|
1645
1690
|
|
1646
|
-
def
|
1691
|
+
def address_view(address_model)
|
1647
1692
|
vertical_box {
|
1648
1693
|
address_model.each_pair do |attribute, value|
|
1649
1694
|
label_pair(address_model, attribute, value)
|
@@ -1673,7 +1718,7 @@ window('Method-Based Custom Keyword') {
|
|
1673
1718
|
stretchy false
|
1674
1719
|
}
|
1675
1720
|
|
1676
|
-
|
1721
|
+
address_view(address1)
|
1677
1722
|
}
|
1678
1723
|
|
1679
1724
|
vertical_separator {
|
@@ -1695,7 +1740,7 @@ window('Method-Based Custom Keyword') {
|
|
1695
1740
|
stretchy false
|
1696
1741
|
}
|
1697
1742
|
|
1698
|
-
|
1743
|
+
address_view(address2)
|
1699
1744
|
}
|
1700
1745
|
}
|
1701
1746
|
}.show
|
@@ -1703,6 +1748,136 @@ window('Method-Based Custom Keyword') {
|
|
1703
1748
|
|
1704
1749
|
![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
|
1705
1750
|
|
1751
|
+
You can also define Custom Window keywords, that is custom controls with `window` being the body root. These are also known as Applications. To define a Custom Window, you `include Glimmer::LibUI::CustomWindow` or `include Glimmer:LibUI::Application` and then you can invoke the `::launch` method on the class.
|
1752
|
+
|
1753
|
+
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
1754
|
+
|
1755
|
+
```ruby
|
1756
|
+
require 'glimmer-dsl-libui'
|
1757
|
+
require 'facets'
|
1758
|
+
|
1759
|
+
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
1760
|
+
|
1761
|
+
class FormField
|
1762
|
+
include Glimmer::LibUI::CustomControl
|
1763
|
+
|
1764
|
+
options :model, :attribute
|
1765
|
+
|
1766
|
+
body {
|
1767
|
+
entry { |e|
|
1768
|
+
label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
|
1769
|
+
text <=> [model, attribute]
|
1770
|
+
}
|
1771
|
+
}
|
1772
|
+
end
|
1773
|
+
|
1774
|
+
class AddressForm
|
1775
|
+
include Glimmer::LibUI::CustomControl
|
1776
|
+
|
1777
|
+
options :address
|
1778
|
+
|
1779
|
+
body {
|
1780
|
+
form {
|
1781
|
+
form_field(model: address, attribute: :street)
|
1782
|
+
form_field(model: address, attribute: :p_o_box)
|
1783
|
+
form_field(model: address, attribute: :city)
|
1784
|
+
form_field(model: address, attribute: :state)
|
1785
|
+
form_field(model: address, attribute: :zip_code)
|
1786
|
+
}
|
1787
|
+
}
|
1788
|
+
end
|
1789
|
+
|
1790
|
+
class LabelPair
|
1791
|
+
include Glimmer::LibUI::CustomControl
|
1792
|
+
|
1793
|
+
options :model, :attribute, :value
|
1794
|
+
|
1795
|
+
body {
|
1796
|
+
horizontal_box {
|
1797
|
+
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
|
1798
|
+
label(value.to_s) {
|
1799
|
+
text <= [model, attribute]
|
1800
|
+
}
|
1801
|
+
}
|
1802
|
+
}
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
class AddressView
|
1806
|
+
include Glimmer::LibUI::CustomControl
|
1807
|
+
|
1808
|
+
options :address
|
1809
|
+
|
1810
|
+
body {
|
1811
|
+
vertical_box {
|
1812
|
+
address.each_pair do |attribute, value|
|
1813
|
+
label_pair(model: address, attribute: attribute, value: value)
|
1814
|
+
end
|
1815
|
+
}
|
1816
|
+
}
|
1817
|
+
end
|
1818
|
+
|
1819
|
+
class ClassBasedCustomControls
|
1820
|
+
include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
|
1821
|
+
|
1822
|
+
before_body do
|
1823
|
+
@address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
1824
|
+
@address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
body {
|
1828
|
+
window('Class-Based Custom Keyword') {
|
1829
|
+
margined true
|
1830
|
+
|
1831
|
+
horizontal_box {
|
1832
|
+
vertical_box {
|
1833
|
+
label('Address 1') {
|
1834
|
+
stretchy false
|
1835
|
+
}
|
1836
|
+
|
1837
|
+
address_form(address: @address1)
|
1838
|
+
|
1839
|
+
horizontal_separator {
|
1840
|
+
stretchy false
|
1841
|
+
}
|
1842
|
+
|
1843
|
+
label('Address 1 (Saved)') {
|
1844
|
+
stretchy false
|
1845
|
+
}
|
1846
|
+
|
1847
|
+
address_view(address: @address1)
|
1848
|
+
}
|
1849
|
+
|
1850
|
+
vertical_separator {
|
1851
|
+
stretchy false
|
1852
|
+
}
|
1853
|
+
|
1854
|
+
vertical_box {
|
1855
|
+
label('Address 2') {
|
1856
|
+
stretchy false
|
1857
|
+
}
|
1858
|
+
|
1859
|
+
address_form(address: @address2)
|
1860
|
+
|
1861
|
+
horizontal_separator {
|
1862
|
+
stretchy false
|
1863
|
+
}
|
1864
|
+
|
1865
|
+
label('Address 2 (Saved)') {
|
1866
|
+
stretchy false
|
1867
|
+
}
|
1868
|
+
|
1869
|
+
address_view(address: @address2)
|
1870
|
+
}
|
1871
|
+
}
|
1872
|
+
}
|
1873
|
+
}
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
ClassBasedCustomControls.launch
|
1877
|
+
```
|
1878
|
+
|
1879
|
+
![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
|
1880
|
+
|
1706
1881
|
The [`area`](#area-api) control can be utilized to build non-native custom controls from scratch by leveraging vector graphics, formattable text, keyboard events, and mouse events. This is demonstrated in the [Area-Based Custom Controls](#area-based-custom-controls) example.
|
1707
1882
|
|
1708
1883
|
Defining custom keywords enables unlimited extension of the [Glimmer GUI DSL](#glimmer-gui-dsl). The sky is the limit on what can be done with custom keywords as a result. You can compose new visual vocabulary to build applications in any domain from higher concepts rather than [mere standard controls](#supported-keywords). For example, in a traffic signaling app, you could define `street`, `light_signal`, `traffic_sign`, and `car` as custom keywords and build your application from these concepts directly, saving enormous time and achieving much higher productivity.
|
@@ -1978,6 +2153,7 @@ Data-bound model attribute can be:
|
|
1978
2153
|
- **Direct:** `Symbol` representing attribute reader/writer (e.g. `[person, :name`])
|
1979
2154
|
- **Nested:** `String` representing nested attribute path (e.g. `[company, 'address.street']`). That results in "nested data-binding"
|
1980
2155
|
- **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
|
2156
|
+
- **Keyed:** `String` containing hash attribute key (e.g. `[customer, 'addresses[:main].street']`). That results in "keyed data-binding"
|
1981
2157
|
|
1982
2158
|
Data-binding options include:
|
1983
2159
|
- `before_read {|value| ...}`: performs an operation before reading data from Model to update View.
|
@@ -4855,6 +5031,62 @@ end
|
|
4855
5031
|
BasicDrawText.new.launch
|
4856
5032
|
```
|
4857
5033
|
|
5034
|
+
#### Basic Code Area
|
5035
|
+
|
5036
|
+
[examples/basic_code_area.rb](examples/basic_code_area.rb)
|
5037
|
+
|
5038
|
+
Run with this command from the root of the project if you cloned the project:
|
5039
|
+
|
5040
|
+
```
|
5041
|
+
ruby -r './lib/glimmer-dsl-libui' examples/basic_code_area.rb
|
5042
|
+
```
|
5043
|
+
|
5044
|
+
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
|
5045
|
+
|
5046
|
+
```
|
5047
|
+
ruby -r glimmer-dsl-libui -e "require 'examples/basic_code_area'"
|
5048
|
+
```
|
5049
|
+
|
5050
|
+
Mac | Windows | Linux
|
5051
|
+
----|---------|------
|
5052
|
+
![glimmer-dsl-libui-mac-basic-code-area.png](images/glimmer-dsl-libui-mac-basic-code-area.png) | ![glimmer-dsl-libui-windows-basic-code-area.png](images/glimmer-dsl-libui-windows-basic-code-area.png) | ![glimmer-dsl-libui-linux-basic-code-area.png](images/glimmer-dsl-libui-linux-basic-code-area.png)
|
5053
|
+
|
5054
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
5055
|
+
|
5056
|
+
```ruby
|
5057
|
+
require 'glimmer-dsl-libui'
|
5058
|
+
|
5059
|
+
class BasicCodeArea
|
5060
|
+
include Glimmer::LibUI::Application
|
5061
|
+
|
5062
|
+
before_body do
|
5063
|
+
@code = <<~CODE
|
5064
|
+
# Greets target with greeting
|
5065
|
+
def greet(greeting: 'Hello', target: 'World')
|
5066
|
+
|
5067
|
+
puts "\#{greeting}, \#{target}!"
|
5068
|
+
end
|
5069
|
+
|
5070
|
+
greet
|
5071
|
+
greet(target: 'Robert')
|
5072
|
+
greet(greeting: 'Aloha')
|
5073
|
+
greet(greeting: 'Aloha', target: 'Nancy')
|
5074
|
+
greet(greeting: 'Howdy', target: 'Doodle')
|
5075
|
+
CODE
|
5076
|
+
end
|
5077
|
+
|
5078
|
+
body {
|
5079
|
+
window('Basic Code Area', 400, 300) {
|
5080
|
+
margined true
|
5081
|
+
|
5082
|
+
code_area(language: 'ruby', code: @code)
|
5083
|
+
}
|
5084
|
+
}
|
5085
|
+
end
|
5086
|
+
|
5087
|
+
BasicCodeArea.launch
|
5088
|
+
```
|
5089
|
+
|
4858
5090
|
### Advanced Examples
|
4859
5091
|
|
4860
5092
|
#### Area Gallery
|
@@ -8580,26 +8812,26 @@ window('Login') {
|
|
8580
8812
|
}.show
|
8581
8813
|
```
|
8582
8814
|
|
8583
|
-
#### Method-Based Custom
|
8815
|
+
#### Method-Based Custom Controls
|
8584
8816
|
|
8585
8817
|
[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
8818
|
|
8587
|
-
This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom
|
8819
|
+
This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
|
8588
8820
|
|
8589
8821
|
The custom keywords are defined via methods (thus are "method-based").
|
8590
8822
|
|
8591
|
-
[examples/
|
8823
|
+
[examples/method_based_custom_controls.rb](examples/method_based_custom_controls.rb)
|
8592
8824
|
|
8593
8825
|
Run with this command from the root of the project if you cloned the project:
|
8594
8826
|
|
8595
8827
|
```
|
8596
|
-
ruby -r './lib/glimmer-dsl-libui' examples/
|
8828
|
+
ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_controls.rb
|
8597
8829
|
```
|
8598
8830
|
|
8599
8831
|
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
|
8600
8832
|
|
8601
8833
|
```
|
8602
|
-
ruby -r glimmer-dsl-libui -e "require 'examples/
|
8834
|
+
ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_controls'"
|
8603
8835
|
```
|
8604
8836
|
|
8605
8837
|
Mac | Windows | Linux
|
@@ -8624,13 +8856,13 @@ def form_field(model, attribute)
|
|
8624
8856
|
}
|
8625
8857
|
end
|
8626
8858
|
|
8627
|
-
def address_form(
|
8859
|
+
def address_form(address_model)
|
8628
8860
|
form {
|
8629
|
-
form_field(
|
8630
|
-
form_field(
|
8631
|
-
form_field(
|
8632
|
-
form_field(
|
8633
|
-
form_field(
|
8861
|
+
form_field(address_model, :street)
|
8862
|
+
form_field(address_model, :p_o_box)
|
8863
|
+
form_field(address_model, :city)
|
8864
|
+
form_field(address_model, :state)
|
8865
|
+
form_field(address_model, :zip_code)
|
8634
8866
|
}
|
8635
8867
|
end
|
8636
8868
|
|
@@ -8643,10 +8875,10 @@ def label_pair(model, attribute, value)
|
|
8643
8875
|
}
|
8644
8876
|
end
|
8645
8877
|
|
8646
|
-
def
|
8878
|
+
def address_view(address_model)
|
8647
8879
|
vertical_box {
|
8648
|
-
|
8649
|
-
label_pair(
|
8880
|
+
address_model.each_pair do |attribute, value|
|
8881
|
+
label_pair(address_model, attribute, value)
|
8650
8882
|
end
|
8651
8883
|
}
|
8652
8884
|
end
|
@@ -8654,7 +8886,7 @@ end
|
|
8654
8886
|
address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
8655
8887
|
address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
8656
8888
|
|
8657
|
-
window('Method-Based Custom
|
8889
|
+
window('Method-Based Custom Controls') {
|
8658
8890
|
margined true
|
8659
8891
|
|
8660
8892
|
horizontal_box {
|
@@ -8673,7 +8905,7 @@ window('Method-Based Custom Keyword') {
|
|
8673
8905
|
stretchy false
|
8674
8906
|
}
|
8675
8907
|
|
8676
|
-
|
8908
|
+
address_view(address1)
|
8677
8909
|
}
|
8678
8910
|
|
8679
8911
|
vertical_separator {
|
@@ -8695,7 +8927,7 @@ window('Method-Based Custom Keyword') {
|
|
8695
8927
|
stretchy false
|
8696
8928
|
}
|
8697
8929
|
|
8698
|
-
|
8930
|
+
address_view(address2)
|
8699
8931
|
}
|
8700
8932
|
}
|
8701
8933
|
}.show
|
@@ -8723,13 +8955,13 @@ def form_field(model, property)
|
|
8723
8955
|
}
|
8724
8956
|
end
|
8725
8957
|
|
8726
|
-
def address_form(
|
8958
|
+
def address_form(address_model)
|
8727
8959
|
form {
|
8728
|
-
form_field(
|
8729
|
-
form_field(
|
8730
|
-
form_field(
|
8731
|
-
form_field(
|
8732
|
-
form_field(
|
8960
|
+
form_field(address_model, :street)
|
8961
|
+
form_field(address_model, :p_o_box)
|
8962
|
+
form_field(address_model, :city)
|
8963
|
+
form_field(address_model, :state)
|
8964
|
+
form_field(address_model, :zip_code)
|
8733
8965
|
}
|
8734
8966
|
end
|
8735
8967
|
|
@@ -8745,10 +8977,10 @@ def label_pair(model, attribute, value)
|
|
8745
8977
|
end
|
8746
8978
|
end
|
8747
8979
|
|
8748
|
-
def
|
8980
|
+
def address_view(address_model)
|
8749
8981
|
vertical_box {
|
8750
|
-
|
8751
|
-
label_pair(
|
8982
|
+
address_model.each_pair do |attribute, value|
|
8983
|
+
label_pair(address_model, attribute, value)
|
8752
8984
|
end
|
8753
8985
|
}
|
8754
8986
|
end
|
@@ -8756,7 +8988,7 @@ end
|
|
8756
8988
|
address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
8757
8989
|
address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
8758
8990
|
|
8759
|
-
window('Method-Based Custom
|
8991
|
+
window('Method-Based Custom Controls') {
|
8760
8992
|
margined true
|
8761
8993
|
|
8762
8994
|
horizontal_box {
|
@@ -8775,7 +9007,7 @@ window('Method-Based Custom Keyword') {
|
|
8775
9007
|
stretchy false
|
8776
9008
|
}
|
8777
9009
|
|
8778
|
-
|
9010
|
+
address_view(address1)
|
8779
9011
|
}
|
8780
9012
|
|
8781
9013
|
vertical_separator {
|
@@ -8797,12 +9029,164 @@ window('Method-Based Custom Keyword') {
|
|
8797
9029
|
stretchy false
|
8798
9030
|
}
|
8799
9031
|
|
8800
|
-
|
9032
|
+
address_view(address2)
|
8801
9033
|
}
|
8802
9034
|
}
|
8803
9035
|
}.show
|
8804
9036
|
```
|
8805
9037
|
|
9038
|
+
#### Class-Based Custom Controls
|
9039
|
+
|
9040
|
+
[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.
|
9041
|
+
|
9042
|
+
This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
|
9043
|
+
|
9044
|
+
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.
|
9045
|
+
|
9046
|
+
[examples/class_based_custom_controls.rb](examples/class_based_custom_controls.rb)
|
9047
|
+
|
9048
|
+
Run with this command from the root of the project if you cloned the project:
|
9049
|
+
|
9050
|
+
```
|
9051
|
+
ruby -r './lib/glimmer-dsl-libui' examples/class_based_custom_controls.rb
|
9052
|
+
```
|
9053
|
+
|
9054
|
+
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
|
9055
|
+
|
9056
|
+
```
|
9057
|
+
ruby -r glimmer-dsl-libui -e "require 'examples/class_based_custom_controls'"
|
9058
|
+
```
|
9059
|
+
|
9060
|
+
Mac | Windows | Linux
|
9061
|
+
----|---------|------
|
9062
|
+
![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)
|
9063
|
+
|
9064
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
9065
|
+
|
9066
|
+
```ruby
|
9067
|
+
require 'glimmer-dsl-libui'
|
9068
|
+
require 'facets'
|
9069
|
+
|
9070
|
+
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
9071
|
+
|
9072
|
+
class FormField
|
9073
|
+
include Glimmer::LibUI::CustomControl
|
9074
|
+
|
9075
|
+
options :model, :attribute
|
9076
|
+
|
9077
|
+
body {
|
9078
|
+
entry { |e|
|
9079
|
+
label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
|
9080
|
+
text <=> [model, attribute]
|
9081
|
+
}
|
9082
|
+
}
|
9083
|
+
end
|
9084
|
+
|
9085
|
+
class AddressForm
|
9086
|
+
include Glimmer::LibUI::CustomControl
|
9087
|
+
|
9088
|
+
options :address
|
9089
|
+
|
9090
|
+
body {
|
9091
|
+
form {
|
9092
|
+
form_field(model: address, attribute: :street)
|
9093
|
+
form_field(model: address, attribute: :p_o_box)
|
9094
|
+
form_field(model: address, attribute: :city)
|
9095
|
+
form_field(model: address, attribute: :state)
|
9096
|
+
form_field(model: address, attribute: :zip_code)
|
9097
|
+
}
|
9098
|
+
}
|
9099
|
+
end
|
9100
|
+
|
9101
|
+
class LabelPair
|
9102
|
+
include Glimmer::LibUI::CustomControl
|
9103
|
+
|
9104
|
+
options :model, :attribute, :value
|
9105
|
+
|
9106
|
+
body {
|
9107
|
+
horizontal_box {
|
9108
|
+
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
|
9109
|
+
label(value.to_s) {
|
9110
|
+
text <= [model, attribute]
|
9111
|
+
}
|
9112
|
+
}
|
9113
|
+
}
|
9114
|
+
end
|
9115
|
+
|
9116
|
+
class AddressView
|
9117
|
+
include Glimmer::LibUI::CustomControl
|
9118
|
+
|
9119
|
+
options :address
|
9120
|
+
|
9121
|
+
body {
|
9122
|
+
vertical_box {
|
9123
|
+
address.each_pair do |attribute, value|
|
9124
|
+
label_pair(model: address, attribute: attribute, value: value)
|
9125
|
+
end
|
9126
|
+
}
|
9127
|
+
}
|
9128
|
+
end
|
9129
|
+
|
9130
|
+
class ClassBasedCustomControls
|
9131
|
+
include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
|
9132
|
+
|
9133
|
+
before_body do
|
9134
|
+
@address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
9135
|
+
@address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
9136
|
+
end
|
9137
|
+
|
9138
|
+
body {
|
9139
|
+
window('Class-Based Custom Keyword') {
|
9140
|
+
margined true
|
9141
|
+
|
9142
|
+
horizontal_box {
|
9143
|
+
vertical_box {
|
9144
|
+
label('Address 1') {
|
9145
|
+
stretchy false
|
9146
|
+
}
|
9147
|
+
|
9148
|
+
address_form(address: @address1)
|
9149
|
+
|
9150
|
+
horizontal_separator {
|
9151
|
+
stretchy false
|
9152
|
+
}
|
9153
|
+
|
9154
|
+
label('Address 1 (Saved)') {
|
9155
|
+
stretchy false
|
9156
|
+
}
|
9157
|
+
|
9158
|
+
address_view(address: @address1)
|
9159
|
+
}
|
9160
|
+
|
9161
|
+
vertical_separator {
|
9162
|
+
stretchy false
|
9163
|
+
}
|
9164
|
+
|
9165
|
+
vertical_box {
|
9166
|
+
label('Address 2') {
|
9167
|
+
stretchy false
|
9168
|
+
}
|
9169
|
+
|
9170
|
+
address_form(address: @address2)
|
9171
|
+
|
9172
|
+
horizontal_separator {
|
9173
|
+
stretchy false
|
9174
|
+
}
|
9175
|
+
|
9176
|
+
label('Address 2 (Saved)') {
|
9177
|
+
stretchy false
|
9178
|
+
}
|
9179
|
+
|
9180
|
+
address_view(address: @address2)
|
9181
|
+
}
|
9182
|
+
}
|
9183
|
+
}
|
9184
|
+
}
|
9185
|
+
end
|
9186
|
+
|
9187
|
+
ClassBasedCustomControls.launch
|
9188
|
+
```
|
9189
|
+
|
8806
9190
|
#### Area-Based Custom Controls
|
8807
9191
|
|
8808
9192
|
[Custom keywords](#custom-keywords) can be defined for graphical custom controls (components) built completely from scratch as vector-graphics on top of the [`area`](#area-api) control while leveraging keyboard and mouse listeners.
|
@@ -9514,6 +9898,8 @@ Snake provides an example of building a desktop application [test-first](/spec/e
|
|
9514
9898
|
|
9515
9899
|
Use arrows to move and spacebar to pause/resume.
|
9516
9900
|
|
9901
|
+
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.
|
9902
|
+
|
9517
9903
|
[examples/snake.rb](examples/snake.rb)
|
9518
9904
|
|
9519
9905
|
Run with this command from the root of the project if you cloned the project:
|
@@ -9738,6 +10124,8 @@ Snake.new.launch
|
|
9738
10124
|
|
9739
10125
|
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
10126
|
|
10127
|
+
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.
|
10128
|
+
|
9741
10129
|
[examples/tetris.rb](examples/tetris.rb)
|
9742
10130
|
|
9743
10131
|
Run with this command from the root of the project if you cloned the project:
|