glimmer-dsl-libui 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +624 -24
- data/VERSION +1 -1
- data/examples/basic_entry.rb +27 -24
- data/examples/basic_entry2.rb +31 -0
- data/examples/form.rb +42 -30
- data/examples/form2.rb +37 -0
- data/examples/form_table.rb +100 -85
- data/examples/form_table2.rb +95 -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/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/data_binding_expression.rb +4 -6
- data/lib/glimmer/libui/control_proxy/entry_proxy.rb +5 -0
- data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +5 -0
- data/lib/glimmer/libui/control_proxy.rb +12 -0
- metadata +10 -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.3
|
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.3'
|
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.
|
@@ -779,9 +779,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
|
|
779
779
|
- `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
780
|
- `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
781
|
|
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)
|
782
|
+
Mac |Linux
|
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) | ![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
785
|
|
786
786
|
Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
|
787
787
|
|
@@ -1329,7 +1329,7 @@ The [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) (a
|
|
1329
1329
|
|
1330
1330
|
![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
1331
|
|
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,
|
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, 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
1333
|
- `Object` becomes `Glimmer::DataBinding::ObservableModel`, which supports observing specified `Object` model attributes.
|
1334
1334
|
- `Hash` becomes `Glimmer::DataBinding::ObservableHash`, which supports observing all `Hash` keys or a specific `Hash` key
|
1335
1335
|
- `Array` becomes `Glimmer::DataBinding::ObservableArray`, which supports observing `Array` changes like those done with `push`, `<<`, `delete`, and `map!` methods (all mutation methods).
|
@@ -1350,13 +1350,37 @@ See examples of the `observe` keyword at [Color The Circles](#color-the-circles)
|
|
1350
1350
|
|
1351
1351
|
### Data-Binding
|
1352
1352
|
|
1353
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports
|
1353
|
+
[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
1354
|
|
1355
|
-
|
1355
|
+
[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):
|
1356
|
+
- `entry` `text` property
|
1357
|
+
- `multiline_entry` `text` property
|
1358
|
+
- `non_wrapping_multiline_entry` `text` property
|
1359
|
+
- `search_entry` `text` property
|
1356
1360
|
|
1357
|
-
|
1361
|
+
Example of bidirectional data-binding:
|
1358
1362
|
|
1359
|
-
|
1363
|
+
```ruby
|
1364
|
+
entry {
|
1365
|
+
text <=> [contract, :legal_text]
|
1366
|
+
}
|
1367
|
+
```
|
1368
|
+
|
1369
|
+
That is data-binding a contract's legal text to an `entry` `text` property.
|
1370
|
+
|
1371
|
+
Another example of bidirectional data-binding with an option:
|
1372
|
+
|
1373
|
+
```ruby
|
1374
|
+
entry {
|
1375
|
+
text <=> [self, :entry_text, after_write: ->(text) {puts text}]
|
1376
|
+
}
|
1377
|
+
```
|
1378
|
+
|
1379
|
+
That is data-binding `entry_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
|
1380
|
+
|
1381
|
+
[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).
|
1382
|
+
|
1383
|
+
Example of unidirectional data-binding:
|
1360
1384
|
|
1361
1385
|
```ruby
|
1362
1386
|
square(0, 0, CELL_SIZE) {
|
@@ -1366,7 +1390,7 @@ square(0, 0, CELL_SIZE) {
|
|
1366
1390
|
|
1367
1391
|
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
1392
|
|
1369
|
-
Another Example:
|
1393
|
+
Another Example of unidirectional data-binding with an option:
|
1370
1394
|
|
1371
1395
|
```ruby
|
1372
1396
|
window {
|
@@ -1376,14 +1400,41 @@ window {
|
|
1376
1400
|
|
1377
1401
|
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
1402
|
|
1379
|
-
|
1403
|
+
The data-binding API:
|
1404
|
+
- Bidirectional (two-way) data-binding to a Model attribute accessor: `view_property <=> [model, attribute, *read_or_write_options]`
|
1405
|
+
- Unidirectional (one-way) data-binding to a Model attribute reader: `view_property <= [model, attribute, *read_only_options]`
|
1406
|
+
|
1407
|
+
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.
|
1408
|
+
|
1409
|
+
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.
|
1410
|
+
|
1411
|
+
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.
|
1412
|
+
|
1413
|
+
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).
|
1380
1414
|
|
1381
|
-
|
1415
|
+
![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
|
1416
|
+
|
1417
|
+
Data-binding options include:
|
1382
1418
|
- `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
|
1383
1419
|
- `on_read {|value| ...}`: converts value read from Model to update the View.
|
1384
1420
|
- `after_read {|converted_value| ...}`: performs an operation after read from Model and updating the View.
|
1421
|
+
- `before_write {|value| ...}`: performs an operation before writing data to Model from View.
|
1422
|
+
- `on_write {|value| ...}`: converts value read from View to update the Model.
|
1423
|
+
- `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
|
1424
|
+
|
1425
|
+
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.
|
1426
|
+
|
1427
|
+
Example:
|
1428
|
+
|
1429
|
+
```ruby
|
1430
|
+
entry {
|
1431
|
+
text <=> [product, :price, on_read: :to_s, on_write: :to_i]
|
1432
|
+
}
|
1433
|
+
```
|
1434
|
+
|
1435
|
+
Gotcha: never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
|
1385
1436
|
|
1386
|
-
Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
|
1437
|
+
Learn more from data-binding usage in [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Login](#login), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
|
1387
1438
|
|
1388
1439
|
### API Gotchas
|
1389
1440
|
|
@@ -2169,7 +2220,44 @@ UI.main
|
|
2169
2220
|
UI.quit
|
2170
2221
|
```
|
2171
2222
|
|
2172
|
-
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2223
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2224
|
+
|
2225
|
+
```ruby
|
2226
|
+
require 'glimmer-dsl-libui'
|
2227
|
+
|
2228
|
+
class BasicEntry
|
2229
|
+
include Glimmer
|
2230
|
+
|
2231
|
+
attr_accessor :entry_text
|
2232
|
+
|
2233
|
+
def launch
|
2234
|
+
window('Basic Entry', 300, 50) {
|
2235
|
+
horizontal_box {
|
2236
|
+
entry {
|
2237
|
+
# stretchy true # Smart default option for appending to horizontal_box
|
2238
|
+
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.
|
2239
|
+
}
|
2240
|
+
|
2241
|
+
button('Button') {
|
2242
|
+
stretchy false # stretchy property is available when control is nested under horizontal_box
|
2243
|
+
|
2244
|
+
on_clicked do
|
2245
|
+
msg_box('You entered', entry_text)
|
2246
|
+
end
|
2247
|
+
}
|
2248
|
+
}
|
2249
|
+
|
2250
|
+
on_closing do
|
2251
|
+
puts 'Bye Bye'
|
2252
|
+
end
|
2253
|
+
}.show
|
2254
|
+
end
|
2255
|
+
end
|
2256
|
+
|
2257
|
+
BasicEntry.new.launch
|
2258
|
+
```
|
2259
|
+
|
2260
|
+
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2173
2261
|
|
2174
2262
|
```ruby
|
2175
2263
|
require 'glimmer-dsl-libui'
|
@@ -2493,7 +2581,59 @@ Mac | Windows | Linux
|
|
2493
2581
|
----|---------|------
|
2494
2582
|
![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
2583
|
|
2496
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
2584
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
2585
|
+
|
2586
|
+
```ruby
|
2587
|
+
require 'glimmer-dsl-libui'
|
2588
|
+
|
2589
|
+
class Form
|
2590
|
+
include Glimmer
|
2591
|
+
|
2592
|
+
attr_accessor :first_name, :last_name, :phone, :email
|
2593
|
+
|
2594
|
+
def launch
|
2595
|
+
window('Form') {
|
2596
|
+
margined true
|
2597
|
+
|
2598
|
+
vertical_box {
|
2599
|
+
form {
|
2600
|
+
entry {
|
2601
|
+
label 'First Name' # label property is available when control is nested under form
|
2602
|
+
text <=> [self, :first_name] # bidirectional data-binding of entry text property to self first_name attribute
|
2603
|
+
}
|
2604
|
+
|
2605
|
+
entry {
|
2606
|
+
label 'Last Name' # label property is available when control is nested under form
|
2607
|
+
text <=> [self, :last_name]
|
2608
|
+
}
|
2609
|
+
|
2610
|
+
entry {
|
2611
|
+
label 'Phone' # label property is available when control is nested under form
|
2612
|
+
text <=> [self, :phone]
|
2613
|
+
}
|
2614
|
+
|
2615
|
+
entry {
|
2616
|
+
label 'Email' # label property is available when control is nested under form
|
2617
|
+
text <=> [self, :email]
|
2618
|
+
}
|
2619
|
+
}
|
2620
|
+
|
2621
|
+
button('Display Info') {
|
2622
|
+
stretchy false
|
2623
|
+
|
2624
|
+
on_clicked do
|
2625
|
+
msg_box('Info', "#{first_name} #{last_name} has phone #{phone} and email #{email}")
|
2626
|
+
end
|
2627
|
+
}
|
2628
|
+
}
|
2629
|
+
}.show
|
2630
|
+
end
|
2631
|
+
end
|
2632
|
+
|
2633
|
+
Form.new.launch
|
2634
|
+
```
|
2635
|
+
|
2636
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
2497
2637
|
|
2498
2638
|
```ruby
|
2499
2639
|
require 'glimmer-dsl-libui'
|
@@ -5944,7 +6084,120 @@ Mac | Windows | Linux
|
|
5944
6084
|
----|---------|------
|
5945
6085
|
![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
6086
|
|
5947
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
6087
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6088
|
+
|
6089
|
+
```ruby
|
6090
|
+
require 'glimmer-dsl-libui'
|
6091
|
+
|
6092
|
+
class FormTable
|
6093
|
+
include Glimmer
|
6094
|
+
|
6095
|
+
attr_accessor :name, :email, :phone, :city, :state, :filter_value
|
6096
|
+
|
6097
|
+
def initialize
|
6098
|
+
@data = [
|
6099
|
+
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
|
6100
|
+
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
|
6101
|
+
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
|
6102
|
+
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
|
6103
|
+
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
|
6104
|
+
]
|
6105
|
+
end
|
6106
|
+
|
6107
|
+
def launch
|
6108
|
+
window('Contacts', 600, 600) { |w|
|
6109
|
+
margined true
|
6110
|
+
|
6111
|
+
vertical_box {
|
6112
|
+
form {
|
6113
|
+
stretchy false
|
6114
|
+
|
6115
|
+
entry {
|
6116
|
+
label 'Name'
|
6117
|
+
text <=> [self, :name]
|
6118
|
+
}
|
6119
|
+
|
6120
|
+
entry {
|
6121
|
+
label 'Email'
|
6122
|
+
text <=> [self, :email]
|
6123
|
+
}
|
6124
|
+
|
6125
|
+
entry {
|
6126
|
+
label 'Phone'
|
6127
|
+
text <=> [self, :phone]
|
6128
|
+
}
|
6129
|
+
|
6130
|
+
entry {
|
6131
|
+
label 'City'
|
6132
|
+
text <=> [self, :city]
|
6133
|
+
}
|
6134
|
+
|
6135
|
+
entry {
|
6136
|
+
label 'State'
|
6137
|
+
text <=> [self, :state]
|
6138
|
+
}
|
6139
|
+
}
|
6140
|
+
|
6141
|
+
button('Save Contact') {
|
6142
|
+
stretchy false
|
6143
|
+
|
6144
|
+
on_clicked do
|
6145
|
+
new_row = [name, email, phone, city, state]
|
6146
|
+
if new_row.include?('')
|
6147
|
+
msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
|
6148
|
+
else
|
6149
|
+
@data << new_row # automatically inserts a row into the table due to implicit data-binding
|
6150
|
+
@unfiltered_data = @data.dup
|
6151
|
+
self.name = '' # automatically clears name entry through explicit data-binding
|
6152
|
+
self.email = ''
|
6153
|
+
self.phone = ''
|
6154
|
+
self.city = ''
|
6155
|
+
self.state = ''
|
6156
|
+
end
|
6157
|
+
end
|
6158
|
+
}
|
6159
|
+
|
6160
|
+
search_entry {
|
6161
|
+
stretchy false
|
6162
|
+
text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
|
6163
|
+
after_write: ->(filter_value) { # execute after write to self.filter_value
|
6164
|
+
@unfiltered_data ||= @data.dup
|
6165
|
+
# Unfilter first to remove any previous filters
|
6166
|
+
@data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
|
6167
|
+
# Now, apply filter if entered
|
6168
|
+
unless filter_value.empty?
|
6169
|
+
@data.filter! do |row_data| # affects table indirectly through implicit data-binding
|
6170
|
+
row_data.any? do |cell|
|
6171
|
+
cell.to_s.downcase.include?(filter_value.downcase)
|
6172
|
+
end
|
6173
|
+
end
|
6174
|
+
end
|
6175
|
+
}
|
6176
|
+
]
|
6177
|
+
}
|
6178
|
+
|
6179
|
+
table {
|
6180
|
+
text_column('Name')
|
6181
|
+
text_column('Email')
|
6182
|
+
text_column('Phone')
|
6183
|
+
text_column('City')
|
6184
|
+
text_column('State')
|
6185
|
+
|
6186
|
+
cell_rows @data # implicit data-binding
|
6187
|
+
|
6188
|
+
on_changed do |row, type, row_data|
|
6189
|
+
puts "Row #{row} #{type}: #{row_data}"
|
6190
|
+
end
|
6191
|
+
}
|
6192
|
+
}
|
6193
|
+
}.show
|
6194
|
+
end
|
6195
|
+
end
|
6196
|
+
|
6197
|
+
FormTable.new.launch
|
6198
|
+
```
|
6199
|
+
|
6200
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
5948
6201
|
|
5949
6202
|
```ruby
|
5950
6203
|
require 'glimmer-dsl-libui'
|
@@ -6523,7 +6776,259 @@ Mac | Windows | Linux
|
|
6523
6776
|
----|---------|------
|
6524
6777
|
![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
6778
|
|
6526
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
6779
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6780
|
+
|
6781
|
+
```ruby
|
6782
|
+
require 'glimmer-dsl-libui'
|
6783
|
+
|
6784
|
+
class Login
|
6785
|
+
include Glimmer
|
6786
|
+
|
6787
|
+
attr_accessor :username, :password, :logged_in
|
6788
|
+
|
6789
|
+
def launch
|
6790
|
+
window('Login') {
|
6791
|
+
margined true
|
6792
|
+
|
6793
|
+
vertical_box {
|
6794
|
+
form {
|
6795
|
+
entry {
|
6796
|
+
label 'Username:'
|
6797
|
+
text <=> [self, :username]
|
6798
|
+
enabled <= [self, :logged_in, on_read: :!]
|
6799
|
+
}
|
6800
|
+
|
6801
|
+
password_entry {
|
6802
|
+
label 'Password:'
|
6803
|
+
text <=> [self, :password]
|
6804
|
+
enabled <= [self, :logged_in, on_read: :!]
|
6805
|
+
}
|
6806
|
+
}
|
6807
|
+
|
6808
|
+
horizontal_box {
|
6809
|
+
button('Login') {
|
6810
|
+
enabled <= [self, :logged_in, on_read: :!]
|
6811
|
+
|
6812
|
+
on_clicked do
|
6813
|
+
self.logged_in = true
|
6814
|
+
end
|
6815
|
+
}
|
6816
|
+
|
6817
|
+
button('Logout') {
|
6818
|
+
enabled <= [self, :logged_in]
|
6819
|
+
|
6820
|
+
on_clicked do
|
6821
|
+
self.logged_in = false
|
6822
|
+
self.username = ''
|
6823
|
+
self.password = ''
|
6824
|
+
end
|
6825
|
+
}
|
6826
|
+
}
|
6827
|
+
}
|
6828
|
+
}.show
|
6829
|
+
end
|
6830
|
+
end
|
6831
|
+
|
6832
|
+
Login.new.launch
|
6833
|
+
```
|
6834
|
+
|
6835
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
|
6836
|
+
|
6837
|
+
```ruby
|
6838
|
+
require 'glimmer-dsl-libui'
|
6839
|
+
|
6840
|
+
class Login
|
6841
|
+
include Glimmer
|
6842
|
+
|
6843
|
+
attr_accessor :username, :password, :logged_in
|
6844
|
+
|
6845
|
+
def logged_out
|
6846
|
+
!logged_in
|
6847
|
+
end
|
6848
|
+
|
6849
|
+
def launch
|
6850
|
+
window('Login') {
|
6851
|
+
margined true
|
6852
|
+
|
6853
|
+
vertical_box {
|
6854
|
+
form {
|
6855
|
+
entry {
|
6856
|
+
label 'Username:'
|
6857
|
+
text <=> [self, :username]
|
6858
|
+
enabled <= [self, :logged_out, computed_by: :logged_in] # computed_by option ensures being notified of changes to logged_in
|
6859
|
+
}
|
6860
|
+
|
6861
|
+
password_entry {
|
6862
|
+
label 'Password:'
|
6863
|
+
text <=> [self, :password]
|
6864
|
+
enabled <= [self, :logged_out, computed_by: :logged_in]
|
6865
|
+
}
|
6866
|
+
}
|
6867
|
+
|
6868
|
+
horizontal_box {
|
6869
|
+
button('Login') {
|
6870
|
+
enabled <= [self, :logged_out, computed_by: :logged_in]
|
6871
|
+
|
6872
|
+
on_clicked do
|
6873
|
+
self.logged_in = true
|
6874
|
+
end
|
6875
|
+
}
|
6876
|
+
|
6877
|
+
button('Logout') {
|
6878
|
+
enabled <= [self, :logged_in]
|
6879
|
+
|
6880
|
+
on_clicked do
|
6881
|
+
self.logged_in = false
|
6882
|
+
self.username = ''
|
6883
|
+
self.password = ''
|
6884
|
+
end
|
6885
|
+
}
|
6886
|
+
}
|
6887
|
+
}
|
6888
|
+
}.show
|
6889
|
+
end
|
6890
|
+
end
|
6891
|
+
|
6892
|
+
Login.new.launch
|
6893
|
+
```
|
6894
|
+
|
6895
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6896
|
+
|
6897
|
+
```ruby
|
6898
|
+
require 'glimmer-dsl-libui'
|
6899
|
+
|
6900
|
+
class Login
|
6901
|
+
include Glimmer
|
6902
|
+
|
6903
|
+
attr_accessor :username, :password
|
6904
|
+
attr_reader :logged_in
|
6905
|
+
|
6906
|
+
def logged_in=(value)
|
6907
|
+
@logged_in = value
|
6908
|
+
self.logged_out = !value # calling logged_out= method notifies logged_out observers
|
6909
|
+
end
|
6910
|
+
|
6911
|
+
def logged_out=(value)
|
6912
|
+
self.logged_in = !value unless logged_in == !value
|
6913
|
+
end
|
6914
|
+
|
6915
|
+
def logged_out
|
6916
|
+
!logged_in
|
6917
|
+
end
|
6918
|
+
|
6919
|
+
def launch
|
6920
|
+
window('Login') {
|
6921
|
+
margined true
|
6922
|
+
|
6923
|
+
vertical_box {
|
6924
|
+
form {
|
6925
|
+
entry {
|
6926
|
+
label 'Username:'
|
6927
|
+
text <=> [self, :username]
|
6928
|
+
enabled <= [self, :logged_out]
|
6929
|
+
}
|
6930
|
+
|
6931
|
+
password_entry {
|
6932
|
+
label 'Password:'
|
6933
|
+
text <=> [self, :password]
|
6934
|
+
enabled <= [self, :logged_out]
|
6935
|
+
}
|
6936
|
+
}
|
6937
|
+
|
6938
|
+
horizontal_box {
|
6939
|
+
button('Login') {
|
6940
|
+
enabled <= [self, :logged_out]
|
6941
|
+
|
6942
|
+
on_clicked do
|
6943
|
+
self.logged_in = true
|
6944
|
+
end
|
6945
|
+
}
|
6946
|
+
|
6947
|
+
button('Logout') {
|
6948
|
+
enabled <= [self, :logged_in]
|
6949
|
+
|
6950
|
+
on_clicked do
|
6951
|
+
self.logged_in = false
|
6952
|
+
self.username = ''
|
6953
|
+
self.password = ''
|
6954
|
+
end
|
6955
|
+
}
|
6956
|
+
}
|
6957
|
+
}
|
6958
|
+
}.show
|
6959
|
+
end
|
6960
|
+
end
|
6961
|
+
|
6962
|
+
Login.new.launch
|
6963
|
+
```
|
6964
|
+
|
6965
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
6966
|
+
|
6967
|
+
```ruby
|
6968
|
+
require 'glimmer-dsl-libui'
|
6969
|
+
|
6970
|
+
class Login
|
6971
|
+
include Glimmer
|
6972
|
+
|
6973
|
+
attr_accessor :username, :password
|
6974
|
+
attr_reader :logged_in
|
6975
|
+
|
6976
|
+
def logged_in=(value)
|
6977
|
+
@logged_in = value
|
6978
|
+
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
|
6979
|
+
end
|
6980
|
+
|
6981
|
+
def logged_out
|
6982
|
+
!logged_in
|
6983
|
+
end
|
6984
|
+
|
6985
|
+
def launch
|
6986
|
+
window('Login') {
|
6987
|
+
margined true
|
6988
|
+
|
6989
|
+
vertical_box {
|
6990
|
+
form {
|
6991
|
+
entry {
|
6992
|
+
label 'Username:'
|
6993
|
+
text <=> [self, :username]
|
6994
|
+
enabled <= [self, :logged_out]
|
6995
|
+
}
|
6996
|
+
|
6997
|
+
password_entry {
|
6998
|
+
label 'Password:'
|
6999
|
+
text <=> [self, :password]
|
7000
|
+
enabled <= [self, :logged_out]
|
7001
|
+
}
|
7002
|
+
}
|
7003
|
+
|
7004
|
+
horizontal_box {
|
7005
|
+
button('Login') {
|
7006
|
+
enabled <= [self, :logged_out]
|
7007
|
+
|
7008
|
+
on_clicked do
|
7009
|
+
self.logged_in = true
|
7010
|
+
end
|
7011
|
+
}
|
7012
|
+
|
7013
|
+
button('Logout') {
|
7014
|
+
enabled <= [self, :logged_in]
|
7015
|
+
|
7016
|
+
on_clicked do
|
7017
|
+
self.logged_in = false
|
7018
|
+
self.username = ''
|
7019
|
+
self.password = ''
|
7020
|
+
end
|
7021
|
+
}
|
7022
|
+
}
|
7023
|
+
}
|
7024
|
+
}.show
|
7025
|
+
end
|
7026
|
+
end
|
7027
|
+
|
7028
|
+
Login.new.launch
|
7029
|
+
```
|
7030
|
+
|
7031
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (without [data-binding](#data-binding)):
|
6527
7032
|
|
6528
7033
|
```ruby
|
6529
7034
|
require 'glimmer-dsl-libui'
|
@@ -6591,7 +7096,102 @@ Mac | Windows | Linux
|
|
6591
7096
|
----|---------|------
|
6592
7097
|
![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
7098
|
|
6594
|
-
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
7099
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
|
7100
|
+
|
7101
|
+
```ruby
|
7102
|
+
require 'glimmer-dsl-libui'
|
7103
|
+
require 'facets'
|
7104
|
+
|
7105
|
+
include Glimmer
|
7106
|
+
|
7107
|
+
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
7108
|
+
|
7109
|
+
def form_field(model, attribute)
|
7110
|
+
attribute = attribute.to_s
|
7111
|
+
entry { |e|
|
7112
|
+
label attribute.underscore.split('_').map(&:capitalize).join(' ')
|
7113
|
+
text <=> [model, attribute]
|
7114
|
+
}
|
7115
|
+
end
|
7116
|
+
|
7117
|
+
def address_form(address)
|
7118
|
+
form {
|
7119
|
+
form_field(address, :street)
|
7120
|
+
form_field(address, :p_o_box)
|
7121
|
+
form_field(address, :city)
|
7122
|
+
form_field(address, :state)
|
7123
|
+
form_field(address, :zip_code)
|
7124
|
+
}
|
7125
|
+
end
|
7126
|
+
|
7127
|
+
def label_pair(model, attribute, value)
|
7128
|
+
horizontal_box {
|
7129
|
+
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
|
7130
|
+
label(value.to_s) {
|
7131
|
+
text <= [model, attribute]
|
7132
|
+
}
|
7133
|
+
}
|
7134
|
+
end
|
7135
|
+
|
7136
|
+
def address(address)
|
7137
|
+
vertical_box {
|
7138
|
+
address.each_pair do |attribute, value|
|
7139
|
+
label_pair(address, attribute, value)
|
7140
|
+
end
|
7141
|
+
}
|
7142
|
+
end
|
7143
|
+
|
7144
|
+
address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
|
7145
|
+
address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
|
7146
|
+
|
7147
|
+
window('Method-Based Custom Keyword') {
|
7148
|
+
margined true
|
7149
|
+
|
7150
|
+
horizontal_box {
|
7151
|
+
vertical_box {
|
7152
|
+
label('Address 1') {
|
7153
|
+
stretchy false
|
7154
|
+
}
|
7155
|
+
|
7156
|
+
address_form(address1)
|
7157
|
+
|
7158
|
+
horizontal_separator {
|
7159
|
+
stretchy false
|
7160
|
+
}
|
7161
|
+
|
7162
|
+
label('Address 1 (Saved)') {
|
7163
|
+
stretchy false
|
7164
|
+
}
|
7165
|
+
|
7166
|
+
address(address1)
|
7167
|
+
}
|
7168
|
+
|
7169
|
+
vertical_separator {
|
7170
|
+
stretchy false
|
7171
|
+
}
|
7172
|
+
|
7173
|
+
vertical_box {
|
7174
|
+
label('Address 2') {
|
7175
|
+
stretchy false
|
7176
|
+
}
|
7177
|
+
|
7178
|
+
address_form(address2)
|
7179
|
+
|
7180
|
+
horizontal_separator {
|
7181
|
+
stretchy false
|
7182
|
+
}
|
7183
|
+
|
7184
|
+
label('Address 2 (Saved)') {
|
7185
|
+
stretchy false
|
7186
|
+
}
|
7187
|
+
|
7188
|
+
address(address2)
|
7189
|
+
}
|
7190
|
+
}
|
7191
|
+
}.show
|
7192
|
+
```
|
7193
|
+
|
7194
|
+
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
|
6595
7195
|
|
6596
7196
|
```ruby
|
6597
7197
|
require 'glimmer-dsl-libui'
|