glimmer-dsl-libui 0.2.0 → 0.2.4

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +975 -23
  4. data/VERSION +1 -1
  5. data/examples/area_gallery.rb +7 -1
  6. data/examples/area_gallery2.rb +14 -4
  7. data/examples/area_gallery3.rb +8 -2
  8. data/examples/area_gallery4.rb +15 -5
  9. data/examples/basic_draw_text.rb +65 -0
  10. data/examples/basic_draw_text2.rb +68 -0
  11. data/examples/color_the_circles.rb +220 -0
  12. data/examples/custom_draw_text.rb +84 -0
  13. data/examples/custom_draw_text2.rb +95 -0
  14. data/examples/midi_player.rb +2 -2
  15. data/examples/timer.rb +135 -0
  16. data/glimmer-dsl-libui.gemspec +0 -0
  17. data/lib/glimmer/dsl/libui/control_expression.rb +0 -1
  18. data/lib/glimmer/dsl/libui/property_expression.rb +3 -1
  19. data/lib/glimmer/dsl/libui/string_expression.rb +48 -0
  20. data/lib/glimmer/libui/attributed_string.rb +135 -0
  21. data/lib/glimmer/libui/control_proxy/area_proxy.rb +14 -3
  22. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +0 -1
  23. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +18 -0
  24. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +3 -3
  25. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +69 -0
  26. data/lib/glimmer/libui/control_proxy/menu_proxy.rb +3 -0
  27. data/lib/glimmer/libui/control_proxy/path_proxy.rb +0 -2
  28. data/lib/glimmer/libui/control_proxy/text_proxy.rb +172 -0
  29. data/lib/glimmer/libui/control_proxy/window_proxy.rb +0 -6
  30. data/lib/glimmer/libui/control_proxy.rb +7 -1
  31. data/lib/glimmer/libui/shape/arc.rb +6 -3
  32. data/lib/glimmer/libui/shape/circle.rb +50 -0
  33. data/lib/glimmer/libui/shape/rectangle.rb +4 -0
  34. data/lib/glimmer/libui/shape/square.rb +4 -0
  35. data/lib/glimmer/libui.rb +73 -6
  36. metadata +13 -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.1.11
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.2.4
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
  [![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
@@ -118,11 +118,17 @@ window('Area Gallery', 400, 400) {
118
118
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
119
119
  }
120
120
  path { # declarative stable path
121
- arc(200, 200, 90, 0, 360, false)
121
+ circle(200, 200, 90)
122
122
 
123
123
  fill r: 202, g: 102, b: 204, a: 0.5
124
124
  stroke r: 0, g: 0, b: 0, thickness: 2
125
125
  }
126
+ path { # declarative stable path
127
+ arc(400, 220, 180, 90, 90, false)
128
+
129
+ fill r: 204, g: 102, b: 204, a: 0.5
130
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
131
+ }
126
132
 
127
133
  on_mouse_event do |area_mouse_event|
128
134
  p area_mouse_event
@@ -191,7 +197,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
191
197
 
192
198
  ## Table of Contents
193
199
 
194
- - [Glimmer DSL for LibUI 0.1.10](#-glimmer-dsl-for-libui-0111)
200
+ - [Glimmer DSL for LibUI 0.2.4](#-glimmer-dsl-for-libui-024)
195
201
  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
196
202
  - [Usage](#usage)
197
203
  - [Girb (Glimmer IRB)](#girb-glimmer-irb)
@@ -199,6 +205,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
199
205
  - [Supported Controls](#supported-controls)
200
206
  - [Common Control Properties](#common-control-properties)
201
207
  - [Common Control Operations](#common-control-operations)
208
+ - [LibUI Operations](#libui-operations)
202
209
  - [Extra Dialogs](#extra-dialogs)
203
210
  - [Extra Operations](#extra-operations)
204
211
  - [Table API](#table-api)
@@ -235,6 +242,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
235
242
  - [Histogram](#histogram)
236
243
  - [Basic Transform](#basic-transform)
237
244
  - [Login](#login)
245
+ - [Timer](#timer)
246
+ - [Color The Circles](#color-the-circles)
247
+ - [Basic Draw Text](#basic-draw-text)
248
+ - [Custom Draw Text](#custom-draw-text)
238
249
  - [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
239
250
  - [Help](#help)
240
251
  - [Issues](#issues)
@@ -322,7 +333,7 @@ gem install glimmer-dsl-libui
322
333
  Or install via Bundler `Gemfile`:
323
334
 
324
335
  ```ruby
325
- gem 'glimmer-dsl-libui', '~> 0.1.10'
336
+ gem 'glimmer-dsl-libui', '~> 0.2.4'
326
337
  ```
327
338
 
328
339
  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.
@@ -475,6 +486,13 @@ Control(Args) | Properties | Listeners
475
486
  - `hide`
476
487
  - `show`
477
488
 
489
+ ### LibUI Operations
490
+
491
+ All operations that could normally be called on `LibUI` can also be called on `Glimmer::LibUI`, but some have enhancements as detailed below.
492
+
493
+ - `Glimmer::LibUI::queue_main(&block)`: queues an operation to be run on the main event loop at the earliest opportunity possible
494
+ - `Glimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, &block)`: calls block after time_in_seconds has elapsed, repeating indefinitely unless repeat is `false` or an `Integer` for finite number of repeats. Block can return `false` or `true` to override next repetition.
495
+
478
496
  ### Extra Dialogs
479
497
 
480
498
  - `open_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
@@ -766,6 +784,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
766
784
 
767
785
  `fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-number hex or 3-number hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
768
786
 
787
+ Available [X11](https://en.wikipedia.org/wiki/X11_color_names) colors can be obtained through `Glimmer::LibUI.x11_colors` method.
788
+
769
789
  Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wikipedia.org/wiki/X11_color_names) colors.
770
790
 
771
791
  Check [Histogram](#histogram) example for use of hex colors.
@@ -1258,9 +1278,7 @@ window('Notepad', 500, 300) {
1258
1278
 
1259
1279
  ### Midi Player
1260
1280
 
1261
- This example has prerequisites:
1262
- - Install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
1263
- - Add `*.mid` files to `~/Music` directory (you may copy the ones included in [sounds](sounds) directory)
1281
+ To run this example, install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
1264
1282
 
1265
1283
  [examples/midi_player.rb](examples/midi_player.rb)
1266
1284
 
@@ -1399,7 +1417,7 @@ class TinyMidiPlayer
1399
1417
 
1400
1418
  def initialize
1401
1419
  @pid = nil
1402
- @music_directory = File.expand_path(ARGV[0] || '~/Music/')
1420
+ @music_directory = File.expand_path('../sounds', __dir__)
1403
1421
  @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
1404
1422
  .sort_by { |path| File.basename(path) }
1405
1423
  at_exit { stop_midi }
@@ -1438,7 +1456,7 @@ class TinyMidiPlayer
1438
1456
  end
1439
1457
 
1440
1458
  def create_gui
1441
- menu('Help') { |m|
1459
+ menu('Help') {
1442
1460
  menu_item('Version') {
1443
1461
  on_clicked do
1444
1462
  show_version
@@ -3523,11 +3541,17 @@ window('Area Gallery', 400, 400) {
3523
3541
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3524
3542
  }
3525
3543
  path { # declarative stable path
3526
- arc(200, 200, 90, 0, 360, false)
3544
+ circle(200, 200, 90)
3527
3545
 
3528
3546
  fill r: 202, g: 102, b: 204, a: 0.5
3529
3547
  stroke r: 0, g: 0, b: 0, thickness: 2
3530
3548
  }
3549
+ path { # declarative stable path
3550
+ arc(400, 220, 180, 90, 90, false)
3551
+
3552
+ fill r: 204, g: 102, b: 204, a: 0.5
3553
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3554
+ }
3531
3555
 
3532
3556
  on_mouse_event do |area_mouse_event|
3533
3557
  p area_mouse_event
@@ -3680,18 +3704,28 @@ window('Area Gallery', 400, 400) {
3680
3704
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3681
3705
  }
3682
3706
  path { # declarative stable path
3683
- arc {
3707
+ circle {
3684
3708
  x_center 200
3685
3709
  y_center 200
3686
3710
  radius 90
3687
- start_angle 0
3688
- sweep 360
3689
- is_negative false
3690
3711
  }
3691
3712
 
3692
3713
  fill r: 202, g: 102, b: 204, a: 0.5
3693
3714
  stroke r: 0, g: 0, b: 0, thickness: 2
3694
3715
  }
3716
+ path { # declarative stable path
3717
+ arc {
3718
+ x_center 400
3719
+ y_center 220
3720
+ radius 180
3721
+ start_angle 90
3722
+ sweep 90
3723
+ is_negative false
3724
+ }
3725
+
3726
+ fill r: 204, g: 102, b: 204, a: 0.5
3727
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3728
+ }
3695
3729
 
3696
3730
  on_mouse_event do |area_mouse_event|
3697
3731
  p area_mouse_event
@@ -3791,13 +3825,19 @@ window('Area Gallery', 400, 400) {
3791
3825
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3792
3826
  }
3793
3827
  path { # a dynamic path is added semi-declaratively inside on_draw block
3794
- arc(200, 200, 90, 0, 360, false)
3795
-
3828
+ circle(200, 200, 90)
3829
+
3796
3830
  fill r: 202, g: 102, b: 204, a: 0.5
3797
3831
  stroke r: 0, g: 0, b: 0, thickness: 2
3798
3832
  }
3833
+ path { # a dynamic path is added semi-declaratively inside on_draw block
3834
+ arc(400, 220, 180, 90, 90, false)
3835
+
3836
+ fill r: 204, g: 102, b: 204, a: 0.5
3837
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3838
+ }
3799
3839
  end
3800
-
3840
+
3801
3841
  on_mouse_event do |area_mouse_event|
3802
3842
  p area_mouse_event
3803
3843
  end
@@ -3950,20 +3990,30 @@ window('Area Gallery', 400, 400) {
3950
3990
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3951
3991
  }
3952
3992
  path { # a dynamic path is added semi-declaratively inside on_draw block
3953
- arc {
3993
+ circle {
3954
3994
  x_center 200
3955
3995
  y_center 200
3956
3996
  radius 90
3957
- start_angle 0
3958
- sweep 360
3959
- is_negative false
3960
3997
  }
3961
-
3998
+
3962
3999
  fill r: 202, g: 102, b: 204, a: 0.5
3963
4000
  stroke r: 0, g: 0, b: 0, thickness: 2
3964
4001
  }
4002
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4003
+ arc {
4004
+ x_center 400
4005
+ y_center 220
4006
+ radius 180
4007
+ start_angle 90
4008
+ sweep 90
4009
+ is_negative false
4010
+ }
4011
+
4012
+ fill r: 204, g: 102, b: 204, a: 0.5
4013
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4014
+ }
3965
4015
  end
3966
-
4016
+
3967
4017
  on_mouse_event do |area_mouse_event|
3968
4018
  p area_mouse_event
3969
4019
  end
@@ -4486,6 +4536,908 @@ window('Login') {
4486
4536
  }.show
4487
4537
  ```
4488
4538
 
4539
+ ### Timer
4540
+
4541
+ To run this example, install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
4542
+
4543
+ [examples/timer.rb](examples/timer.rb)
4544
+
4545
+ Run with this command from the root of the project if you cloned the project:
4546
+
4547
+ ```
4548
+ ruby -r './lib/glimmer-dsl-libui' examples/timer.rb
4549
+ ```
4550
+
4551
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4552
+
4553
+ ```
4554
+ ruby -r glimmer-dsl-libui -e "require 'examples/timer'"
4555
+ ```
4556
+
4557
+ Mac
4558
+
4559
+ ![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png)
4560
+ ![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png)
4561
+
4562
+ Linux
4563
+
4564
+ ![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png)
4565
+ ![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
4566
+
4567
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4568
+
4569
+ ```ruby
4570
+ require 'glimmer-dsl-libui'
4571
+
4572
+ class Timer
4573
+ include Glimmer
4574
+
4575
+ SECOND_MAX = 59
4576
+ MINUTE_MAX = 59
4577
+ HOUR_MAX = 23
4578
+
4579
+ def initialize
4580
+ @pid = nil
4581
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
4582
+ at_exit { stop_alarm }
4583
+ setup_timer
4584
+ create_gui
4585
+ end
4586
+
4587
+ def stop_alarm
4588
+ if @pid
4589
+ if @th.alive?
4590
+ Process.kill(:SIGKILL, @pid)
4591
+ @pid = nil
4592
+ else
4593
+ @pid = nil
4594
+ end
4595
+ end
4596
+ end
4597
+
4598
+ def play_alarm
4599
+ stop_alarm
4600
+ if @pid.nil?
4601
+ begin
4602
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
4603
+ @th = Process.detach @pid
4604
+ rescue Errno::ENOENT
4605
+ warn 'Timidty++ not found. Please install Timidity++.'
4606
+ warn 'https://sourceforge.net/projects/timidity/'
4607
+ end
4608
+ end
4609
+ end
4610
+
4611
+ def setup_timer
4612
+ unless @setup_timer
4613
+ Glimmer::LibUI.timer(1) do
4614
+ if @started
4615
+ seconds = @sec_spinbox.value
4616
+ minutes = @min_spinbox.value
4617
+ hours = @hour_spinbox.value
4618
+ if seconds > 0
4619
+ @sec_spinbox.value = seconds -= 1
4620
+ end
4621
+ if seconds == 0
4622
+ if minutes > 0
4623
+ @min_spinbox.value = minutes -= 1
4624
+ @sec_spinbox.value = seconds = SECOND_MAX
4625
+ end
4626
+ if minutes == 0
4627
+ if hours > 0
4628
+ @hour_spinbox.value = hours -= 1
4629
+ @min_spinbox.value = minutes = MINUTE_MAX
4630
+ @sec_spinbox.value = seconds = SECOND_MAX
4631
+ end
4632
+ if hours == 0 && minutes == 0 && seconds == 0
4633
+ @start_button.enabled = true
4634
+ @stop_button.enabled = false
4635
+ @started = false
4636
+ unless @played
4637
+ play_alarm
4638
+ msg_box('Alarm', 'Countdown Is Finished!')
4639
+ @played = true
4640
+ end
4641
+ end
4642
+ end
4643
+ end
4644
+ end
4645
+ end
4646
+ @setup_timer = true
4647
+ end
4648
+ end
4649
+
4650
+ def create_gui
4651
+ window('Timer') {
4652
+ margined true
4653
+
4654
+ group('Countdown') {
4655
+ vertical_box {
4656
+ horizontal_box {
4657
+ @hour_spinbox = spinbox(0, HOUR_MAX) {
4658
+ stretchy false
4659
+ value 0
4660
+ }
4661
+ label(':') {
4662
+ stretchy false
4663
+ }
4664
+ @min_spinbox = spinbox(0, MINUTE_MAX) {
4665
+ stretchy false
4666
+ value 0
4667
+ }
4668
+ label(':') {
4669
+ stretchy false
4670
+ }
4671
+ @sec_spinbox = spinbox(0, SECOND_MAX) {
4672
+ stretchy false
4673
+ value 0
4674
+ }
4675
+ }
4676
+ horizontal_box {
4677
+ @start_button = button('Start') {
4678
+ on_clicked do
4679
+ @start_button.enabled = false
4680
+ @stop_button.enabled = true
4681
+ @started = true
4682
+ @played = false
4683
+ end
4684
+ }
4685
+
4686
+ @stop_button = button('Stop') {
4687
+ enabled false
4688
+
4689
+ on_clicked do
4690
+ @start_button.enabled = true
4691
+ @stop_button.enabled = false
4692
+ @started = false
4693
+ end
4694
+ }
4695
+ }
4696
+ }
4697
+ }
4698
+ }.show
4699
+ end
4700
+ end
4701
+
4702
+ Timer.new
4703
+ ```
4704
+
4705
+ ### Color The Circles
4706
+
4707
+ [examples/color_the_circles.rb](examples/color_the_circles.rb)
4708
+
4709
+ Run with this command from the root of the project if you cloned the project:
4710
+
4711
+ ```
4712
+ ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
4713
+ ```
4714
+
4715
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4716
+
4717
+ ```
4718
+ ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
4719
+ ```
4720
+
4721
+ Mac
4722
+
4723
+ ![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png)
4724
+ ![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png)
4725
+ ![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png)
4726
+
4727
+ Linux
4728
+
4729
+ ![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png)
4730
+ ![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png)
4731
+ ![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
4732
+
4733
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4734
+
4735
+ ```ruby
4736
+ require 'glimmer-dsl-libui'
4737
+
4738
+ class ColorTheCircles
4739
+ include Glimmer
4740
+
4741
+ WINDOW_WIDTH = 800
4742
+ WINDOW_HEIGHT = 600
4743
+ CIRCLE_MIN_RADIUS = 15
4744
+ CIRCLE_MAX_RADIUS = 50
4745
+ MARGIN_WIDTH = 55
4746
+ MARGIN_HEIGHT = 155
4747
+ TIME_MAX_EASY = 4
4748
+ TIME_MAX_MEDIUM = 3
4749
+ TIME_MAX_HARD = 2
4750
+ TIME_MAX_INSANE = 1
4751
+
4752
+ attr_accessor :score
4753
+
4754
+ def initialize
4755
+ @circles_data = []
4756
+ @score = 0
4757
+ @time_max = TIME_MAX_HARD
4758
+ register_observers
4759
+ setup_circle_factory
4760
+ end
4761
+
4762
+ def register_observers
4763
+ observer = Glimmer::DataBinding::Observer.proc do |new_score|
4764
+ @score_label.text = new_score.to_s
4765
+ if new_score == -20
4766
+ msg_box('You Lost!', 'Sorry! Your score reached -20')
4767
+ restart_game
4768
+ elsif new_score == 0
4769
+ msg_box('You Won!', 'Congratulations! Your score reached 0')
4770
+ restart_game
4771
+ end
4772
+ end
4773
+ observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
4774
+ end
4775
+
4776
+ def setup_circle_factory
4777
+ consumer = Proc.new do
4778
+ if @circles_data.empty?
4779
+ # start with 3 circles to make more challenging
4780
+ add_circle until @circles_data.size > 3
4781
+ else
4782
+ add_circle
4783
+ end
4784
+ delay = rand * @time_max
4785
+ Glimmer::LibUI.timer(delay, repeat: false, &consumer)
4786
+ end
4787
+ Glimmer::LibUI.queue_main(&consumer)
4788
+ end
4789
+
4790
+ def add_circle
4791
+ circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4792
+ circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4793
+ circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
4794
+ stroke_color = Glimmer::LibUI.x11_colors.sample
4795
+ @circles_data << {
4796
+ args: [circle_x_center, circle_y_center, circle_radius],
4797
+ fill: nil,
4798
+ stroke: stroke_color
4799
+ }
4800
+ @area.queue_redraw_all
4801
+ self.score -= 1 # notifies score observers automatically of change
4802
+ end
4803
+
4804
+ def restart_game
4805
+ @score = 0 # update variable directly to avoid notifying observers
4806
+ @circles_data.clear
4807
+ end
4808
+
4809
+ def launch
4810
+ menu('Actions') {
4811
+ menu_item('Restart') {
4812
+ on_clicked do
4813
+ restart_game
4814
+ end
4815
+ }
4816
+
4817
+ quit_menu_item
4818
+ }
4819
+
4820
+ menu('Difficulty') {
4821
+ radio_menu_item('Easy') {
4822
+ on_clicked do
4823
+ @time_max = TIME_MAX_EASY
4824
+ end
4825
+ }
4826
+
4827
+ radio_menu_item('Medium') {
4828
+ on_clicked do
4829
+ @time_max = TIME_MAX_MEDIUM
4830
+ end
4831
+ }
4832
+
4833
+ radio_menu_item('Hard') {
4834
+ checked true
4835
+
4836
+ on_clicked do
4837
+ @time_max = TIME_MAX_HARD
4838
+ end
4839
+ }
4840
+
4841
+ radio_menu_item('Insane') {
4842
+ on_clicked do
4843
+ @time_max = TIME_MAX_INSANE
4844
+ end
4845
+ }
4846
+ }
4847
+
4848
+ menu('Help') {
4849
+ menu_item('Instructions') {
4850
+ on_clicked do
4851
+ msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
4852
+ end
4853
+ }
4854
+ }
4855
+
4856
+ window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
4857
+ margined true
4858
+
4859
+ grid {
4860
+ button('Restart') {
4861
+ left 0
4862
+ top 0
4863
+ halign :center
4864
+
4865
+ on_clicked do
4866
+ restart_game
4867
+ end
4868
+ }
4869
+
4870
+ label('Score goes down as circles are added. If it reaches -20, you lose!') {
4871
+ left 0
4872
+ top 1
4873
+ halign :center
4874
+ }
4875
+
4876
+ label('Click circles to color and score! Once score reaches 0, you win!') {
4877
+ left 0
4878
+ top 2
4879
+ halign :center
4880
+ }
4881
+
4882
+ horizontal_box {
4883
+ left 0
4884
+ top 3
4885
+ halign :center
4886
+
4887
+ label('Score:') {
4888
+ stretchy false
4889
+ }
4890
+
4891
+ @score_label = label(@score.to_s) {
4892
+ stretchy false
4893
+ }
4894
+ }
4895
+
4896
+ vertical_box {
4897
+ left 0
4898
+ top 4
4899
+ hexpand true
4900
+ vexpand true
4901
+ halign :fill
4902
+ valign :fill
4903
+
4904
+ @area = area {
4905
+ on_draw do |area_draw_params|
4906
+ path {
4907
+ rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
4908
+
4909
+ fill :white
4910
+ }
4911
+
4912
+ @circles_data.each do |circle_data|
4913
+ path {
4914
+ circle_data[:circle] = circle(*circle_data[:args])
4915
+
4916
+ fill circle_data[:fill]
4917
+ stroke circle_data[:stroke]
4918
+ }
4919
+ end
4920
+ end
4921
+
4922
+ on_mouse_down do |area_mouse_event|
4923
+ clicked_circle_data = @circles_data.find do |circle_data|
4924
+ circle_data[:fill].nil? && circle_data[:circle].include?(area_mouse_event[:x], area_mouse_event[:y])
4925
+ end
4926
+ if clicked_circle_data
4927
+ clicked_circle_data[:fill] = clicked_circle_data[:stroke]
4928
+ @area.queue_redraw_all
4929
+ self.score += 1 # notifies score observers automatically of change
4930
+ end
4931
+ end
4932
+ }
4933
+ }
4934
+
4935
+ }
4936
+ }.show
4937
+ end
4938
+ end
4939
+
4940
+ ColorTheCircles.new.launch
4941
+ ```
4942
+
4943
+ ### Basic Draw Text
4944
+
4945
+ [examples/basic_draw_text.rb](examples/basic_draw_text.rb)
4946
+
4947
+ Run with this command from the root of the project if you cloned the project:
4948
+
4949
+ ```
4950
+ ruby -r './lib/glimmer-dsl-libui' examples/basic_draw_text.rb
4951
+ ```
4952
+
4953
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4954
+
4955
+ ```
4956
+ ruby -r glimmer-dsl-libui -e "require 'examples/basic_draw_text'"
4957
+ ```
4958
+
4959
+ Mac
4960
+
4961
+ ![glimmer-dsl-libui-mac-basic-draw-text.png](images/glimmer-dsl-libui-mac-basic-draw-text.png)
4962
+
4963
+ Linux
4964
+
4965
+ ![glimmer-dsl-libui-linux-basic-draw-text.png](images/glimmer-dsl-libui-linux-basic-draw-text.png)
4966
+
4967
+ [LibUI](https://github.com/kojix2/LibUI) Original Version:
4968
+
4969
+ ```ruby
4970
+ require 'libui'
4971
+
4972
+ UI = LibUI
4973
+
4974
+ UI.init
4975
+
4976
+ handler = UI::FFI::AreaHandler.malloc
4977
+ area = UI.new_area(handler)
4978
+
4979
+ # Michael Ende (1929-1995)
4980
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
4981
+ # The English version, translated by Ralph Manheim, was published in 1983.
4982
+
4983
+ TITLE = 'Michael Ende (1929-1995) The Neverending Story'
4984
+
4985
+ str1 = \
4986
+ ' At last Ygramul sensed that something was coming toward ' \
4987
+ 'her. With the speed of lightning, she turned about, confronting ' \
4988
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
4989
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
4990
+
4991
+ str2 = \
4992
+ ' A cry of fear escaped Bastian. '
4993
+
4994
+ str3 = \
4995
+ ' A cry of terror passed through the ravine and echoed from ' \
4996
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
4997
+ 'someone else had arrived, for that sound could not have been ' \
4998
+ 'made by the boy who stood there as though paralyzed with ' \
4999
+ 'horror. '
5000
+
5001
+ str4 = \
5002
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5003
+ "But that's not possible. "
5004
+
5005
+ str5 = \
5006
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5007
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5008
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5009
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5010
+ 'words. '
5011
+
5012
+ str = ''
5013
+ attr_str = UI.new_attributed_string(str)
5014
+
5015
+ def attr_str.append(what, color)
5016
+ case color
5017
+ when :red
5018
+ color_attribute = UI.new_color_attribute(0.0, 0.5, 0.0, 0.7)
5019
+ when :green
5020
+ color_attribute = UI.new_color_attribute(0.5, 0.0, 0.25, 0.7)
5021
+ end
5022
+ start = UI.attributed_string_len(self)
5023
+ UI.attributed_string_append_unattributed(self, what)
5024
+ UI.attributed_string_set_attribute(self, color_attribute, start, start + what.size)
5025
+ UI.attributed_string_append_unattributed(self, "\n\n")
5026
+ end
5027
+
5028
+ attr_str.append(str1, :green)
5029
+ attr_str.append(str2, :red)
5030
+ attr_str.append(str3, :green)
5031
+ attr_str.append(str4, :red)
5032
+ attr_str.append(str5, :green)
5033
+
5034
+ Georgia = 'Georgia'
5035
+
5036
+ handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, adp|
5037
+ area_draw_params = UI::FFI::AreaDrawParams.new(adp)
5038
+ default_font = UI::FFI::FontDescriptor.malloc
5039
+ default_font.Family = Georgia
5040
+ default_font.Size = 13
5041
+ default_font.Weight = 500
5042
+ default_font.Italic = 0
5043
+ default_font.Stretch = 4
5044
+ params = UI::FFI::DrawTextLayoutParams.malloc
5045
+
5046
+ # UI.font_button_font(font_button, default_font)
5047
+ params.String = attr_str
5048
+ params.DefaultFont = default_font
5049
+ params.Width = area_draw_params.AreaWidth
5050
+ params.Align = 0
5051
+ text_layout = UI.draw_new_text_layout(params)
5052
+ UI.draw_text(area_draw_params.Context, text_layout, 0, 0)
5053
+ UI.draw_free_text_layout(text_layout)
5054
+ end
5055
+
5056
+ handler.Draw = handler_draw_event
5057
+ # Assigning to local variables
5058
+ # This is intended to protect Fiddle::Closure from garbage collection.
5059
+ handler.MouseEvent = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5060
+ handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5061
+ handler.DragBroken = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5062
+ handler.KeyEvent = (c4 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5063
+
5064
+ box = UI.new_vertical_box
5065
+ UI.box_set_padded(box, 1)
5066
+ UI.box_append(box, area, 1)
5067
+
5068
+ main_window = UI.new_window(TITLE, 600, 400, 1)
5069
+ UI.window_set_margined(main_window, 1)
5070
+ UI.window_set_child(main_window, box)
5071
+
5072
+ UI.window_on_closing(main_window) do
5073
+ UI.control_destroy(main_window)
5074
+ UI.quit
5075
+ 0
5076
+ end
5077
+ UI.control_show(main_window)
5078
+
5079
+ UI.main
5080
+ UI.quit
5081
+ ```
5082
+
5083
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5084
+
5085
+ ```ruby
5086
+ require 'glimmer-dsl-libui'
5087
+
5088
+ # Michael Ende (1929-1995)
5089
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5090
+ # The English version, translated by Ralph Manheim, was published in 1983.
5091
+ class BasicDrawText
5092
+ include Glimmer
5093
+
5094
+ def alternating_color_string(initial: false, &block)
5095
+ @index = 0 if initial
5096
+ @index += 1
5097
+ string {
5098
+ if @index.odd?
5099
+ color r: 0.5, g: 0, b: 0.25, a: 0.7
5100
+ else
5101
+ color r: 0, g: 0.5, b: 0, a: 0.7
5102
+ end
5103
+
5104
+ block.call + "\n\n"
5105
+ }
5106
+ end
5107
+
5108
+ def launch
5109
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5110
+ margined true
5111
+
5112
+ area {
5113
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5114
+ # align :left # default alignment
5115
+ default_font family: 'Georgia', size: 13, weight: :medium, italic: :normal, stretch: :normal
5116
+
5117
+ alternating_color_string(initial: true) {
5118
+ ' At last Ygramul sensed that something was coming toward ' \
5119
+ 'her. With the speed of lightning, she turned about, confronting ' \
5120
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5121
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
5122
+ }
5123
+ alternating_color_string {
5124
+ ' A cry of fear escaped Bastian. '
5125
+ }
5126
+ alternating_color_string {
5127
+ ' A cry of terror passed through the ravine and echoed from ' \
5128
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5129
+ 'someone else had arrived, for that sound could not have been ' \
5130
+ 'made by the boy who stood there as though paralyzed with ' \
5131
+ 'horror. '
5132
+ }
5133
+ alternating_color_string {
5134
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5135
+ "But that's not possible. "
5136
+ }
5137
+ alternating_color_string {
5138
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5139
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5140
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5141
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5142
+ 'words. '
5143
+ }
5144
+ }
5145
+ }
5146
+ }.show
5147
+ end
5148
+ end
5149
+
5150
+ BasicDrawText.new.launch
5151
+ ```
5152
+
5153
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2:
5154
+
5155
+ ```ruby
5156
+ require 'glimmer-dsl-libui'
5157
+
5158
+ # Michael Ende (1929-1995)
5159
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5160
+ # The English version, translated by Ralph Manheim, was published in 1983.
5161
+ class BasicDrawText
5162
+ include Glimmer
5163
+
5164
+ def alternating_color_string(initial: false, &block)
5165
+ @index = 0 if initial
5166
+ @index += 1
5167
+ string {
5168
+ if @index.odd?
5169
+ color r: 0.5, g: 0, b: 0.25, a: 0.7
5170
+ else
5171
+ color r: 0, g: 0.5, b: 0, a: 0.7
5172
+ end
5173
+
5174
+ block.call + "\n\n"
5175
+ }
5176
+ end
5177
+
5178
+ def launch
5179
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5180
+ margined true
5181
+
5182
+ area {
5183
+ on_draw do |area_draw_params|
5184
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5185
+ # align :left # default alignment
5186
+ default_font family: 'Georgia', size: 13, weight: :medium, italic: :normal, stretch: :normal
5187
+
5188
+ alternating_color_string(initial: true) {
5189
+ ' At last Ygramul sensed that something was coming toward ' \
5190
+ 'her. With the speed of lightning, she turned about, confronting ' \
5191
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5192
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
5193
+ }
5194
+ alternating_color_string {
5195
+ ' A cry of fear escaped Bastian. '
5196
+ }
5197
+ alternating_color_string {
5198
+ ' A cry of terror passed through the ravine and echoed from ' \
5199
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5200
+ 'someone else had arrived, for that sound could not have been ' \
5201
+ 'made by the boy who stood there as though paralyzed with ' \
5202
+ 'horror. '
5203
+ }
5204
+ alternating_color_string {
5205
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5206
+ "But that's not possible. "
5207
+ }
5208
+ alternating_color_string {
5209
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5210
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5211
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5212
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5213
+ 'words. '
5214
+ }
5215
+ }
5216
+ end
5217
+ }
5218
+ }.show
5219
+ end
5220
+ end
5221
+
5222
+ BasicDrawText.new.launch
5223
+ ```
5224
+
5225
+ ### Custom Draw Text
5226
+
5227
+ [examples/custom_draw_text.rb](examples/custom_draw_text.rb)
5228
+
5229
+ Run with this command from the root of the project if you cloned the project:
5230
+
5231
+ ```
5232
+ ruby -r './lib/glimmer-dsl-libui' examples/custom_draw_text.rb
5233
+ ```
5234
+
5235
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5236
+
5237
+ ```
5238
+ ruby -r glimmer-dsl-libui -e "require 'examples/custom_draw_text'"
5239
+ ```
5240
+
5241
+ Mac
5242
+
5243
+ ![glimmer-dsl-libui-mac-custom-draw-text.png](images/glimmer-dsl-libui-mac-custom-draw-text.png)
5244
+ ![glimmer-dsl-libui-mac-custom-draw-text-changed.png](images/glimmer-dsl-libui-mac-custom-draw-text-changed.png)
5245
+
5246
+ Linux
5247
+
5248
+ ![glimmer-dsl-libui-linux-custom-draw-text.png](images/glimmer-dsl-libui-linux-custom-draw-text.png)
5249
+ ![glimmer-dsl-libui-linux-custom-draw-text-changed.png](images/glimmer-dsl-libui-linux-custom-draw-text-changed.png)
5250
+
5251
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5252
+
5253
+ ```ruby
5254
+ require 'glimmer-dsl-libui'
5255
+
5256
+ # Michael Ende (1929-1995)
5257
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5258
+ # The English version, translated by Ralph Manheim, was published in 1983.
5259
+ class CustomDrawText
5260
+ include Glimmer
5261
+
5262
+ def launch
5263
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5264
+ margined true
5265
+
5266
+ vertical_box {
5267
+ form {
5268
+ stretchy false
5269
+
5270
+ font_button { |fb|
5271
+ label 'Font'
5272
+
5273
+ on_changed do
5274
+ @string.font = fb.font
5275
+ end
5276
+ }
5277
+ color_button { |cb|
5278
+ label 'Color'
5279
+
5280
+ on_changed do
5281
+ @string.color = cb.color
5282
+ end
5283
+ }
5284
+ color_button { |cb|
5285
+ label 'Background'
5286
+
5287
+ on_changed do
5288
+ @string.background = cb.color
5289
+ end
5290
+ }
5291
+ combobox { |c|
5292
+ label 'Underline'
5293
+ items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
5294
+ selected 'None'
5295
+
5296
+ on_selected do
5297
+ @string.underline = c.selected_item.underscore
5298
+ end
5299
+ }
5300
+ }
5301
+
5302
+ area {
5303
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5304
+ # align :left # default alignment
5305
+
5306
+ @string = string {
5307
+ ' At last Ygramul sensed that something was coming toward ' \
5308
+ 'her. With the speed of lightning, she turned about, confronting ' \
5309
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5310
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. ' \
5311
+ "\n\n" \
5312
+ ' A cry of fear escaped Bastian. ' \
5313
+ "\n\n" \
5314
+ ' A cry of terror passed through the ravine and echoed from ' \
5315
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5316
+ 'someone else had arrived, for that sound could not have been ' \
5317
+ 'made by the boy who stood there as though paralyzed with ' \
5318
+ 'horror. ' \
5319
+ "\n\n" \
5320
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5321
+ "But that's not possible. " \
5322
+ "\n\n" \
5323
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5324
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5325
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5326
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5327
+ 'words. ' \
5328
+ "\n\n"
5329
+ }
5330
+ }
5331
+ }
5332
+ }
5333
+ }.show
5334
+ end
5335
+ end
5336
+
5337
+ CustomDrawText.new.launch
5338
+ ```
5339
+
5340
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2:
5341
+
5342
+ ```ruby
5343
+ require 'glimmer-dsl-libui'
5344
+
5345
+ # Michael Ende (1929-1995)
5346
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5347
+ # The English version, translated by Ralph Manheim, was published in 1983.
5348
+ class CustomDrawText
5349
+ include Glimmer
5350
+
5351
+ def launch
5352
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5353
+ margined true
5354
+
5355
+ vertical_box {
5356
+ form {
5357
+ stretchy false
5358
+
5359
+ font_button { |fb|
5360
+ label 'Font'
5361
+
5362
+ on_changed do
5363
+ @font = fb.font
5364
+ @area.queue_redraw_all
5365
+ end
5366
+ }
5367
+ color_button { |cb|
5368
+ label 'Color'
5369
+
5370
+ on_changed do
5371
+ @color = cb.color
5372
+ @area.queue_redraw_all
5373
+ end
5374
+ }
5375
+ color_button { |cb|
5376
+ label 'Background'
5377
+
5378
+ on_changed do
5379
+ @background = cb.color
5380
+ @area.queue_redraw_all
5381
+ end
5382
+ }
5383
+ combobox { |c|
5384
+ label 'Underline'
5385
+ items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
5386
+ selected 'None'
5387
+
5388
+ on_selected do
5389
+ @underline = c.selected_item.underscore
5390
+ @area.queue_redraw_all
5391
+ end
5392
+ }
5393
+ }
5394
+
5395
+ @area = area {
5396
+ on_draw do |area_draw_params|
5397
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5398
+ # align :left # default alignment
5399
+
5400
+ @string = string {
5401
+ font @font unless @font.nil?
5402
+ color @color unless @color.nil?
5403
+ background @background unless @background.nil?
5404
+ underline @underline unless @underline.nil?
5405
+
5406
+ ' At last Ygramul sensed that something was coming toward ' \
5407
+ 'her. With the speed of lightning, she turned about, confronting ' \
5408
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5409
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. ' \
5410
+ "\n\n" \
5411
+ ' A cry of fear escaped Bastian. ' \
5412
+ "\n\n" \
5413
+ ' A cry of terror passed through the ravine and echoed from ' \
5414
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5415
+ 'someone else had arrived, for that sound could not have been ' \
5416
+ 'made by the boy who stood there as though paralyzed with ' \
5417
+ 'horror. ' \
5418
+ "\n\n" \
5419
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5420
+ "But that's not possible. " \
5421
+ "\n\n" \
5422
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5423
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5424
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5425
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5426
+ 'words. ' \
5427
+ "\n\n"
5428
+ }
5429
+ }
5430
+ end
5431
+ }
5432
+ }
5433
+ }.show
5434
+ end
5435
+ end
5436
+
5437
+ CustomDrawText.new.launch
5438
+
5439
+ ```
5440
+
4489
5441
  ## Contributing to glimmer-dsl-libui
4490
5442
 
4491
5443
  - Check out the latest master to make sure the feature hasn't been