glimmer-dsl-libui 0.4.2 → 0.4.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 +33 -0
- data/README.md +1359 -200
- data/VERSION +1 -1
- data/examples/basic_entry.rb +27 -24
- data/examples/basic_entry2.rb +31 -0
- data/examples/color_button.rb +18 -13
- data/examples/color_button2.rb +14 -0
- data/examples/dynamic_area.rb +77 -90
- data/examples/dynamic_area2.rb +14 -12
- data/examples/dynamic_area3.rb +90 -0
- data/examples/dynamic_area4.rb +95 -0
- data/examples/font_button.rb +17 -12
- data/examples/font_button2.rb +18 -0
- data/examples/form.rb +42 -30
- data/examples/form2.rb +37 -0
- data/examples/form_table.rb +100 -87
- data/examples/form_table2.rb +93 -0
- data/examples/histogram.rb +93 -91
- data/examples/histogram2.rb +109 -0
- data/examples/login.rb +45 -39
- data/examples/login2.rb +55 -0
- data/examples/login3.rb +65 -0
- data/examples/login4.rb +61 -0
- data/examples/login5.rb +43 -0
- data/examples/meta_example.rb +9 -6
- data/examples/method_based_custom_keyword.rb +8 -15
- data/examples/method_based_custom_keyword2.rb +97 -0
- data/examples/timer.rb +28 -31
- data/examples/timer2.rb +129 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/data_binding_expression.rb +4 -6
- data/lib/glimmer/libui/attributed_string.rb +3 -0
- data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +5 -0
- data/lib/glimmer/libui/control_proxy/entry_proxy.rb +5 -0
- data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +4 -0
- data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +5 -0
- data/lib/glimmer/libui/control_proxy/slider_proxy.rb +38 -0
- data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +38 -0
- data/lib/glimmer/libui/control_proxy.rb +4 -0
- data/lib/glimmer/libui/data_bindable.rb +39 -0
- data/lib/glimmer/libui/shape.rb +2 -0
- metadata +19 -2
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.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)
|
@@ -14,11 +14,11 @@ Mac | Windows | Linux
|
|
14
14
|
The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) as opposed to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) or [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk) is the fact that [SWT](https://www.eclipse.org/swt/) and [Tk](https://www.tcl.tk/) are more mature than mid-alpha [libui](https://github.com/andlabs/libui) as GUI toolkits. Still, if there is only a need to build a small simple application, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) could be a good convenient choice due to having zero prerequisites beyond the dependencies included in the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui). Also, just like [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk), its apps start instantly and have a small memory footprint. [LibUI](https://github.com/kojix2/LibUI) is a promising new GUI toolkit that might prove quite worthy in the future.
|
15
15
|
|
16
16
|
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) aims to provide a DSL similar to the [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) to enable more productive desktop development in Ruby with:
|
17
|
-
- Declarative DSL syntax that visually maps to the GUI control hierarchy
|
18
|
-
- Convention over configuration via smart defaults and automation of low-level details
|
19
|
-
- Requiring the least amount of syntax possible to build GUI
|
20
|
-
- Custom Keyword support
|
21
|
-
- Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with
|
17
|
+
- [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy
|
18
|
+
- [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details
|
19
|
+
- Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI
|
20
|
+
- [Custom Keyword](#custom-keywords) support
|
21
|
+
- [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models
|
22
22
|
- [Far Future Plan] Scaffolding for new custom controls, apps, and gems
|
23
23
|
- [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
|
24
24
|
|
@@ -373,7 +373,7 @@ gem install glimmer-dsl-libui
|
|
373
373
|
Or install via Bundler `Gemfile`:
|
374
374
|
|
375
375
|
```ruby
|
376
|
-
gem 'glimmer-dsl-libui', '~> 0.4.
|
376
|
+
gem 'glimmer-dsl-libui', '~> 0.4.6'
|
377
377
|
```
|
378
378
|
|
379
379
|
Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
|
@@ -620,93 +620,112 @@ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
|
620
620
|
```ruby
|
621
621
|
require 'glimmer-dsl-libui'
|
622
622
|
|
623
|
-
|
624
|
-
|
625
|
-
data = [
|
626
|
-
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
|
627
|
-
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
|
628
|
-
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
|
629
|
-
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
|
630
|
-
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
|
631
|
-
]
|
632
|
-
|
633
|
-
window('Contacts', 600, 600) { |w|
|
634
|
-
margined true
|
623
|
+
class FormTable
|
624
|
+
include Glimmer
|
635
625
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
@
|
641
|
-
|
642
|
-
|
643
|
-
@
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
}
|
652
|
-
@state_entry = entry {
|
653
|
-
label 'State'
|
654
|
-
}
|
655
|
-
}
|
656
|
-
|
657
|
-
button('Save Contact') {
|
658
|
-
stretchy false
|
659
|
-
|
660
|
-
on_clicked do
|
661
|
-
new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
|
662
|
-
if new_row.include?('')
|
663
|
-
msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
|
664
|
-
else
|
665
|
-
data << new_row # automatically inserts a row into the table due to implicit data-binding
|
666
|
-
@unfiltered_data = data.dup
|
667
|
-
@name_entry.text = ''
|
668
|
-
@email_entry.text = ''
|
669
|
-
@phone_entry.text = ''
|
670
|
-
@city_entry.text = ''
|
671
|
-
@state_entry.text = ''
|
672
|
-
end
|
673
|
-
end
|
674
|
-
}
|
675
|
-
|
676
|
-
search_entry { |se|
|
677
|
-
stretchy false
|
626
|
+
attr_accessor :name, :email, :phone, :city, :state, :filter_value
|
627
|
+
|
628
|
+
def initialize
|
629
|
+
@data = [
|
630
|
+
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
|
631
|
+
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
|
632
|
+
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
|
633
|
+
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
|
634
|
+
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
|
635
|
+
]
|
636
|
+
end
|
637
|
+
|
638
|
+
def launch
|
639
|
+
window('Contacts', 600, 600) { |w|
|
640
|
+
margined true
|
678
641
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
642
|
+
vertical_box {
|
643
|
+
form {
|
644
|
+
stretchy false
|
645
|
+
|
646
|
+
entry {
|
647
|
+
label 'Name'
|
648
|
+
text <=> [self, :name]
|
649
|
+
}
|
650
|
+
|
651
|
+
entry {
|
652
|
+
label 'Email'
|
653
|
+
text <=> [self, :email]
|
654
|
+
}
|
655
|
+
|
656
|
+
entry {
|
657
|
+
label 'Phone'
|
658
|
+
text <=> [self, :phone]
|
659
|
+
}
|
660
|
+
|
661
|
+
entry {
|
662
|
+
label 'City'
|
663
|
+
text <=> [self, :city]
|
664
|
+
}
|
665
|
+
|
666
|
+
entry {
|
667
|
+
label 'State'
|
668
|
+
text <=> [self, :state]
|
669
|
+
}
|
670
|
+
}
|
671
|
+
|
672
|
+
button('Save Contact') {
|
673
|
+
stretchy false
|
674
|
+
|
675
|
+
on_clicked do
|
676
|
+
new_row = [name, email, phone, city, state]
|
677
|
+
if new_row.include?('')
|
678
|
+
msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
|
679
|
+
else
|
680
|
+
@data << new_row # automatically inserts a row into the table due to implicit data-binding
|
681
|
+
@unfiltered_data = @data.dup
|
682
|
+
self.name = '' # automatically clears name entry through explicit data-binding
|
683
|
+
self.email = ''
|
684
|
+
self.phone = ''
|
685
|
+
self.city = ''
|
686
|
+
self.state = ''
|
689
687
|
end
|
690
688
|
end
|
691
|
-
|
692
|
-
|
693
|
-
|
689
|
+
}
|
690
|
+
|
691
|
+
search_entry {
|
692
|
+
stretchy false
|
693
|
+
text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
|
694
|
+
after_write: ->(filter_value) { # execute after write to self.filter_value
|
695
|
+
@unfiltered_data ||= @data.dup
|
696
|
+
# Unfilter first to remove any previous filters
|
697
|
+
@data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
|
698
|
+
# Now, apply filter if entered
|
699
|
+
unless filter_value.empty?
|
700
|
+
@data.filter! do |row_data| # affects table indirectly through implicit data-binding
|
701
|
+
row_data.any? do |cell|
|
702
|
+
cell.to_s.downcase.include?(filter_value.downcase)
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
}
|
707
|
+
]
|
708
|
+
}
|
709
|
+
|
710
|
+
table {
|
711
|
+
text_column('Name')
|
712
|
+
text_column('Email')
|
713
|
+
text_column('Phone')
|
714
|
+
text_column('City')
|
715
|
+
text_column('State')
|
694
716
|
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
717
|
+
cell_rows @data # implicit data-binding
|
718
|
+
|
719
|
+
on_changed do |row, type, row_data|
|
720
|
+
puts "Row #{row} #{type}: #{row_data}"
|
721
|
+
end
|
722
|
+
}
|
723
|
+
}
|
724
|
+
}.show
|
725
|
+
end
|
726
|
+
end
|
701
727
|
|
702
|
-
|
703
|
-
|
704
|
-
on_changed do |row, type, row_data|
|
705
|
-
puts "Row #{row} #{type}: #{row_data}"
|
706
|
-
end
|
707
|
-
}
|
708
|
-
}
|
709
|
-
}.show
|
728
|
+
FormTable.new.launch
|
710
729
|
```
|
711
730
|
|
712
731
|
![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
|
@@ -779,9 +798,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
|
|
779
798
|
- `scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)`: scrolls to `x`/`y` location with `width` and `height` viewport size.
|
780
799
|
- `set_size(width as Numeric, height as Numeric)`: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
|
781
800
|
|
782
|
-
Mac |
|
783
|
-
|
784
|
-
![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png)
|
801
|
+
Mac |Linux
|
802
|
+
----|-----
|
803
|
+
![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
|
785
804
|
|
786
805
|
Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
|
787
806
|
|
@@ -877,7 +896,7 @@ Given that it is very new and is not a [libui](https://github.com/andlabs/libui)
|
|
877
896
|
- Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
|
878
897
|
- Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image.
|
879
898
|
|
880
|
-
Currently, it is recommended to use `image` with very small `width` and `height` values only.
|
899
|
+
Currently, it is recommended to use `image` with very small `width` and `height` values only (e.g. 24x24).
|
881
900
|
|
882
901
|
Setting a [`transform` `matrix`](#area-transform-matrix) is supported under `image` just like it is under `path` and `text` inside `area`.
|
883
902
|
|
@@ -1033,6 +1052,8 @@ window('Basic Image', 96, 96) {
|
|
1033
1052
|
|
1034
1053
|
One final note is that in Linux, table images grow and shrink with the image size unlike on the Mac where table row heights are constant regardless of image sizes. As such, you may be able to repurpose a table with a single image column and a single row as an image control with more native libui rendering if you are only targeting Linux with your app.
|
1035
1054
|
|
1055
|
+
![linux table image](images/glimmer-dsl-libui-linux-basic-table-image.png)
|
1056
|
+
|
1036
1057
|
Check out [examples/basic_image.rb](#basic-image) (all versions) for examples of using `image` Glimmer custom control.
|
1037
1058
|
|
1038
1059
|
#### Colors
|
@@ -1329,7 +1350,7 @@ The [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) (a
|
|
1329
1350
|
|
1330
1351
|
![MVC - Model View Controller](https://www.researchgate.net/profile/Danny-Weyns/publication/269303611/figure/fig2/AS:858133056462866@1581606272800/Smalltalk80-MVC-pattern-View-and-Controller-work-as-a-pair-allowing-the-user-to-interact.ppm)
|
1331
1352
|
|
1332
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) via the `observe(model,
|
1353
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) via the `observe(model, attribute_or_key=nil)` keyword, which can observe `Object` models with attributes, `Hash`es with keys, and `Array`s. It automatically enhances objects as needed to support automatically notifying observers of changes via `observable#notify_observers(attribute_or_key = nil)` method:
|
1333
1354
|
- `Object` becomes `Glimmer::DataBinding::ObservableModel`, which supports observing specified `Object` model attributes.
|
1334
1355
|
- `Hash` becomes `Glimmer::DataBinding::ObservableHash`, which supports observing all `Hash` keys or a specific `Hash` key
|
1335
1356
|
- `Array` becomes `Glimmer::DataBinding::ObservableArray`, which supports observing `Array` changes like those done with `push`, `<<`, `delete`, and `map!` methods (all mutation methods).
|
@@ -1350,13 +1371,49 @@ See examples of the `observe` keyword at [Color The Circles](#color-the-circles)
|
|
1350
1371
|
|
1351
1372
|
### Data-Binding
|
1352
1373
|
|
1353
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports
|
1374
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports both bidirectional (two-way) data-binding and unidirectional (one-way) data-binding.
|
1354
1375
|
|
1355
|
-
|
1376
|
+
Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes without writing many lines or pages of imperative code doing the same thing, increasing productivity immensely.
|
1356
1377
|
|
1357
|
-
|
1378
|
+
Data-binding automatically takes advantage of the [Observer Pattern](#observer-pattern) behind the scenes and is very well suited to declaring View property data sources piecemeal. On the other hand, explicit use of the [Observer Pattern](#observer-pattern) is sometimes more suitable when needing to make multiple View updates upon a single Model attribute change.
|
1358
1379
|
|
1359
|
-
|
1380
|
+
Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) flavor of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) by observing both the View and a Presenter for changes and updating the opposite side upon encountering them. This enables writing more decoupled cleaner code that keeps View code and Model code disentangled and highly maintainable. For example, check out the Snake game presenters for [Grid](/examples/snake/presenter/grid.rb) and [Cell](/examples/snake/presenter/cell.rb), which act as proxies for the actual Snake game models [Snake](/examples/snake/model/snake.rb) and [Apple](/examples/snake/model/apple.rb), mediating synchronization of data between them and the [Snake View GUI](/examples/snake.rb).
|
1381
|
+
|
1382
|
+
![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
|
1383
|
+
|
1384
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports bidirectional (two-way) data-binding of the following controls/properties via the `<=>` operator (indicating data is moving in both directions between View and Model):
|
1385
|
+
- `color_button`: `color`
|
1386
|
+
- `entry`: `text`
|
1387
|
+
- `font_button`: `font`
|
1388
|
+
- `multiline_entry`: `text`
|
1389
|
+
- `non_wrapping_multiline_entry`: `text`
|
1390
|
+
- `search_entry`: `text`
|
1391
|
+
- `slider`: `value`
|
1392
|
+
- `spinbox`: `value`
|
1393
|
+
|
1394
|
+
Example of bidirectional data-binding:
|
1395
|
+
|
1396
|
+
```ruby
|
1397
|
+
entry {
|
1398
|
+
text <=> [contract, :legal_text]
|
1399
|
+
}
|
1400
|
+
```
|
1401
|
+
|
1402
|
+
That is data-binding a contract's legal text to an `entry` `text` property.
|
1403
|
+
|
1404
|
+
Another example of bidirectional data-binding with an option:
|
1405
|
+
|
1406
|
+
```ruby
|
1407
|
+
entry {
|
1408
|
+
text <=> [self, :entered_text, after_write: ->(text) {puts text}]
|
1409
|
+
}
|
1410
|
+
```
|
1411
|
+
|
1412
|
+
That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
|
1413
|
+
|
1414
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` operator (indicating data is moving from the right side, which is the Model, to the left side, which is the GUI View object).
|
1415
|
+
|
1416
|
+
Example of unidirectional data-binding:
|
1360
1417
|
|
1361
1418
|
```ruby
|
1362
1419
|
square(0, 0, CELL_SIZE) {
|
@@ -1366,7 +1423,7 @@ square(0, 0, CELL_SIZE) {
|
|
1366
1423
|
|
1367
1424
|
That is data-binding a grid cell color to a `square` shape's `fill` property. That means if the `color` attribute of the grid cell is updated, the `fill` property of the `square` shape is automatically updated accordingly.
|
1368
1425
|
|
1369
|
-
Another Example:
|
1426
|
+
Another Example of unidirectional data-binding with an option:
|
1370
1427
|
|
1371
1428
|
```ruby
|
1372
1429
|
window {
|
@@ -1376,14 +1433,36 @@ window {
|
|
1376
1433
|
|
1377
1434
|
That is data-binding the `window` `title` property to the `score` attribute of a `@game`, but converting on read from the Model to a `String`.
|
1378
1435
|
|
1379
|
-
|
1436
|
+
To summarize the data-binding API:
|
1437
|
+
- `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
|
1438
|
+
- `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
|
1439
|
+
|
1440
|
+
This is also known as the [Glimmer Shine](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax for data-binding, a [Glimmer](https://github.com/AndyObtiva/glimmer)-only unique innovation that takes advantage of [Ruby](https://www.ruby-lang.org/en/)'s highly expressive syntax and malleable DSL support.
|
1380
1441
|
|
1381
|
-
|
1442
|
+
Data-binding options include:
|
1382
1443
|
- `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
|
1383
1444
|
- `on_read {|value| ...}`: converts value read from Model to update the View.
|
1384
1445
|
- `after_read {|converted_value| ...}`: performs an operation after read from Model and updating the View.
|
1446
|
+
- `before_write {|value| ...}`: performs an operation before writing data to Model from View.
|
1447
|
+
- `on_write {|value| ...}`: converts value read from View to update the Model.
|
1448
|
+
- `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
|
1449
|
+
- `computed_by attribute` or `computed_by [attribute1, attribute2, ...]`: indicates model attribute is computed from specified attribute(s), thus updated when they are updated (see in [Login example version 2](/examples/login2.rb))
|
1450
|
+
|
1451
|
+
Note that with both `on_read` and `on_write` converters, you could pass a `Symbol` representing the name of a method on the value object to invoke.
|
1452
|
+
|
1453
|
+
Example:
|
1454
|
+
|
1455
|
+
```ruby
|
1456
|
+
entry {
|
1457
|
+
text <=> [product, :price, on_read: :to_s, on_write: :to_i]
|
1458
|
+
}
|
1459
|
+
```
|
1460
|
+
|
1461
|
+
Data-binding gotchas:
|
1462
|
+
- Never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
|
1463
|
+
- Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so you cannot hook into the listener directly anymore as that would negate data-binding. Instead, you can add an `after_write: ->(val) {}` option to perform something on trigger of the control listener instead.
|
1385
1464
|
|
1386
|
-
Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
|
1465
|
+
Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
|
1387
1466
|
|
1388
1467
|
### API Gotchas
|
1389
1468
|
|
@@ -1799,7 +1878,7 @@ Example:
|
|
1799
1878
|
|
1800
1879
|
## Examples
|
1801
1880
|
|
1802
|
-
The following examples include reimplementions of the examples in the [LibUI](https://github.com/kojix2/LibUI) project utilizing the [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) as well as brand new examples.
|
1881
|
+
The following examples include reimplementions of the examples in the [LibUI](https://github.com/kojix2/LibUI) project utilizing the [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) (with and without data-binding) as well as brand new examples.
|
1803
1882
|
|
1804
1883
|
To browse all examples, simply launch the [Meta-Example](examples/meta_example.rb), which lists all examples and displays each example's code when selected. It also enables code editing to facilitate experimentation and learning.
|
1805
1884
|
|
@@ -1826,17 +1905,23 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
|
|
1826
1905
|
```ruby
|
1827
1906
|
require 'glimmer-dsl-libui'
|
1828
1907
|
require 'facets'
|
1908
|
+
require 'fileutils'
|
1829
1909
|
|
1830
1910
|
class MetaExample
|
1831
1911
|
include Glimmer
|
1832
1912
|
|
1913
|
+
ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
|
1914
|
+
|
1915
|
+
attr_accessor :code_text
|
1916
|
+
|
1833
1917
|
def initialize
|
1834
|
-
@selected_example_index =
|
1918
|
+
@selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
|
1919
|
+
@code_text = File.read(file_path_for(selected_example))
|
1835
1920
|
end
|
1836
1921
|
|
1837
1922
|
def examples
|
1838
1923
|
if @examples.nil?
|
1839
|
-
example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '
|
1924
|
+
example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
|
1840
1925
|
example_file_names = example_files.map { |f| File.basename(f, '.rb') }
|
1841
1926
|
example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
|
1842
1927
|
@examples = example_file_names.map { |f| f.underscore.titlecase }
|
@@ -1850,12 +1935,20 @@ class MetaExample
|
|
1850
1935
|
end
|
1851
1936
|
end
|
1852
1937
|
|
1938
|
+
def basic_examples_with_versions
|
1939
|
+
examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
def advanced_examples_with_versions
|
1943
|
+
examples_with_versions - basic_examples_with_versions
|
1944
|
+
end
|
1945
|
+
|
1853
1946
|
def file_path_for(example)
|
1854
1947
|
File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
|
1855
1948
|
end
|
1856
1949
|
|
1857
1950
|
def version_count_for(example)
|
1858
|
-
Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(
|
1951
|
+
Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
|
1859
1952
|
end
|
1860
1953
|
|
1861
1954
|
def glimmer_dsl_libui_file
|
@@ -1891,17 +1984,47 @@ class MetaExample
|
|
1891
1984
|
vertical_box {
|
1892
1985
|
stretchy false
|
1893
1986
|
|
1894
|
-
|
1987
|
+
tab {
|
1895
1988
|
stretchy false
|
1896
|
-
items examples_with_versions
|
1897
|
-
selected @selected_example_index
|
1898
1989
|
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1990
|
+
tab_item('Basic') {
|
1991
|
+
vertical_box {
|
1992
|
+
@basic_example_radio_buttons = radio_buttons {
|
1993
|
+
stretchy false
|
1994
|
+
items basic_examples_with_versions
|
1995
|
+
selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
|
1996
|
+
|
1997
|
+
on_selected do
|
1998
|
+
@selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
|
1999
|
+
example = selected_example
|
2000
|
+
self.code_text = File.read(file_path_for(example))
|
2001
|
+
@version_spinbox.value = 1
|
2002
|
+
end
|
2003
|
+
}
|
2004
|
+
|
2005
|
+
label # filler
|
2006
|
+
label # filler
|
2007
|
+
}
|
2008
|
+
}
|
2009
|
+
|
2010
|
+
tab_item('Advanced') {
|
2011
|
+
vertical_box {
|
2012
|
+
@advanced_example_radio_buttons = radio_buttons {
|
2013
|
+
stretchy false
|
2014
|
+
items advanced_examples_with_versions
|
2015
|
+
|
2016
|
+
on_selected do
|
2017
|
+
@selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
|
2018
|
+
example = selected_example
|
2019
|
+
self.code_text = File.read(file_path_for(example))
|
2020
|
+
@version_spinbox.value = 1
|
2021
|
+
end
|
2022
|
+
}
|
2023
|
+
|
2024
|
+
label # filler
|
2025
|
+
label # filler
|
2026
|
+
}
|
2027
|
+
}
|
1905
2028
|
}
|
1906
2029
|
|
1907
2030
|
horizontal_box {
|
@@ -1919,7 +2042,7 @@ class MetaExample
|
|
1919
2042
|
else
|
1920
2043
|
version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
|
1921
2044
|
example = "#{selected_example}#{version_number}"
|
1922
|
-
|
2045
|
+
self.code_text = File.read(file_path_for(example))
|
1923
2046
|
end
|
1924
2047
|
end
|
1925
2048
|
}
|
@@ -1931,9 +2054,15 @@ class MetaExample
|
|
1931
2054
|
button('Launch') {
|
1932
2055
|
on_clicked do
|
1933
2056
|
begin
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
2057
|
+
parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
|
2058
|
+
FileUtils.mkdir_p(parent_dir)
|
2059
|
+
example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
|
2060
|
+
File.write(example_file, code_text)
|
2061
|
+
example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
|
2062
|
+
FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
|
2063
|
+
FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
|
2064
|
+
FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
|
2065
|
+
run_example(example_file)
|
1937
2066
|
rescue => e
|
1938
2067
|
puts e.full_message
|
1939
2068
|
puts 'Unable to write code changes! Running original example...'
|
@@ -1943,14 +2072,14 @@ class MetaExample
|
|
1943
2072
|
}
|
1944
2073
|
button('Reset') {
|
1945
2074
|
on_clicked do
|
1946
|
-
|
2075
|
+
self.code_text = File.read(file_path_for(selected_example))
|
1947
2076
|
end
|
1948
2077
|
}
|
1949
2078
|
}
|
1950
2079
|
}
|
1951
2080
|
|
1952
2081
|
@code_entry = non_wrapping_multiline_entry {
|
1953
|
-
text
|
2082
|
+
text <=> [self, :code_text]
|
1954
2083
|
}
|
1955
2084
|
}
|
1956
2085
|
}.show
|
@@ -2169,7 +2298,44 @@ UI.main
|
|
2169
2298
|
UI.quit
|
2170
2299
|
```
|
2171
2300
|
|
2172
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2301
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2302
|
+
|
2303
|
+
```ruby
|
2304
|
+
require 'glimmer-dsl-libui'
|
2305
|
+
|
2306
|
+
class BasicEntry
|
2307
|
+
include Glimmer
|
2308
|
+
|
2309
|
+
attr_accessor :entry_text
|
2310
|
+
|
2311
|
+
def launch
|
2312
|
+
window('Basic Entry', 300, 50) {
|
2313
|
+
horizontal_box {
|
2314
|
+
entry {
|
2315
|
+
# stretchy true # Smart default option for appending to horizontal_box
|
2316
|
+
text <=> [self, :entry_text, after_write: ->(text) {puts text; $stdout.flush}] # bidirectional data-binding between text property and entry_text attribute, printing after write to model.
|
2317
|
+
}
|
2318
|
+
|
2319
|
+
button('Button') {
|
2320
|
+
stretchy false # stretchy property is available when control is nested under horizontal_box
|
2321
|
+
|
2322
|
+
on_clicked do
|
2323
|
+
msg_box('You entered', entry_text)
|
2324
|
+
end
|
2325
|
+
}
|
2326
|
+
}
|
2327
|
+
|
2328
|
+
on_closing do
|
2329
|
+
puts 'Bye Bye'
|
2330
|
+
end
|
2331
|
+
}.show
|
2332
|
+
end
|
2333
|
+
end
|
2334
|
+
|
2335
|
+
BasicEntry.new.launch
|
2336
|
+
```
|
2337
|
+
|
2338
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2173
2339
|
|
2174
2340
|
```ruby
|
2175
2341
|
require 'glimmer-dsl-libui'
|
@@ -2327,7 +2493,33 @@ UI.quit
|
|
2327
2493
|
|
2328
2494
|
```
|
2329
2495
|
|
2330
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2496
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2497
|
+
|
2498
|
+
```ruby
|
2499
|
+
require 'glimmer-dsl-libui'
|
2500
|
+
|
2501
|
+
class FontButton
|
2502
|
+
include Glimmer
|
2503
|
+
|
2504
|
+
attr_accessor :font_descriptor
|
2505
|
+
|
2506
|
+
def launch
|
2507
|
+
window('hello world', 300, 200) {
|
2508
|
+
font_button {
|
2509
|
+
font <=> [self, :font_descriptor, after_write: -> { p font_descriptor }]
|
2510
|
+
}
|
2511
|
+
|
2512
|
+
on_closing do
|
2513
|
+
puts 'Bye Bye'
|
2514
|
+
end
|
2515
|
+
}.show
|
2516
|
+
end
|
2517
|
+
end
|
2518
|
+
|
2519
|
+
FontButton.new.launch
|
2520
|
+
```
|
2521
|
+
|
2522
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2331
2523
|
|
2332
2524
|
```ruby
|
2333
2525
|
require 'glimmer-dsl-libui'
|
@@ -2368,7 +2560,33 @@ Mac | Windows | Linux
|
|
2368
2560
|
----|---------|------
|
2369
2561
|
![glimmer-dsl-libui-mac-color-button.png](images/glimmer-dsl-libui-mac-color-button.png) ![glimmer-dsl-libui-mac-color-button-selection.png](images/glimmer-dsl-libui-mac-color-button-selection.png) | ![glimmer-dsl-libui-windows-color-button.png](images/glimmer-dsl-libui-windows-color-button.png) ![glimmer-dsl-libui-windows-color-button-selection.png](images/glimmer-dsl-libui-windows-color-button-selection.png) | ![glimmer-dsl-libui-linux-color-button.png](images/glimmer-dsl-libui-linux-color-button.png) ![glimmer-dsl-libui-linux-color-button-selection.png](images/glimmer-dsl-libui-linux-color-button-selection.png)
|
2370
2562
|
|
2371
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2563
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2564
|
+
|
2565
|
+
```ruby
|
2566
|
+
require 'glimmer-dsl-libui'
|
2567
|
+
|
2568
|
+
class ColorButton
|
2569
|
+
include Glimmer
|
2570
|
+
|
2571
|
+
attr_accessor :selected_color
|
2572
|
+
|
2573
|
+
def initialize
|
2574
|
+
@selected_color = :blue
|
2575
|
+
end
|
2576
|
+
|
2577
|
+
def launch
|
2578
|
+
window('color button', 240) {
|
2579
|
+
color_button {
|
2580
|
+
color <=> [self, :selected_color, after_write: ->(color) {p color}]
|
2581
|
+
}
|
2582
|
+
}.show
|
2583
|
+
end
|
2584
|
+
end
|
2585
|
+
|
2586
|
+
ColorButton.new.launch
|
2587
|
+
```
|
2588
|
+
|
2589
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2372
2590
|
|
2373
2591
|
```ruby
|
2374
2592
|
require 'glimmer-dsl-libui'
|
@@ -2493,32 +2711,84 @@ Mac | Windows | Linux
|
|
2493
2711
|
----|---------|------
|
2494
2712
|
![glimmer-dsl-libui-mac-form.png](images/glimmer-dsl-libui-mac-form.png) ![glimmer-dsl-libui-mac-form-msg-box.png](images/glimmer-dsl-libui-mac-form-msg-box.png) | ![glimmer-dsl-libui-windows-form.png](images/glimmer-dsl-libui-windows-form.png) ![glimmer-dsl-libui-windows-form-msg-box.png](images/glimmer-dsl-libui-windows-form-msg-box.png) | ![glimmer-dsl-libui-linux-form.png](images/glimmer-dsl-libui-linux-form.png) ![glimmer-dsl-libui-linux-form-msg-box.png](images/glimmer-dsl-libui-linux-form-msg-box.png)
|
2495
2713
|
|
2496
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2714
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2497
2715
|
|
2498
2716
|
```ruby
|
2499
2717
|
require 'glimmer-dsl-libui'
|
2500
2718
|
|
2501
|
-
|
2502
|
-
|
2503
|
-
window('Form') {
|
2504
|
-
margined true
|
2719
|
+
class Form
|
2720
|
+
include Glimmer
|
2505
2721
|
|
2506
|
-
|
2507
|
-
|
2508
|
-
|
2509
|
-
|
2510
|
-
|
2511
|
-
|
2512
|
-
@last_name_entry = entry {
|
2513
|
-
label 'Last Name' # label property is available when control is nested under form
|
2514
|
-
}
|
2515
|
-
|
2516
|
-
@phone_entry = entry {
|
2517
|
-
label 'Phone' # label property is available when control is nested under form
|
2518
|
-
}
|
2722
|
+
attr_accessor :first_name, :last_name, :phone, :email
|
2723
|
+
|
2724
|
+
def launch
|
2725
|
+
window('Form') {
|
2726
|
+
margined true
|
2519
2727
|
|
2520
|
-
|
2521
|
-
|
2728
|
+
vertical_box {
|
2729
|
+
form {
|
2730
|
+
entry {
|
2731
|
+
label 'First Name' # label property is available when control is nested under form
|
2732
|
+
text <=> [self, :first_name] # bidirectional data-binding of entry text property to self first_name attribute
|
2733
|
+
}
|
2734
|
+
|
2735
|
+
entry {
|
2736
|
+
label 'Last Name' # label property is available when control is nested under form
|
2737
|
+
text <=> [self, :last_name]
|
2738
|
+
}
|
2739
|
+
|
2740
|
+
entry {
|
2741
|
+
label 'Phone' # label property is available when control is nested under form
|
2742
|
+
text <=> [self, :phone]
|
2743
|
+
}
|
2744
|
+
|
2745
|
+
entry {
|
2746
|
+
label 'Email' # label property is available when control is nested under form
|
2747
|
+
text <=> [self, :email]
|
2748
|
+
}
|
2749
|
+
}
|
2750
|
+
|
2751
|
+
button('Display Info') {
|
2752
|
+
stretchy false
|
2753
|
+
|
2754
|
+
on_clicked do
|
2755
|
+
msg_box('Info', "#{first_name} #{last_name} has phone #{phone} and email #{email}")
|
2756
|
+
end
|
2757
|
+
}
|
2758
|
+
}
|
2759
|
+
}.show
|
2760
|
+
end
|
2761
|
+
end
|
2762
|
+
|
2763
|
+
Form.new.launch
|
2764
|
+
```
|
2765
|
+
|
2766
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2767
|
+
|
2768
|
+
```ruby
|
2769
|
+
require 'glimmer-dsl-libui'
|
2770
|
+
|
2771
|
+
include Glimmer
|
2772
|
+
|
2773
|
+
window('Form') {
|
2774
|
+
margined true
|
2775
|
+
|
2776
|
+
vertical_box {
|
2777
|
+
form {
|
2778
|
+
@first_name_entry = entry {
|
2779
|
+
label 'First Name' # label property is available when control is nested under form
|
2780
|
+
}
|
2781
|
+
|
2782
|
+
@last_name_entry = entry {
|
2783
|
+
label 'Last Name' # label property is available when control is nested under form
|
2784
|
+
}
|
2785
|
+
|
2786
|
+
@phone_entry = entry {
|
2787
|
+
label 'Phone' # label property is available when control is nested under form
|
2788
|
+
}
|
2789
|
+
|
2790
|
+
@email_entry = entry {
|
2791
|
+
label 'Email' # label property is available when control is nested under form
|
2522
2792
|
}
|
2523
2793
|
}
|
2524
2794
|
|
@@ -5608,7 +5878,96 @@ Mac | Windows | Linux
|
|
5608
5878
|
----|---------|------
|
5609
5879
|
![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-dynamic-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-dynamic-area-updated.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-dynamic-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-dynamic-area-updated.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-dynamic-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-dynamic-area-updated.png)
|
5610
5880
|
|
5611
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
5881
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
5882
|
+
|
5883
|
+
```ruby
|
5884
|
+
require 'glimmer-dsl-libui'
|
5885
|
+
|
5886
|
+
class DynamicArea
|
5887
|
+
include Glimmer
|
5888
|
+
|
5889
|
+
attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
|
5890
|
+
|
5891
|
+
def initialize
|
5892
|
+
@rectangle_x = 25
|
5893
|
+
@rectangle_y = 25
|
5894
|
+
@rectangle_width = 150
|
5895
|
+
@rectangle_height = 150
|
5896
|
+
@rectangle_red = 102
|
5897
|
+
@rectangle_green = 102
|
5898
|
+
@rectangle_blue = 204
|
5899
|
+
@rectangle_alpha = 100
|
5900
|
+
end
|
5901
|
+
|
5902
|
+
def launch
|
5903
|
+
window('Dynamic Area', 240, 600) {
|
5904
|
+
margined true
|
5905
|
+
|
5906
|
+
vertical_box {
|
5907
|
+
label('Rectangle Properties') {
|
5908
|
+
stretchy false
|
5909
|
+
}
|
5910
|
+
|
5911
|
+
form {
|
5912
|
+
stretchy false
|
5913
|
+
|
5914
|
+
spinbox(0, 1000) {
|
5915
|
+
label 'x'
|
5916
|
+
value <=> [self, :rectangle_x, after_write: -> {@area.queue_redraw_all}]
|
5917
|
+
}
|
5918
|
+
|
5919
|
+
spinbox(0, 1000) {
|
5920
|
+
label 'y'
|
5921
|
+
value <=> [self, :rectangle_y, after_write: -> {@area.queue_redraw_all}]
|
5922
|
+
}
|
5923
|
+
|
5924
|
+
spinbox(0, 1000) {
|
5925
|
+
label 'width'
|
5926
|
+
value <=> [self, :rectangle_width, after_write: -> {@area.queue_redraw_all}]
|
5927
|
+
}
|
5928
|
+
|
5929
|
+
spinbox(0, 1000) {
|
5930
|
+
label 'height'
|
5931
|
+
value <=> [self, :rectangle_height, after_write: -> {@area.queue_redraw_all}]
|
5932
|
+
}
|
5933
|
+
|
5934
|
+
spinbox(0, 255) {
|
5935
|
+
label 'red'
|
5936
|
+
value <=> [self, :rectangle_red, after_write: -> {@area.queue_redraw_all}]
|
5937
|
+
}
|
5938
|
+
|
5939
|
+
spinbox(0, 255) {
|
5940
|
+
label 'green'
|
5941
|
+
value <=> [self, :rectangle_green, after_write: -> {@area.queue_redraw_all}]
|
5942
|
+
}
|
5943
|
+
|
5944
|
+
spinbox(0, 255) {
|
5945
|
+
label 'blue'
|
5946
|
+
value <=> [self, :rectangle_blue, after_write: -> {@area.queue_redraw_all}]
|
5947
|
+
}
|
5948
|
+
|
5949
|
+
spinbox(0, 100) {
|
5950
|
+
label 'alpha'
|
5951
|
+
value <=> [self, :rectangle_alpha, after_write: -> {@area.queue_redraw_all}]
|
5952
|
+
}
|
5953
|
+
}
|
5954
|
+
|
5955
|
+
@area = area {
|
5956
|
+
on_draw do |area_draw_params|
|
5957
|
+
rectangle(rectangle_x, rectangle_y, rectangle_width, rectangle_height) { # a dynamic path is added semi-declaratively inside on_draw block
|
5958
|
+
fill r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0
|
5959
|
+
}
|
5960
|
+
end
|
5961
|
+
}
|
5962
|
+
}
|
5963
|
+
}.show
|
5964
|
+
end
|
5965
|
+
end
|
5966
|
+
|
5967
|
+
DynamicArea.new.launch
|
5968
|
+
```
|
5969
|
+
|
5970
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
5612
5971
|
|
5613
5972
|
```ruby
|
5614
5973
|
require 'glimmer-dsl-libui'
|
@@ -5710,7 +6069,102 @@ window('Dynamic Area', 240, 600) {
|
|
5710
6069
|
}.show
|
5711
6070
|
```
|
5712
6071
|
|
5713
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
|
6072
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (declarative stable `path` approach with [data-binding](#data-binding)):
|
6073
|
+
|
6074
|
+
```ruby
|
6075
|
+
require 'glimmer-dsl-libui'
|
6076
|
+
|
6077
|
+
class DynamicArea
|
6078
|
+
include Glimmer
|
6079
|
+
|
6080
|
+
attr_accessor :rectangle_x, :rectangle_y, :rectangle_width, :rectangle_height, :rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha
|
6081
|
+
|
6082
|
+
def initialize
|
6083
|
+
@rectangle_x = 25
|
6084
|
+
@rectangle_y = 25
|
6085
|
+
@rectangle_width = 150
|
6086
|
+
@rectangle_height = 150
|
6087
|
+
@rectangle_red = 102
|
6088
|
+
@rectangle_green = 102
|
6089
|
+
@rectangle_blue = 204
|
6090
|
+
@rectangle_alpha = 100
|
6091
|
+
end
|
6092
|
+
|
6093
|
+
def rectangle_fill
|
6094
|
+
{ r: rectangle_red, g: rectangle_green, b: rectangle_blue, a: rectangle_alpha / 100.0 }
|
6095
|
+
end
|
6096
|
+
|
6097
|
+
def launch
|
6098
|
+
window('Dynamic Area', 240, 600) {
|
6099
|
+
margined true
|
6100
|
+
|
6101
|
+
vertical_box {
|
6102
|
+
label('Rectangle Properties') {
|
6103
|
+
stretchy false
|
6104
|
+
}
|
6105
|
+
|
6106
|
+
form {
|
6107
|
+
stretchy false
|
6108
|
+
|
6109
|
+
@x_spinbox = spinbox(0, 1000) {
|
6110
|
+
label 'x'
|
6111
|
+
value <=> [self, :rectangle_x]
|
6112
|
+
}
|
6113
|
+
|
6114
|
+
@y_spinbox = spinbox(0, 1000) {
|
6115
|
+
label 'y'
|
6116
|
+
value <=> [self, :rectangle_y]
|
6117
|
+
}
|
6118
|
+
|
6119
|
+
@width_spinbox = spinbox(0, 1000) {
|
6120
|
+
label 'width'
|
6121
|
+
value <=> [self, :rectangle_width]
|
6122
|
+
}
|
6123
|
+
|
6124
|
+
@height_spinbox = spinbox(0, 1000) {
|
6125
|
+
label 'height'
|
6126
|
+
value <=> [self, :rectangle_height]
|
6127
|
+
}
|
6128
|
+
|
6129
|
+
@red_spinbox = spinbox(0, 255) {
|
6130
|
+
label 'red'
|
6131
|
+
value <=> [self, :rectangle_red]
|
6132
|
+
}
|
6133
|
+
|
6134
|
+
@green_spinbox = spinbox(0, 255) {
|
6135
|
+
label 'green'
|
6136
|
+
value <=> [self, :rectangle_green]
|
6137
|
+
}
|
6138
|
+
|
6139
|
+
@blue_spinbox = spinbox(0, 255) {
|
6140
|
+
label 'blue'
|
6141
|
+
value <=> [self, :rectangle_blue]
|
6142
|
+
}
|
6143
|
+
|
6144
|
+
@alpha_spinbox = spinbox(0, 100) {
|
6145
|
+
label 'alpha'
|
6146
|
+
value <=> [self, :rectangle_alpha]
|
6147
|
+
}
|
6148
|
+
}
|
6149
|
+
|
6150
|
+
area {
|
6151
|
+
@rectangle = rectangle { # stable implicit path shape
|
6152
|
+
x <= [self, :rectangle_x]
|
6153
|
+
y <= [self, :rectangle_y]
|
6154
|
+
width <= [self, :rectangle_width]
|
6155
|
+
height <= [self, :rectangle_height]
|
6156
|
+
fill <= [self, :rectangle_fill, computed_by: [:rectangle_red, :rectangle_green, :rectangle_blue, :rectangle_alpha]]
|
6157
|
+
}
|
6158
|
+
}
|
6159
|
+
}
|
6160
|
+
}.show
|
6161
|
+
end
|
6162
|
+
end
|
6163
|
+
|
6164
|
+
DynamicArea.new.launch
|
6165
|
+
```
|
6166
|
+
|
6167
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (declarative stable `path` approach without [data-binding](#data-binding)):
|
5714
6168
|
|
5715
6169
|
```ruby
|
5716
6170
|
require 'glimmer-dsl-libui'
|
@@ -5802,7 +6256,7 @@ window('Dynamic Area', 240, 600) {
|
|
5802
6256
|
}
|
5803
6257
|
|
5804
6258
|
area {
|
5805
|
-
@rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable path
|
6259
|
+
@rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value) { # stable implicit path shape
|
5806
6260
|
fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
|
5807
6261
|
}
|
5808
6262
|
}
|
@@ -5944,7 +6398,120 @@ Mac | Windows | Linux
|
|
5944
6398
|
----|---------|------
|
5945
6399
|
![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) ![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png) ![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) ![glimmer-dsl-libui-windows-form-table-contact-entered.png](images/glimmer-dsl-libui-windows-form-table-contact-entered.png) ![glimmer-dsl-libui-windows-form-table-filtered.png](images/glimmer-dsl-libui-windows-form-table-filtered.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) ![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png) ![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
|
5946
6400
|
|
5947
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
6401
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6402
|
+
|
6403
|
+
```ruby
|
6404
|
+
require 'glimmer-dsl-libui'
|
6405
|
+
|
6406
|
+
class FormTable
|
6407
|
+
include Glimmer
|
6408
|
+
|
6409
|
+
attr_accessor :name, :email, :phone, :city, :state, :filter_value
|
6410
|
+
|
6411
|
+
def initialize
|
6412
|
+
@data = [
|
6413
|
+
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
|
6414
|
+
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
|
6415
|
+
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
|
6416
|
+
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
|
6417
|
+
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
|
6418
|
+
]
|
6419
|
+
end
|
6420
|
+
|
6421
|
+
def launch
|
6422
|
+
window('Contacts', 600, 600) { |w|
|
6423
|
+
margined true
|
6424
|
+
|
6425
|
+
vertical_box {
|
6426
|
+
form {
|
6427
|
+
stretchy false
|
6428
|
+
|
6429
|
+
entry {
|
6430
|
+
label 'Name'
|
6431
|
+
text <=> [self, :name]
|
6432
|
+
}
|
6433
|
+
|
6434
|
+
entry {
|
6435
|
+
label 'Email'
|
6436
|
+
text <=> [self, :email]
|
6437
|
+
}
|
6438
|
+
|
6439
|
+
entry {
|
6440
|
+
label 'Phone'
|
6441
|
+
text <=> [self, :phone]
|
6442
|
+
}
|
6443
|
+
|
6444
|
+
entry {
|
6445
|
+
label 'City'
|
6446
|
+
text <=> [self, :city]
|
6447
|
+
}
|
6448
|
+
|
6449
|
+
entry {
|
6450
|
+
label 'State'
|
6451
|
+
text <=> [self, :state]
|
6452
|
+
}
|
6453
|
+
}
|
6454
|
+
|
6455
|
+
button('Save Contact') {
|
6456
|
+
stretchy false
|
6457
|
+
|
6458
|
+
on_clicked do
|
6459
|
+
new_row = [name, email, phone, city, state]
|
6460
|
+
if new_row.include?('')
|
6461
|
+
msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
|
6462
|
+
else
|
6463
|
+
@data << new_row # automatically inserts a row into the table due to implicit data-binding
|
6464
|
+
@unfiltered_data = @data.dup
|
6465
|
+
self.name = '' # automatically clears name entry through explicit data-binding
|
6466
|
+
self.email = ''
|
6467
|
+
self.phone = ''
|
6468
|
+
self.city = ''
|
6469
|
+
self.state = ''
|
6470
|
+
end
|
6471
|
+
end
|
6472
|
+
}
|
6473
|
+
|
6474
|
+
search_entry {
|
6475
|
+
stretchy false
|
6476
|
+
text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
|
6477
|
+
after_write: ->(filter_value) { # execute after write to self.filter_value
|
6478
|
+
@unfiltered_data ||= @data.dup
|
6479
|
+
# Unfilter first to remove any previous filters
|
6480
|
+
@data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
|
6481
|
+
# Now, apply filter if entered
|
6482
|
+
unless filter_value.empty?
|
6483
|
+
@data.filter! do |row_data| # affects table indirectly through implicit data-binding
|
6484
|
+
row_data.any? do |cell|
|
6485
|
+
cell.to_s.downcase.include?(filter_value.downcase)
|
6486
|
+
end
|
6487
|
+
end
|
6488
|
+
end
|
6489
|
+
}
|
6490
|
+
]
|
6491
|
+
}
|
6492
|
+
|
6493
|
+
table {
|
6494
|
+
text_column('Name')
|
6495
|
+
text_column('Email')
|
6496
|
+
text_column('Phone')
|
6497
|
+
text_column('City')
|
6498
|
+
text_column('State')
|
6499
|
+
|
6500
|
+
cell_rows @data # implicit data-binding
|
6501
|
+
|
6502
|
+
on_changed do |row, type, row_data|
|
6503
|
+
puts "Row #{row} #{type}: #{row_data}"
|
6504
|
+
end
|
6505
|
+
}
|
6506
|
+
}
|
6507
|
+
}.show
|
6508
|
+
end
|
6509
|
+
end
|
6510
|
+
|
6511
|
+
FormTable.new.launch
|
6512
|
+
```
|
6513
|
+
|
6514
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
5948
6515
|
|
5949
6516
|
```ruby
|
5950
6517
|
require 'glimmer-dsl-libui'
|
@@ -6387,53 +6954,168 @@ UI.main
|
|
6387
6954
|
UI.quit
|
6388
6955
|
```
|
6389
6956
|
|
6390
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
6957
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6391
6958
|
|
6392
6959
|
```ruby
|
6393
6960
|
# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
|
6394
6961
|
|
6395
6962
|
require 'glimmer-dsl-libui'
|
6396
6963
|
|
6397
|
-
|
6398
|
-
|
6399
|
-
|
6400
|
-
|
6401
|
-
|
6402
|
-
|
6403
|
-
|
6404
|
-
|
6405
|
-
|
6406
|
-
|
6407
|
-
|
6408
|
-
|
6409
|
-
|
6410
|
-
|
6411
|
-
|
6412
|
-
end
|
6413
|
-
|
6414
|
-
def point_locations(width, height)
|
6415
|
-
xincr = width / 9.0 # 10 - 1 to make the last point be at the end
|
6416
|
-
yincr = height / 100.0
|
6417
|
-
|
6418
|
-
@datapoints.each_with_index.map do |value, i|
|
6419
|
-
val = 100 - value
|
6420
|
-
[xincr * i, yincr * val]
|
6964
|
+
class Histogram
|
6965
|
+
include Glimmer
|
6966
|
+
|
6967
|
+
X_OFF_LEFT = 20
|
6968
|
+
Y_OFF_TOP = 20
|
6969
|
+
X_OFF_RIGHT = 20
|
6970
|
+
Y_OFF_BOTTOM = 20
|
6971
|
+
POINT_RADIUS = 5
|
6972
|
+
COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
|
6973
|
+
|
6974
|
+
attr_accessor :datapoints, :histogram_color
|
6975
|
+
|
6976
|
+
def initialize
|
6977
|
+
@datapoints = 10.times.map {Random.new.rand(90)}
|
6978
|
+
@histogram_color = COLOR_BLUE
|
6421
6979
|
end
|
6422
|
-
|
6423
|
-
|
6424
|
-
|
6425
|
-
|
6426
|
-
|
6427
|
-
|
6428
|
-
|
6429
|
-
|
6430
|
-
|
6431
|
-
|
6980
|
+
|
6981
|
+
def graph_size(area_width, area_height)
|
6982
|
+
graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
|
6983
|
+
graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
|
6984
|
+
[graph_width, graph_height]
|
6985
|
+
end
|
6986
|
+
|
6987
|
+
def point_locations(width, height)
|
6988
|
+
xincr = width / 9.0 # 10 - 1 to make the last point be at the end
|
6989
|
+
yincr = height / 100.0
|
6990
|
+
|
6991
|
+
@datapoints.each_with_index.map do |value, i|
|
6992
|
+
val = 100 - value
|
6993
|
+
[xincr * i, yincr * val]
|
6432
6994
|
end
|
6433
|
-
|
6434
|
-
|
6435
|
-
|
6436
|
-
|
6995
|
+
end
|
6996
|
+
|
6997
|
+
# method-based custom control representing a graph path
|
6998
|
+
def graph_path(width, height, should_extend, &block)
|
6999
|
+
locations = point_locations(width, height).flatten
|
7000
|
+
path {
|
7001
|
+
if should_extend
|
7002
|
+
polygon(locations + [width, height, 0, height])
|
7003
|
+
else
|
7004
|
+
polyline(locations)
|
7005
|
+
end
|
7006
|
+
|
7007
|
+
# apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
|
7008
|
+
transform {
|
7009
|
+
translate X_OFF_LEFT, Y_OFF_TOP
|
7010
|
+
}
|
7011
|
+
|
7012
|
+
block.call
|
7013
|
+
}
|
7014
|
+
end
|
7015
|
+
|
7016
|
+
def launch
|
7017
|
+
window('histogram example', 640, 480) {
|
7018
|
+
margined true
|
7019
|
+
|
7020
|
+
horizontal_box {
|
7021
|
+
vertical_box {
|
7022
|
+
stretchy false
|
7023
|
+
|
7024
|
+
10.times do |i|
|
7025
|
+
spinbox(0, 100) { |sb|
|
7026
|
+
stretchy false
|
7027
|
+
value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
|
7028
|
+
}
|
7029
|
+
end
|
7030
|
+
|
7031
|
+
color_button { |cb|
|
7032
|
+
stretchy false
|
7033
|
+
color <=> [self, :histogram_color, after_write: -> { @area.queue_redraw_all }]
|
7034
|
+
}
|
7035
|
+
}
|
7036
|
+
|
7037
|
+
@area = area {
|
7038
|
+
on_draw do |area_draw_params|
|
7039
|
+
rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
|
7040
|
+
fill 0xFFFFFF
|
7041
|
+
}
|
7042
|
+
|
7043
|
+
graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
|
7044
|
+
|
7045
|
+
figure(X_OFF_LEFT, Y_OFF_TOP) {
|
7046
|
+
line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
|
7047
|
+
line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
|
7048
|
+
|
7049
|
+
stroke 0x000000, thickness: 2, miter_limit: 10
|
7050
|
+
}
|
7051
|
+
|
7052
|
+
# now create the fill for the graph below the graph line
|
7053
|
+
graph_path(graph_width, graph_height, true) {
|
7054
|
+
fill @histogram_color.merge(a: 0.5)
|
7055
|
+
}
|
7056
|
+
|
7057
|
+
# now draw the histogram line
|
7058
|
+
graph_path(graph_width, graph_height, false) {
|
7059
|
+
stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
|
7060
|
+
}
|
7061
|
+
end
|
7062
|
+
}
|
7063
|
+
}
|
7064
|
+
}.show
|
7065
|
+
end
|
7066
|
+
end
|
7067
|
+
|
7068
|
+
Histogram.new.launch
|
7069
|
+
```
|
7070
|
+
|
7071
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
7072
|
+
|
7073
|
+
```ruby
|
7074
|
+
# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
|
7075
|
+
|
7076
|
+
require 'glimmer-dsl-libui'
|
7077
|
+
|
7078
|
+
include Glimmer
|
7079
|
+
|
7080
|
+
X_OFF_LEFT = 20
|
7081
|
+
Y_OFF_TOP = 20
|
7082
|
+
X_OFF_RIGHT = 20
|
7083
|
+
Y_OFF_BOTTOM = 20
|
7084
|
+
POINT_RADIUS = 5
|
7085
|
+
COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
|
7086
|
+
|
7087
|
+
@datapoints = 10.times.map {Random.new.rand(90)}
|
7088
|
+
@color = COLOR_BLUE
|
7089
|
+
|
7090
|
+
def graph_size(area_width, area_height)
|
7091
|
+
graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
|
7092
|
+
graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
|
7093
|
+
[graph_width, graph_height]
|
7094
|
+
end
|
7095
|
+
|
7096
|
+
def point_locations(width, height)
|
7097
|
+
xincr = width / 9.0 # 10 - 1 to make the last point be at the end
|
7098
|
+
yincr = height / 100.0
|
7099
|
+
|
7100
|
+
@datapoints.each_with_index.map do |value, i|
|
7101
|
+
val = 100 - value
|
7102
|
+
[xincr * i, yincr * val]
|
7103
|
+
end
|
7104
|
+
end
|
7105
|
+
|
7106
|
+
# method-based custom control representing a graph path
|
7107
|
+
def graph_path(width, height, should_extend, &block)
|
7108
|
+
locations = point_locations(width, height).flatten
|
7109
|
+
path {
|
7110
|
+
if should_extend
|
7111
|
+
polygon(locations + [width, height, 0, height])
|
7112
|
+
else
|
7113
|
+
polyline(locations)
|
7114
|
+
end
|
7115
|
+
|
7116
|
+
# apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
|
7117
|
+
transform {
|
7118
|
+
translate X_OFF_LEFT, Y_OFF_TOP
|
6437
7119
|
}
|
6438
7120
|
|
6439
7121
|
block.call
|
@@ -6459,11 +7141,12 @@ window('histogram example', 640, 480) {
|
|
6459
7141
|
}
|
6460
7142
|
end
|
6461
7143
|
|
6462
|
-
|
7144
|
+
color_button { |cb|
|
6463
7145
|
stretchy false
|
6464
7146
|
color COLOR_BLUE
|
6465
7147
|
|
6466
7148
|
on_changed do
|
7149
|
+
@color = cb.color
|
6467
7150
|
@area.queue_redraw_all
|
6468
7151
|
end
|
6469
7152
|
}
|
@@ -6471,31 +7154,27 @@ window('histogram example', 640, 480) {
|
|
6471
7154
|
|
6472
7155
|
@area = area {
|
6473
7156
|
on_draw do |area_draw_params|
|
6474
|
-
|
6475
|
-
rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
|
6476
|
-
|
7157
|
+
rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
|
6477
7158
|
fill 0xFFFFFF
|
6478
7159
|
}
|
6479
7160
|
|
6480
7161
|
graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
|
6481
7162
|
|
6482
|
-
|
6483
|
-
|
6484
|
-
|
6485
|
-
line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
|
6486
|
-
}
|
7163
|
+
figure(X_OFF_LEFT, Y_OFF_TOP) {
|
7164
|
+
line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
|
7165
|
+
line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
|
6487
7166
|
|
6488
7167
|
stroke 0x000000, thickness: 2, miter_limit: 10
|
6489
7168
|
}
|
6490
7169
|
|
6491
7170
|
# now create the fill for the graph below the graph line
|
6492
7171
|
graph_path(graph_width, graph_height, true) {
|
6493
|
-
fill @
|
7172
|
+
fill @color.merge(a: 0.5)
|
6494
7173
|
}
|
6495
7174
|
|
6496
7175
|
# now draw the histogram line
|
6497
7176
|
graph_path(graph_width, graph_height, false) {
|
6498
|
-
stroke @
|
7177
|
+
stroke @color.merge(thickness: 2, miter_limit: 10)
|
6499
7178
|
}
|
6500
7179
|
end
|
6501
7180
|
}
|
@@ -6523,7 +7202,259 @@ Mac | Windows | Linux
|
|
6523
7202
|
----|---------|------
|
6524
7203
|
![glimmer-dsl-libui-mac-login.png](images/glimmer-dsl-libui-mac-login.png) ![glimmer-dsl-libui-mac-login-logged-in.png](images/glimmer-dsl-libui-mac-login-logged-in.png) | ![glimmer-dsl-libui-windows-login.png](images/glimmer-dsl-libui-windows-login.png) ![glimmer-dsl-libui-windows-login-logged-in.png](images/glimmer-dsl-libui-windows-login-logged-in.png) | ![glimmer-dsl-libui-linux-login.png](images/glimmer-dsl-libui-linux-login.png) ![glimmer-dsl-libui-linux-login-logged-in.png](images/glimmer-dsl-libui-linux-login-logged-in.png)
|
6525
7204
|
|
6526
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
7205
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
7206
|
+
|
7207
|
+
```ruby
|
7208
|
+
require 'glimmer-dsl-libui'
|
7209
|
+
|
7210
|
+
class Login
|
7211
|
+
include Glimmer
|
7212
|
+
|
7213
|
+
attr_accessor :username, :password, :logged_in
|
7214
|
+
|
7215
|
+
def launch
|
7216
|
+
window('Login') {
|
7217
|
+
margined true
|
7218
|
+
|
7219
|
+
vertical_box {
|
7220
|
+
form {
|
7221
|
+
entry {
|
7222
|
+
label 'Username:'
|
7223
|
+
text <=> [self, :username]
|
7224
|
+
enabled <= [self, :logged_in, on_read: :!]
|
7225
|
+
}
|
7226
|
+
|
7227
|
+
password_entry {
|
7228
|
+
label 'Password:'
|
7229
|
+
text <=> [self, :password]
|
7230
|
+
enabled <= [self, :logged_in, on_read: :!]
|
7231
|
+
}
|
7232
|
+
}
|
7233
|
+
|
7234
|
+
horizontal_box {
|
7235
|
+
button('Login') {
|
7236
|
+
enabled <= [self, :logged_in, on_read: :!]
|
7237
|
+
|
7238
|
+
on_clicked do
|
7239
|
+
self.logged_in = true
|
7240
|
+
end
|
7241
|
+
}
|
7242
|
+
|
7243
|
+
button('Logout') {
|
7244
|
+
enabled <= [self, :logged_in]
|
7245
|
+
|
7246
|
+
on_clicked do
|
7247
|
+
self.logged_in = false
|
7248
|
+
self.username = ''
|
7249
|
+
self.password = ''
|
7250
|
+
end
|
7251
|
+
}
|
7252
|
+
}
|
7253
|
+
}
|
7254
|
+
}.show
|
7255
|
+
end
|
7256
|
+
end
|
7257
|
+
|
7258
|
+
Login.new.launch
|
7259
|
+
```
|
7260
|
+
|
7261
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
|
7262
|
+
|
7263
|
+
```ruby
|
7264
|
+
require 'glimmer-dsl-libui'
|
7265
|
+
|
7266
|
+
class Login
|
7267
|
+
include Glimmer
|
7268
|
+
|
7269
|
+
attr_accessor :username, :password, :logged_in
|
7270
|
+
|
7271
|
+
def logged_out
|
7272
|
+
!logged_in
|
7273
|
+
end
|
7274
|
+
|
7275
|
+
def launch
|
7276
|
+
window('Login') {
|
7277
|
+
margined true
|
7278
|
+
|
7279
|
+
vertical_box {
|
7280
|
+
form {
|
7281
|
+
entry {
|
7282
|
+
label 'Username:'
|
7283
|
+
text <=> [self, :username]
|
7284
|
+
enabled <= [self, :logged_out, computed_by: :logged_in] # computed_by option ensures being notified of changes to logged_in
|
7285
|
+
}
|
7286
|
+
|
7287
|
+
password_entry {
|
7288
|
+
label 'Password:'
|
7289
|
+
text <=> [self, :password]
|
7290
|
+
enabled <= [self, :logged_out, computed_by: :logged_in]
|
7291
|
+
}
|
7292
|
+
}
|
7293
|
+
|
7294
|
+
horizontal_box {
|
7295
|
+
button('Login') {
|
7296
|
+
enabled <= [self, :logged_out, computed_by: :logged_in]
|
7297
|
+
|
7298
|
+
on_clicked do
|
7299
|
+
self.logged_in = true
|
7300
|
+
end
|
7301
|
+
}
|
7302
|
+
|
7303
|
+
button('Logout') {
|
7304
|
+
enabled <= [self, :logged_in]
|
7305
|
+
|
7306
|
+
on_clicked do
|
7307
|
+
self.logged_in = false
|
7308
|
+
self.username = ''
|
7309
|
+
self.password = ''
|
7310
|
+
end
|
7311
|
+
}
|
7312
|
+
}
|
7313
|
+
}
|
7314
|
+
}.show
|
7315
|
+
end
|
7316
|
+
end
|
7317
|
+
|
7318
|
+
Login.new.launch
|
7319
|
+
```
|
7320
|
+
|
7321
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (with [data-binding](#data-binding)):
|
7322
|
+
|
7323
|
+
```ruby
|
7324
|
+
require 'glimmer-dsl-libui'
|
7325
|
+
|
7326
|
+
class Login
|
7327
|
+
include Glimmer
|
7328
|
+
|
7329
|
+
attr_accessor :username, :password
|
7330
|
+
attr_reader :logged_in
|
7331
|
+
|
7332
|
+
def logged_in=(value)
|
7333
|
+
@logged_in = value
|
7334
|
+
self.logged_out = !value # calling logged_out= method notifies logged_out observers
|
7335
|
+
end
|
7336
|
+
|
7337
|
+
def logged_out=(value)
|
7338
|
+
self.logged_in = !value unless logged_in == !value
|
7339
|
+
end
|
7340
|
+
|
7341
|
+
def logged_out
|
7342
|
+
!logged_in
|
7343
|
+
end
|
7344
|
+
|
7345
|
+
def launch
|
7346
|
+
window('Login') {
|
7347
|
+
margined true
|
7348
|
+
|
7349
|
+
vertical_box {
|
7350
|
+
form {
|
7351
|
+
entry {
|
7352
|
+
label 'Username:'
|
7353
|
+
text <=> [self, :username]
|
7354
|
+
enabled <= [self, :logged_out]
|
7355
|
+
}
|
7356
|
+
|
7357
|
+
password_entry {
|
7358
|
+
label 'Password:'
|
7359
|
+
text <=> [self, :password]
|
7360
|
+
enabled <= [self, :logged_out]
|
7361
|
+
}
|
7362
|
+
}
|
7363
|
+
|
7364
|
+
horizontal_box {
|
7365
|
+
button('Login') {
|
7366
|
+
enabled <= [self, :logged_out]
|
7367
|
+
|
7368
|
+
on_clicked do
|
7369
|
+
self.logged_in = true
|
7370
|
+
end
|
7371
|
+
}
|
7372
|
+
|
7373
|
+
button('Logout') {
|
7374
|
+
enabled <= [self, :logged_in]
|
7375
|
+
|
7376
|
+
on_clicked do
|
7377
|
+
self.logged_in = false
|
7378
|
+
self.username = ''
|
7379
|
+
self.password = ''
|
7380
|
+
end
|
7381
|
+
}
|
7382
|
+
}
|
7383
|
+
}
|
7384
|
+
}.show
|
7385
|
+
end
|
7386
|
+
end
|
7387
|
+
|
7388
|
+
Login.new.launch
|
7389
|
+
```
|
7390
|
+
|
7391
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with [data-binding](#data-binding)):
|
7392
|
+
|
7393
|
+
```ruby
|
7394
|
+
require 'glimmer-dsl-libui'
|
7395
|
+
|
7396
|
+
class Login
|
7397
|
+
include Glimmer
|
7398
|
+
|
7399
|
+
attr_accessor :username, :password
|
7400
|
+
attr_reader :logged_in
|
7401
|
+
|
7402
|
+
def logged_in=(value)
|
7403
|
+
@logged_in = value
|
7404
|
+
notify_observers(:logged_out) # manually notify observers of logged_out upon logged_in changes; this method comes automatically from enhancement as Glimmer::DataBinding::ObservableModel via data-binding
|
7405
|
+
end
|
7406
|
+
|
7407
|
+
def logged_out
|
7408
|
+
!logged_in
|
7409
|
+
end
|
7410
|
+
|
7411
|
+
def launch
|
7412
|
+
window('Login') {
|
7413
|
+
margined true
|
7414
|
+
|
7415
|
+
vertical_box {
|
7416
|
+
form {
|
7417
|
+
entry {
|
7418
|
+
label 'Username:'
|
7419
|
+
text <=> [self, :username]
|
7420
|
+
enabled <= [self, :logged_out]
|
7421
|
+
}
|
7422
|
+
|
7423
|
+
password_entry {
|
7424
|
+
label 'Password:'
|
7425
|
+
text <=> [self, :password]
|
7426
|
+
enabled <= [self, :logged_out]
|
7427
|
+
}
|
7428
|
+
}
|
7429
|
+
|
7430
|
+
horizontal_box {
|
7431
|
+
button('Login') {
|
7432
|
+
enabled <= [self, :logged_out]
|
7433
|
+
|
7434
|
+
on_clicked do
|
7435
|
+
self.logged_in = true
|
7436
|
+
end
|
7437
|
+
}
|
7438
|
+
|
7439
|
+
button('Logout') {
|
7440
|
+
enabled <= [self, :logged_in]
|
7441
|
+
|
7442
|
+
on_clicked do
|
7443
|
+
self.logged_in = false
|
7444
|
+
self.username = ''
|
7445
|
+
self.password = ''
|
7446
|
+
end
|
7447
|
+
}
|
7448
|
+
}
|
7449
|
+
}
|
7450
|
+
}.show
|
7451
|
+
end
|
7452
|
+
end
|
7453
|
+
|
7454
|
+
Login.new.launch
|
7455
|
+
```
|
7456
|
+
|
7457
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (without [data-binding](#data-binding)):
|
6527
7458
|
|
6528
7459
|
```ruby
|
6529
7460
|
require 'glimmer-dsl-libui'
|
@@ -6591,7 +7522,102 @@ Mac | Windows | Linux
|
|
6591
7522
|
----|---------|------
|
6592
7523
|
![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)
|
6593
7524
|
|
6594
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
7525
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
7526
|
+
|
7527
|
+
```ruby
|
7528
|
+
require 'glimmer-dsl-libui'
|
7529
|
+
require 'facets'
|
7530
|
+
|
7531
|
+
include Glimmer
|
7532
|
+
|
7533
|
+
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
7534
|
+
|
7535
|
+
def form_field(model, attribute)
|
7536
|
+
attribute = attribute.to_s
|
7537
|
+
entry { |e|
|
7538
|
+
label attribute.underscore.split('_').map(&:capitalize).join(' ')
|
7539
|
+
text <=> [model, attribute]
|
7540
|
+
}
|
7541
|
+
end
|
7542
|
+
|
7543
|
+
def address_form(address)
|
7544
|
+
form {
|
7545
|
+
form_field(address, :street)
|
7546
|
+
form_field(address, :p_o_box)
|
7547
|
+
form_field(address, :city)
|
7548
|
+
form_field(address, :state)
|
7549
|
+
form_field(address, :zip_code)
|
7550
|
+
}
|
7551
|
+
end
|
7552
|
+
|
7553
|
+
def label_pair(model, attribute, value)
|
7554
|
+
horizontal_box {
|
7555
|
+
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
|
7556
|
+
label(value.to_s) {
|
7557
|
+
text <= [model, attribute]
|
7558
|
+
}
|
7559
|
+
}
|
7560
|
+
end
|
7561
|
+
|
7562
|
+
def address(address)
|
7563
|
+
vertical_box {
|
7564
|
+
address.each_pair do |attribute, value|
|
7565
|
+
label_pair(address, attribute, value)
|
7566
|
+
end
|
7567
|
+
}
|
7568
|
+
end
|
7569
|
+
|
7570
|
+
address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
7571
|
+
address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
7572
|
+
|
7573
|
+
window('Method-Based Custom Keyword') {
|
7574
|
+
margined true
|
7575
|
+
|
7576
|
+
horizontal_box {
|
7577
|
+
vertical_box {
|
7578
|
+
label('Address 1') {
|
7579
|
+
stretchy false
|
7580
|
+
}
|
7581
|
+
|
7582
|
+
address_form(address1)
|
7583
|
+
|
7584
|
+
horizontal_separator {
|
7585
|
+
stretchy false
|
7586
|
+
}
|
7587
|
+
|
7588
|
+
label('Address 1 (Saved)') {
|
7589
|
+
stretchy false
|
7590
|
+
}
|
7591
|
+
|
7592
|
+
address(address1)
|
7593
|
+
}
|
7594
|
+
|
7595
|
+
vertical_separator {
|
7596
|
+
stretchy false
|
7597
|
+
}
|
7598
|
+
|
7599
|
+
vertical_box {
|
7600
|
+
label('Address 2') {
|
7601
|
+
stretchy false
|
7602
|
+
}
|
7603
|
+
|
7604
|
+
address_form(address2)
|
7605
|
+
|
7606
|
+
horizontal_separator {
|
7607
|
+
stretchy false
|
7608
|
+
}
|
7609
|
+
|
7610
|
+
label('Address 2 (Saved)') {
|
7611
|
+
stretchy false
|
7612
|
+
}
|
7613
|
+
|
7614
|
+
address(address2)
|
7615
|
+
}
|
7616
|
+
}
|
7617
|
+
}.show
|
7618
|
+
```
|
7619
|
+
|
7620
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
6595
7621
|
|
6596
7622
|
```ruby
|
6597
7623
|
require 'glimmer-dsl-libui'
|
@@ -7537,7 +8563,140 @@ Mac | Windows | Linux
|
|
7537
8563
|
----|---------|------
|
7538
8564
|
![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png) ![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png) | ![glimmer-dsl-libui-windows-timer.png](images/glimmer-dsl-libui-windows-timer.png) ![glimmer-dsl-libui-windows-timer-in-progress.png](images/glimmer-dsl-libui-windows-timer-in-progress.png) | ![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png) ![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
|
7539
8565
|
|
7540
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
8566
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
8567
|
+
|
8568
|
+
```ruby
|
8569
|
+
require 'glimmer-dsl-libui'
|
8570
|
+
|
8571
|
+
class Timer
|
8572
|
+
include Glimmer
|
8573
|
+
|
8574
|
+
SECOND_MAX = 59
|
8575
|
+
MINUTE_MAX = 59
|
8576
|
+
HOUR_MAX = 23
|
8577
|
+
|
8578
|
+
attr_accessor :hour, :min, :sec, :started, :played
|
8579
|
+
|
8580
|
+
def initialize
|
8581
|
+
@pid = nil
|
8582
|
+
@alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
|
8583
|
+
@hour = @min = @sec = 0
|
8584
|
+
at_exit { stop_alarm }
|
8585
|
+
setup_timer
|
8586
|
+
create_gui
|
8587
|
+
end
|
8588
|
+
|
8589
|
+
def stop_alarm
|
8590
|
+
if @pid
|
8591
|
+
Process.kill(:SIGKILL, @pid) if @th.alive?
|
8592
|
+
@pid = nil
|
8593
|
+
end
|
8594
|
+
end
|
8595
|
+
|
8596
|
+
def play_alarm
|
8597
|
+
stop_alarm
|
8598
|
+
if @pid.nil?
|
8599
|
+
begin
|
8600
|
+
@pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
|
8601
|
+
@th = Process.detach @pid
|
8602
|
+
rescue Errno::ENOENT
|
8603
|
+
warn 'Timidty++ not found. Please install Timidity++.'
|
8604
|
+
warn 'https://sourceforge.net/projects/timidity/'
|
8605
|
+
end
|
8606
|
+
end
|
8607
|
+
end
|
8608
|
+
|
8609
|
+
def setup_timer
|
8610
|
+
unless @setup_timer
|
8611
|
+
Glimmer::LibUI.timer(1) do
|
8612
|
+
if @started
|
8613
|
+
seconds = @sec
|
8614
|
+
minutes = @min
|
8615
|
+
hours = @hour
|
8616
|
+
if seconds > 0
|
8617
|
+
self.sec = seconds -= 1
|
8618
|
+
end
|
8619
|
+
if seconds == 0
|
8620
|
+
if minutes > 0
|
8621
|
+
self.min = minutes -= 1
|
8622
|
+
self.sec = seconds = SECOND_MAX
|
8623
|
+
end
|
8624
|
+
if minutes == 0
|
8625
|
+
if hours > 0
|
8626
|
+
self.hour = hours -= 1
|
8627
|
+
self.min = minutes = MINUTE_MAX
|
8628
|
+
self.sec = seconds = SECOND_MAX
|
8629
|
+
end
|
8630
|
+
if hours == 0 && minutes == 0 && seconds == 0
|
8631
|
+
self.started = false
|
8632
|
+
unless @played
|
8633
|
+
play_alarm
|
8634
|
+
msg_box('Alarm', 'Countdown Is Finished!')
|
8635
|
+
self.played = true
|
8636
|
+
end
|
8637
|
+
end
|
8638
|
+
end
|
8639
|
+
end
|
8640
|
+
end
|
8641
|
+
end
|
8642
|
+
@setup_timer = true
|
8643
|
+
end
|
8644
|
+
end
|
8645
|
+
|
8646
|
+
def create_gui
|
8647
|
+
window('Timer') {
|
8648
|
+
margined true
|
8649
|
+
|
8650
|
+
group('Countdown') {
|
8651
|
+
vertical_box {
|
8652
|
+
horizontal_box {
|
8653
|
+
spinbox(0, HOUR_MAX) {
|
8654
|
+
stretchy false
|
8655
|
+
value <=> [self, :hour]
|
8656
|
+
}
|
8657
|
+
label(':') {
|
8658
|
+
stretchy false
|
8659
|
+
}
|
8660
|
+
spinbox(0, MINUTE_MAX) {
|
8661
|
+
stretchy false
|
8662
|
+
value <=> [self, :min]
|
8663
|
+
}
|
8664
|
+
label(':') {
|
8665
|
+
stretchy false
|
8666
|
+
}
|
8667
|
+
spinbox(0, SECOND_MAX) {
|
8668
|
+
stretchy false
|
8669
|
+
value <=> [self, :sec]
|
8670
|
+
}
|
8671
|
+
}
|
8672
|
+
horizontal_box {
|
8673
|
+
button('Start') {
|
8674
|
+
enabled <= [self, :started, on_read: :!]
|
8675
|
+
|
8676
|
+
on_clicked do
|
8677
|
+
self.started = true
|
8678
|
+
self.played = false
|
8679
|
+
end
|
8680
|
+
}
|
8681
|
+
|
8682
|
+
button('Stop') {
|
8683
|
+
enabled <= [self, :started]
|
8684
|
+
|
8685
|
+
on_clicked do
|
8686
|
+
self.started = false
|
8687
|
+
end
|
8688
|
+
}
|
8689
|
+
}
|
8690
|
+
}
|
8691
|
+
}
|
8692
|
+
}.show
|
8693
|
+
end
|
8694
|
+
end
|
8695
|
+
|
8696
|
+
Timer.new
|
8697
|
+
```
|
8698
|
+
|
8699
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
7541
8700
|
|
7542
8701
|
```ruby
|
7543
8702
|
require 'glimmer-dsl-libui'
|