glimmer-dsl-libui 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a37cb8210f604b9307693af84b47ba0e21cf2a2b1059139af1d4ffa2eb727bf8
4
- data.tar.gz: 88443d069ff718e8d28c90dbb59fc6ff5e5a05a5f3e9f5be78418d1754868769
3
+ metadata.gz: effb65102f9db3aa2977bac55edd4f67f91f1eafdae26403b8c044c714048a03
4
+ data.tar.gz: 3923d80a19afd62a0220026f33720ee953ad166880e17d60883aed53f9977d86
5
5
  SHA512:
6
- metadata.gz: 1a2e8c3f162744fe2266d66a088bceb7595b04643a141b0dd526309b6155362e24fad6c760cc8eb060030fec35352ab96b9dfad7e601e5a168271e1150aa7789
7
- data.tar.gz: 90674bbf9623bcd0e985a94824698c3117a8a70bea79f7ee81d11364054c70d57f260466123717d2f292aa50f113cfb11201580a3f74bc74400a100f9c049d6d
6
+ metadata.gz: 6e123f42fd61ce49b126e7f51caf738e0c769eca12b177c879007cedaba6ec38974d16ca864e8d8922546f79cfe2dc8946056935684f0bab9d88892e425c5d79
7
+ data.tar.gz: 8d0d79d7632e30568f747a3db5d9bbdef387e854f55e0cf6559a4d8d242075bc8c1836e00e2554a30eb5eded934bb23c267a764b127cfe768c39e679ff47671e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.11.0
4
+
5
+ - Control `content` data-binding to generate nested controls dynamically based on a model attribute change
6
+ - `examples/dynamic_form.rb` to demonstrate control `content` data-binding
7
+
3
8
  ## 0.10.2
4
9
 
5
10
  - In Snake example, change snake direction on key press instead of key release to be more responsive for players who are not used to releasing pressed keys quickly
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.10.2
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.11.0
2
2
  ## Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library ([Fukuoka Award Winning](http://www.digitalfukuoka.jp/topics/187?locale=ja))
3
3
  ### The Quickest Way From Zero To GUI
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
@@ -431,7 +431,7 @@ gem install glimmer-dsl-libui
431
431
  Or install via Bundler `Gemfile`:
432
432
 
433
433
  ```ruby
434
- gem 'glimmer-dsl-libui', '~> 0.10.2'
434
+ gem 'glimmer-dsl-libui', '~> 0.11.0'
435
435
  ```
436
436
 
437
437
  Test that installation worked by running the [Glimmer Meta-Example](#examples):
@@ -452,8 +452,19 @@ Mac | Windows | Linux
452
452
 
453
453
  ## Usage
454
454
 
455
- Require [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) (whether through a Ruby `require` statement or `Bundler`) and then include the `Glimmer` or `Glimmer::LibUI::Application` module to enable access to the Glimmer GUI DSL in one of multiple approaches.
456
-
455
+ Start by requiring the [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) Ruby gem, whether through a Ruby `require 'glimmer-dsl-libui` statement or `Bundler`.
456
+
457
+ Afterwards, to access the Glimmer GUI DSL:
458
+ - If you are learning/experimenting/prototyping with Glimmer DSL for LibUI, include the `Glimmer` module into the top-level scope or a Ruby class.
459
+ - If you are building a serious application, include `Glimmer::LibUI::Application` into the main view Ruby class
460
+ - If you are building a custom control, include [`Glimmer::LibUI::CustomControl`](#custom-components) into a Ruby class
461
+ - If you are building a cusotm window, include [`Glimmer::LibUI::CustomWindow`](#custom-components) into a Ruby class
462
+ - If you are building a custom shape, include [`Glimmer::LibUI::CustomShape`](#custom-components) into a Ruby class.
463
+
464
+ You may learn more about the different options above with basic examples in the following subsections: [Experimentation Usage](#experimentation-usage), [Prototyping Usage](#prototyping-usage), [Serious Usage](#serious-usage).
465
+
466
+ If you are new to [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) (beginner), after going through the subsections below, check out the RubyConf 2022 talk ["Building Native GUI Apps in Ruby"](https://andymaleh.blogspot.com/2023/02/rubyconf-2022-talk-video-for-building.html), [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts), [Glimmer Command](#glimmer-command) (just the basics, how to run an app, and how to run examples to start), [Girb](#girb-glimmer-irb) and [Examples](#examples) to quickly learn through copy/paste. It is very important for beginners to go through all the [Examples](#examples) from the most basic to the most advanced while reading the README topics that relate to the examples. You may refer to the [API](#api) on once you have gotten your feet wet with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) and need more detailed reference information.
467
+
457
468
  ### Experimentation Usage
458
469
 
459
470
  For experimenting and learning, add `include Glimmer` into the top-level main object and start using the Glimmer GUI DSL directly.
@@ -534,9 +545,6 @@ SomeGlimmerApp.launch
534
545
 
535
546
  (note: `Glimmer::LibUI::Application` is an alias for `Glimmer::LibUI::CustomWindow` since that is what it represents)
536
547
 
537
- If you are new to [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), check out the [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts), [Glimmer Command](#glimmer-command), [Girb](#girb-glimmer-irb) and [Examples](#examples) to quickly learn through copy/paste. You may refer to the [API](#api) later on once you have gotten your feet wet with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) and need more detailed reference information.
538
-
539
-
540
548
  ## Glimmer Command
541
549
 
542
550
  The `glimmer` command allows you to conveniently run applications (`glimmer app_path`), run examples (`glimmer examples`), and scaffold applications (`glimmer "scaffold[app_name]"`).
@@ -1591,6 +1599,43 @@ button('greet') { |b|
1591
1599
  }
1592
1600
  ```
1593
1601
 
1602
+ If there is ever a need to add more content to a control, you can re-open its content with the `control.content { ... }` method.
1603
+
1604
+ Example:
1605
+
1606
+ ```ruby
1607
+ box1 = vertical_box {
1608
+ label('First Name')
1609
+ }
1610
+ # re-open content of box1 and add another control
1611
+ box1.content {
1612
+ entry {
1613
+ text 'fill in your first name'
1614
+ }
1615
+ }
1616
+ ```
1617
+
1618
+ You can also use [data-binding](#data-binding) with content blocks to generate content dynamically based on changes in a model attribute. The only difference in syntax in this case would be to wrap the content with an explicit `content(*binding_args) { ... }` block that includes data-binding arguments for a model attribute.
1619
+
1620
+ Example:
1621
+
1622
+ ```ruby
1623
+ form {
1624
+ stretchy false
1625
+
1626
+ content(@user, :customizable_attributes) {
1627
+ @user.customizable_attributes.each do |attribute|
1628
+ entry {
1629
+ label attribute.to_s.split('_').map(&:capitalize).join(' ')
1630
+ text <=> [@user, attribute]
1631
+ }
1632
+ end
1633
+ }
1634
+ }
1635
+ ```
1636
+
1637
+ The form above will only display fields for a model's customizable attributes, so if they change, the form content will change too. Learn more at the [Dynamic Form](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#dynamic-form) example.
1638
+
1594
1639
  **Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title "hello world"` inside `group`). Behind the scenes, properties correspond to `LibUI.control_set_property` methods.
1595
1640
 
1596
1641
  **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (always followed by a `do; end` style block to signify logic).
@@ -3387,11 +3432,11 @@ Another example of bidirectional data-binding with an option:
3387
3432
 
3388
3433
  ```ruby
3389
3434
  entry {
3390
- text <=> [self, :entered_text, after_write: ->(text) {puts text}]
3435
+ text <=> [model, :entered_text, after_write: ->(text) {puts text}]
3391
3436
  }
3392
3437
  ```
3393
3438
 
3394
- That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
3439
+ That is data-binding `entered_text` attribute on `model` to `entry` `text` property and printing text after write to the model.
3395
3440
 
3396
3441
  ##### Table Data-Binding
3397
3442
 
@@ -3573,6 +3618,27 @@ window {
3573
3618
 
3574
3619
  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`.
3575
3620
 
3621
+ You can also use unidirectional [data-binding](#data-binding) with content blocks to generate content dynamically based on changes in a model attribute. The only difference in syntax in this case would be to wrap the content with an explicit `content(*binding_args) { ... }` block that includes data-binding arguments for a model attribute.
3622
+
3623
+ Example:
3624
+
3625
+ ```ruby
3626
+ form {
3627
+ stretchy false
3628
+
3629
+ content(@user, :customizable_attributes) {
3630
+ @user.customizable_attributes.each do |attribute|
3631
+ entry {
3632
+ label attribute.to_s.split('_').map(&:capitalize).join(' ')
3633
+ text <=> [@user, attribute]
3634
+ }
3635
+ end
3636
+ }
3637
+ }
3638
+ ```
3639
+
3640
+ The form above will only display fields for a model's customizable attributes, so if they change, the form content will change too. Learn more at the [Dynamic Form](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#dynamic-form) example.
3641
+
3576
3642
  #### Data-Binding API
3577
3643
 
3578
3644
  To summarize the data-binding API:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.2
1
+ 0.11.0
@@ -8,6 +8,7 @@
8
8
  - [CPU Percentage](#cpu-percentage)
9
9
  - [Custom Draw Text](#custom-draw-text)
10
10
  - [Dynamic Area](#dynamic-area)
11
+ - [Dynamic Form](#dynamic-form)
11
12
  - [Editable Column Table](#editable-column-table)
12
13
  - [Editable Table](#editable-table)
13
14
  - [Form Table](#form-table)
@@ -211,6 +212,33 @@ Version 4 (declarative stable `path` approach without [data-binding](#data-bindi
211
212
 
212
213
  [examples/dynamic_area4.rb](/examples/dynamic_area4.rb)
213
214
 
215
+
216
+ ## Dynamic Form
217
+
218
+ [examples/dynamic_form.rb](/examples/dynamic_form.rb)
219
+
220
+ Run with this command from the root of the project if you cloned the project:
221
+
222
+ ```
223
+ ruby -r './lib/glimmer-dsl-libui' examples/dynamic_form.rb
224
+ ```
225
+
226
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
227
+
228
+ ```
229
+ ruby -r glimmer-dsl-libui -e "require 'examples/dynamic_form'"
230
+ ```
231
+
232
+ ![glimmer-dsl-libui-mac-dynamic-form.png](/images/glimmer-dsl-libui-mac-dynamic-form.png)
233
+
234
+ ![glimmer-dsl-libui-mac-dynamic-form-filled.png](/images/glimmer-dsl-libui-mac-dynamic-form-filled.png)
235
+
236
+ ![glimmer-dsl-libui-mac-dynamic-form-summarized.png](/images/glimmer-dsl-libui-mac-dynamic-form-summarized.png)
237
+
238
+ ![glimmer-dsl-libui-mac-dynamic-form-without-some-fields.png](/images/glimmer-dsl-libui-mac-dynamic-form-without-some-fields.png)
239
+
240
+ ![glimmer-dsl-libui-mac-dynamic-form-without-some-fields-summarized.png](/images/glimmer-dsl-libui-mac-dynamic-form-without-some-fields-summarized.png)
241
+
214
242
  ## Editable Column Table
215
243
 
216
244
  [examples/editable_column_table.rb](/examples/editable_column_table.rb)
@@ -1,28 +1,35 @@
1
1
  require 'glimmer-dsl-libui'
2
2
 
3
- class ButtonCounter
4
- include Glimmer
5
-
3
+ class Counter
6
4
  attr_accessor :count
7
-
5
+
8
6
  def initialize
9
7
  self.count = 0
10
8
  end
9
+ end
11
10
 
12
- def launch
11
+ class ButtonCounter
12
+ include Glimmer::LibUI::Application
13
+
14
+ before_body do
15
+ @counter = Counter.new
16
+ end
17
+
18
+ body {
13
19
  window('Hello, Button!', 190, 20) {
14
20
  vertical_box {
15
21
  button {
16
- # data-bind button text to self count, converting to string on read.
17
- text <= [self, :count, on_read: ->(count) {"Count: #{count}"}]
22
+ # data-bind button text to @counter count, converting to string on read from model.
23
+ text <= [@counter, :count, on_read: ->(count) {"Count: #{count}"}]
18
24
 
19
25
  on_clicked do
20
- self.count += 1
26
+ # This change will automatically propagate to button text through data-binding above
27
+ @counter.count += 1
21
28
  end
22
29
  }
23
30
  }
24
- }.show
25
- end
31
+ }
32
+ }
26
33
  end
27
34
 
28
- ButtonCounter.new.launch
35
+ ButtonCounter.launch
@@ -0,0 +1,69 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class User
4
+ ALL_ATTRIBUTES = [:first_name, :last_name, :email, :street, :city, :state, :zip_code, :country]
5
+
6
+ attr_accessor :customizable_attributes, *ALL_ATTRIBUTES
7
+
8
+ def initialize
9
+ # allow customizing all attributes by default
10
+ self.customizable_attributes = ALL_ATTRIBUTES.dup
11
+ end
12
+ end
13
+
14
+ class DynamicForm
15
+ include Glimmer::LibUI::Application
16
+
17
+ before_body do
18
+ @user = User.new
19
+ end
20
+
21
+ body {
22
+ window('Dynamic Form') {
23
+ margined true
24
+
25
+ vertical_box {
26
+ horizontal_box {
27
+ User::ALL_ATTRIBUTES.each do |attribute|
28
+ checkbox(attribute.to_s) {
29
+ checked <=> [@user, :customizable_attributes,
30
+ on_read: -> (attributes) { @user.customizable_attributes.include?(attribute) },
31
+ on_write: -> (checked_value) {
32
+ if checked_value
33
+ @user.customizable_attributes.push(attribute)
34
+ else
35
+ @user.customizable_attributes.delete(attribute)
36
+ end
37
+ @user.customizable_attributes.sort_by {|attribute| User::ALL_ATTRIBUTES.index(attribute)}
38
+ },
39
+ ]
40
+ }
41
+ end
42
+ }
43
+
44
+ form {
45
+ stretchy false
46
+
47
+ # Control content data-binding allows dynamically changing content based on changes in a model attribute
48
+ content(@user, :customizable_attributes) {
49
+ @user.customizable_attributes.each do |attribute|
50
+ entry {
51
+ label attribute.to_s.split('_').map(&:capitalize).join(' ')
52
+ text <=> [@user, attribute]
53
+ }
54
+ end
55
+ }
56
+ }
57
+
58
+ button('Summarize') {
59
+ on_clicked do
60
+ summary = @user.customizable_attributes.map { |attribute| @user.send(attribute) }.join(', ')
61
+ msg_box('Summary', summary)
62
+ end
63
+ }
64
+ }
65
+ }
66
+ }
67
+ end
68
+
69
+ DynamicForm.launch
Binary file
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/dsl/static_expression'
23
+
24
+ module Glimmer
25
+ module DSL
26
+ module Libui
27
+ class ContentExpression < StaticExpression
28
+ def can_interpret?(parent, keyword, *args, &block)
29
+ keyword == 'content' &&
30
+ block_given? &&
31
+ args.size > 0 &&
32
+ parent&.respond_to?(:bind_content)
33
+ end
34
+
35
+ def interpret(parent, keyword, *args, &block)
36
+ parent.bind_content(*args, &block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -40,6 +40,7 @@ module Glimmer
40
40
  data_binding
41
41
  shine_data_binding
42
42
  property
43
+ content
43
44
  string
44
45
  operation
45
46
  control
@@ -384,6 +384,19 @@ module Glimmer
384
384
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::ControlExpression.new, @keyword, {post_add_content: @content_added}, &block)
385
385
  end
386
386
 
387
+ # Data-binds the generation of nested content to a model/property (in binding args)
388
+ # consider providing an option to avoid initial rendering without any changes happening
389
+ def bind_content(*binding_args, &content_block)
390
+ # TODO in the future, consider optimizing code by diffing content if that makes sense
391
+ content_binding_work = proc do |*values|
392
+ children.dup.each { |child| child.destroy }
393
+ content(&content_block)
394
+ end
395
+ content_binding_observer = Glimmer::DataBinding::Observer.proc(&content_binding_work)
396
+ content_binding_observer.observe(*binding_args)
397
+ content_binding_work.call # TODO inspect if we need to pass args here (from observed attributes) [but it's simpler not to pass anything at first]
398
+ end
399
+
387
400
  private
388
401
 
389
402
  def build_control
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-28 00:00:00.000000000 Z
11
+ date: 2023-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -437,6 +437,7 @@ files:
437
437
  - examples/dynamic_area2.rb
438
438
  - examples/dynamic_area3.rb
439
439
  - examples/dynamic_area4.rb
440
+ - examples/dynamic_form.rb
440
441
  - examples/editable_column_table.rb
441
442
  - examples/editable_table.rb
442
443
  - examples/font_button.rb
@@ -500,6 +501,7 @@ files:
500
501
  - lib/glimmer-dsl-libui/ext/rouge/theme/glimmer.rb
501
502
  - lib/glimmer/Rakefile
502
503
  - lib/glimmer/dsl/libui/bind_expression.rb
504
+ - lib/glimmer/dsl/libui/content_expression.rb
503
505
  - lib/glimmer/dsl/libui/control_expression.rb
504
506
  - lib/glimmer/dsl/libui/custom_control_expression.rb
505
507
  - lib/glimmer/dsl/libui/custom_shape_expression.rb