{"id":25776588,"url":"https://github.com/AndyObtiva/glimmer-dsl-gtk","last_synced_at":"2026-08-13T10:30:15.842Z","repository":{"id":50624787,"uuid":"425000299","full_name":"AndyObtiva/glimmer-dsl-gtk","owner":"AndyObtiva","description":"Glimmer DSL for GTK - Ruby-GNOME Desktop Development GUI Library","archived":false,"fork":false,"pushed_at":"2023-06-29T17:36:28.000Z","size":3290,"stargazers_count":24,"open_issues_count":6,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-07-25T22:33:27.433Z","etag":null,"topics":["desktop","dsl","dsl-syntax","framework","glimmer","glimmer-dsl","gtk","gtk3","gui","ruby","ruby-gem","ruby-library","rubygem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndyObtiva.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-05T15:41:44.000Z","updated_at":"2026-07-05T18:33:30.000Z","dependencies_parsed_at":"2025-04-13T13:43:25.145Z","dependency_job_id":null,"html_url":"https://github.com/AndyObtiva/glimmer-dsl-gtk","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/AndyObtiva/glimmer-dsl-gtk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-gtk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-gtk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-gtk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-gtk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndyObtiva","download_url":"https://codeload.github.com/AndyObtiva/glimmer-dsl-gtk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-gtk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36604623,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-06T04:43:03.162Z","status":"online","status_checked_at":"2026-08-13T02:00:06.325Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["desktop","dsl","dsl-syntax","framework","glimmer","glimmer-dsl","gtk","gtk3","gui","ruby","ruby-gem","ruby-library","rubygem"],"created_at":"2025-02-27T06:01:27.219Z","updated_at":"2026-08-13T10:30:15.407Z","avatar_url":"https://github.com/AndyObtiva.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# [\u003cimg src=\"https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png\" height=85 /\u003e](https://github.com/AndyObtiva/glimmer) Glimmer DSL for GTK 0.0.9\n## Ruby-GNOME Desktop Development GUI Library\n[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-gtk.svg)](http://badge.fury.io/rb/glimmer-dsl-gtk)\n[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [GTK](https://www.gtk.org/) enables building desktop applications with [Ruby-GNOME](https://github.com/ruby-gnome/ruby-gnome) (including [Cairo graphics](#declarative-cairo-graphics)) using [MRI Ruby](https://www.ruby-lang.org).\n\n[GTK](https://www.gtk.org/) (aka GIMP-Toolkit or [incorrectly] GNOME-Toolkit) is the premiere desktop GUI toolkit on [Linux](https://www.gtk.org/docs/installations/linux/) (included in [GNOME flavors of Linux](https://www.gnome.org/getting-gnome/), where [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk)'s forte is), which also runs on [Mac](https://www.gtk.org/docs/installations/macos/) ([Quartz GTK+](https://wiki.gnome.org/Projects/GTK/OSX)) and [Windows](https://www.gtk.org/docs/installations/windows).\n\n[Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk) aims to supercharge productivity and maintainability in developing [Ruby-GNOME](https://github.com/ruby-gnome/ruby-gnome) applications by providing a DSL similar to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) having:\n- Declarative DSL syntax that visually maps to the GUI widget hierarchy\n- Convention over configuration via smart defaults and automation of low-level details\n- Requiring the least amount of syntax possible to build GUI\n- Custom Keyword support\n- Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models\n- Scaffolding for new custom widgets, apps, and gems\n- Native-Executable packaging on Mac, Windows, and Linux.\n\nHello, World!\n\n```ruby\nwindow {\n  title 'Hello, World!'\n  \n  label('Hello, World!')\n}.show\n```\n\nLinux | Mac | Windows\n------|-----|--------\n![hello world screenshot Windows](/screenshots/glimmer-dsl-gtk-linux-hello-world.png) | ![hello world screenshot Mac](/screenshots/glimmer-dsl-gtk-mac-hello-world.png) | ![hello world screenshot Windows](/screenshots/glimmer-dsl-gtk-windows-hello-world.png)\n\nNOTE: Glimmer DSL for GTK is currently in early alpha mode (incomplete proof-of-concept). If you want it developed faster, then [open an issue report](https://github.com/AndyObtiva/glimmer-dsl-gtk/issues/new). I have completed some GitHub project features much faster before due to [issue reports](https://github.com/AndyObtiva/glimmer-dsl-gtk/issues) and [pull requests](https://github.com/AndyObtiva/glimmer-dsl-gtk/pulls). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.\n\nLearn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at the **[Glimmer DSL Comparison Table](https://github.com/AndyObtiva/glimmer#glimmer-dsl-comparison-table)**.\n\n## Prerequisites\n\n### Linux\n\nGNOME-based Linux installations do not need any prerequisites since they have GTK built-in, so you can skip to [Setup](#setup) right away.\n\nStill, if you run into issues installing the GTK gem in [Setup](#setup), you may try these commands first:\n```\nsudo apt-get install libgtk-3-dev\n```\n\nOr run this command if all else fails:\n```\nsudo apt install -y -V libgirepository1.0-dev\n```\n\n### Mac\n\nOn the Mac, make sure to:\n- Have [Homebrew](https://brew.sh/) installed\n- Run this [Homebrew](https://brew.sh/) command to have GTK display GUI icons: `brew install adwaita-icon-theme`\n- (Optional) You can upgrade your GTK3/GTK4/GTK+ by running: `brew install gtk+3` / `brew install gtk+4` / `brew install gtk+`\n\n### Windows\n\nMake sure to install [Ruby+Devkit](https://rubyinstaller.org/downloads/) from [RubyInstaller](https://rubyinstaller.org/). When prompted to run `ridk install` at the end, go ahead with it, and then make sure to run all 3 commands in order: \n1. MSYS2 base installation\n2. MSYS2 system update\n3. MSYS2 and MINGW development toolchain  \n\n## Setup\n\n### Option 1: Install\n\nRun this command to install directly:\n```\ngem install glimmer-dsl-gtk\n```\n\n### Option 2: Bundler\n\nAdd the following to `Gemfile`:\n```\ngem 'glimmer-dsl-gtk', '~\u003e 0.0.9'\n```\n\nAnd, then run:\n```\nbundle\n```\n\n## Usage\n\nRequire the library and mixin the `Glimmer` module to utilize the Glimmer GUI DSL for GTK:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Demo'\n  \n  on(:destroy) do\n    puts 'Bye Bye'\n    ::Gtk.main_quit\n  end\n}.show\n```\n\nFor actual application development outside of simple demos, mixin the `Glimmer` module into a custom application class instead:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\nclass SomeGlimmerApplication\n  include Glimmer\n  \n  def launch\n    application('org.glimmer.hello-application', :flags_none) {\n      on(:activate) do |app|\n        application_window(app) {\n          title 'Actual Application'\n        }.present\n      end\n    }.run\n  end\nend\n\nSomeGlimmerApplication.new.launch\n```\n\n### Glimmer GUI DSL\n\n- Keywords: All GTK widgets are supported via lowercase underscored names accepting their constructor args (e.g. `application_window(app)` for `Gtk::ApplicationWindow.new(app)`). Keywords can be nested under other keywords to represent the true hierarchy of nested widgets on the screen (e.g. `window { label('Hello') }` is a `label` nested under a `window`). Note that widget objects returned are proxies of the GTK widget counterparts. This shields consumers of GTK from its lower-level details via composition (Proxy Design Pattern). To access lower-level GTK widget, simply call `#gtk` method on widget proxy object (e.g. `@w = window {...}; @w.gtk # Gtk::Window widget object`).\n- Content: widget keywords can have a block of content that could contain nested widget keywords, properties, and signals. The block can optionally receive one argument representing the widget (e.g. `window {|w| ... }`):\n  - Properties: All GTK widget properties can be set via lowercase underscored names (without the 'set_' prefix) nested under widget keywords (e.g. `window {title 'Hello, World'}` sets `title` property of `window`)\n  - Signals: All GTK signals can be wired with `on(signal) { ... }` syntax (e.g. `on(:activate) { do_something }`)\n\n### MVC Observer Pattern\n\nIn Smalltalk-MVC ([Model View Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) Architectural Pattern), the View is an active View that observes the Model for changes and updates itself.\n\n![MVC](http://3.bp.blogspot.com/-4eW59Ao0ess/ToiBzAiYdZI/AAAAAAAAAOg/SiYa6XHwBFE/s320/Screen+shot+2011-10-02+at+10.22.11+AM.png)\n\nThis can be achieved with the Glimmer GUI DSL using the `observe` keyword, which takes a model (any object, including `self`) and attribute Symbol or String expression (e.g. `:count` or `'address.street'`).\n\nThe model is automatically enhanced as an `Glimmer::DataBinding::ObservableModel` / `Glimmer::DataBinding::ObservableHash` / `Glimmer::DataBinding::ObservableArray` depending on its type to support notifying observers of attribute changes (when performed using the attribute writer, which automatically calls added method `notify_observers(attribute)`)\n\nNote that it is usually recommended to observe external model objects (not `self`), but `self` is OK in very simple cases or presentation-related attributes only.\n\n### Declarative Cairo Graphics\n\n[Cairo](https://www.cairographics.org/) is the engine behind drawing arbitrary 2D geometric shapes in [GTK](https://www.gtk.org/).\n\nIn [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk), you can draw Cairo shapes declaratively in a way similar to how SVG works, but using one language; Ruby, thus being able to utilize Ruby logic (e.g. if statement or each loop) with it effortlessly when needed. Declarative syntax also yields less code that is simpler, not dependent on ordering of nested properties, and more understandable/maintainable.\n\nBelow is a quick tutorial consisting of samples inspired and ported from [Mohit Sindhwani's blog post \"Cairo with Ruby - Samples using RCairo\"](https://notepad.onghu.com/2021/cairo-samples-in-ruby/).\n\n#### Arc\n\n[samples/cairo/arc.rb](/samples/cairo/arc.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Arc'\n  default_size 256, 256\n  \n  drawing_area {\n    # Surface Paint\n    paint 242.25, 242.25, 242.25\n    \n    # Set up the parameters\n    xc = 128.0\n    yc = 128.0\n    radius = 100.0\n    angle1 = 45.0  * (Math::PI/180.0) # angles are specified\n    angle2 = 180.0  * (Math::PI/180.0) # in radians\n    \n    # The main arc\n    arc(xc, yc, radius, angle1, angle2) {\n      stroke 0, 0, 0\n      line_width 10\n    }\n    \n    # Draw helping lines\n    \n    # First, the circle at the centre\n    arc(xc, yc, 10.0, 0, 2*Math::PI) {\n      fill 255, 51, 51, 0.6\n    }\n    \n    # Then, the lines reaching out\n    path {\n      arc xc, yc, radius, angle1, angle1\n      line_to xc, yc\n      arc xc, yc, radius, angle2, angle2\n      line_to xc, yc\n      \n      stroke 255, 51, 51, 0.6\n      line_width 6\n    }\n  }\n}.show\n```\n\n![Arc](/screenshots/glimmer-dsl-gtk-mac-cairo-arc.png)\n\n#### Arc Negative\n\n[samples/cairo/arc_negative.rb](/samples/cairo/arc_negative.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Arc Negative'\n  default_size 256, 256\n  \n  drawing_area {\n    # Surface Paint\n    paint 255, 255, 255\n    \n    # Set up the parameters\n    xc = 128.0\n    yc = 128.0\n    radius = 100.0\n    angle1 = 45.0  * (Math::PI/180.0) # angles are specified\n    angle2 = 180.0  * (Math::PI/180.0) # in radians\n    \n    # The main negative arc\n    arc_negative(xc, yc, radius, angle1, angle2) {\n      stroke 0, 0, 0\n      line_width 10\n    }\n    \n    # Draw helping lines\n    \n    # First, the circle at the centre\n    arc(xc, yc, 10.0, 0, 2*Math::PI) {\n      fill 255, 51, 51, 0.6\n    }\n    \n    # Then, the lines reaching out\n    path {\n      arc(xc, yc, radius, angle1, angle1)\n      line_to(xc, yc)\n      arc(xc, yc, radius, angle2, angle2)\n      line_to(xc, yc)\n      \n      stroke 255, 51, 51, 0.6\n      line_width 6\n    }\n  }\n}.show\n```\n\n![Arc Negative](/screenshots/glimmer-dsl-gtk-mac-cairo-arc-negative.png)\n\n#### Clip\n\n[samples/cairo/clip.rb](/samples/cairo/clip.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Clip'\n  default_size 256, 256\n  \n  drawing_area {\n    # Surface Paint\n    paint 255, 255, 255\n    \n    # Designate arc as the clipping area\n    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {\n      clip true\n    }\n    \n    # Rectangle will get clipped by arc\n    rectangle(0, 0, 256, 256) {\n      fill 0, 0, 0\n    }\n    \n    # Path will get clipped by arc\n    path {\n      move_to 0, 0\n      line_to 256, 256\n      move_to 256, 0\n      line_to 0, 256\n      \n      stroke 0, 255, 0\n      line_width 10\n    }\n  }\n}.show\n```\n\n![Clip](/screenshots/glimmer-dsl-gtk-mac-cairo-clip.png)\n\n#### Clip Image\n\n[samples/cairo/clip_image.rb](/samples/cairo/clip_image.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\nrequire 'net/http'\n\nimage_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))\nimage_file = File.join(Dir.home, 'breaking-blue-wave.png')\nFile.write(image_file, image_content)\n\ninclude Glimmer\n\nwindow {\n  title 'Clip Image'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n\n    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {\n      clip true # designate arc as the clipping area\n    }\n\n    rectangle(0, 0, 256, 256) {\n      # Source image is from:\n      # - https://www.publicdomainpictures.net/en/view-image.php?image=7683\u0026picture=breaking-blue-wave\n      # Converted to PNG before using it\n      image = Cairo::ImageSurface.from_png(image_file)\n      w = image.width\n      h = image.height\n  \n      scale 256.0/w, 256.0/h, exclude: :shape # applies scale to fill source image only\n      fill image, 0, 0\n    }\n  }\n}.show\n```\n\n![Clip Image](/screenshots/glimmer-dsl-gtk-mac-cairo-clip-image.png)\n\n#### Curve to\n\n[samples/cairo/curve_to.rb](/samples/cairo/curve_to.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Curve to'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    x=25.6\n    y=128.0\n    x1=102.4\n    y1=230.4\n    x2=153.6\n    y2=25.6\n    x3=230.4\n    y3=128.0\n    \n    path {\n      move_to x, y\n      curve_to x1, y1, x2, y2, x3, y3\n      \n      line_width 10\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to x,y\n      line_to x1,y1\n      move_to x2,y2\n      line_to x3,y3\n      \n      line_width 6\n      stroke 255, 51, 51, 0.6\n    }\n  }\n}.show\n```\n\n![Curve to](/screenshots/glimmer-dsl-gtk-mac-cairo-curve-to.png)\n\n#### Dashes\n\n[samples/cairo/dashes.rb](/samples/cairo/dashes.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Dashes'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n\n    dashes = [ 50.0, # ink\n                       10.0,  # skip\n                       10.0,  # ink\n                       10.0   # skip\n            ]\n    offset = -50.0\n    \n    path {\n      move_to 128.0, 25.6\n      line_to 230.4, 230.4\n      rel_line_to -102.4, 0.0\n      curve_to 51.2, 230.4, 51.2, 128.0, 128.0, 128.0\n      \n      line_width 10\n      dash dashes, offset\n      stroke 0, 0, 0\n    }\n  }\n}.show\n```\n\n![Dashes](/screenshots/glimmer-dsl-gtk-mac-cairo-dashes.png)\n\n#### Fill and Stroke 2\n\n(note: there is no Fill and Stroke 1; this was adopted from [Mohit's blog post](https://notepad.onghu.com/2021/cairo-samples-in-ruby/), which only mentioned Fill and Stroke 2)\n\n[samples/cairo/fill_and_stroke2.rb](/samples/cairo/fill_and_stroke2.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Fill and Stroke 2'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    path {\n      move_to 128.0, 25.6\n      line_to 230.4, 230.4\n      rel_line_to -102.4, 0.0\n      curve_to 51.2, 230.4, 51.2, 128.0, 128.0, 128.0\n      close_path\n\n      fill 0, 0, 255\n      stroke 0, 0, 0\n      line_width 10\n    }\n    \n    path {\n      move_to 64.0, 25.6\n      rel_line_to 51.2, 51.2\n      rel_line_to -51.2, 51.2\n      rel_line_to -51.2, -51.2\n      close_path\n\n      fill 0, 0, 255\n      stroke 0, 0, 0\n      line_width 10\n    }\n  }\n}.show\n```\n\n![Fill and Stroke 2](/screenshots/glimmer-dsl-gtk-mac-cairo-fill-and-stroke2.png)\n\n#### Fill Style\n\n[samples/cairo/fill_style.rb](/samples/cairo/fill_style.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Fill Style'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    path {\n      rectangle 12, 12, 232, 70\n      path { # sub-path\n        arc 64, 64, 40, 0, 2*Math::PI\n      }\n      path { # sub-path\n        arc_negative 192, 64, 40, 0, -2*Math::PI\n      }\n      \n      fill_rule Cairo::FILL_RULE_EVEN_ODD\n      line_width 6\n      fill 0, 178.5, 0\n      stroke 0, 0, 0\n    }\n    \n    path {\n      rectangle 12, 12, 232, 70\n      path { # sub-path\n        arc 64, 64, 40, 0, 2*Math::PI\n      }\n      path { # sub-path\n        arc_negative 192, 64, 40, 0, -2*Math::PI\n      }\n      \n      translate 0, 128\n      fill_rule Cairo::FILL_RULE_WINDING\n      line_width 6\n      fill 0, 0, 229.5\n      stroke 0, 0, 0\n    }\n  }\n}.show\n```\n\n![Fill Style](/screenshots/glimmer-dsl-gtk-mac-cairo-fill-style.png)\n\n#### Gradient\n\n[samples/cairo/gradient.rb](/samples/cairo/gradient.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Gradient'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n        \n    # Create the Linear Pattern\n    rectangle(0, 0, 256, 256) {\n      pat = Cairo::LinearPattern.new(0.0, 0.0,  0.0, 256.0)\n      pat.add_color_stop_rgba(1, 0, 0, 0, 1)\n      pat.add_color_stop_rgba(0, 1, 1, 1, 1)\n      \n      fill pat\n    }\n    \n    # Create the radial pattern\n    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {\n      pat = Cairo::RadialPattern.new(115.2, 102.4, 25.6,\n                                         102.4,  102.4, 128.0)\n      pat.add_color_stop_rgba(0, 1, 1, 1, 1)\n      pat.add_color_stop_rgba(1, 0, 0, 0, 1)\n      \n      fill pat\n    }\n  }\n}.show\n```\n\n![Gradient](/screenshots/glimmer-dsl-gtk-mac-cairo-gradient.png)\n\n#### Image\n\n[samples/cairo/image.rb](/samples/cairo/image.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\nrequire 'net/http'\n\nimage_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))\nimage_file = File.join(Dir.home, 'breaking-blue-wave.png')\nFile.write(image_file, image_content)\n\ninclude Glimmer\n\nwindow {\n  title 'Image'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n        \n    image = Cairo::ImageSurface.from_png(image_file)\n    w = image.width\n    h = image.height\n    \n    translate 128.0, 128.0\n    rotate 45*Math::PI/180\n    scale 256.0/w, 256.0/h\n    translate -0.5*w, -0.5*h\n    \n    paint image, 0, 0\n  }\n}.show\n```\n\n![Image](/screenshots/glimmer-dsl-gtk-mac-cairo-image.png)\n\n#### Image Gradient\n\n[samples/cairo/image_gradient.rb](/samples/cairo/image_gradient.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\nrequire 'net/http'\n\nimage_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))\nimage_file = File.join(Dir.home, 'breaking-blue-wave.png')\nFile.write(image_file, image_content)\n\ninclude Glimmer\n\nwindow {\n  title 'Image Gradient'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    image = Cairo::ImageSurface.from_png(image_file)\n    w = image.width\n    h = image.height\n    \n    # Load the image as a surface pattern\n    pattern = Cairo::SurfacePattern.new(image)\n    pattern.extend = Cairo::EXTEND_REPEAT\n    \n    # Set up the scale matrix\n    pattern.matrix = Cairo::Matrix.scale(w/256.0 * 5.0, h/256.0 * 5.0)\n    \n    rectangle(0, 0, 256, 256) {\n      translate 128.0, 128.0\n      rotate Math::PI / 4\n      scale 1/Math.sqrt(2), 1/Math.sqrt(2)\n      translate -128.0, -128.0\n        \n      fill pattern\n    }\n  }\n}.show\n```\n\n![Image Gradient](/screenshots/glimmer-dsl-gtk-mac-cairo-image-gradient.png)\n\n#### Multi Segment Caps\n\n[samples/cairo/multi_segment_caps.rb](/samples/cairo/multi_segment_caps.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Multi Segment Caps'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    path {\n      move_to 50.0, 75.0\n      line_to 200.0, 75.0\n      \n      move_to 50.0, 125.0\n      line_to 200.0, 125.0\n      \n      move_to 50.0, 175.0\n      line_to 200.0, 175.0\n      \n      line_width 30\n      line_cap Cairo::LINE_CAP_ROUND\n      stroke 0, 0, 0\n    }\n  }\n}.show\n```\n\n![Multi Segment Caps](/screenshots/glimmer-dsl-gtk-mac-cairo-multi-segment-caps.png)\n\n#### Rounded Rectangle\n\n[samples/cairo/rounded_rectangle.rb](/samples/cairo/rounded_rectangle.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Rounded Rectangle'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    path {\n      rounded_rectangle(25.6, 25.6, 204.8, 204.8, 20)\n    \n      fill 127.5, 127.5, 255\n      line_width 10.0\n      stroke 127.5, 0, 0, 0.5\n    }\n  }\n}.show\n```\n\n![Rounded Rectangle](/screenshots/glimmer-dsl-gtk-mac-cairo-rounded-rectangle.png)\n\n#### Set line cap\n\n[samples/cairo/set_line_cap.rb](/samples/cairo/set_line_cap.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Set line cap'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    # The main code\n    path {\n      move_to 64.0, 50.0\n      line_to 64.0, 200.0\n      \n      line_cap Cairo::LINE_CAP_BUTT #  default\n      line_width 30\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to 128.0, 50.0\n      line_to 128.0, 200.0\n      \n      line_cap Cairo::LINE_CAP_ROUND\n      line_width 30\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to 192.0, 50.0\n      line_to 192.0, 200.0\n      \n      line_cap Cairo::LINE_CAP_SQUARE\n      line_width 30\n      stroke 0, 0, 0\n    }\n    \n    #  draw helping lines */\n    path {\n      move_to 64.0, 50.0\n      line_to 64.0, 200.0\n      move_to 128.0, 50.0\n      line_to 128.0, 200.0\n      move_to 192.0, 50.0\n      line_to 192.0, 200.0\n      \n      line_width 2.56\n      stroke 255, 51, 51\n    }\n  }\n}.show\n```\n\n![Set line cap](/screenshots/glimmer-dsl-gtk-mac-cairo-set-line-cap.png)\n\n#### Set line join\n\n[samples/cairo/set_line_join.rb](/samples/cairo/set_line_join.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Set line join'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    # The main code\n    path {\n      move_to 76.8, 84.48\n      rel_line_to 51.2, -51.2\n      rel_line_to 51.2, 51.2\n      \n      line_join Cairo::LINE_JOIN_MITER # default\n      line_width 40.96\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to 76.8, 161.28\n      rel_line_to 51.2, -51.2\n      rel_line_to 51.2, 51.2\n      \n      line_join Cairo::LINE_JOIN_BEVEL\n      line_width 40.96\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to 76.8, 238.08\n      rel_line_to 51.2, -51.2\n      rel_line_to 51.2, 51.2\n      \n      line_join Cairo::LINE_JOIN_ROUND\n      line_width 40.96\n      stroke 0, 0, 0\n    }\n  }\n}.show\n```\n\n![Set line join](/screenshots/glimmer-dsl-gtk-mac-cairo-set-line-join.png)\n\n#### Text\n\n[samples/cairo/text.rb](/samples/cairo/text.rb)\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Text'\n  default_size 256, 256\n  \n  drawing_area {\n    paint 242.25, 242.25, 242.25\n    \n    font_family = OS.linux? ? 'Sans' : (OS.mac? ? 'Helvetica' : 'Arial')\n    \n    # The main code\n    path {\n      move_to 10.0, 135.0\n      show_text 'Hello'\n      \n      font_face font_family, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD\n      font_size 90.0\n      line_width 2.56\n      fill 0, 0, 0\n      stroke 0, 0, 0\n    }\n    \n    path {\n      move_to 70.0, 165.0\n      text_path 'void'\n      \n      font_face font_family, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD\n      font_size 90.0\n      line_width 2.56\n      fill 127.5, 127.5, 255\n      stroke 0, 0, 0\n    }\n    \n    # draw helping lines\n    path {\n      arc 10.0, 135.0, 5.12, 0, 2*Math::PI\n      close_path\n      arc 70.0, 165.0, 5.12, 0, 2*Math::PI\n      \n      fill 255, 51, 51, 0.6\n    }\n  }\n}.show\n```\n\n![Text](/screenshots/glimmer-dsl-gtk-mac-cairo-text.png)\n\n## Girb (Glimmer IRB)\n\nYou can run the `girb` command (`bin/girb` if you cloned the project locally):\n\n```\ngirb\n```\n\nLinux\n\n![girb screenshot linux](/screenshots/glimmer-dsl-gtk-linux-girb.png)\n\nMac\n\n![girb screenshot mac](/screenshots/glimmer-dsl-gtk-mac-girb.png)\n\nWindows\n\n![girb screenshot windows](/screenshots/glimmer-dsl-gtk-windows-girb.png)\n\nThis gives you `irb` with the `glimmer-dsl-gtk` gem loaded and the `Glimmer` module mixed into the main object for easy experimentation with GUI.\n\nGotcha: On the Mac, when you close a window opened in `girb`, it remains open until you enter exit or open another GUI window.\n\n## Samples\n\nYou may checkout the [samples](samples) directory for examples of using [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk).\n\n### Hello Samples\n\n#### Hello, World!\n\n[samples/hello/hello_world.rb](/samples/hello/hello_world.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello world screenshot Windows](/screenshots/glimmer-dsl-gtk-linux-hello-world.png) | ![hello world screenshot Mac](/screenshots/glimmer-dsl-gtk-mac-hello-world.png) | ![hello world screenshot Windows](/screenshots/glimmer-dsl-gtk-windows-hello-world.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_world'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_world.rb\n```\n\nCode:\n\n```ruby\nwindow {\n  title 'Hello, World!'\n  \n  label('Hello, World!')\n}.show\n```\n\n#### Hello, Application!\n\n[samples/hello/hello_application.rb](/samples/hello/hello_application.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello application screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-application.png) | ![hello application screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-application.png) | ![hello application screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-application.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_application'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_application.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\napplication('org.glimmer.hello-application', :flags_none) {\n  on(:activate) do |app|\n    application_window(app) {\n      title 'Hello, Application!'\n    }.present\n  end\n}.run\n```\n\n#### Hello, Button!\n\n[samples/hello/hello_button.rb](/samples/hello/hello_button.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello button screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-button.png) ![hello button clicked screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-button-clicked.png) | ![hello button screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-button.png) ![hello button clicked screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-button-clicked.png) | ![hello button screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-button.png) ![hello button clicked screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-button-clicked.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_button'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_button.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow { |w|\n  title 'Hello, Button!'\n  \n  button(label: 'Button') {\n    on(:clicked) do\n      message_dialog(parent: w) { |md|\n        title 'Information'\n        text 'You clicked the button'\n        \n        on(:response) do\n          md.destroy\n        end\n      }.show\n    end\n  }\n}.show\n```\n\n#### Hello, Entry!\n\n[samples/hello/hello_entry.rb](/samples/hello/hello_entry.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello entry screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-entry.png) ![hello entry submitted screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-entry-submitted.png) | ![hello entry screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-entry.png) ![hello entry submitted screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-entry-submitted.png) | ![hello entry screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-entry.png) ![hello entry submitted screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-entry-submitted.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_entry'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_entry.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow { |w|\n  title 'Hello, Entry!'\n  default_size 300, 50\n    \n  box(:vertical) {\n    e = entry {\n      on(:changed) do\n        puts e.text\n        $stdout.flush # For Windows\n      end\n    }\n    \n    button(label: 'Button') {\n      on(:clicked) do\n        message_dialog(parent: w) { |md|\n          title 'You entered'\n          text e.text\n          \n          on(:response) do\n            md.destroy\n          end\n        }.show\n      end\n    }\n  }\n}.show\n```\n\n#### Hello, Drawing Area!\n\nThis demonstrates the very intuitive (Glimmer-only) declarative cairo shape drawing syntax for the `drawing_area` widget.\n\n[samples/hello/hello_drawing_area.rb](/samples/hello/hello_drawing_area.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello drawing area screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-drawing-area.png) | ![hello drawing area screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-drawing-area.png) | ![hello drawing area screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-drawing-area.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_drawing_area'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_drawing_area.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Hello, Drawing Area!'\n  default_size 400, 400\n  \n  drawing_area {\n    paint 255, 255, 255\n    \n    arc(85, 85, 45, (Math::PI/180)*90, -(Math::PI/180)*90) {\n      fill 255, 0, 0\n      stroke 0, 128, 255\n      line_width 3\n    }\n\n    arc(85, 185, 45, (Math::PI/180)*100, -(Math::PI/180)*30) {\n      fill 255, 0, 0\n      stroke 0, 128, 255\n      line_width 3\n    }\n    \n    circle(85, 285, 45) {\n      fill 255, 0, 0\n      stroke 0, 128, 255\n      line_width 3\n    }\n    \n    rectangle(140, 40, 180, 90) {\n      fill 255, 255, 0\n      stroke 255, 0, 0\n      line_width 3\n    }\n    \n    rounded_rectangle(140, 140, 180, 90, 30, 20) {\n      fill 255, 255, 0\n      stroke 255, 0, 0\n      line_width 3\n    }\n\n    triangle(140, 240, 320, 240, 230, 330) {\n      fill 255, 255, 0\n      stroke 255, 0, 0\n      line_width 3\n    }\n\n    path {\n      move_to 160, 100\n      curve_to 190, 60, 200, 80, 210, 70\n      curve_to 240, 80, 250, 100, 260, 90\n      curve_to 290, 90, 300, 110, 310, 100\n\n      fill 0, 255, 0\n      stroke 0, 0, 255\n      line_width 3\n    }\n\n    path {\n      move_to 200, 150\n      line_to 270, 170\n      line_to 250, 220\n      line_to 220, 190\n      line_to 200, 200\n      line_to 180, 170\n      close_path\n\n      fill 0, 255, 0\n      stroke 0, 0, 255\n      line_width 3\n    }\n    \n    polygon(200, 260, 270, 270, 250, 290, 220, 290, 200, 280, 180, 270) {\n      fill 0, 255, 0\n      stroke 0, 0, 255\n      line_width 3\n    }\n    \n    polyline(270, 320, 250, 340, 220, 340, 200, 330, 180, 320) {\n      stroke 0, 0, 255\n      line_width 3\n    }\n  }\n}.show\n```\n\n#### Hello, Drawing Area (Manual)!\n\nThis demonstrates the manual (non-Glimmer) imperative cairo shape drawing syntax for the `drawing_area` widget (might be useful in very exceptional rare cases).\n\n[samples/hello/hello_drawing_area_manual.rb](/samples/hello/hello_drawing_area_manual.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![hello drawing area manual screenshot](/screenshots/glimmer-dsl-gtk-linux-hello-drawing-area-manual.png) | ![hello drawing area manual screenshot](/screenshots/glimmer-dsl-gtk-mac-hello-drawing-area-manual.png) | ![hello drawing area manual screenshot](/screenshots/glimmer-dsl-gtk-windows-hello-drawing-area-manual.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/hello/hello_drawing_area_manual'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/hello/hello_drawing_area_manual.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\nwindow {\n  title 'Hello, Drawing Area (Manual)!'\n  default_size 400, 400\n  \n  drawing_area {\n    on(:draw) do |drawing_area_widget, cairo_context|\n      cairo_context.set_source_rgb(255/255.0, 255/255.0, 255/255.0)\n      cairo_context.paint\n      \n      cairo_context.arc(85, 85, 45, (Math::PI/180)*90, -(Math::PI/180)*90)\n      cairo_context.set_source_rgb(255, 0, 0)\n      cairo_context.fill\n      \n      cairo_context.arc(85, 85, 45, (Math::PI/180)*90, -(Math::PI/180)*90)\n      cairo_context.set_source_rgb(0, 128/255.0, 255/255.0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n\n      cairo_context.arc(85, 185, 45, (Math::PI/180)*100, -(Math::PI/180)*30)\n      cairo_context.set_source_rgb(255/255.0, 0, 0)\n      cairo_context.fill\n      \n      cairo_context.arc(85, 185, 45, (Math::PI/180)*100, -(Math::PI/180)*30)\n      cairo_context.set_source_rgb(0, 128/255.0, 255/255.0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n      \n      cairo_context.circle(85, 285, 45)\n      cairo_context.set_source_rgb(255/255.0, 0, 0)\n      cairo_context.fill\n      \n      cairo_context.circle(85, 285, 45)\n      cairo_context.set_source_rgb(0, 128/255.0, 255/255.0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n      \n      cairo_context.rectangle(140, 40, 180, 90)\n      cairo_context.set_source_rgb(255/255.0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.rectangle(140, 40, 180, 90)\n      cairo_context.set_source_rgb(255/255.0, 0, 0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n      \n      cairo_context.rounded_rectangle(140, 140, 180, 90, 30, 20)\n      cairo_context.set_source_rgb(255/255.0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.rounded_rectangle(140, 140, 180, 90, 30, 20)\n      cairo_context.set_source_rgb(255/255.0, 0, 0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n      \n      cairo_context.triangle(140, 240, 320, 240, 230, 330)\n      cairo_context.set_source_rgb(255/255.0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.triangle(140, 240, 320, 240, 230, 330)\n      cairo_context.set_source_rgb(255/255.0, 0, 0)\n      cairo_context.set_line_width(3)\n      cairo_context.stroke\n      \n      cairo_context.new_path\n      cairo_context.move_to 160, 100\n      cairo_context.curve_to 190, 60, 200, 80, 210, 70\n      cairo_context.curve_to 240, 80, 250, 100, 260, 90\n      cairo_context.curve_to 290, 90, 300, 110, 310, 100\n      cairo_context.set_source_rgb(0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.new_path\n      cairo_context.move_to 160, 100\n      cairo_context.curve_to 190, 60, 200, 80, 210, 70\n      cairo_context.curve_to 240, 80, 250, 100, 260, 90\n      cairo_context.curve_to 290, 90, 300, 110, 310, 100\n      cairo_context.set_source_rgb(0, 0, 255/255.0)\n      cairo_context.stroke\n      \n      cairo_context.new_path\n      cairo_context.move_to 200, 150\n      cairo_context.line_to 270, 170\n      cairo_context.line_to 250, 220\n      cairo_context.line_to 220, 190\n      cairo_context.line_to 200, 200\n      cairo_context.line_to 180, 170\n      cairo_context.close_path\n      cairo_context.set_source_rgb(0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.new_path\n      cairo_context.move_to 200, 150\n      cairo_context.line_to 270, 170\n      cairo_context.line_to 250, 220\n      cairo_context.line_to 220, 190\n      cairo_context.line_to 200, 200\n      cairo_context.line_to 180, 170\n      cairo_context.close_path\n      cairo_context.set_source_rgb(0, 0, 255/255.0)\n      cairo_context.stroke\n      \n      cairo_context.new_path\n      cairo_context.move_to 200, 260\n      cairo_context.line_to 270, 270\n      cairo_context.line_to 250, 290\n      cairo_context.line_to 220, 290\n      cairo_context.line_to 200, 280\n      cairo_context.line_to 180, 270\n      cairo_context.close_path\n      cairo_context.set_source_rgb(0, 255/255.0, 0)\n      cairo_context.fill\n      \n      cairo_context.new_path\n      cairo_context.move_to 200, 260\n      cairo_context.line_to 270, 270\n      cairo_context.line_to 250, 290\n      cairo_context.line_to 220, 290\n      cairo_context.line_to 200, 280\n      cairo_context.line_to 180, 270\n      cairo_context.close_path\n      cairo_context.set_source_rgb(0, 0, 255/255.0)\n      cairo_context.stroke\n      \n      cairo_context.new_path\n      cairo_context.move_to 200, 260\n      cairo_context.move_to 270, 320\n      cairo_context.line_to 250, 340\n      cairo_context.line_to 220, 340\n      cairo_context.line_to 200, 330\n      cairo_context.line_to 180, 320\n      cairo_context.set_source_rgb(0, 0, 255/255.0)\n      cairo_context.stroke\n    end\n  }\n}.show\n```\n\n### Elaborate Samples\n\n#### Widget Gallery\n\n[samples/elaborate/widget_gallery.rb](/samples/elaborate/widget_gallery.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![widget gallery text](/screenshots/glimmer-dsl-gtk-linux-widget-gallery-text.png) ![widget gallery button](/screenshots/glimmer-dsl-gtk-linux-widget-gallery-button.png) ![widget gallery selection](/screenshots/glimmer-dsl-gtk-linux-widget-gallery-selection.png) ![widget gallery organizer](/screenshots/glimmer-dsl-gtk-linux-widget-gallery-organizer.png) ![widget gallery progress](/screenshots/glimmer-dsl-gtk-linux-widget-gallery-progress.png) | ![widget gallery text](/screenshots/glimmer-dsl-gtk-mac-widget-gallery-text.png) ![widget gallery button](/screenshots/glimmer-dsl-gtk-mac-widget-gallery-button.png) ![widget gallery selection](/screenshots/glimmer-dsl-gtk-mac-widget-gallery-selection.png) ![widget gallery organizer](/screenshots/glimmer-dsl-gtk-mac-widget-gallery-organizer.png) ![widget gallery progress](/screenshots/glimmer-dsl-gtk-mac-widget-gallery-progress.png) | ![widget gallery text](/screenshots/glimmer-dsl-gtk-windows-widget-gallery-text.png) ![widget gallery button](/screenshots/glimmer-dsl-gtk-windows-widget-gallery-button.png) ![widget gallery selection](/screenshots/glimmer-dsl-gtk-windows-widget-gallery-selection.png) ![widget gallery organizer](/screenshots/glimmer-dsl-gtk-windows-widget-gallery-organizer.png) ![widget gallery progress](/screenshots/glimmer-dsl-gtk-windows-widget-gallery-progress.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/elaborate/widget_gallery'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/elaborate/widget_gallery.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\ninclude Glimmer\n\napplication('org.glimmer.hello-application') {\n  on(:activate) do |app|\n    application_window(app) { |aw|\n      title 'Widget Gallery'\n      \n      notebook { |n|\n        f1 = frame {\n          alignment(1, 1, 1, 1) {\n            padding 15, 15, 15, 15\n            \n            box(:vertical) {\n              spacing 10\n              \n              label('Entry')\n              entry {\n                text 'Enter One Line of Text'\n              }\n              \n              label('Search Entry')\n              search_entry {\n                text 'Enter Search Term'\n              }\n              \n              label('Spin Button')\n              spin_button(1, 100, 1) {\n              }\n              \n              label('Combo Box Text')\n              cb = combo_box_text {\n              }\n              3.times { |n| cb.append_text \"Option #{n + 1}\" }\n            }\n          }\n        }\n        n.set_tab_label_text(f1.gtk, 'Text')\n        \n        f2 = frame {\n          alignment(1, 1, 1, 1) {\n            padding 15, 15, 15, 15\n            \n            box(:vertical) {\n              spacing 10\n              \n              label('Button')\n              button(label: 'Push Me') {\n                on(:clicked) do\n                  message_dialog(parent: aw) { |md|\n                    title 'Information'\n                    text 'You clicked the button'\n                    \n                    on(:response) do\n                      md.destroy\n                    end\n                  }.show\n                end\n              }\n              \n              label('Radio Button')\n              box(:horizontal) {\n                rb = radio_button(label: 'One')\n                radio_button(label: 'Two', member: rb)\n                radio_button(label: 'Three', member: rb)\n              }\n              \n              label('Check Button')\n              box(:horizontal) {\n                check_button('One')\n                check_button('Two')\n                check_button('Three')\n              }\n            }\n          }\n        }\n        n.set_tab_label_text(f2.gtk, 'Button')\n        \n        f3 = frame {\n          alignment(1, 1, 1, 1) {\n            padding 15, 15, 15, 15\n            \n            box(:vertical) {\n              spacing 10\n              \n              label('Horizontal Scale')\n              scale(:horizontal, 1, 100, 1) {\n                visible true\n              }\n              \n              label('Vertical Scale')\n              scale(:vertical, 1, 100, 1) {\n                visible true\n                height_request 200\n              }\n            }\n          }\n        }\n        n.set_tab_label_text(f3.gtk, 'Selection')\n        \n        f4 = frame {\n          alignment(1, 1, 1, 1) {\n            padding 15, 15, 15, 15\n            \n            box(:vertical) {\n              spacing 10\n              \n              label('Expander')\n              3.times do |n|\n                expander {\n                  label \"Item #{n + 1}\"\n                  \n                  label(\"Item #{n + 1} Expanded\") {\n                  }\n                }\n              end\n            }\n          }\n        }\n        n.set_tab_label_text(f4.gtk, 'Organizer')\n        \n        f5 = frame {\n          alignment(1, 1, 1, 1) {\n            padding 15, 15, 15, 15\n            \n            box(:vertical) {\n              spacing 10\n              \n              label('Progress Bar')\n              pb = progress_bar {\n                text 'Progress Bar'\n              }\n              Thread.new do\n                101.times.cycle do |n|\n                  pb.fraction = n / 100.0\n                  sleep(0.1) # yields back to main GUI thread\n                end\n              end\n              \n              label('Spinner')\n              spinner {\n                active true\n              }\n            }\n          }\n        }\n        n.set_tab_label_text(f5.gtk, 'Progress')\n      }\n    }.present\n  end\n}.run\n```\n\n#### Tetris\n\n[samples/elaborate/tetris.rb](/samples/elaborate/tetris.rb)\n\nLinux | Mac | Windows\n------|-----|--------\n![tetris](/screenshots/glimmer-dsl-gtk-linux-tetris.png) | ![tetris](/screenshots/glimmer-dsl-gtk-mac-tetris.png) | ![tetris](/screenshots/glimmer-dsl-gtk-windows-tetris.png)\n\nRun (via installed gem):\n\n```\nruby -r glimmer-dsl-gtk -e \"require 'samples/elaborate/tetris'\"\n```\n\nRun (via locally cloned project):\n\n```\nruby -r ./lib/glimmer-dsl-gtk.rb samples/elaborate/tetris.rb\n```\n\nCode:\n\n```ruby\nrequire 'glimmer-dsl-gtk'\n\nrequire_relative 'tetris/model/game'\n\nclass Tetris\n  include Glimmer\n  \n  BLOCK_SIZE = 25\n  BEVEL_CONSTANT = 20\n  COLOR_GRAY = [192, 192, 192]\n    \n  def initialize\n    @game = Model::Game.new\n  end\n  \n  def launch\n    create_gui\n    register_observers\n    @game.start!\n    @main_window.show\n  end\n  \n  def create_gui\n    @main_window = window {\n      title 'Glimmer Tetris'\n      default_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE # + 98\n      \n      box(:vertical) {\n        tetris_menu_bar\n\n        box(:horizontal) {\n          @playfield_blocks = playfield(playfield_width: @game.playfield_width, playfield_height: @game.playfield_height, block_size: BLOCK_SIZE)\n          \n          score_board\n        }\n        \n      }\n      \n      on(:key_press_event) do |widget, key_event|\n        case key_event.keyval\n        when 65364 # down arrow\n          @game.down!\n        when 32 # space\n          @game.down!(instant: true)\n        when 65362 # up arrow\n          case @game.up_arrow_action\n          when :instant_down\n            @game.down!(instant: true)\n          when :rotate_right\n            @game.rotate!(:right)\n          when :rotate_left\n            @game.rotate!(:left)\n          end\n        when 65361 # left arrow\n          @game.left!\n        when 65363 # right arrow\n          @game.right!\n        when 65506 # right shift\n          @game.rotate!(:right)\n        when 65505 # left shift\n          @game.rotate!(:left)\n        else\n          # Do Nothing\n        end\n      end\n    }\n  end\n  \n  def register_observers\n    observe(@game, :game_over) do |game_over|\n      if game_over\n        show_game_over_dialog\n      else\n        start_moving_tetrominos_down\n      end\n    end\n    \n    @game.playfield_height.times do |row|\n      @game.playfield_width.times do |column|\n        observe(@game.playfield[row][column], :color) do |new_color|\n          color = new_color\n          block = @playfield_blocks[row][column]\n          block[:background_square].fill = color\n          block[:top_bevel_edge].fill = [color[0] + 4*BEVEL_CONSTANT, color[1] + 4*BEVEL_CONSTANT, color[2] + 4*BEVEL_CONSTANT]\n          block[:right_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]\n          block[:bottom_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]\n          block[:left_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]\n          block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color\n          block[:drawing_area].queue_draw\n          false\n        end\n      end\n    end\n    \n    Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|\n      Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|\n        preview_updater = proc do\n          block = @preview_playfield_blocks[row][column]\n          if @game.show_preview_tetromino?\n            new_color = @game.preview_playfield[row][column].color\n            block[:background_square].fill = new_color\n            block[:top_bevel_edge].fill = [new_color[0] + 4*BEVEL_CONSTANT, new_color[1] + 4*BEVEL_CONSTANT, new_color[2] + 4*BEVEL_CONSTANT]\n            block[:right_bevel_edge].fill = [new_color[0] - BEVEL_CONSTANT, new_color[1] - BEVEL_CONSTANT, new_color[2] - BEVEL_CONSTANT]\n            block[:bottom_bevel_edge].fill = [new_color[0] - BEVEL_CONSTANT, new_color[1] - BEVEL_CONSTANT, new_color[2] - BEVEL_CONSTANT]\n            block[:left_bevel_edge].fill = [new_color[0] - BEVEL_CONSTANT, new_color[1] - BEVEL_CONSTANT, new_color[2] - BEVEL_CONSTANT]\n            block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : new_color\n            @next_label.text = 'Next'\n          else\n            transparent_color = [0, 0, 0, 0]\n            block[:background_square].fill = transparent_color\n            block[:top_bevel_edge].fill = transparent_color\n            block[:right_bevel_edge].fill = transparent_color\n            block[:bottom_bevel_edge].fill = transparent_color\n            block[:left_bevel_edge].fill = transparent_color\n            block[:border_square].stroke = transparent_color\n            @next_label.text = ''\n          end\n          block[:drawing_area].queue_draw\n        end\n      \n        observe(@game.preview_playfield[row][column], :color, \u0026preview_updater)\n        observe(@game, :show_preview_tetromino, \u0026preview_updater)\n      end\n    end\n    \n    observe(@game, :score) do |new_score|\n      @score_label.text = new_score.to_s\n    end\n\n    observe(@game, :lines) do |new_lines|\n      @lines_label.text = new_lines.to_s\n    end\n\n    observe(@game, :level) do |new_level|\n      @level_label.text = new_level.to_s\n    end\n  end\n  \n  def tetris_menu_bar\n    menu_bar {\n      menu_item(label: 'Game') { |mi|\n        m = menu {\n          check_menu_item(label: 'Pause') {\n            on(:activate) do\n              @game.paused = !@game.paused?\n            end\n          }\n          \n          menu_item(label: 'Restart') {\n            on(:activate) do\n              @game.restart!\n            end\n          }\n          \n          separator_menu_item\n          \n          menu_item(label: 'Exit') {\n            on(:activate) do\n              @main_window.close\n            end\n          }\n        }\n        mi.submenu = m.gtk\n      }\n      \n      menu_item(label: 'View') { |mi|\n        m = menu {\n          check_menu_item(label: 'Show Next Block Preview') {\n            active @game.show_preview_tetromino?\n          \n            on(:activate) do\n              @game.show_preview_tetromino = !@game.show_preview_tetromino?\n            end\n          }\n          \n          separator_menu_item\n          \n          menu_item(label: 'Show High Scores') {\n            on(:activate) do\n              show_high_score_dialog\n            end\n          }\n          \n          menu_item(label: 'Clear High Scores') {\n            on(:activate) do\n              @game.clear_high_scores!\n            end\n          }\n        }\n        mi.submenu = m.gtk\n      }\n      \n      menu_item(label: 'Speed') { |mi|\n        m = menu {\n          rmi = radio_menu_item(nil, Model::Game::SPEEDS.first.to_s.capitalize) {\n            active true\n            \n            on(:activate) do\n              @game.send(\"speed_#{Model::Game::SPEEDS.first}=\", true)\n            end\n          }\n          \n          Model::Game::SPEEDS.drop(1).each do |speed|\n            radio_menu_item(rmi.group, speed.to_s.capitalize) {\n              on(:activate) do\n                @game.send(\"speed_#{speed}=\", true)\n              end\n            }\n          end\n        }\n        mi.submenu = m.gtk\n      }\n      \n      menu_item(label: 'Options') { |mi|\n        m = menu {\n          rmi = radio_menu_item(nil, 'Instant Down on Up') {\n            on(:activate) do\n              @game.instant_down_on_up!\n            end\n          }\n          \n          default_rmi = radio_menu_item(rmi.group, 'Rotate Right on Up') {\n            on(:activate) do\n              @game.rotate_right_on_up!\n            end\n          }\n          default_rmi.activate\n          \n          radio_menu_item(rmi.group, 'Rotate Left on Up') {\n            on(:activate) do\n              @game.rotate_left_on_up!\n            end\n          }\n        }\n        mi.submenu = m.gtk\n      }\n      \n      menu_item(label: 'Help') { |mi|\n        m = menu {\n          menu_item(label: 'About') {\n            on(:activate) do\n              show_about_dialog\n            end\n          }\n        }\n        mi.submenu = m.gtk\n      }\n    }\n  end\n  \n  def score_board\n    box(:vertical) {\n      label\n      @next_label = label('Next')\n      @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)\n      \n      label\n      label('Score')\n      @score_label = label\n\n      label\n      label('Lines')\n      @lines_label = label\n\n      label\n      label('Level')\n      @level_label = label\n      label\n    }\n  end\n  \n  def playfield(playfield_width: , playfield_height: , block_size: , \u0026extra_content)\n    blocks = []\n    box(:vertical) {\n      playfield_height.times.map do |row|\n        blocks \u003c\u003c []\n        box(:horizontal) {\n          playfield_width.times.map do |column|\n            blocks.last \u003c\u003c block(row: row, column: column, block_size: block_size)\n          end\n        }\n      end\n      \n      extra_content\u0026.call\n    }\n    blocks\n  end\n  \n  def block(row: , column: , block_size: , \u0026extra_content)\n    block = {}\n    bevel_pixel_size = 0.16 * block_size.to_f\n    color = Model::Block::COLOR_CLEAR\n    block[:drawing_area] = drawing_area {\n      size_request block_size, block_size\n      \n      block[:background_square] = square(0, 0, block_size) {\n        fill *color\n      }\n      \n      block[:top_bevel_edge] = polygon(0, 0, block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {\n        fill color[0] + 4*BEVEL_CONSTANT, color[1] + 4*BEVEL_CONSTANT, color[2] + 4*BEVEL_CONSTANT\n      }\n       \n      block[:right_bevel_edge] = polygon(block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size, block_size) {\n        fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT\n      }\n       \n      block[:bottom_bevel_edge] = polygon(block_size, block_size, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size) {\n        fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT\n      }\n      \n      block[:left_bevel_edge] = polygon(0, 0, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {\n        fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT\n      }\n      \n      block[:border_square] = square(0, 0, block_size) {\n        stroke *COLOR_GRAY\n      }\n      \n      extra_content\u0026.call\n    }\n    block\n  end\n  \n  def start_moving_tetrominos_down\n    unless @tetrominos_start_moving_down\n      @tetrominos_start_moving_down = true\n      tetromino_move = proc do\n        @game.down! if !@game.game_over? \u0026\u0026 !@game.paused?\n        GLib::Timeout.add(@game.delay*1000, \u0026tetromino_move)\n        false # do not repeat\n      end\n      GLib::Timeout.add(@game.delay*1000, \u0026tetromino_move)\n    end\n  end\n  \n  def show_game_over_dialog\n    @game.paused = true\n    message_dialog(parent: @main_window) { |md|\n      title 'Game Over!'\n      text \"Score: #{@game.high_scores.first.score}\\nLines: #{@game.high_scores.first.lines}\\nLevel: #{@game.high_scores.first.level}\"\n      \n      on(:response) do\n        @game.restart!\n        md.destroy\n      end\n    }.show\n  end\n  \n  def show_high_score_dialog\n    @game.paused = true\n    \n    if @game.high_scores.empty?\n      high_scores_string = \"No games have been scored yet.\"\n    else\n      high_scores_string = @game.high_scores.map do |high_score|\n        \"#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}\"\n      end.join(\"\\n\")\n    end\n    \n    message_dialog(parent: @main_window) { |md|\n      title 'High Scores'\n      text high_scores_string\n      \n      on(:response) do\n        @game.paused = false\n        md.destroy\n      end\n    }.show\n  end\n  \n  def show_about_dialog\n    message_dialog(parent: @main_window) { |md|\n      title 'About'\n      text \"Glimmer Tetris\\n\\nGlimmer DSL for GTK\\n\\nElaborate Sample\\n\\nCopyright (c) 2021-2022 Andy Maleh\"\n      \n      on(:response) do\n        md.destroy\n      end\n    }.show\n  end\nend\n\nTetris.new.launch\n```\n\n## Resources\n\n- Ruby-Gnome Project: https://github.com/ruby-gnome/ruby-gnome\n- GTK3 API Documentation: https://gnome.pages.gitlab.gnome.org/gtk/gtk3/\n- GTK3 Widget Gallery: https://docs.gtk.org/gtk3/visual_index.html\n\n## Process\n\n[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)\n\n## Help\n\n### Issues\n\nIf you encounter [issues](https://github.com/AndyObtiva/glimmer-dsl-gtk/issues) that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use GTK than what is possible with [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk), you may submit an [issue](https://github.com/AndyObtiva/glimmer-dsl-gtk/issues/new) or [pull request](https://github.com/AndyObtiva/glimmer-dsl-gtk/compare) on [GitHub](https://github.com). In the meantime while waiting for a fix, you may try older gem versions of [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk) in case you find one that does not have the issue and actually works.\n\n### Chat\n\nIf you need live help, try to [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n## Planned Features and Feature Suggestions\n\nThese features have been planned or suggested. You might see them in a future version of [Glimmer DSL for GTK](https://rubygems.org/gems/glimmer-dsl-gtk). You are welcome to contribute more feature suggestions.\n\n[TODO.md](TODO.md)\n\n## Change Log\n\n[CHANGELOG.md](CHANGELOG.md)\n\n## Contributing\n\n-   Check out the latest master to make sure the feature hasn't been\n    implemented or the bug hasn't been fixed yet.\n-   Check out the issue tracker to make sure someone already hasn't\n    requested it and/or contributed it.\n-   Fork the project.\n-   Start a feature/bugfix branch.\n-   Commit and push until you are happy with your contribution.\n-   Make sure to add tests for it. This is important so I don't break it\n    in a future version unintentionally.\n-   Please try not to mess with the Rakefile, version, or history. If\n    you want to have your own version, or is otherwise necessary, that\n    is fine, but please isolate to its own commit so I can cherry-pick\n    around it.\n\n## Contributors\n\n* [Andy Maleh](https://github.com/AndyObtiva) (Founder)\n\n[Click here to view contributor commits.](https://github.com/AndyObtiva/glimmer-dsl-gtk/graphs/contributors)\n\n## Copyright\n\n[LGPL](LICENSE.txt)\n\nCopyright (c) 2021 Andy Maleh.\n\n--\n\n[\u003cimg src=\"https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png\" height=40 /\u003e](https://github.com/AndyObtiva/glimmer) Built for [Glimmer](https://github.com/AndyObtiva/glimmer) (DSL Framework).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-gtk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-gtk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-gtk/lists"}