[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: \"github-actions\"\n  directory: \"/\"\n  schedule:\n    interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/doc.yml",
    "content": "name: doc\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ruby\n      - name: Generate document\n        run: gem install -N yard && yard doc\n      - name: Publish Documentation on GitHub Pages\n        uses: peaceiris/actions-gh-pages@v4\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./doc\n"
  },
  {
    "path": ".github/workflows/release-rubygems.yml",
    "content": "name: Release libui to RubyGems\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n  workflow_dispatch:\n\njobs:\n  push:\n    environment: release-rubygems\n    strategy:\n      fail-fast: false\n      matrix:\n        # Default Ruby version: 3.4 (used for all platforms except x64-mingw32)\n        include:\n          - os: ubuntu-latest\n            ruby-version: \"3.4\"\n            gem_platform: x86_64-linux\n          - os: ubuntu-24.04-arm\n            ruby-version: \"3.4\"\n            gem_platform: aarch64-linux\n          - os: macos-13 # Intel\n            ruby-version: \"3.4\"\n            gem_platform: x86_64-darwin\n          - os: macos-14 # Apple Silicon\n            ruby-version: \"3.4\"\n            gem_platform: arm64-darwin\n          - os: windows-latest\n            ruby-version: \"2.7\" # Ruby 2.7 is used for x64-mingw32 because newer Ruby versions default to x64-mingw-ucrt platform, causing bundler platform resolution conflicts\n            gem_platform: x64-mingw32\n          - os: windows-latest\n            ruby-version: \"3.4\"\n            gem_platform: x64-mingw-ucrt\n\n    runs-on: ${{ matrix.os }}\n\n    permissions:\n      id-token: write # IMPORTANT: this permission is mandatory for trusted publishing\n      contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag\n\n    env:\n      GEM_PLATFORM: ${{ matrix.gem_platform }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          persist-credentials: false\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby-version }}\n          bundler-cache: true\n\n      - name: Show versions\n        run: |\n          ruby -v\n          gem -v\n          bundle -v\n        shell: bash\n\n      - name: Update RubyGems for Windows Ruby 2.7 (enable 'gem exec')\n        if: ${{ matrix.gem_platform == 'x64-mingw32' }}\n        run: |\n          gem update --system 3.4.22\n          gem --version\n        shell: bash\n\n      - name: Prepare vendored binary (bundle exec rake vendor:auto)\n        run: |\n          bundle exec rake vendor:auto\n        shell: bash\n\n      - name: Release gem to RubyGems.org\n        uses: rubygems/release-gem@v1\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\non:\n  - push\n  - pull_request\n  - workflow_dispatch\njobs:\n  build:\n    name: ${{ matrix.runner }} Ruby ${{ matrix.ruby }}\n    strategy:\n      fail-fast: false\n      matrix:\n        runner:\n          - ubuntu-latest\n          - ubuntu-24.04-arm\n          - macos-15-intel\n          - macos-latest\n          - windows-latest\n        ruby: [\"2.7\", \"3.2\", \"3.3\", \"3.4\", \"4.0\"]\n    runs-on: ${{ matrix.runner }}\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n      - name: Install dependencies\n        run: bundle install\n      - name: Download libui shared libraries\n        run: bundle exec rake vendor:auto\n      - name: Rake test (XVFB)\n        uses: coactions/setup-xvfb@v1\n        with:\n          run: bundle exec rake test\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n*.lock\n/vendor/bundle\n/vendor/*libui*\nbuild.log\n\n# gpt2 exmaple\n*.onnx\ngpt2.bin\ngpt2.i2w"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in libui.gemspec\ngemspec\n\ngem 'minitest'\ngem 'rake'\ngem 'rubyzip'\n\n# group :development do\n#   gem 'chunky_png'\n#   gem 'numo-narray'\n# end\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020-present kojix2\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# LibUI\n\n[![test](https://github.com/kojix2/LibUI/actions/workflows/test.yml/badge.svg)](https://github.com/kojix2/LibUI/actions/workflows/test.yml)\n[![Gem Version](https://badge.fury.io/rb/libui.svg)](https://badge.fury.io/rb/libui)\n<a href=\"https://github.com/AndyObtiva/glimmer-dsl-libui\"><img alt=\"glimmer-dsl-libui\" src=\"https://github.com/AndyObtiva/glimmer/blob/master/images/glimmer-logo-hi-res.svg\" width=\"50\" height=\"50\" align=\"right\"></a>\n[![Pre-build](https://github.com/kojix2/libui-ng/actions/workflows/pre-build.yml/badge.svg?branch=pre-build)](https://github.com/kojix2/libui-ng/actions/workflows/pre-build.yml)\n[![Lines of Code](https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fkojix2%2FLibUI%2Flines)](https://tokei.kojix2.net/github/kojix2/LibUI)\n\nLibUI is a Ruby wrapper for libui family.\n\n:rocket: [libui-ng](https://github.com/libui-ng/libui-ng) - A cross-platform portable GUI library\n\n:wrench: [libui-dev](https://github.com/petabyt/libui-dev) - Native UI library for C - with some extras\n\n:radio_button: [libui](https://github.com/andlabs/libui) - Original version by andlabs.\n\n## Installation\n\n```sh\ngem install libui # --pre\n```\n\n- The gem package includes the libui-ng shared library for Windows, Mac, and Linux.\n  - Namely `libui.x64.dll`/`libui.x86.dll`, `libui.x86_64.dylib`/`libui.arm64.dylib`, or `libui.x86_64.so`/`libui.aarch64.so`.\n- No dependencies required.\n  - The libui gem uses the standard Ruby library [Fiddle](https://github.com/ruby/fiddle) to call C functions.\n\n| Windows                                                                                                          | Mac                                                                                                              | Linux                                                                                                            |\n| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| <img src=\"https://user-images.githubusercontent.com/5798442/103118046-900ea780-46b0-11eb-81fc-32626762e4df.png\"> | <img src=\"https://user-images.githubusercontent.com/5798442/103118059-99980f80-46b0-11eb-9d12-324ec4d297c9.png\"> | <img src=\"https://user-images.githubusercontent.com/5798442/103118068-a0bf1d80-46b0-11eb-8c5c-3bdcc3dcfb26.png\"> |\n\nNotes:\n\n- If you are using the 32-bit (x86) version of Ruby, you need to download the 32-bit (x86) native dll. See the [Development](#development) section.\n- On Windows, libui may not work due to missing DLLs. In that case, you need to install [Visual C++ Redistributable](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist). See ([#48](https://github.com/kojix2/LibUI/issues/48))\n- Users with [Raspberry Pi](https://www.raspberrypi.com/) or other platforms will need to compile the C libui library. See the [Development](#development) section.\n\n## Usage\n\n```ruby\nrequire 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 200, 100, 1)\n\nbutton = UI.new_button('Button')\n\nUI.button_on_clicked(button) do\n  UI.msg_box(main_window, 'Information', 'You clicked the button')\nend\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.window_set_child(main_window, button)\nUI.control_show(main_window)\n\nUI.main\nUI.quit\n```\n\nFor more examples, see the [examples](https://github.com/kojix2/libui/tree/main/examples) directory.\n\n### General Rules\n\nCompared to the original libui library written in C:\n\n- Method names use snake_case.\n- The last argument can be omitted if it's nil.\n- A block can be passed as a callback.\n  - The block will be converted to a Proc object and added as the last argument.\n  - The last argument can still be omitted when nil.\n\nYou can use [the libui-ng API documentation](https://libui-ng.github.io/libui-ng/) as a reference.\n\n### DSLs for LibUI\n\nLibUI is not object-oriented because it is a thin Ruby wrapper (binding) for the procedural C libui library, mirroring its API structure.\n\nTo build actual applications, it is recommended to use a DSL for LibUI, as they enable writing object-oriented code the Ruby way (instead of procedural code the C way):\n\n- [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui)\n- [libui_paradise](https://rubygems.org/gems/libui_paradise)\n\n### Working with fiddle pointers\n\n```ruby\nrequire 'libui'\nUI = LibUI\nUI.init\n```\n\nTo convert a pointer to a string:\n\n```ruby\nlabel = UI.new_label(\"Ruby\")\np pointer = UI.label_text(label) # #<Fiddle::Pointer>\np pointer.to_s # Ruby\n```\n\nIf you need to use C structs, you can do the following:\n\n```ruby\nfont_button = UI.new_font_button\n\n# Allocate memory\nfont_descriptor = UI::FFI::FontDescriptor.malloc\nfont_descriptor.to_ptr.free = Fiddle::RUBY_FREE\n# font_descriptor = UI::FFI::FontDescriptor.malloc(Fiddle::RUBY_FREE) # fiddle 1.0.1 or higher\n\nUI.font_button_on_changed(font_button) do\n  UI.font_button_font(font_button, font_descriptor)\n  p family:  font_descriptor.Family.to_s,\n    size:    font_descriptor.Size,\n    weight:  font_descriptor.Weight,\n    italic:  font_descriptor.Italic,\n    stretch: font_descriptor.Stretch\nend\n```\n\n- Callbacks\n  - In Ruby/Fiddle, a C callback function is written as an object of\n    `Fiddle::Closure::BlockCaller` or `Fiddle::Closure`.\n    Be careful about Ruby's garbage collection - if the function object is collected, memory will be freed resulting in a segmentation violation when the callback is invoked.\n\n```ruby\n# Assign to a local variable to prevent it from being collected by GC.\nhandler.MouseEvent   = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})\nhandler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})\nhandler.DragBroken   = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})\n```\n\n### Creating a Windows executable (.exe) with OCRA\n\nOCRA (One-Click Ruby Application) builds Windows executables from Ruby source code.\n\n- https://github.com/larsch/ocra/\n\nTo build an exe with Ocra, include 3 DLLs from the ruby_builtin_dlls folder:\n\n```sh\nocra examples/control_gallery.rb        ^\n  --dll ruby_builtin_dlls/libssp-0.dll  ^\n  --dll ruby_builtin_dlls/libgmp-10.dll ^\n  --dll ruby_builtin_dlls/libffi-7.dll  ^\n  --gem-all=fiddle                      ^\n```\n\nAdd additional options below if necessary:\n\n```sh\n  --window                              ^\n  --add-all-core                        ^\n  --chdir-first                         ^\n  --icon assets\\app.ico                 ^\n  --verbose                             ^\n  --output out\\gallery.exe\n```\n\n## Development\n\n```sh\ngit clone https://github.com/kojix2/libui\ncd libui\nbundle install\nbundle exec rake vendor:auto\nbundle exec rake test\n```\n\n### Pre-built shared libraries for libui-ng\n\nDownload pre-built libui-ng shared libraries (per your current platform):\n\n```sh\nbundle exec rake vendor:auto\n```\n\nClean downloaded vendor files (keeps `vendor/{LICENSE,README}.md`):\n\n```sh\nbundle exec rake vendor:clean\n```\n\n### Using your own libui build\n\nIf you build libui-ng yourself, set `LIBUIDIR` to the directory containing the compiled `.so/.dylib/.dll` so LibUI can load it. You may also replace files under `vendor/` with your build if preferred. See [#46](https://github.com/kojix2/LibUI/issues/46#issuecomment-1041575792).\n\n### Publishing gems\n\n#### Automated Publishing\n\nPush a version tag to automatically publish platform-specific gems:\n\n```sh\ngit tag v0.2.0\ngit push origin v0.2.0\n```\n\nRequires `RUBYGEMS_API_KEY` repository secret with scoped API key.\n\n#### Manual Publishing\n\n```sh\nls vendor             # check the vendor directory\nrm -rf pkg            # remove previously built gems\nrake build_platform   # build gems\n\n# Check the contents of the gem\nfind pkg -name *.gem -exec sh -c \"echo; echo \\# {}; tar -O -f {} -x data.tar.gz | tar zt\" \\;\n\nrake release_platform # publish gems\n```\n\nWindows Ruby (x64-mingw32 or x64-mingw-ucrt)\n\n```sh\ngem install rake rubyzip\nGEM_PLATFORM=x64-mingw32 rake vendor:clean\nGEM_PLATFORM=x64-mingw32 rake vendor:auto\nGEM_PLATFORM=x64-mingw32 gem build libui.gemspec\ngem push libui-0.2.0-x64-mingw32.gem\n```\n\n### libui or libui-ng\n\n- From version 0.1.X, LibUI supports only libui-ng.\n- Version 0.0.X only supports andlabs/libui.\n\n## Contributing\n\nWould you like to contribute to LibUI?\n\n- Please feel free to send us your [pull requests](https://github.com/kojix2/libui/pulls).\n  - Small corrections, such as typo fixes, are appreciated.\n- Did you find any bugs? Submit them in the [issues](https://github.com/kojix2/LibUI/issues) section!\n\nDo you need commit rights?\n\n- If you need commit rights to my repository or want to get admin rights and take over the project, please feel free to contact @kojix2.\n- Many OSS projects become abandoned because only the founder has commit rights to the original repository.\n\nSupport libui-ng development\n\n- Contributing to the development of libui-ng is a contribution to the entire libui community, including Ruby's LibUI.\n- For example, it would be easier to release LibUI in Ruby if libui-ng could be built easily and official shared libraries could be distributed.\n\n## Acknowledgements\n\nThis project is inspired by libui-ruby.\n\n- https://github.com/jamescook/libui-ruby\n\nWhile libui-ruby uses [Ruby-FFI](https://github.com/ffi/ffi), this gem uses [Fiddle](https://github.com/ruby/fiddle).\n\n## License\n\n[MIT License](https://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'rake/testtask'\nrequire 'rbconfig'\nrequire 'fileutils'\nrequire 'zip'\nrequire 'bundler/gem_tasks'\n\nrequire_relative 'lib/libui/version'\n\n# Configuration\nCOMMIT_HASH = ENV['LIBUI_NG_COMMIT_HASH'] || 'd37a4d8'\n\n# Path constants\nBUILD_DIR = 'builddir'\nMESON_OUT_DIR = \"#{BUILD_DIR}/meson-out\"\nDEBUG_DIR = 'libui/debug'\n\n# Platform-specific configuration for shared libraries\nPLATFORM_CONFIG = {\n  'arm64-darwin' => [\n    { zip: 'macOS-arm64-shared-release.zip', src: 'builddir/meson-out/libui.dylib', dest: 'vendor/libui.arm64.dylib' }\n  ],\n  'x86_64-darwin' => [\n    { zip: 'macOS-x64-shared-release.zip', src: 'builddir/meson-out/libui.dylib', dest: 'vendor/libui.x86_64.dylib' }\n  ],\n  'x86_64-linux' => [\n    { zip: 'Ubuntu-x64-shared-release.zip', src: 'builddir/meson-out/libui.so', dest: 'vendor/libui.x86_64.so' }\n  ],\n  'aarch64-linux' => [\n    { zip: 'Ubuntu-arm64-shared-release.zip', src: 'builddir/meson-out/libui.so', dest: 'vendor/libui.aarch64.so' }\n  ],\n  'x64-mingw32' => [\n    { zip: 'Windows-x64-msvc-shared-release.zip', src: 'builddir/meson-out/libui.dll', dest: 'vendor/libui.x64.dll' }\n  ],\n  'x86-mingw32' => [\n    { zip: 'Windows-x86-msvc-shared-release.zip', src: 'builddir/meson-out/libui.dll', dest: 'vendor/libui.x86.dll' }\n  ]\n}.freeze\n\n# Test configuration\nRake::TestTask.new(:test) do |t|\n  t.libs << 'test'\n  t.libs << 'lib'\n  t.test_files = FileList['test/**/*_test.rb']\nend\n\ntask default: :test\n\n# Utility functions\ndef log_message(message)\n  puts \"[Rake] #{message}\"\nend\n\ndef detect_platform_config_key\n  # Environment variable override\n  if ENV['GEM_PLATFORM']\n    platform_str = ENV['GEM_PLATFORM']\n    return platform_str if PLATFORM_CONFIG.key?(platform_str)\n  end\n\n  current_platform = Gem::Platform.local\n\n  # Try exact match first\n  return current_platform.to_s if PLATFORM_CONFIG.key?(current_platform.to_s)\n\n  # Use RubyGems matching with custom Windows mingw support\n  PLATFORM_CONFIG.keys.find do |config_key|\n    config_platform = Gem::Platform.new(config_key)\n\n    # Standard RubyGems matching\n    if Gem::Platform.send(:match_platforms?, current_platform, [config_platform])\n      true\n    # Custom Windows mingw matching for x64 variants\n    elsif config_key == 'x64-mingw32' &&\n          current_platform.os.start_with?('mingw') &&\n          %w[x64 x86_64].include?(current_platform.cpu)\n      true\n    # Custom Windows mingw matching for x86 variants\n    elsif config_key == 'x86-mingw32' &&\n          current_platform.os.start_with?('mingw') &&\n          %w[x86 i386 i686].include?(current_platform.cpu)\n      true\n    else\n      false\n    end\n  end\nend\n\ndef url_for_libui_ng_commit(file_name)\n  \"https://github.com/kojix2/libui-ng/releases/download/commit-#{COMMIT_HASH}/#{file_name}\"\nend\n\ndef download_file(file_name, url)\n  log_message \"Running: curl -L -o #{file_name} #{url}\"\n  curl_status = system(\"curl -L -o #{file_name} #{url}\")\n  return true if curl_status && File.exist?(file_name)\n\n  warn \"Warning: Failed to download #{file_name} from #{url}\"\n  false\nend\n\ndef extract_zip_files(file_name, lib_paths)\n  return unless file_name.end_with?('.zip')\n\n  Zip::File.open(file_name) do |zip_file|\n    zip_file.each do |entry|\n      # Extract only exact matches for shared libraries\n      next unless lib_paths.include?(entry.name)\n\n      print \"Extracting #{entry.name} from #{file_name}...\"\n\n      # Preserve complete directory structure\n      target_path = entry.name\n      FileUtils.mkdir_p(File.dirname(target_path)) unless entry.directory?\n\n      unless entry.directory?\n        entry.extract(target_path) { true } # Overwrite if exists\n      end\n      puts 'done'\n    end\n  end\nend\n\ndef download_from_url(lib_paths, file_name, url)\n  log_message \"Downloading #{lib_paths} from #{url}\"\n\n  download_file(file_name, url)\n  extract_zip_files(file_name, lib_paths)\nensure\n  File.delete(file_name) if File.exist?(file_name)\nend\n\n# Mid-level functions\ndef download_libui_ng_nightly(lib_paths, file_name)\n  url = url_for_libui_ng_commit(file_name)\n  download_from_url(lib_paths, file_name, url)\nend\n\ndef download_and_place(zip_name, src, dest)\n  url = url_for_libui_ng_commit(zip_name)\n  success = download_file(zip_name, url)\n\n  unless success\n    warn \"Error: Failed to download #{zip_name}\"\n    exit 1\n  end\n\n  extract_zip_files(zip_name, [src])\n  FileUtils.mkdir_p File.dirname(dest)\n  FileUtils.cp src, dest\nensure\n  File.delete(zip_name) if File.exist?(zip_name)\nend\n\n# High-level processing functions\ndef process_config_entry(entry)\n  # Standard download and place for shared libraries only\n  download_and_place(entry[:zip], entry[:src], entry[:dest])\nend\n\ndef process_platform(platform_entries)\n  platform_entries.each do |entry|\n    process_config_entry(entry)\n  end\nend\n\n# Platform gem building\nplatforms = %w[\n  x86_64-linux\n  aarch64-linux\n  x86_64-darwin\n  arm64-darwin\n  x64-mingw32\n  x86-mingw32\n]\n\ntask :build_platform do\n  platforms.each do |platform|\n    sh 'gem', 'build', '--platform', platform\n  end\n\n  FileUtils.mkdir_p('pkg')\n  Dir['*.gem'].each do |file|\n    FileUtils.move(file, 'pkg')\n  end\nend\n\ntask :release_platform do\n  Dir[\"pkg/libui-#{LibUI::VERSION}-*.gem\"].each do |file|\n    sh 'gem', 'push', file\n  end\nend\n\n# Vendor tasks\nnamespace 'vendor' do\n  desc 'Download pre-built libraries for current platform'\n  task :auto do\n    platform_key = detect_platform_config_key\n    if platform_key && PLATFORM_CONFIG[platform_key]\n      log_message \"Processing platform: #{platform_key}\"\n      process_platform(PLATFORM_CONFIG[platform_key])\n    else\n      current_platform = Gem::Platform.local\n      log_message \"No configuration found for current platform: #{current_platform}\"\n      log_message \"Available platforms: #{PLATFORM_CONFIG.keys.join(', ')}\"\n      exit 1\n    end\n  ensure\n    # Clean up temporary directory after processing\n    Rake::Task['vendor:cleanup'].invoke\n  end\n\n  desc 'Clean vendor directory'\n  task :clean do\n    vendor_files = Dir['vendor/*'] - Dir['vendor/{LICENSE,README}.md']\n    vendor_files.each do |f|\n      FileUtils.rm_rf(f)\n      log_message \"Removed #{f}\"\n    end\n  end\n\n  # Clean up temporary directory\n  task :cleanup do\n    if Dir.exist?(BUILD_DIR)\n      FileUtils.rm_rf BUILD_DIR\n      log_message \"Cleaned up #{BUILD_DIR}\"\n    end\n  end\nend\n"
  },
  {
    "path": "examples/basic_area.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\narea    = UI.new_area(handler)\nbrush   = UI::FFI::DrawBrush.malloc\nbrush.to_ptr.free = Fiddle::RUBY_FREE\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|\n  path = UI.draw_new_path(0)\n  UI.draw_path_add_rectangle(path, 0, 0, 400, 400)\n  UI.draw_path_end(path)\n  brush.Type = 0\n  brush.R = 0.4\n  brush.G = 0.4\n  brush.B = 0.8\n  brush.A = 1.0\n  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)\n  UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)\n  UI.draw_free_path(path)\nend\n\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\n\nhandler.Draw         = handler_draw_event\nhandler.MouseEvent   = do_nothing\nhandler.MouseCrossed = do_nothing\nhandler.DragBroken   = do_nothing\nhandler.KeyEvent     = key_event\n\nbox = UI.new_vertical_box\nUI.box_set_padded(box, 1)\nUI.box_append(box, area, 1)\n\nmain_window = UI.new_window('Basic Area', 400, 400, 1)\nUI.window_set_margined(main_window, 1)\nUI.window_set_child(main_window, box)\n\nUI.window_on_closing(main_window) do\n  UI.quit\n  1\nend\nUI.control_show(main_window)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/basic_button.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nbutton = UI.new_button('Button')\n\nUI.button_on_clicked(button) do\n  UI.msg_box(main_window, 'Information', 'You clicked the button')\nend\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.window_set_child(main_window, button)\nUI.control_show(main_window)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/basic_draw_text.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\narea    = UI.new_area(handler)\n\n# Michael Ende (1929-1995)\n# The Neverending Story is a fantasy novel by German writer Michael Ende,\n# The English version, translated by Ralph Manheim, was published in 1983.\n\nTITLE = 'Michael Ende (1929-1995) The Neverending Story'\n\nstr1 = \\\n  '  At last Ygramul sensed that something was coming toward ' \\\n  'her. With the speed of lightning, she turned about, confronting ' \\\n  'Atreyu with an enormous steel-blue face. Her single eye had a ' \\\n  'vertical pupil, which stared at Atreyu with inconceivable malignancy. '\n\nstr2 = \\\n  '  A cry of fear escaped Bastian. '\n\nstr3 = \\\n  '  A cry of terror passed through the ravine and echoed from ' \\\n  'side to side. Ygramul turned her eye to left and right, to see if ' \\\n  'someone else had arrived, for that sound could not have been ' \\\n  'made by the boy who stood there as though paralyzed with ' \\\n  'horror. '\n\nstr4 = \\\n  '  Could she have heard my cry? Bastion wondered in alarm. ' \\\n  \"But that's not possible. \"\n\nstr5 = \\\n  '  And then Atreyu heard Ygramuls voice. It was very high ' \\\n  'and slightly hoarse, not at all the right kind of voice for that ' \\\n  'enormous face. Her lips did not move as she spoke. It was the ' \\\n  'buzzing of a great swarm of hornets that shaped itself into ' \\\n  'words. '\n\nstr = ''\nattr_str = UI.new_attributed_string(str)\n\ndef attr_str.append(what, color)\n  c = case color\n      when :red\n        [0.0, 0.5, 0.0, 0.7]\n      when :green\n        [0.5, 0.0, 0.25, 0.7]\n      end\n  color_attribute = UI.new_color_attribute(*c)\n  start = UI.attributed_string_len(self)\n  UI.attributed_string_append_unattributed(self, what)\n  UI.attributed_string_set_attribute(self, color_attribute, start, start + what.size)\n  UI.attributed_string_append_unattributed(self, \"\\n\\n\")\nend\n\nattr_str.append(str1, :green)\nattr_str.append(str2, :red)\nattr_str.append(str3, :green)\nattr_str.append(str4, :red)\nattr_str.append(str5, :green)\n\nGeorgia = 'Georgia'\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, adp|\n  area_draw_params = UI::FFI::AreaDrawParams.new(adp)\n  default_font = UI::FFI::FontDescriptor.malloc\n  default_font.to_ptr.free = Fiddle::RUBY_FREE\n  default_font.Family = Georgia\n  default_font.Size = 13\n  default_font.Weight = 500\n  default_font.Italic = 0\n  default_font.Stretch = 4\n  params = UI::FFI::DrawTextLayoutParams.malloc\n  params.to_ptr.free = Fiddle::RUBY_FREE\n\n  # UI.font_button_font(font_button, default_font)\n  params.String = attr_str\n  params.DefaultFont = default_font\n  params.Width = area_draw_params.AreaWidth\n  params.Align = 0\n  text_layout = UI.draw_new_text_layout(params)\n  UI.draw_text(area_draw_params.Context, text_layout, 0, 0)\n  UI.draw_free_text_layout(text_layout)\nend\n\nhandler.Draw = handler_draw_event\n\n# Assigning to local variables\n# This is intended to protect Fiddle::Closure from garbage collection.\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\nhandler.MouseEvent   = do_nothing\nhandler.MouseCrossed = do_nothing\nhandler.DragBroken   = do_nothing\nhandler.KeyEvent     = key_event\n\nbox = UI.new_vertical_box\nUI.box_set_padded(box, 1)\nUI.box_append(box, area, 1)\n\nmain_window = UI.new_window(TITLE, 600, 400, 1)\nUI.window_set_margined(main_window, 1)\nUI.window_set_child(main_window, box)\n\nUI.window_on_closing(main_window) do\n  UI.quit\n  1\nend\nUI.control_show(main_window)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/basic_entry.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('Basic Entry', 300, 50, 1)\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nhbox = UI.new_horizontal_box\nUI.window_set_child(main_window, hbox)\n\nentry = UI.new_entry\nUI.entry_on_changed(entry) do\n  puts UI.entry_text(entry).to_s\n  $stdout.flush # For Windows\nend\nUI.box_append(hbox, entry, 1)\n\nbutton = UI.new_button('Button')\nUI.button_on_clicked(button) do\n  text = UI.entry_text(entry).to_s\n  UI.msg_box(main_window, 'You entered', text)\n  0\nend\n\nUI.box_append(hbox, button, 0)\n\nUI.control_show(main_window)\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/basic_table.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('Animal sounds', 300, 200, 1)\n\nhbox = UI.new_horizontal_box\nUI.window_set_child(main_window, hbox)\n\ndata = [\n  %w[cat meow],\n  %w[dog woof],\n  %w[chicken cock-a-doodle-doo],\n  %w[horse neigh],\n  %w[cow moo]\n]\n\n# Protects BlockCaller objects from garbage collection.\n@block_callers = []\ndef rbcallback(*args, &block)\n  args << [0] if args.size == 1 # Argument types are omitted\n  block_caller = Fiddle::Closure::BlockCaller.new(*args, &block)\n  @block_callers << block_caller\n  block_caller\nend\n\nmodel_handler = UI::FFI::TableModelHandler.malloc\nmodel_handler.to_ptr.free = Fiddle::RUBY_FREE\nmodel_handler.NumColumns   = rbcallback(4) { 2 }\nmodel_handler.ColumnType   = rbcallback(4) { 0 }\nmodel_handler.NumRows      = rbcallback(4) { 5 }\nmodel_handler.CellValue    = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, column|\n  UI.new_table_value_string(data[row][column])\nend\nmodel_handler.SetCellValue = rbcallback(0, [0]) {}\n\nmodel = UI.new_table_model(model_handler)\n\ntable_params = UI::FFI::TableParams.malloc\ntable_params.to_ptr.free = Fiddle::RUBY_FREE\ntable_params.Model = model\ntable_params.RowBackgroundColorModelColumn = -1\n\ntable = UI.new_table(table_params)\nUI.table_append_text_column(table, 'Animal', 0, -1)\nUI.table_append_text_column(table, 'Description', 1, -1)\n\nUI.box_append(hbox, table, 1)\nUI.control_show(main_window)\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.main\nUI.free_table_model(model)\nUI.uninit\n"
  },
  {
    "path": "examples/basic_table_image.rb",
    "content": "# NOTE:\n# This example displays images that can be freely downloaded from the Studio Ghibli website.\n# https://www.ghibli.jp/works/red-turtle/\n# \"Please feel free to use them within the scope of common sense.\"　Toshio Suzuki (producer)\n\nrequire 'libui'\nrequire 'chunky_png'\nrequire 'open-uri'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('The Red Turtle (2016)', 310, 350, 0)\n\nhbox = UI.new_horizontal_box\nUI.window_set_child(main_window, hbox)\n\nIMAGES = Array.new(50) do |i|\n  url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', i + 1)\n  f = URI.open(url)\n  canvas = ChunkyPNG::Canvas.from_io(f)\n  f.close\n  data = canvas.to_rgba_stream\n  width = canvas.width\n  height = canvas.height\n  image = UI.new_image(width, height)\n  UI.image_append(image, data, width, height, width * 4)\n  image\nrescue StandardError => e\n  warn url, e.message\nend\n\n# Protects BlockCaller objects from garbage collection.\n@block_callers = []\ndef rbcallback(*args, &block)\n  args << [0] if args.size == 1 # Argument types are omitted\n  block_caller = Fiddle::Closure::BlockCaller.new(*args, &block)\n  @block_callers << block_caller\n  block_caller\nend\n\nmodel_handler = UI::FFI::TableModelHandler.malloc\nmodel_handler.to_ptr.free = Fiddle::RUBY_FREE\nmodel_handler.NumColumns   = rbcallback(4) { 1 }\nmodel_handler.ColumnType   = rbcallback(4) { 1 } # Image\nmodel_handler.NumRows      = rbcallback(4) { IMAGES.size }\nmodel_handler.CellValue    = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, _column|\n  UI.new_table_value_image(IMAGES[row])\nend\nmodel_handler.SetCellValue = rbcallback(0, [0]) {}\n\nmodel = UI.new_table_model(model_handler)\n\ntable_params = UI::FFI::TableParams.malloc\ntable_params.to_ptr.free = Fiddle::RUBY_FREE\ntable_params.Model = model\ntable_params.RowBackgroundColorModelColumn = -1\n\ntable = UI.new_table(table_params)\nUI.table_append_image_column(table, 'Directed by Michaël Dudok de Wit', -1)\n\nUI.box_append(hbox, table, 1)\nUI.control_show(main_window)\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.main\nUI.free_table_model(model)\nIMAGES.each { |i| UI.free_image(i) }\nUI.uninit\n"
  },
  {
    "path": "examples/basic_window.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nUI.control_show(main_window)\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/control_gallery.rb",
    "content": "require 'libui'\nUI = LibUI\n\nUI.init\n\n# File menu\nmenu = UI.new_menu('File')\nopen_menu_item = UI.menu_append_item(menu, 'Open')\nUI.menu_item_on_clicked(open_menu_item) do\n  pt = UI.open_file(MAIN_WINDOW)\n  puts pt unless pt.null?\nend\nsave_menu_item = UI.menu_append_item(menu, 'Save')\nUI.menu_item_on_clicked(save_menu_item) do\n  pt = UI.save_file(MAIN_WINDOW)\n  puts pt unless pt.null?\nend\nUI.menu_append_separator(menu)\nshould_quit_item = UI.menu_append_check_item(menu, 'Should Quit_')\nUI.menu_item_set_checked(should_quit_item, 1)\nUI.menu_append_quit_item(menu)\n# onShouldQuit callback is called when the user presses the quit menu item.\nUI.on_should_quit do\n  if UI.menu_item_checked(should_quit_item) == 1\n    puts 'Bye Bye (on_should_quit)'\n    UI.control_destroy(MAIN_WINDOW) # You have to destroy the window manually.\n    1 # UI.quit is automatically called in the C function onQuitClicked().\n  else\n    UI.msg_box(MAIN_WINDOW, 'Warning', 'Please check \"Should Quit\"')\n    0 # Don't quit\n  end\nend\n\n# Edit menu\nedit_menu = UI.new_menu('Edit')\nUI.menu_append_check_item(edit_menu, 'Checkable Item_')\nUI.menu_append_separator(edit_menu)\ndisabled_item = UI.menu_append_item(edit_menu, 'Disabled Item_')\nUI.menu_item_disable(disabled_item)\n\nUI.menu_append_preferences_item(menu)\n\n# Help menu\nhelp_menu = UI.new_menu('Help')\nUI.menu_append_item(help_menu, 'Help')\nUI.menu_append_about_item(help_menu)\n\n# Main Window\nMAIN_WINDOW = UI.new_window('Control Gallery', 600, 500, 1)\nUI.window_set_margined(MAIN_WINDOW, 1)\nUI.window_on_closing(MAIN_WINDOW) do\n  puts 'Bye Bye'\n  UI.quit\n  # return 1 to destroys the window automatically.\n  # return 0 to keep the window. (You can destroy it manually.)\n  1\nend\n\nvbox = UI.new_vertical_box\nUI.window_set_child(MAIN_WINDOW, vbox)\nhbox = UI.new_horizontal_box\nUI.box_set_padded(vbox, 1)\nUI.box_set_padded(hbox, 1)\n\nUI.box_append(vbox, hbox, 1)\n\n# Group - Basic Controls\ngroup = UI.new_group('Basic Controls')\nUI.group_set_margined(group, 1)\nUI.box_append(hbox, group, 1) # OSX bug?\n\ninner = UI.new_vertical_box\nUI.box_set_padded(inner, 1)\nUI.group_set_child(group, inner)\n\n# Button\nbutton = UI.new_button('Button')\nUI.button_on_clicked(button) do\n  UI.msg_box(MAIN_WINDOW, 'Information', 'You clicked the button')\nend\nUI.box_append(inner, button, 0)\n\n# Checkbox\ncheckbox = UI.new_checkbox('Checkbox')\nUI.checkbox_on_toggled(checkbox) do |ptr|\n  checked = UI.checkbox_checked(ptr) == 1\n  UI.window_set_title(MAIN_WINDOW, \"Checkbox is #{checked}\")\n  UI.checkbox_set_text(ptr, \"I am the checkbox (#{checked})\")\nend\nUI.box_append(inner, checkbox, 0)\n\n# Label\nUI.box_append(inner, UI.new_label('Label'), 0)\n\n# Separator\nUI.box_append(inner, UI.new_horizontal_separator, 0)\n\n# Date Picker\nUI.box_append(inner, UI.new_date_picker, 0)\n\n# Time Picker\nUI.box_append(inner, UI.new_time_picker, 0)\n\n# Date Time Picker\nUI.box_append(inner, UI.new_date_time_picker, 0)\n\n# Font Button\nUI.box_append(inner, UI.new_font_button, 0)\n\n# Color Button\nUI.box_append(inner, UI.new_color_button, 0)\n\ninner2 = UI.new_vertical_box\nUI.box_set_padded(inner2, 1)\nUI.box_append(hbox, inner2, 1)\n\n# Group - Numbers\ngroup = UI.new_group('Numbers')\nUI.group_set_margined(group, 1)\nUI.box_append(inner2, group, 0)\n\ninner = UI.new_vertical_box\nUI.box_set_padded(inner, 1)\nUI.group_set_child(group, inner)\n\n# Spinbox\nspinbox = UI.new_spinbox(0, 100)\nUI.spinbox_set_value(spinbox, 42)\nUI.spinbox_on_changed(spinbox) do |ptr|\n  puts \"New Spinbox value: #{UI.spinbox_value(ptr)}\"\nend\nUI.box_append(inner, spinbox, 0)\n\n# Slider\nslider = UI.new_slider(0, 100)\nUI.box_append(inner, slider, 0)\n\n# Progressbar\nprogressbar = UI.new_progress_bar\nUI.box_append(inner, progressbar, 0)\n\nUI.slider_on_changed(slider) do |ptr|\n  v = UI.slider_value(ptr)\n  puts \"New Slider value: #{v}\"\n  UI.progress_bar_set_value(progressbar, v)\nend\n\n# Group - Lists\ngroup = UI.new_group('Lists')\nUI.group_set_margined(group, 1)\nUI.box_append(inner2, group, 0)\n\ninner = UI.new_vertical_box\nUI.box_set_padded(inner, 1)\nUI.group_set_child(group, inner)\n\n# Combobox\ncbox = UI.new_combobox\nUI.combobox_append(cbox, 'combobox Item 1')\nUI.combobox_append(cbox, 'combobox Item 2')\nUI.combobox_append(cbox, 'combobox Item 3')\nUI.box_append(inner, cbox, 0)\nUI.combobox_on_selected(cbox) do |ptr|\n  puts \"New combobox selection: #{UI.combobox_selected(ptr)}\"\nend\n\n# Editable Combobox\nebox = UI.new_editable_combobox\nUI.editable_combobox_append(ebox, 'Editable Item 1')\nUI.editable_combobox_append(ebox, 'Editable Item 2')\nUI.editable_combobox_append(ebox, 'Editable Item 3')\nUI.box_append(inner, ebox, 0)\n\n# Radio Buttons\nrb = UI.new_radio_buttons\nUI.radio_buttons_append(rb, 'Radio Button 1')\nUI.radio_buttons_append(rb, 'Radio Button 2')\nUI.radio_buttons_append(rb, 'Radio Button 3')\nUI.box_append(inner, rb, 1)\n\n# Tab\ntab = UI.new_tab\nhbox1 = UI.new_horizontal_box\nhbox2 = UI.new_horizontal_box\nUI.tab_append(tab, 'Page 1', hbox1)\nUI.tab_append(tab, 'Page 2', hbox2)\nUI.tab_append(tab, 'Page 3', UI.new_horizontal_box)\nUI.box_append(inner2, tab, 1)\n\n# Text Entry\ntext_entry = UI.new_entry\nUI.entry_set_text text_entry, 'Please enter your feelings'\nUI.entry_on_changed(text_entry) do |ptr|\n  puts \"Current textbox data: '#{UI.entry_text(ptr)}'\"\nend\nUI.box_append(hbox1, text_entry, 1)\n\nUI.control_show(MAIN_WINDOW)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/date_time_picker.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nvbox = UI.new_vertical_box\n\ndate_time_picker = UI.new_date_time_picker\n\ntime = UI::FFI::TM.malloc\ntime.to_ptr.free = Fiddle::RUBY_FREE\n\nUI.date_time_picker_on_changed(date_time_picker) do\n  UI.date_time_picker_time(date_time_picker, time)\n  p sec: time.tm_sec,\n    min: time.tm_min,\n    hour: time.tm_hour,\n    mday: time.tm_mday,\n    mon: time.tm_mon,\n    year: time.tm_year,\n    wday: time.tm_wday,\n    yday: time.tm_yday,\n    isdst: time.tm_isdst\nend\nUI.box_append(vbox, date_time_picker, 1)\n\nmain_window = UI.new_window('Date Time Pickers', 300, 200, 1)\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\nUI.window_set_child(main_window, vbox)\nUI.control_show(main_window)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/draw_text.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\ndef append_with_attribute(attr_str, what, attr1, attr2)\n  start_pos = UI.attributed_string_len(attr_str)\n  end_pos = start_pos + what.length\n  UI.attributed_string_append_unattributed(attr_str, what)\n  UI.attributed_string_set_attribute(attr_str, attr1, start_pos, end_pos)\n  UI.attributed_string_set_attribute(attr_str, attr2, start_pos, end_pos) if attr2\nend\n\ndef make_attribute_string\n  attr_str = UI.new_attributed_string(\n    \"Drawing strings with libui is done with the uiAttributedString and uiDrawTextLayout objects.\\n\" \\\n     'uiAttributedString lets you have a variety of attributes: '\n  )\n\n  attr1 = UI.new_family_attribute('Courier New')\n  append_with_attribute(attr_str, 'font family', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_size_attribute(18)\n  append_with_attribute(attr_str, 'font size', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_weight_attribute(UI::TextWeightBold)\n  append_with_attribute(attr_str, 'font weight', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_italic_attribute(UI::TextItalicItalic)\n  append_with_attribute(attr_str, 'font italicness', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_stretch_attribute(UI::TextStretchCondensed)\n  append_with_attribute(attr_str, 'font stretch', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_color_attribute(0.75, 0.25, 0.5, 0.75)\n  append_with_attribute(attr_str, 'text color', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_background_attribute(0.5, 0.5, 0.25, 0.5)\n  append_with_attribute(attr_str, 'text background color', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  attr1 = UI.new_underline_attribute(UI::UnderlineSingle)\n  append_with_attribute(attr_str, 'underline style', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ', ')\n\n  UI.attributed_string_append_unattributed(attr_str, 'and ')\n  attr1 = UI.new_underline_attribute(UI::UnderlineDouble)\n  attr2 = UI.new_underline_color_attribute(UI::UnderlineColorCustom, 1.0, 0.0, 0.5, 1.0)\n  append_with_attribute(attr_str, 'underline color', attr1, attr2)\n  UI.attributed_string_append_unattributed(attr_str, '. ')\n\n  UI.attributed_string_append_unattributed(attr_str, 'Furthermore, there are attributes allowing for ')\n  attr1 = UI.new_underline_attribute(UI::UnderlineSuggestion)\n  attr2 = UI.new_underline_color_attribute(UI::UnderlineColorSpelling, 0, 0, 0, 0)\n  append_with_attribute(attr_str, 'special underlines for indicating spelling errors', attr1, attr2)\n  UI.attributed_string_append_unattributed(attr_str, ' (and other types of errors) ')\n\n  UI.attributed_string_append_unattributed(attr_str,\n                                           'and control over OpenType features such as ligatures (for instance, ')\n  otf = UI.new_open_type_features\n  UI.open_type_features_add(otf, 'l', 'i', 'g', 'a', 0)\n  attr1 = UI.new_features_attribute(otf)\n  append_with_attribute(attr_str, 'afford', attr1, nil)\n  UI.attributed_string_append_unattributed(attr_str, ' vs. ')\n  UI.open_type_features_add(otf, 'l', 'i', 'g', 'a', 1)\n  attr1 = UI.new_features_attribute(otf)\n  append_with_attribute(attr_str, 'afford', attr1, nil)\n  UI.free_open_type_features(otf)\n  UI.attributed_string_append_unattributed(attr_str, \").\\n\")\n\n  UI.attributed_string_append_unattributed(attr_str,\n                                           'Use the controls opposite to the text to control properties of the text.')\n  attr_str\nend\n\ndef on_font_changed(area)\n  UI.area_queue_redraw_all(area)\nend\n\ndef on_combobox_selected(area)\n  UI.area_queue_redraw_all(area)\nend\n\ndef draw_event(adp, attr_str, font_button, alignment)\n  area_draw_params = UI::FFI::AreaDrawParams.new(adp)\n  default_font = UI::FFI::FontDescriptor.malloc\n  default_font.to_ptr.free = Fiddle::RUBY_FREE\n  params = UI::FFI::DrawTextLayoutParams.malloc\n  params.to_ptr.free = Fiddle::RUBY_FREE\n\n  params.String = attr_str\n  UI.font_button_font(font_button, default_font)\n  params.DefaultFont = default_font\n  params.Width = area_draw_params.AreaWidth\n  params.Align = UI.combobox_selected(alignment)\n  text_layout = UI.draw_new_text_layout(params)\n  UI.draw_text(area_draw_params.Context, text_layout, 0, 0)\n  UI.draw_free_text_layout(text_layout)\n  UI.free_font_button_font(default_font)\nend\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _area, adp|\n  draw_event(adp, @attr_str, @font_button, @alignment)\nend\n\nhandler.Draw = handler_draw_event\n\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\nhandler.MouseEvent   = do_nothing\nhandler.MouseCrossed = do_nothing\nhandler.DragBroken   = do_nothing\nhandler.KeyEvent     = key_event\n\n@attr_str = make_attribute_string\n\nmain_window = UI.new_window('Text-Drawing Example', 640, 480, 1)\nUI.window_set_margined(main_window, 1)\nUI.window_on_closing(main_window) do\n  UI.quit\n  1\nend\n\nhbox = UI.new_horizontal_box\nUI.box_set_padded(hbox, 1)\nUI.window_set_child(main_window, hbox)\n\nvbox = UI.new_vertical_box\nUI.box_set_padded(vbox, 1)\nUI.box_append(hbox, vbox, 0)\n\n@font_button = UI.new_font_button\nUI.font_button_on_changed(@font_button) { on_font_changed(@area) }\nUI.box_append(vbox, @font_button, 0)\n\nform = UI.new_form\nUI.form_set_padded(form, 1)\nUI.box_append(vbox, form, 0)\n\n@alignment = UI.new_combobox\nUI.combobox_append(@alignment, 'Left')\nUI.combobox_append(@alignment, 'Center')\nUI.combobox_append(@alignment, 'Right')\nUI.combobox_set_selected(@alignment, 0)\nUI.combobox_on_selected(@alignment) { on_combobox_selected(@area) }\nUI.form_append(form, 'Alignment', @alignment, 0)\n\n@area = UI.new_area(handler)\nUI.box_append(hbox, @area, 1)\n\nUI.control_show(main_window)\nUI.main\n\nUI.free_attributed_string(@attr_str)\nUI.uninit\n"
  },
  {
    "path": "examples/font_button.rb",
    "content": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nfont_button = UI.new_font_button\nfont_descriptor = UI::FFI::FontDescriptor.malloc\nfont_descriptor.to_ptr.free = Fiddle::RUBY_FREE\nUI.font_button_on_changed(font_button) do\n  UI.font_button_font(font_button, font_descriptor)\n  p family: font_descriptor.Family.to_s,\n    size: font_descriptor.Size,\n    weight: font_descriptor.Weight,\n    italic: font_descriptor.Italic,\n    stretch: font_descriptor.Stretch\nend\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.window_set_child(main_window, font_button)\nUI.control_show(main_window)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/gpt2_notepad.rb",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'libui'\nrequire 'onnxruntime'\nrequire 'blingfire'\nrequire 'numo/narray'\n\n# GPT-2 model\n# Transformer-based language model for text generation.\n# https://github.com/onnx/models/tree/main/text/machine_comprehension/gpt-2\n\nDir.chdir(__dir__) do\n  %w[\n    https://github.com/microsoft/BlingFire/raw/master/dist-pypi/blingfire/gpt2.bin\n    https://github.com/microsoft/BlingFire/raw/master/dist-pypi/blingfire/gpt2.i2w\n    https://github.com/onnx/models/raw/main/text/machine_comprehension/gpt-2/model/gpt2-lm-head-10.onnx\n  ].each do |url|\n    fname = File.basename(url)\n    next if File.exist?(fname)\n\n    print \"Downloading #{fname}...\"\n    require 'open-uri'\n    File.binwrite(fname, URI.open(url).read)\n    puts 'done'\n  end\n  @encoder = BlingFire.load_model('gpt2.bin')\n  @decoder = BlingFire.load_model('gpt2.i2w')\n  @model = OnnxRuntime::Model.new('gpt2-lm-head-10.onnx')\nend\n\ndef softmax(y)\n  Numo::NMath.exp(y) / Numo::NMath.exp(y).sum(1, keepdims: true)\nend\n\ndef predict(a, prob: true)\n  outputs = @model.predict({ input1: [[a]] })\n  logits = Numo::DFloat.cast(outputs['output1'][0])\n  logits = logits[true, -1, true]\n  return logits.argmax unless prob\n\n  log_probs = softmax(logits)\n  cum_probs = log_probs.cumsum(1)\n  r = rand(0..cum_probs[-1]) # 0..1\n  (cum_probs < r).count\nend\n\ndef predict_text(s, max = 30)\n  a = @encoder.text_to_ids(s)\n  max.times do\n    id = predict(a)\n    a << id\n    break if id == 13 # .\n  end\n  @decoder.ids_to_text(a)\nend\n\n# GUI\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('GPT-2 Notepad', 500, 300, 1)\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nhbox = UI.new_vertical_box\nUI.box_set_padded(hbox, 1)\nUI.window_set_child(main_window, hbox)\n\nbbox = UI.new_horizontal_box\nUI.box_append(hbox, bbox, 0)\nclear_button = UI.new_button('Clear')\nwrite_button = UI.new_button('Continue the sentence(s)')\nUI.box_append(bbox, clear_button, 1)\nUI.box_append(bbox, write_button, 1)\n\nentry = UI.new_multiline_entry\nUI.box_append(hbox, entry, 1)\n\nUI.button_on_clicked(clear_button) do\n  UI.multiline_entry_set_text(entry, '')\nend\n\nUI.button_on_clicked(write_button) do\n  s = UI.multiline_entry_text(entry).to_s\n  if s.empty?\n    UI.msg_box(main_window, 'Empty!', 'Please enter some text first.')\n  else\n    s2 = predict_text(s)\n    UI.multiline_entry_set_text(entry, s2)\n  end\nend\n\nUI.control_show(main_window)\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/histogram.rb",
    "content": "# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb\n\nrequire 'libui'\n\nUI = LibUI\n\nX_OFF_LEFT   = 20\nY_OFF_TOP    = 20\nX_OFF_RIGHT  = 20\nY_OFF_BOTTOM = 20\nPOINT_RADIUS = 5\n\ninit         = UI.init\nhandler      = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\nhistogram    = UI.new_area(handler)\nbrush        = UI::FFI::DrawBrush.malloc\nbrush.to_ptr.free = Fiddle::RUBY_FREE\ncolor_button = UI.new_color_button\nblue         = 0x1E90FF\ndatapoints   = []\n\ndef graph_size(area_width, area_height)\n  graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT\n  graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM\n  [graph_width, graph_height]\nend\n\nmatrix = UI::FFI::DrawMatrix.malloc\nmatrix.to_ptr.free = Fiddle::RUBY_FREE\n\ndef point_locations(datapoints, width, height)\n  xincr = width / 9.0 # 10 - 1 to make the last point be at the end\n  yincr = height / 100.0\n\n  data = []\n  datapoints.each_with_index do |dp, i|\n    val = 100 - UI.spinbox_value(dp)\n    data << [xincr * i, yincr * val]\n  end\n\n  data\nend\n\ndef construct_graph(datapoints, width, height, should_extend)\n  locations = point_locations(datapoints, width, height)\n  path = UI.draw_new_path(0) # winding\n  first_location = locations[0] # x and y\n  UI.draw_path_new_figure(path, first_location[0], first_location[1])\n  locations.each do |loc|\n    UI.draw_path_line_to(path, loc[0], loc[1])\n  end\n\n  if should_extend\n    UI.draw_path_line_to(path, width, height)\n    UI.draw_path_line_to(path, 0, height)\n    UI.draw_path_close_figure(path)\n  end\n\n  UI.draw_path_end(path)\n\n  path\nend\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(\n  0, [1, 1, 1]\n) do |_area_handler, _area, area_draw_params|\n  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)\n  path = UI.draw_new_path(0) # winding\n  UI.draw_path_add_rectangle(path, 0, 0, area_draw_params.AreaWidth, area_draw_params.AreaHeight)\n  UI.draw_path_end(path)\n  set_solid_brush(brush, 0xFFFFFF, 1.0) # white\n  UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)\n  UI.draw_free_path(path)\n  dsp = UI::FFI::DrawStrokeParams.malloc\n  dsp.to_ptr.free = Fiddle::RUBY_FREE\n  dsp.Cap = 0 # flat\n  dsp.Join = 0 # miter\n  dsp.Thickness = 2\n  dsp.MiterLimit = 10 # DEFAULT_MITER_LIMIT\n  dashes = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)\n  dsp.Dashes = dashes\n  dsp.NumDashes = 0\n  dsp.DashPhase = 0\n\n  # draw axes\n  set_solid_brush(brush, 0x000000, 1.0) # black\n  graph_width, graph_height = *graph_size(area_draw_params.AreaWidth, area_draw_params.AreaHeight)\n\n  path = UI.draw_new_path(0) # winding\n  UI.draw_path_new_figure(path, X_OFF_LEFT, Y_OFF_TOP)\n  UI.draw_path_line_to(path, X_OFF_LEFT, Y_OFF_TOP + graph_height)\n  UI.draw_path_line_to(path, X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)\n  UI.draw_path_end(path)\n  UI.draw_stroke(area_draw_params.Context, path, brush, dsp)\n  UI.draw_free_path(path)\n\n  # now transform the coordinate space so (0, 0) is the top-left corner of the graph\n  UI.draw_matrix_set_identity(matrix)\n  UI.draw_matrix_translate(matrix, X_OFF_LEFT, Y_OFF_TOP)\n  UI.draw_transform(area_draw_params.Context, matrix)\n\n  # now get the color for the graph itself and set up the brush\n  # uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA)\n  graph_r = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\n  graph_g = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\n  graph_b = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\n  graph_a = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\n\n  UI.color_button_color(color_button, graph_r, graph_g, graph_b, graph_a)\n  brush.Type = 0 # solid\n  brush.R = graph_r[0, 8].unpack1('d')\n  brush.G = graph_g[0, 8].unpack1('d')\n  brush.B = graph_b[0, 8].unpack1('d')\n\n  # now create the fill for the graph below the graph line\n  path = construct_graph(datapoints, graph_width, graph_height, true)\n  brush.A = graph_a[0, 8].unpack1('d') / 2.0\n  UI.draw_fill(area_draw_params.Context, path, brush)\n  UI.draw_free_path(path)\n\n  # now draw the histogram line\n  path = construct_graph(datapoints, graph_width, graph_height, false)\n  brush.A = graph_a[0, 8].unpack1('d')\n  UI.draw_stroke(area_draw_params.Context, path, brush, dsp)\n  UI.draw_free_path(path)\nend\n\n# Assigning to local variables\n# This is intended to protect Fiddle::Closure from garbage collection.\n# See https://github.com/kojix2/LibUI/issues/8\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\nhandler.Draw         = handler_draw_event\nhandler.MouseEvent   = do_nothing\nhandler.MouseCrossed = do_nothing\nhandler.DragBroken   = do_nothing\nhandler.KeyEvent     = key_event\n\nUI.freeInitError(init) unless init.nil?\n\nhbox = UI.new_horizontal_box\nUI.box_set_padded(hbox, 1)\n\nvbox = UI.new_vertical_box\nUI.box_set_padded(vbox, 1)\nUI.box_append(hbox, vbox, 0)\nUI.box_append(hbox, histogram, 1)\n\ndatapoints = Array.new(10) do\n  UI.new_spinbox(0, 100).tap do |datapoint|\n    UI.spinbox_set_value(datapoint, Random.new.rand(90))\n    UI.spinbox_on_changed(datapoint) do\n      UI.area_queue_redraw_all(histogram)\n    end\n    UI.box_append(vbox, datapoint, 0)\n  end\nend\n\ndef set_solid_brush(brush, color, alpha)\n  brush.Type = 0 # solid\n  brush.R = ((color >> 16) & 0xFF) / 255.0\n  brush.G = ((color >> 8) & 0xFF) / 255.0\n  brush.B = (color & 0xFF) / 255.0\n  brush.A = alpha\n  brush\nend\n\nset_solid_brush(brush, blue, 1.0)\nUI.color_button_set_color(color_button, brush.R, brush.G, brush.B, brush.A)\n\nUI.color_button_on_changed(color_button) do\n  UI.area_queue_redraw_all(histogram)\nend\n\nUI.box_append(vbox, color_button, 0)\n\nMAIN_WINDOW = UI.new_window('histogram example', 640, 480, 1)\nUI.window_set_margined(MAIN_WINDOW, 1)\nUI.window_set_child(MAIN_WINDOW, hbox)\n\nUI.window_on_closing(MAIN_WINDOW) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nUI.control_show(MAIN_WINDOW)\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/midi_player.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire 'libui'\nUI = LibUI\n\nclass TinyMidiPlayer\n  VERSION = '0.0.1'\n\n  def initialize\n    UI.init\n    @pid = nil\n    @music_directory = File.expand_path(ARGV[0] || '~/Music/')\n    @midi_files      = Dir.glob(File.join(@music_directory, '**/*.mid'))\n                          .sort_by { |path| File.basename(path) }\n    at_exit { stop_midi }\n    create_gui\n  end\n\n  def stop_midi\n    if @pid\n      Process.kill(:SIGKILL, @pid) if @th.alive?\n      @pid = nil\n    end\n  end\n\n  def play_midi\n    stop_midi\n    if @pid.nil? && @selected_file\n      begin\n        @pid = spawn \"timidity #{@selected_file}\"\n        @th = Process.detach @pid\n      rescue Errno::ENOENT\n        warn 'Timidty++ not found. Please install Timidity++.'\n        warn 'https://sourceforge.net/projects/timidity/'\n      end\n    end\n  end\n\n  def show_version(main_window)\n    UI.msg_box(main_window,\n               'Tiny Midi Player',\n               \"Written in Ruby\\n\" \\\n               \"https://github.com/kojix2/libui\\n\" \\\n               \"Version #{VERSION}\")\n  end\n\n  def create_gui\n    # loop_menu = UI.new_menu('Repeat')\n    # items = %w[Off One].map do |item_name|\n    #   item = UI.menu_append_check_item(loop_menu, item_name)\n    # end\n    # items.each_with_index do |item, idx|\n    #   UI.menu_item_on_clicked(item) do\n    #     @repeat = idx\n    #     (items - [item]).each do |i|\n    #       UI.menu_item_set_checked(i, 0)\n    #     end\n    #     0\n    #   end\n    # end\n\n    help_menu = UI.new_menu('Help')\n    version_item = UI.menu_append_item(help_menu, 'Version')\n\n    UI.new_window('Tiny Midi Player', 200, 50, 1).tap do |main_window|\n      UI.menu_item_on_clicked(version_item) { show_version(main_window) }\n\n      UI.window_on_closing(main_window) do\n        UI.quit\n        1\n      end\n\n      UI.new_horizontal_box.tap do |hbox|\n        UI.new_vertical_box.tap do |vbox|\n          UI.new_button('▶').tap do |button1|\n            UI.button_on_clicked(button1) { play_midi }\n            UI.box_append(vbox, button1, 1)\n          end\n          UI.new_button('■').tap do |button2|\n            UI.button_on_clicked(button2) { stop_midi }\n            UI.box_append(vbox, button2, 1)\n          end\n          UI.box_append(hbox, vbox, 0)\n        end\n        UI.window_set_child(main_window, hbox)\n\n        UI.new_combobox.tap do |cbox|\n          @midi_files.each do |path|\n            name = File.basename(path)\n            UI.combobox_append(cbox, name)\n          end\n          UI.combobox_on_selected(cbox) do |ptr|\n            @selected_file = @midi_files[UI.combobox_selected(ptr)]\n            play_midi if @th&.alive?\n            0\n          end\n          UI.box_append(hbox, cbox, 1)\n        end\n      end\n      UI.control_show(main_window)\n    end\n    UI.main\n    UI.uninit\n  end\nend\n\nTinyMidiPlayer.new\n"
  },
  {
    "path": "examples/simple_notepad.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('Notepad', 500, 300, 1)\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.quit\n  1\nend\n\nvbox = UI.new_vertical_box\nUI.window_set_child(main_window, vbox)\n\nentry = UI.new_non_wrapping_multiline_entry\nUI.box_append(vbox, entry, 1)\n\nUI.control_show(main_window)\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/spectrum.rb",
    "content": "#!/usr/bin/env ruby\n\n# Please play your favorite music or video on your computer\n# when running this spectrum example.\n\nrequire 'libui'\nrequire 'ffi-portaudio'  # https://github.com/nanki/ffi-portaudio\nrequire 'numo/pocketfft' # https://github.com/yoshoku/numo-pocketfft\n\n# ---------------------------------------------------------------------------- #\n\nclass FFTStream < FFI::PortAudio::Stream\n  def process(input, _output, frame_count, _time_info, _status_flags, _user_data)\n    i = Numo::Int16.cast(input.read_array_of_int16(frame_count))\n    @spec = (Numo::Pocketfft.rfft(i)[0..511].abs / 1000.0).to_a\n    :paContinue\n  end\n\n  def spec\n    @spec || [0] * 512\n  end\nend\n\nFFI::PortAudio::API.Pa_Initialize\n\ninput = FFI::PortAudio::API::PaStreamParameters.new\ninput[:device] = FFI::PortAudio::API.Pa_GetDefaultInputDevice\ninput[:channelCount] = 1\ninput[:sampleFormat] = FFI::PortAudio::API::Int16\ninput[:suggestedLatency] = 0\ninput[:hostApiSpecificStreamInfo] = nil\nstream = FFTStream.new\nstream.open(input, nil, 44_100, 1024)\nstream.start\n\n# ---------------------------------------------------------------------------- #\n\nUI = LibUI\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\narea    = UI.new_area(handler)\n\nbrush = UI::FFI::DrawBrush.malloc.tap do |b|\n  b.Type = 0\n  b.R = 0.9\n  b.G = 0.2\n  b.B = 0.6\n  b.A = 1.0\nend\n\ndashes = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)\nstroke_params = UI::FFI::DrawStrokeParams.malloc.tap do |sp|\n  sp.Cap = UI::DrawLineCapFlat\n  sp.Join = UI::DrawLineJoinMiter\n  sp.MiterLimit = 10\n  sp.Dashes = dashes\n  sp.NumDashes = 0\n  sp.DashPhase = 0\n  sp.Thickness = 1.0\nend\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|\n  UI.draw_new_path(UI::DrawFillModeWinding).then do |path|\n    stream.spec.each.with_index do |i, j|\n      UI.draw_path_new_figure(path, 10 + j, 121)\n      UI.draw_path_line_to(path, 10 + j, 120 - [i, 120].min)\n    end\n    UI.draw_path_end(path)\n\n    area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)\n    UI.draw_stroke(area_draw_params.Context, path, brush.to_ptr, stroke_params)\n    UI.draw_free_path(path)\n  end\nend\n\nhandler.Draw = handler_draw_event\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\nhandler.MouseEvent   = do_nothing\nhandler.MouseCrossed = do_nothing\nhandler.DragBroken   = do_nothing\nhandler.KeyEvent     = key_event\n\nbox = UI.new_vertical_box\nUI.box_set_padded(box, 1)\nUI.box_append(box, area, 1)\n\nmain_window = UI.new_window('SPECTRUM', 560, 150, 1)\nUI.window_set_margined(main_window, 1)\nUI.window_set_child(main_window, box)\n\nUI.window_on_closing(main_window) do\n  UI.quit\n  stream.close\n  ::FFI::PortAudio::API.Pa_Terminate\n  1\nend\nUI.control_show(main_window)\n\nUI.queue_main do\n  UI.timer(100) do\n    UI.area_queue_redraw_all(area)\n    1\n  end\nend\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples/turing_pattern.rb",
    "content": "#!/usr/bin/env ruby\n\n# https://en.wikipedia.org/wiki/Turing_pattern\n#\n# > The Turing pattern is a concept introduced by English mathematician Alan Turing\n# in a 1952 paper titled \"The Chemical Basis of Morphogenesis\" which describes\n# how patterns in nature, such as stripes and spots, can arise naturally and\n# autonomously from a homogeneous, uniform state.\n#\n# > In his classic paper, Turing examined the behaviour of a system in which two\n# diffusible substances interact with each other, and found that such a system\n# is able to generate a spatially periodic pattern even from a random or almost\n# uniform initial condition. Turing hypothesized that the resulting wavelike\n# patterns are the chemical basis of morphogenesis.\n\n# Studies of Turing pattern formation in zebrafish skin (2021)\n# https://doi.org/10.1098/rsta.2020.0274\n#\n# > An unexpected discovery was made regarding the mechanism responsible for\n# cell–cell interactions. When isolated melanophores and xanthophores were\n# co-cultured in vitro, the cells were found to repel each other, but the signal\n# was transmitted by short protrusion extending from the xanthophores. Regarding\n# distant interactions, Hamada et al. found that long cell protrusions extending\n# from the melanophores were involved (figure 3a–c). Interestingly, neither\n# short- nor long-range interactions are mediated by ‘diffusion’. Therefore,\n# strictly speaking, pattern formation does not occur by a reaction–diffusion\n# system. However, mathematically, it can be considered as a homologous\n# phenomenon, because the protrusions with two different lengths mimic the role\n# of two different molecules with different diffusion coefficients in a\n# reaction–diffusion system.\n#\n# > What established the pattern is not the shading of the chemicals, but the\n# distribution of cells that behave autonomously. Another major difference is\n# that long-distance signalling is conveyed directly by cell protrusions rather\n# than by molecular diffusion.\n\n# Perhaps Turing and his contemporaries were not right about the precise\n# mechanism of pattern formation. It may be that the propagation of waves of\n# cell signaling through direct contact, rather than the diffusion of morphogens,\n# forms the pattern of the organism.\n# However, the patterns generated by the diffusion reaction system are very\n# impressive...\n\nWAIT_TIME = 200 # Increase this number if you cannot redraw in time.\nNUM_STEPS = 50  # Number of steps before being redrawn.\n\nrequire 'libui'\n# A matrix calculation library for Ruby like NumPy.\nrequire 'numo/narray'\n\n# generate png image from narray(faster)\n# require 'magro'\nrequire 'chunky_png'\n\nmodule GrayScott\n  # shorthand\n  SFloat = Numo::SFloat\n  UInt8  = Numo::UInt8\n\n  class SFloat\n    alias _ inplace\n  end\n\n  module Utils\n    # To avoid mistakes\n    A = (1..-1).freeze\n    B = (0..-2).freeze\n    T = true\n\n    def self.laplacian2d(uv, dx)\n      l_uv = uv.new_zeros\n      l_uv[A, T]._ + uv[B, T]\n      l_uv[T, A]._ + uv[T, B]\n\n      l_uv[0, T]._ + uv[-1, T]\n      l_uv[T, 0]._ + uv[T, -1]\n\n      l_uv[B, T]._ + uv[A, T]\n      l_uv[T, B]._ + uv[T, A]\n\n      l_uv[-1, T]._ + uv[0, T]\n      l_uv[T, -1]._ + uv[T, 0]\n\n      l_uv._ - (uv * 4)\n      l_uv._ / (dx * dx)\n      l_uv\n    end\n  end\n\n  # Gray-Scott model\n  class Model\n    Dx = 0.01\n\n    # Delta t is the change in time for each iteration\n    Dt = 1\n\n    # diffusion rate for U\n    Du = 2e-5\n\n    # diffusion rate for V\n    Dv = 1e-5\n\n    attr_accessor :f, :k, :u, :v\n    attr_reader :width, :height\n\n    def initialize(width: 256, height: 256)\n      @width  = width\n      @height = height\n\n      # Feed rate\n      @f = 0.04\n\n      # Kill rate\n      @k = 0.06\n\n      # concentration of U\n      @u = SFloat.ones height, width\n\n      # concentration of V\n      @v = SFloat.zeros height, width\n    end\n\n    def clear\n      u.fill 1.0\n      v.fill 0.0\n    end\n\n    def step\n      l_u = Utils.laplacian2d u, Dx\n      l_v = Utils.laplacian2d v, Dx\n\n      uvv = u * v * v\n      dudt = Du * l_u - uvv + f * (1.0 - u)\n      dvdt = Dv * l_v + uvv - (f + k) * v\n      u._ + (Dt * dudt)\n      v._ + (Dt * dvdt)\n\n      # clip is better.\n      @u[@u.lt 0.00001] = 0.00001\n      @u[@u.gt 1] = 1\n      @v[@v.lt 0.00001] = 0.00001\n      @v[@v.gt 1] = 1\n    end\n  end\n\n  module Color\n    module_function\n\n    def colorize(ar, color_type)\n      case color_type\n      when 'colorful'\n        hsv2rgb(ar)\n      when 'reverse-colorful'\n        hsv2rgb(1.0 - ar)\n      when 'red'\n        red(ar)\n      when 'green'\n        green(ar)\n      when 'blue'\n        blue(ar)\n      when 'reverse-red'\n        reverse_red(ar)\n      when 'reverse-green'\n        reverse_green(ar)\n      when 'reverse-blue'\n        reverse_blue(ar)\n      when 'grayscale'\n        grayscale(ar)\n      end\n    end\n\n    # speed\n    def uInt8_dstack(ar)\n      x = UInt8.zeros(*ar[0].shape, 3)\n      x[true, true, 0] = ar[0]\n      x[true, true, 1] = ar[1]\n      x[true, true, 2] = ar[2]\n      x\n    end\n\n    def hsv2rgb(h)\n      i = UInt8.cast(h * 6)\n      f = (h * 6.0) - i\n      p = UInt8.zeros(*h.shape)\n      v = UInt8.new(*h.shape).fill 255\n      q = (1.0 - f) * 256\n      t = f * 256\n      rgb = UInt8.zeros(*h.shape, 3)\n      t = UInt8.cast(t)\n      i = uInt8_dstack([i, i, i])\n      rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]\n      rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]\n      rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]\n      rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]\n      rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]\n      rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]\n      rgb\n    end\n\n    def red(ar)\n      uint8_zeros_256(0, ar)\n    end\n\n    def green(ar)\n      uint8_zeros_256(1, ar)\n    end\n\n    def blue(ar)\n      uint8_zeros_256(2, ar)\n    end\n\n    def reverse_red(ar)\n      uint8_zeros_256(0, (1.0 - ar))\n    end\n\n    def reverse_green(ar)\n      uint8_zeros_256(1, (1.0 - ar))\n    end\n\n    def reverse_blue(ar)\n      uint8_zeros_256(2, (1.0 - ar))\n    end\n\n    def grayscale(ar)\n      d = ar * 255\n      uInt8_dstack([d, d, d])\n    end\n\n    def uint8_zeros_256(ch, ar)\n      d = UInt8.zeros(*ar.shape, 3)\n      d[true, true, ch] = UInt8.cast(ar * 256)\n      d\n    end\n  end\nend\n\nUI = LibUI\nUI.init\n\nwidth        = 100\nheight       = 100\nratio        = 2\npix_size     = 4\npointer_size = 5\nmodel_width  = width * ratio\nmodel_height = height * ratio\n\n@model = GrayScott::Model.new(width: model_width, height: model_height)\n@model.clear\n@color_type = 'colorful'\n@uv = 'v'\n@running = false\n\n# menu File\n\nmenu_file = UI.new_menu('File')\nmenu_file_new = UI.menu_append_item(menu_file, 'New')\nUI.menu_item_on_clicked(menu_file_new) do\n  @model.clear\n  UI.area_queue_redraw_all(@area)\nend\n\n# menu File Open\n\nmenu_file_open = UI.menu_append_item(menu_file, 'Open Model')\nUI.menu_item_on_clicked(menu_file_open) do\n  pt = UI.open_file(@main_window)\n  unless pt.null?\n    file_path = pt.to_s\n    begin\n      model = Marshal.load(File.binread(file_path))\n    rescue StandardError => e\n      UI.msg_box_error(\n        @main_window, '⚠️ Error',\n        \"Failed to open file.\\n\" \\\n        \"#{file_path}\\n\" \\\n        \"#{e.message}\"\n      )\n      next\n    end\n    if model.width == @model.width &&\n       model.height == @model.height\n      @model = model\n      UI.area_queue_redraw_all(@area)\n    else\n      UI.msg_box_error(\n        @main_window, '⚠️ Error',\n        \"File shape is different.\\n\" \\\n        \"file: width #{model.width} height #{model.height}\\n\" \\\n        \"model: width #{@model.width} height #{@model.height}\"\n      )\n    end\n  end\nend\n\n@save_file_path = nil\n\nsave_model_as_proc = proc do\n  pt = UI.save_file(@main_window)\n  unless pt.null?\n    @save_file_path = pt.to_s\n    Marshal.dump(@model, File.open(@save_file_path, 'wb'))\n  end\nend\n\n# menu File Save\n\nmenu_file_save = UI.menu_append_item(menu_file, 'Save Model')\nUI.menu_item_on_clicked(menu_file_save) do\n  if @save_file_path\n    Marshal.dump(@model, File.open(@save_file_path, 'wb'))\n  else\n    save_model_as_proc.call\n  end\nend\n\n# menu File Save As\n\nmenu_file_save_as = UI.menu_append_item(menu_file, 'Save Model As')\nUI.menu_item_on_clicked(menu_file_save_as, save_model_as_proc)\n\n# menu File Quit\n\nmenu_file_quit = UI.menu_append_quit_item(menu_file)\nUI.on_should_quit do\n  UI.control_destroy(@main_window)\n  1\nend\n\n# menu Help\n\nmenu_help = UI.new_menu('Help')\nmenu_help_about = UI.menu_append_item(menu_help, 'About')\nUI.menu_item_on_clicked(menu_help_about) do\n  UI.msg_box(\n    @main_window,\n    '🦓 Turing Pattern 🐠',\n    <<~HELP_MESSAGE\n      How to use\n\n      (1) Click on the red area several times. Blue dots will show up.\n      (2) Press the \"▶ START\" button.\n      (3) Try out different parameters.\n\n      Written in Ruby\n      https://github.com/kojix2/LibUI\n    HELP_MESSAGE\n  )\nend\n\n# area\n\narea_handler = UI::FFI::AreaHandler.malloc\narea_handler.to_ptr.free = Fiddle::RUBY_FREE\n@area = UI.new_area(area_handler)\nbrush = UI::FFI::DrawBrush.malloc\nbrush.to_ptr.free = Fiddle::RUBY_FREE\n\nhandler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|\n  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)\n  rgb = (GrayScott::Color.colorize(@model.public_send(@uv.to_sym), @color_type)\n                         .cast_to(Numo::SFloat)\n                         .inplace / 255.0)\n        .reshape!(height, ratio, width, ratio, 3).sum(1, 3) # Resize\n        .inplace / (ratio**2)\n  # 200 x 200 => 100 x 100 because LibUI is slow...\n  height.times do |y|\n    width.times do |x|\n      path = UI.draw_new_path(UI::DrawFillModeWinding)\n      UI.draw_path_add_rectangle(path,\n                                 pix_size * (x + 1), pix_size * (y + 1),\n                                 pix_size, pix_size)\n      UI.draw_path_end(path)\n      brush.Type = 0\n      brush.R = rgb[y, x, 0]\n      brush.G = rgb[y, x, 1]\n      brush.B = rgb[y, x, 2]\n      brush.A = 1.0\n      UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)\n      UI.draw_free_path(path)\n    end\n  end\nend\n\ndo_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}\nkey_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }\n\nhandler_mouse_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, e|\n  e = UI::FFI::AreaMouseEvent.new(e)\n  if e.Down == 1\n    x = e.X * (ratio / pix_size.to_f)\n    y = e.Y * (ratio / pix_size.to_f)\n    next if x >= model_width + pointer_size ||\n            y >= model_height + pointer_size\n\n    yrange = ([(y - pointer_size), 0].max)..([y, (model_height - 1)].min)\n    xrange = ([(x - pointer_size), 0].max)..([x, (model_width - 1)].min)\n    @model.u[yrange, xrange] = 0.5\n    @model.v[yrange, xrange] = 0.5\n    UI.area_queue_redraw_all(@area)\n  end\nend\n\narea_handler.Draw         = handler_draw_event\narea_handler.MouseEvent   = handler_mouse_event\narea_handler.MouseCrossed = do_nothing\narea_handler.DragBroken   = do_nothing\narea_handler.KeyEvent     = key_event\n\n# slide_feed\n\nlabel_f = UI.new_label('f')\nslider_feed = UI.new_slider(0, 100)\nUI.slider_set_value(slider_feed, @model.f * 1000)\nUI.slider_on_changed(slider_feed) do |ptr|\n  @model.f = UI.slider_value(ptr) / 1000.0\nend\n\n# slider_kill\n\nlabel_k = UI.new_label('k')\nslider_kill = UI.new_slider(0, 100)\nUI.slider_set_value(slider_kill, @model.k * 1000)\nUI.slider_on_changed(slider_kill) do |ptr|\n  @model.k = UI.slider_value(ptr) / 1000.0\nend\n\n# combobox preset\n\n# The presets are taken from the following implementations:\n# https://github.com/pmneila/jsexp\n\npresets = [\n  {\n    name: 'Default',\n    feed: 0.037,\n    kill: 0.06\n  },  {\n    name: 'Solitons',\n    feed: 0.03,\n    kill: 0.062\n  },  {\n    name: 'Pulsating Solitons',\n    feed: 0.025,\n    kill: 0.06\n  },  {\n    name: 'Worms',\n    feed: 0.078,\n    kill: 0.061\n  },  {\n    name: 'Mazes',\n    feed: 0.029,\n    kill: 0.057\n  },  {\n    name: 'Holes',\n    feed: 0.039,\n    kill: 0.058\n  },  {\n    name: 'Chaos',\n    feed: 0.026,\n    kill: 0.051\n  },  {\n    name: 'Chaos and holes',\n    feed: 0.034,\n    kill: 0.056\n  },  {\n    name: 'Moving spots',\n    feed: 0.014,\n    kill: 0.054\n  },  {\n    name: 'Spots and loops',\n    feed: 0.018,\n    kill: 0.051\n  },  {\n    name: 'Waves',\n    feed: 0.014,\n    kill: 0.045\n  },  {\n    name: 'The U-Skate World',\n    feed: 0.062,\n    kill: 0.06093\n  }\n]\n\ncbox_presets = UI.new_combobox\npresets.each do |preset|\n  UI.combobox_append(cbox_presets, preset[:name])\nend\nUI.combobox_set_selected(cbox_presets, 0)\nUI.combobox_on_selected(cbox_presets) do |ptr|\n  preset = presets[UI.combobox_selected(ptr)]\n  @model.f = preset[:feed]\n  @model.k = preset[:kill]\n  UI.slider_set_value(slider_feed, preset[:feed] * 1000)\n  UI.slider_set_value(slider_kill, preset[:kill] * 1000)\nend\n\n# combobox u/v\n\nuv = %w[u v]\ncbox_uv = UI.new_combobox\nuv.each do |s|\n  UI.combobox_append(cbox_uv, s)\nend\nUI.combobox_set_selected(cbox_uv, 1)\nUI.combobox_on_selected(cbox_uv) do |ptr|\n  @uv = uv[UI.combobox_selected(ptr)]\n  UI.area_queue_redraw_all(@area) unless @running\nend\n\n# combobox color\n\ncolor_type_list = %w[\n  colorful\n  reverse-colorful\n  red\n  green\n  blue\n  reverse-red\n  reverse-green\n  reverse-blue\n  grayscale\n]\n\ncbox_color = UI.new_combobox\ncolor_type_list.each do |s|\n  UI.combobox_append(cbox_color, s)\nend\nUI.combobox_set_selected(cbox_color, 0)\nUI.combobox_on_selected(cbox_color) do |ptr|\n  @color_type = color_type_list[UI.combobox_selected(ptr)]\n  UI.area_queue_redraw_all(@area) unless @running\nend\n\n# button start/stop\n\nbutton_start = UI.new_button('▶️ START')\nUI.button_on_clicked(button_start) do\n  @running = !@running\n  UI.button_set_text(button_start,\n                     @running ? '🛑 STOP' : '▶️ START')\nend\n\n# button capture\n\nbutton_capture = UI.new_button('📷')\nUI.button_on_clicked(button_capture) do\n  image = GrayScott::Color.colorize(@model.public_send(@uv.to_sym), @color_type)\n\n  pt = UI.save_file(@main_window)\n  unless pt.null?\n    file_path = pt.to_s\n    if defined?(Magro::IO)\n      Magro::IO.imsave(file_path, image)\n    else\n      img = ChunkyPNG::Image.from_rgb_stream(model_width, model_height, image.to_string)\n      img.save(file_path)\n    end\n  end\nend\n\n# hbox\n\nhbox1 = UI.new_horizontal_box\nUI.box_set_padded(hbox1, 1)\nUI.box_append(hbox1, cbox_presets, 0)\nUI.box_append(hbox1, label_f, 0)\nUI.box_append(hbox1, slider_feed, 1)\nUI.box_append(hbox1, label_k, 0)\nUI.box_append(hbox1, slider_kill, 1)\n\nhbox2 = UI.new_horizontal_box\nUI.box_set_padded(hbox2, 1)\nUI.box_append(hbox2, cbox_uv, 0)\nUI.box_append(hbox2, cbox_color, 0)\nUI.box_append(hbox2, button_start, 1)\nUI.box_append(hbox2, button_capture, 0)\n\n# vbox\n\nvbox = UI.new_vertical_box\nUI.box_set_padded(vbox, 1)\nUI.box_append(vbox, hbox1, 0)\nUI.box_append(vbox, hbox2, 0)\nUI.box_append(vbox, @area, 1)\n\n# main window\n\n@main_window = UI.new_window('Turing Pattern', 440, 560, 1)\nUI.window_set_margined(@main_window, 1)\nUI.window_set_child(@main_window, vbox)\n\nUI.window_on_closing(@main_window) do\n  UI.quit\n  1\nend\nUI.control_show(@main_window)\n\n# queue\n\nUI.queue_main do\n  UI.timer(WAIT_TIME) do\n    next 1 unless @running # do nothing\n\n    NUM_STEPS.times do\n      @model.step\n    end\n    UI.area_queue_redraw_all(@area)\n    1 # continue\n  end\nend\n\nUI.main\nUI.uninit\n"
  },
  {
    "path": "examples2/README.md",
    "content": "This directory (examples2/) contains code that refers to widgets and functions made available via the official libui-ng (see https://github.com/libui-ng/libui-ng) bindings (and perhaps eventually libui-dev as well).\r\n\r\nThe rationale (and objective) for this directory here serves at the least the following purposes:\r\n\r\n- Provide standalone (working) .rb files that test individual components of\r\nthe ruby-libui suite, such as the various widgets that are part of (and supported by)\r\nlibui. For instance, button.rb should contain all code that relates to buttons,\r\nincluding functionality to test the on-clicked event. Same for combobox.rb, which\r\nshould include all code specific to the combobox-widget, and so forth. Code used\r\nfor this purpose should be contained within a single .rb file only, as well as the\r\nlibui-bindings necessary to demonstrate its functionality (e. g. toplevel LibUI or\r\nUI, the main window, usually a box to contain the widget at hand, and so forth). This\r\nway a new user can look at the included functionality, learn from it, and quickly\r\nadapt it to his or her use case.\r\n\r\n- Provide explanations to any other methods and functions that are offered\r\nby this project, even if it may not be directly related to a specific widget.\r\nFor instance, querying the current libui-version, if that is made available\r\nby upstream code, and similar functionality that new users may find useful,\r\nor old users may have forgotten - thus, this also serves as a quick refresher\r\nfor remembering how to use something, with a focus on specific widgets.\r\n\r\n- Documentation and explanations within those individual .rb files. That way\r\nnew users of this project may learn the bindings made available by kojix2\r\nmore rapidly so.\r\n\r\nStay tuned for more updates in this regard in the long run. Right now nine\r\nwidgets have been added; expect more code in this regard over the next weeks\r\nor months. \\o/\r\n\r\nI also invite other people to contribute changes, including documentation. Let's improve\r\nthe default experience of ruby + libui for new users, as well as provide working reference\r\nimplementations for all functionality made available in ruby-libui.\r\n\r\nSo far (this update) almost 82 \"components\" are verified by examples in the subdirectory examples2/ here.\r\nI think we are close to 50% in total now or almost at 50%.\r\n\r\nWidgets that have been added to this subdirectory include, as standalone files, in alphabetical\r\norder:\r\n\r\n    button.rb\r\n    checkbox.rb\r\n    color_button.rb\r\n    combobox.rb\r\n    date_picker.rb\r\n    editable_combobox.rb\r\n    entry.rb\r\n    grid.rb\r\n    multiline_entry.rb\r\n    password_entry.rb\r\n    progress_bar.rb\r\n    search_entry.rb\r\n    slider.rb\r\n    window.rb\r\n\r\nNote that this subdirectory here (examples2/) is different to examples/. The examples/ subdirectory has been created by kojix2 to test various parts of ruby-libui, including more complex use cases (see the histogram example for dynamic elements).\r\n\r\nAvailable new-entries in regards to LibUI include:\r\n\r\n- new_area\r\n- new_attributed_string\r\n- new_background_attribute\r\n- new_button\r\n- new_checkbox\r\n- new_color_attribute\r\n- new_color_button\r\n- new_combobox\r\n- new_date_picker\r\n- new_date_time_picker\r\n- new_editable_combobox\r\n- new_entry\r\n- new_family_attribute\r\n- new_features_attribute\r\n- new_font_button\r\n- new_form\r\n- new_grid\r\n- new_group\r\n- new_horizontal_box\r\n- new_horizontal_separator\r\n- new_image\r\n- new_italic_attribute\r\n- new_label\r\n- new_menu\r\n- new_multiline_entry\r\n- new_non_wrapping_multiline_entry\r\n- new_open_type_features\r\n- new_password_entry\r\n- new_progress_bar\r\n- new_radio_buttons\r\n- new_scrolling_area\r\n- new_search_entry\r\n- new_size_attribute\r\n- new_slider\r\n- new_spinbox\r\n- new_stretch_attribute\r\n- new_tab\r\n- new_table\r\n- new_table_model\r\n- new_table_value_color\r\n- new_table_value_image\r\n- new_table_value_int\r\n- new_table_value_string\r\n- new_time_picker\r\n- new_underline_attribute\r\n- new_underline_color_attribute\r\n- new_vertical_box\r\n- new_vertical_separator\r\n- new_weight_attribute\r\n- new_window\r\n\r\n"
  },
  {
    "path": "examples2/button.rb",
    "content": "# ============================================================================ #\n# This example (button.rb) shall demonstrate the following functionality\n# (4 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_button                         # [DONE]\n#   :button_on_clicked                  # [DONE]\n#   :button_set_text                    # [DONE]\n#   :button_text                        # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('button.rb', 400, 240, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_button # Create a new button here.\nLibUI.box_append(hbox, _, 1) # Add the button here.\nLibUI.button_set_text(_, 'This is a generic text for the button.')\n\nputs 'The text for our button is as follows (obtained via LibUI.button_text():'\nputs\nputs \"  #{LibUI.button_text(_)}\"\nputs\n\ncallback_for_the_button = proc {\n  puts 'I was clicked.'\n  0 # This return value does not seem to be necessary, but we use it still, to show that one could use a return value here.\n}\n\nLibUI.button_on_clicked(_, callback_for_the_button)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/checkbox.rb",
    "content": "# ============================================================================ #\n# This example (checkbox.rb) shall demonstrate the following functionality\n# (6 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_checkbox              # [DONE]\n#   :checkbox_checked          # [DONE]\n#   :checkbox_on_toggled       # [DONE]\n#   :checkbox_set_checked      # [DONE]\n#   :checkbox_set_text         # [DONE]\n#   :checkbox_text             # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('checkbox.rb', 400, 240, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_checkbox # Create a new checkbox here.\nLibUI.box_append(hbox, _, 1) # Add it to a box.\nLibUI.checkbox_set_text(_, 'This is a generic text for the checkbox.')\n\nputs 'The text for our checkbox follows (obtained via LibUI.checkbox_text():'\nputs\nputs \"  #{LibUI.checkbox_text(_)}\"\nputs\n\ncallback_for_the_checkbox = proc {\n  puts 'I was toggled. My state is now:'\n  case LibUI.checkbox_checked(_)\n  when 1\n    puts '  checked (active)'\n  when 0\n    puts '  unchecked (inactive)'\n  end\n  0 # This return value does not seem to be necessary, but we use it still, to show that one could use a return value here.\n}\n\nLibUI.checkbox_on_toggled(_, callback_for_the_checkbox)\n\nputs 'Setting the checkbox to checked (is-selected) next.'\nLibUI.checkbox_set_checked(_, 1)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/color_button.rb",
    "content": "# ============================================================================ #\n# This example (color_button.rb) shall demonstrate the following\n# functionality (4 components), as well as their implementation-status\n# in regards to this file:\n#\n#   :new_color_button              # [DONE]\n#   :color_button_color            # [DONE]\n#   :color_button_on_changed       # [DONE]\n#   :color_button_set_color        # [DONE]\n#\n# See an API reference here:\n#\n#   https://libui-ng.github.io/libui-ng/structui_editable_combobox.html\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('color_button.rb', 640, 240, 1)\n\n# ============================================================================ #\n# Get the colours and set up the brush\n# uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA)\n# ============================================================================ #\ngraph_r = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\ngraph_g = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\ngraph_b = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\ngraph_a = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double\n\nvbox = LibUI.new_vertical_box\nLibUI.box_set_padded(vbox, 1)\n_ = LibUI.new_color_button # Create a new color-button here.\nLibUI.box_append(vbox, _, 0) # Add the combobox here. Right now the combobox is empty.\n\nLibUI.color_button_on_changed(_) {\n  puts 'The colour button was changed.'\n}\n\nBRUSH             = LibUI::FFI::DrawBrush.malloc\nBRUSH.to_ptr.free = Fiddle::RUBY_FREE\n\n# === set_solid_brush\ndef set_solid_brush(\n    brush = BRUSH,\n    color = 0x1E90FF,\n    alpha\n  )\n  BRUSH.Type = 0 # solid\n  BRUSH.R = ((color >> 16) & 0xFF) / 255.0\n  BRUSH.G = ((color >>  8) & 0xFF) / 255.0\n  BRUSH.B = (color & 0xFF) / 255.0\n  BRUSH.A = alpha\n  BRUSH\nend\n\nputs 'Set to a blue colour next.'\nset_solid_brush(BRUSH, 0x1E90FF, 1.0)\n\nLibUI.color_button_set_color(_, BRUSH.R, BRUSH.G, BRUSH.B, BRUSH.A)\n\nLibUI.color_button_color(_, graph_r, graph_g, graph_b, graph_a) # Use LibUI.color_button_color() here.\n\nLibUI.window_set_child(main_window, vbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/combobox.rb",
    "content": "# ============================================================================ #\n# This example (combobox.rb) shall demonstrate the following functionality\n# (9 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_combobox                       [DONE]\n#   :combobox_append                    [DONE]\n#   :combobox_clear                     [DONE]\n#   :combobox_delete                    [DONE]          \n#   :combobox_insert_at                 [DONE]\n#   :combobox_num_items                 [DONE]\n#   :combobox_on_selected               [DONE]\n#   :combobox_selected                  [DONE]\n#   :combobox_set_selected              [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\n# ============================================================================ #\n# === populate_the_combobox_with_this_array\n#\n# This method is used as a helper-method, to populate the combobox we use\n# here with data (an Array).\n# ============================================================================ #\ndef populate_the_combobox_with_this_array(\n    the_combobox,\n    this_array\n  )\n  this_array.each {|this_entry|\n    LibUI.combobox_append(the_combobox, this_entry) # Here we add elements to the combobox.\n  }\n  LibUI.combobox_set_selected(the_combobox, 0) # The first element is now the default selected entry.\nend\n\nmain_window = LibUI.new_window('combobox.rb', 400, 240, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_  = LibUI.new_combobox # Create a new combobox here.\nLibUI.box_append(hbox, _, 1) # Add the combobox here. Right now the combobox is empty.\n\n# ============================================================================ #\n# Let's add data to the combobox, as an Array:\n# ============================================================================ #\narray = %w( matz created ruby as efficient alternative to perl )\npopulate_the_combobox_with_this_array(_, array)\n\n# ============================================================================ #\n# Next showing how to clear it:\n# ============================================================================ #\nLibUI.combobox_clear(_)\npopulate_the_combobox_with_this_array(_, array) # And re-populate it here.\n\n# ============================================================================ #\n# Next we delete the third entry; and then insert two new elements\n# at that former position, to showcase the functionality\n# combobox_delete, as well as combobox_insert_at. Note that combobox_delete\n# takes two arguments, whereas combobox_insert_at takes three arguments.\n# ============================================================================ #\nLibUI.combobox_delete(_, 2)\nLibUI.combobox_insert_at(_, 2, 'ruby')\nLibUI.combobox_insert_at(_, 2, 'awesome')\n\n# ============================================================================ #\n# Show how many elements are in that combobox:\n# ============================================================================ #\nputs \"The combobox we are using here has a total \"\\\n     \"of #{LibUI.combobox_num_items(_)} elements.\"\n\nLibUI.combobox_set_selected(_, 3) # Change the selected element next, to item 4.\n\n# ============================================================================ #\n# Show the selected entry next:\n# ============================================================================ #\nputs \"The presently selected entry in our combobox is element number \"\\\n     \"#{LibUI.combobox_selected(_)}.\"\n\nputs 'Last but not least, try to change the combobox to a new value.'\nputs 'This will trigger LibUI.combobox_on_selected().'\n\nLibUI.combobox_on_selected(_) { |pointer|\n  selected = LibUI.combobox_selected(pointer)\n  puts \"The new selection is element number `#{selected}`.\"\n}\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/date_picker.rb",
    "content": "# ============================================================================ #\n# === DatePicker - a widget to allow the user to enter a date\n#\n# This example (date_picker.rb) shall demonstrate the following functionality\n# (3 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_date_picker             # [DONE]\n#   :date_time_picker_on_changed # [DONE]\n#   :date_time_picker_set_time   # [NOT YET IMPLEMENTED]\n#\n# Documentation for the perl-API, for libui, can be seen here:\n#\n#   https://metacpan.org/pod/LibUI::DatePicker\n#\n# While this is not necessarily 1:1 the ruby-API, for the most part it\n# is quite similar.\n#\n# Note that not all functionality related to date_picker is tested for\n# yet in this file. Patches to enhance functionality as well as the\n# documentation are welcome.\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('date_picker.rb', 640, 240, 1)\n\nvbox = LibUI.new_vertical_box\nLibUI.box_set_padded(vbox, 1)\n_ = LibUI.new_date_picker # Create a date-picker widget here.\nLibUI.box_append(vbox, _, 0) # Add the font-button here.\n\ncallback_on_changed = proc { |pointer|\n  puts 'The time was changed.'\n  #puts LibUI.date_time_picker_time(pointer) \n}\nLibUI.date_time_picker_on_changed(_, callback_on_changed)\n\n# uiDateTimePickerSetTime(d : UI::DateTimePicker*, tm : LibC::Tm*)\n# LibUI.date_time_picker_set_time(_, Time.now)\n\nLibUI.window_set_child(main_window, vbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/editable_combobox.rb",
    "content": "# ============================================================================ #\n# This example (editable_combobox.rb) shall demonstrate the following\n# functionality (5 components), as well as their implementation-status in\n# regards to this file:\n#\n#   :new_editable_combobox                 # [DONE]\n#   :editable_combobox_append              # [DONE]\n#   :editable_combobox_on_changed          # [DONE]\n#   :editable_combobox_set_text            # [DONE]\n#   :editable_combobox_text                # [DONE]\n#\n# See an API reference here:\n#\n#   https://libui-ng.github.io/libui-ng/structui_editable_combobox.html\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\n# ============================================================================ #\n# === populate_the_combobox_with_this_array\n#\n# This method is used as a helper-method, to populate the combobox we use\n# here with data (an Array).\n# ============================================================================ #\ndef populate_the_combobox_with_this_array(\n    the_combobox,\n    this_array\n  )\n  this_array.each {|this_entry|\n    LibUI.editable_combobox_append(the_combobox, this_entry) # Here we add elements to the combobox.\n  }\n  LibUI.combobox_set_selected(the_combobox, 0) # The first element is now the default selected entry.\nend\n\nmain_window = LibUI.new_window('combobox.rb', 640, 480, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_  = LibUI.new_editable_combobox # Create a new combobox here.\nLibUI.box_append(hbox, _, 1) # Add the combobox here. Right now the combobox is empty.\n\n# ============================================================================ #\n# Let's add data to the combobox, as an Array:\n# ============================================================================ #\narray = %w( matz created ruby as efficient alternative to perl )\npopulate_the_combobox_with_this_array(_, array)\n\n# ============================================================================ #\n# Next showing how to clear it:\n# ============================================================================ #\nLibUI.combobox_clear(_)\npopulate_the_combobox_with_this_array(_, array) # And re-populate it here.\n\n# ============================================================================ #\n# Next we delete the third entry; and then insert two new elements\n# at that former position, to showcase the functionality\n# combobox_delete, as well as combobox_insert_at. Note that combobox_delete\n# takes two arguments, whereas combobox_insert_at takes three arguments.\n# ============================================================================ #\nLibUI.combobox_delete(_, 2)\nLibUI.combobox_insert_at(_, 2, 'ruby')\n\n# ============================================================================ #\n# Show how many elements are in that combobox:\n# ============================================================================ #\nputs \"The combobox we are using here has a total \"\\\n     \"of #{LibUI.combobox_num_items(_)} elements.\"\n\nLibUI.combobox_set_selected(_, 3) # Change the selected element next, to item 4.\n\n# ============================================================================ #\n# Show the selected entry next:\n# ============================================================================ #\nputs \"The presently selected entry in our combobox is element number \"\\\n     \"#{LibUI.combobox_selected(_)}.\"\n\nputs 'Last but not least, try to change the combobox to a new value.'\nputs 'This will trigger LibUI.combobox_on_selected().'\n\n# ============================================================================ #\n# Testing support for :editable_combobox_on_changed next.\n# ============================================================================ #\nLibUI.editable_combobox_on_changed(_) { |pointer|\n  selected = LibUI.combobox_selected(pointer)\n  puts \"The new selection is element number `#{selected}`.\"\n  puts \"The currently selected text is: `#{LibUI.editable_combobox_text(pointer)}`\"\n}\n\n# ============================================================================ #\n# As explained by kojix2, libui-ng intentionally limits editable comboboxes\n# to simple text get/set functionality.\n#\n# Since users can freely input text in an editable combobox, the concept\n# of \"which item is selected\" becomes ambiguous. This is why rather\n# than using LibUI.combobox_set_selected(), LibUI.editable_combobox_set_text()\n# is used next. \n# ============================================================================ #\nLibUI.editable_combobox_set_text(_, 'Testing a new default text. This will appear first.')\n\nLibUI.editable_combobox_append(_, 'This is a black cat.') # Here we add elements to the combobox.\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/entry.rb",
    "content": "# ============================================================================ #\n# This example (entry.rb) shall demonstrate the following functionality\n# (6 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_entry                   # [DONE]\n#   :entry_on_changed            # [DONE]\n#   :entry_read_only             # [DONE]\n#   :entry_set_read_only         # [DONE]\n#   :entry_set_text              # [DONE]\n#   :entry_text                  # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('entry.rb', 800, 440, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_entry # Create a new entry here.\nLibUI.box_append(hbox, _, 1) # Add the entry here.\n@old_entry_text = 'This is a generic text for the entry.'\nLibUI.entry_set_text(_, @old_entry_text)\n\nputs 'The entry will be set to read-only next, via '\\\n     'LibUI.entry_set_read_only().'\n\nLibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.\nputs\nputs \"Is this entry read-only? #{LibUI.entry_read_only(_)}\"\\\n     \" # note that a 1 here means yes/true\"\nputs\nputs 'The text for the current entry in use is as follows:'\nputs\nputs \"  → #{LibUI.entry_text(_)}\"\nputs\nputs 'Making the entry no longer read-only next:'\nputs\nLibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.\n\ncallback_proc = proc { |pointer|\n  new_text = LibUI.entry_text(pointer).to_s\n  puts\n  puts \"The old entry-text was: '#{@old_entry_text}'\"\n  puts \"The new entry-text is:  '#{new_text}'\"\n  @old_entry_text = new_text \n}\nLibUI.entry_on_changed(_, callback_proc)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/font_button.rb",
    "content": "# ============================================================================ #\n# This example (font_button.rb) shall demonstrate the following\n# functionality (5 components), as well as their implementation-status\n# in regards to this file:\n#\n#   :new_font_button              # [DONE]\n#   :font_button_font             # [NOT YET ADDED]\n#   :font_button_on_changed       # [DONE]\n#   :free_font_button_font        # [NOT YET ADDED]\n#   :free_font_descriptor         #[NOT YET ADDED]\n#\n# Unsure what \":load_control_ font\" is.\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('font_button.rb', 640, 240, 1)\n\nvbox = LibUI.new_vertical_box\nLibUI.box_set_padded(vbox, 1)\n_ = LibUI.new_font_button # Create a new font-button here.\nLibUI.box_append(vbox, _, 0) # Add the font-button here.\n\nLibUI.font_button_on_changed(_) {|entry|\n  puts 'The font was changed. (class '+entry.class.to_s+')'\n}\n\nLibUI.window_set_child(main_window, vbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/grid.rb",
    "content": "# ============================================================================ #\n# This example (grid.rb) shall demonstrate the following functionality\n# (8 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_grid                                   # [DONE]\n#   :grid_append                                # [DONE]\n#   :grid_insert_at                             # Unsure how to do this.\n#   :grid_padded                                # [DONE]\n#   :grid_set_padded                            # [DONE]\n#\n# API documentation can be seen here:\n#\n#   https://libui.dev/structui_grid.html\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('grid.rb', 800, 250, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_grid # Create a new grid here.\nLibUI.box_append(hbox, _, 1) # Add the grid here.\nLibUI.grid_set_padded(_,  0) # Here we could toggle the padded-status.\nputs 'Is the grid padded? '+LibUI.grid_padded(_).to_s+\n     ' (1 means yes)'\n\nbutton1 = LibUI.new_button('Test-Button #1')\nbutton2 = LibUI.new_button('Test-Button #2')\nbutton3 = LibUI.new_button('Test-Button #3')\nbutton4 = LibUI.new_button('Test-Button #4')\n\nleft    = 0\ntop     = 0\nxspan   = 1\nyspan   = 1\nhexpand = 1\nhalign  = 1\nvexpand = 1\nvalign  = 1\n\n# ======================================================================== #\n# left, top, xspan, yspan, hexpand, halign, vexpand, valign\n#  0,    0,    2,     1,      0,      1,       1,      1\n# ======================================================================== #\nLibUI.grid_append(\n  _,\n  button1, # This is the widget that will be added (appended) onto the grid-widget.\n  left,\n  top,\n  xspan,\n  yspan,\n  hexpand,\n  halign,\n  vexpand,\n  valign\n)\n\n\nleft    = 1\ntop     = 0\nxspan   = 1\nyspan   = 1\nhexpand = 1\nhalign  = 1\nvexpand = 1\nvalign  = 1\n\n# ======================================================================== #\n# left, top, xspan, yspan, hexpand, halign, vexpand, valign\n#  0,    0,    2,     1,      0,      1,       1,      1\n# ======================================================================== #\nLibUI.grid_append(\n  _,\n  button2, # This is the widget that will be added (appended) onto the grid-widget.\n  left,\n  top,\n  xspan,\n  yspan,\n  hexpand,\n  halign,\n  vexpand,\n  valign\n)\n\nleft    = 0\ntop     = 1\nxspan   = 1\nyspan   = 1\nhexpand = 1\nhalign  = 1\nvexpand = 1\nvalign  = 1\n\n# ======================================================================== #\n# left, top, xspan, yspan, hexpand, halign, vexpand, valign\n#  0,    1,    2,     1,      0,      1,       1,      1\n# ======================================================================== #\nLibUI.grid_append(\n  _,\n  button3, # This is the widget that will be added (appended) onto the grid-widget.\n  left,\n  top,\n  xspan,\n  yspan,\n  hexpand,\n  halign,\n  vexpand,\n  valign\n)\n\nleft    = 1\ntop     = 1\nxspan   = 1\nyspan   = 1\nhexpand = 1\nhalign  = 1\nvexpand = 1\nvalign  = 1\n\n# ======================================================================== #\n# left, top, xspan, yspan, hexpand, halign, vexpand, valign\n#  1,    1,    2,     1,      0,      1,       1,      1\n# ======================================================================== #\nLibUI.grid_append(\n  _,\n  button4, # This is the widget that will be added (appended) onto the grid-widget.\n  left,\n  top,\n  xspan,\n  yspan,\n  hexpand,\n  halign,\n  vexpand,\n  valign\n)\n\nif false # The next clause does not work correctly yet.\nentry1  = LibUI.new_entry\n# ============================================================================ #\n# See: https://libui.dev/structui_grid.html#ad282fc62adbaed067699f949d619899c\n#\n# Arguments to LibUI.grid_insert_at() are:\n#\n#   void uiGridInsertAt\t(\tuiGrid *\tg,\n#   uiControl *\tc,\n#   uiControl *\texisting,\n#   uiAt\tat,\n#   int\txspan,\n#   int\tyspan,\n#   int\thexpand,\n#   uiAlign\thalign,\n#   int\tvexpand,\n#   uiAlign\tvalign )\n#\n# ============================================================================ #\nLibUI.grid_insert_at(\n  _,\n  entry1,  # The widget to insert.\n  button3, # Our relative widget.\n  LibUI::AtTrailing, # at: Placement specifier in relation to existing control.\n  0, # xspan\n  1, # yspan\n  1,\n  1,\n  1,\n  1\n)\nend\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/multiline_entry.rb",
    "content": "# ============================================================================ #\n# This example (multiline_entry.rb) shall demonstrate the following functionality\n# (6 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :multiline_entry_append        # [DONE]\n#   :multiline_entry_on_changed\n#   :multiline_entry_read_only     # [DONE]\n#   :multiline_entry_set_read_only # [DONE]\n#   :multiline_entry_set_text      # [DONE]\n#   :multiline_entry_text\n#   :new_multiline_entry           # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('entry.rb', 800, 440, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_multiline_entry # Create a new entry here.\nLibUI.box_append(hbox, _, 1) # Add the entry here.\n@old_entry_text = 'This is a generic text for the entry.'\nLibUI.multiline_entry_set_text(_, @old_entry_text)\nputs 'Appending something next.'\nLibUI.multiline_entry_append(_, ' More content.')\n\ncallback_proc = proc { |pointer|\n  new_text = LibUI.multiline_entry_text(pointer).to_s\n  puts\n  puts \"The old entry-text was: '#{@old_entry_text}'\"\n  puts \"The new entry-text is:  '#{new_text}'\"\n  @old_entry_text = new_text \n}\nLibUI.multiline_entry_on_changed(_, callback_proc)\n\n# LibUI.multiline_entry_set_read_only(_, 1) # Set it read-only here.\n# LibUI.multiline_entry_read_only(_) # Query the read-only way here.\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/password_entry.rb",
    "content": "# ============================================================================ #\n# This example (password_entry.rb) shall demonstrate the following functionality\n# (6 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_entry                   # [DONE]\n#   :entry_on_changed            # [DONE]\n#   :entry_read_only             # [DONE]\n#   :entry_set_read_only         # [DONE]\n#   :entry_set_text              # [DONE]\n#   :entry_text                  # [DONE]\n#\n# Note that password-entry is a subclass of uiEntry.\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('password_entry.rb', 800, 440, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_password_entry # Create a new password-entry here.\nLibUI.box_append(hbox, _, 1) # Add the password-entry here.\n@old_entry_text = 'foobar' # Use a very short password here.\nLibUI.entry_set_text(_, @old_entry_text)\n\nputs 'The password-entry will be set to read-onlyn ext, via '\\\n     'LibUI.entry_set_read_only().'\n\nLibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.\nputs\nputs \"Is this entry read-only? #{LibUI.entry_read_only(_)}\"\\\n     \" # note that a 1 here means yes/true\"\nputs\nputs 'The text for the current entry in use is as follows:'\nputs\nputs \"  → #{LibUI.entry_text(_)}\"\nputs\nputs 'Making the entry no longer read-only next:'\nputs\nLibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.\n\ncallback_proc = proc { |pointer|\n  new_text = LibUI.entry_text(pointer).to_s\n  puts\n  puts \"The old entry-text was: '#{@old_entry_text}'\"\n  puts \"The new entry-text is:  '#{new_text}'\"\n  @old_entry_text = new_text\n  puts\n  puts 'Note that this callback can be modified to'\n  puts 'allow for the search functionality'\n  puts\n  puts 'Interestingly the output shows the real password,'\n  puts 'so be careful with this.'\n}\nLibUI.entry_on_changed(_, callback_proc)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/progress_bar.rb",
    "content": "# ============================================================================ #\n# This example (progress_bar.rb) shall demonstrate the following\n# functionality (2 components), as well as their implementation-status\n# in regards to this file:\n#\n#   :progress_bar_set_value       # [DONE]\n#   :progress_bar_value           # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('ProgressBar', 640, 240, 1)\n\nvbox = LibUI.new_vertical_box\nLibUI.box_set_padded(vbox, 1)\n_ = LibUI.new_progress_bar # Create a new progressbar here.\nLibUI.progress_bar_set_value(_, 42) # Show how to set a value to a progress bar.\nLibUI.box_append(vbox, _, 0.5) # Add the progressbar here.\n\nputs 'The current value of the progress bar is: '+\n      LibUI.progress_bar_value(_).to_s\n\nLibUI.window_set_child(main_window, vbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/search_entry.rb",
    "content": "# ============================================================================ #\n# This example (search_entry.rb) shall demonstrate the following functionality\n# (6 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_entry                   # [DONE]\n#   :entry_on_changed            # [DONE]\n#   :entry_read_only             # [DONE]\n#   :entry_set_read_only         # [DONE]\n#   :entry_set_text              # [DONE]\n#   :entry_text                  # [DONE]\n#\n# Note that search-entry is a subclass of uiEntry.\n#\n# See also rust-documentation here:\n#\n#   https://docs.rs/libui/latest/libui/controls/struct.SearchEntry.html\n#\n# Or Go documentation here:\n#\n#   https://pkg.go.dev/github.com/andlabs/ui#NewSearchEntry\n# \n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('search_entry.rb', 800, 440, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n_ = LibUI.new_search_entry # Create a new search-entry here.\nLibUI.box_append(hbox, _, 1) # Add the search-entry here.\n@old_entry_text = 'This is a generic text for the search-entry.'\nLibUI.entry_set_text(_, @old_entry_text)\n\nputs 'The entry will be set to read-onlyn ext, via '\\\n     'LibUI.entry_set_read_only().'\n\nLibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.\nputs\nputs \"Is this entry read-only? #{LibUI.entry_read_only(_)}\"\\\n     \" # note that a 1 here means yes/true\"\nputs\nputs 'The text for the current entry in use is as follows:'\nputs\nputs \"  → #{LibUI.entry_text(_)}\"\nputs\nputs 'Making the entry no longer read-only next:'\nputs\nLibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.\n\ncallback_proc = proc { |pointer|\n  new_text = LibUI.entry_text(pointer).to_s\n  puts\n  puts \"The old entry-text was: '#{@old_entry_text}'\"\n  puts \"The new entry-text is:  '#{new_text}'\"\n  @old_entry_text = new_text\n  puts\n  puts 'Note that this callback can be modified to'\n  puts 'allow for the search functionality'\n}\nLibUI.entry_on_changed(_, callback_proc)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/slider.rb",
    "content": "# ============================================================================ #\n# This example (slider.rb) shall demonstrate the following functionality\n# (8 components), as well as their implementation-status in regards to\n# this file:\n#\n#   :new_slider                                 # [DONE]\n#   :slider_has_tool_tip                        # [DONE]\n#   :slider_on_changed                          # [DONE]\n#   :slider_on_released                         # [DONE]\n#   :slider_set_has_tool_tip                    # [DONE]\n#   :slider_set_range                           # [DONE]\n#   :slider_set_value                           # [DONE]\n#   :slider_value                               # [DONE]\n#\n# API documentation can be seen here:\n#\n#   https://libui.dev/structui_slider.html\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('slider.rb', 800, 440, 1)\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n# new_slider() wants: uiNewSlider (int min, int max)\n_ = LibUI.new_slider(1, 100) # Create a new slider here.\nLibUI.box_append(hbox, _, 1) # Add the slider here.\n\n# ============================================================================ #\n# The default \"tooltip\" is the current value of the slider at hand.\n# ============================================================================ #\nputs 'Does this slider haver a tooltip? '+\n     LibUI.slider_has_tool_tip(_).to_s\n\ncallback_proc_on_changed = proc {|entry| # entry is a Fiddle::Pointer\n  new_value = LibUI.slider_value(entry) # Obtain the current value of the slider here.\n  puts 'The slider was changed. The new value is: '+new_value.to_s\n}\nLibUI.slider_set_has_tool_tip(_, 1) # 1 means true here\n\nputs 'Modifying the range now from -200 to +200.'\nLibUI.slider_set_range(_, -200, 200)\n\nputs 'Setting the value of the slider to 42 now, as a new default.'\nLibUI.slider_set_value(_, 42)\n\nLibUI.slider_on_changed(_, callback_proc_on_changed)\n\ncallback_proc_on_released = proc {|entry| # entry is a Fiddle::Pointer\n  puts 'The slider was released.'\n}\n\nLibUI.slider_on_released(_, callback_proc_on_released)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\n\nLibUI.window_on_closing(main_window) {\n  LibUI.quit\n  1\n}\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "examples2/todo/todo.md",
    "content": "This file is no longer necessary - the parent directory keeps track of what is finished and what is yet-to-be-done.\n"
  },
  {
    "path": "examples2/window.rb",
    "content": "# ============================================================================ #\n# This example (window.rb) shall demonstrate the following functionality\n# (18 components), as well as their implementation-status in regards to\n# this file:\n#\n#  :new_window                                   # [DONE]\n#  :window_borderless                            # [DONE]\n#  :window_content_size                          # Unsure how to use this\n#  :window_focused                               # [DONE] - returns whether or not the window is focused.\n#  :window_fullscreen                            # [DONE]\n#  :window_margined                              # [DONE]\n#  :window_on_closing                            # [DONE]\n#  :window_on_content_size_changed               # [DONE]\n#  :window_on_focus_changed                      # [DONE]\n#  :window_resizeable                            # [DONE]\n#  :window_set_borderless                        # [DONE]\n#  :window_set_child                             # [DONE]\n#  :window_set_content_size                      # [DONE]\n#  :window_set_fullscreen                        # [DONE]\n#  :window_set_margined                          # [DONE]\n#  :window_set_resizeable                        # [DONE]\n#  :window_set_title                             # [DONE]\n#  :window_title                                 # [DONE]\n#\n# ============================================================================ #\nrequire 'libui'\nLibUI.init # Initialize LibUI.\n\nmain_window = LibUI.new_window('window.rb', 880, 640, 1)\nLibUI.window_set_title(main_window, 'TEST TITLE')\nputs 'The temporary title of this window is: '+\n      LibUI.window_title(main_window)\nLibUI.window_set_title(main_window, 'window.rb') # And restore the title here again.\nLibUI.window_set_resizeable(main_window, 1) # Making it resizeable - is better, in my opinion.\nLibUI.window_set_margined(main_window, 1)\nputs 'Is the window margined? '+\n      LibUI.window_margined(main_window).to_s\n\nputs 'The main-window will have a margin, thanks to LibUI.window_set_margined()'\n\nputs 'Can this window be resized? '+\n      LibUI.window_resizeable(main_window).to_s\n\nputs 'Setting this window to fullscreen next, by default.'\nputs '(Actually, no, because this is annoying; the code for this'\nputs 'is LibUI.window_set_fullscreen(main_window, 1), though.'\n# LibUI.window_set_fullscreen(main_window, 1)\nputs 'You can also query it via LibUI.window_fullscreen(main_window)'\n\nhbox = LibUI.new_horizontal_box\nLibUI.box_set_padded(hbox, 1)\n\n# ============================================================================ #\n# LibUI.window_set_content_size() is actually\n# LibUI::FFI.uiWindowSetContentSize\n#\n# The code for this is:\n#\n#   try_extern 'void uiWindowSetContentSize(uiWindow *w, int width, int height)'\n#\n# So it needs two arguments. Note that this can be ignored by the system\n# though.\n#\n# More documentation for this method can be seen here:\n#\n#   https://libui.dev/structui_window.html#a1f33b8462a999bdaf276bcdca07dfe28\n#\n# ============================================================================ #\n_ = LibUI.new_label(\n  \"Just testing the window-widget here.\\n\\n\"\\\n  \"For testing-purposes this window can not be resized, \"\\\n  \"which is\\nNOT recommended. See LibUI.window_set_resizeable()\"\n  ) # Create a new help-text. here.\nLibUI.box_append(hbox, _, 1) # Add it to a box.\n\nputs\nputs 'The window will be borderless, thanks to LibUI.window_set_borderless()'\nputs 'Actually, that is not extremely useful, so we do not use it.'\nLibUI.window_set_borderless(main_window, 0)\nputs 'You can test whether it is borderless via LibUI.borderless()'\n\ncallback_proc = proc { |pointer|\n  puts '_'*80\n  puts 'The focus changed. This happens when the main-window'\n  puts 'is dragged to a new position, for instance, as well as'\n  puts 'on startup.'\n  # === window_content_size\n  #\n  # try_extern 'void uiWindowContentSize(uiWindow *w, int *width, int *height)'\n  #\n  # puts 'The window-content-size is '+\n  #       LibUI.window_content_size(main_window, 15,15).to_s\n  puts '_'*80\n}\n\nLibUI.window_on_focus_changed(main_window, callback_proc)\nputs 'Is the window focused? '+LibUI.window_focused(main_window).to_s\n\ncallback_on_content_size_changed = proc { |pointer|\n  puts 'The content-size of the main window was changed.'\n}\nLibUI.window_on_content_size_changed(main_window, callback_on_content_size_changed)\n\n# ============================================================================ #\n# Moves the window to the specified position.\n# ============================================================================ #\n# puts 'Set to a position:'\n# LibUI.window_set_position(main_window, 2, 2)\n\nLibUI.window_set_child(main_window, hbox)\nLibUI.control_show(main_window)\nLibUI.window_on_closing(main_window) {\n  # Do on-closing actions here.\n  LibUI.quit\n  1 # An Integer must be returned by this block.\n}\n\nputs 'Setting the content-size of the main window to a value of 2200, 500 next,'\nputs 'to test the method called LibUI.window_set_content_size():'\nLibUI.window_set_content_size(main_window, 2200, 500) # This actually works, I tested it recently.\n\nLibUI.main\nLibUI.uninit\n"
  },
  {
    "path": "lib/libui/error.rb",
    "content": "module LibUI\n  # base error class\n  class Error < StandardError; end\n\n  # LibUI shared library not found error\n  class LibraryNotFoundError < Error; end\n\n  # LibUI shared library load error\n  class LibraryLoadError < Error; end\nend\n"
  },
  {
    "path": "lib/libui/ffi.rb",
    "content": "require 'fiddle/import'\nrequire_relative 'fiddle_patch'\nrequire_relative 'error'\n\nmodule LibUI\n\n  module FFI\n    extend Fiddle::Importer\n    extend FiddlePatch\n\n    if LibUI.ffi_lib.nil?\n      raise LibraryNotFoundError, 'Could not find libui shared library. LibUI.ffi_lib is nil.'\n    elsif !File.exist?(LibUI.ffi_lib)\n      raise LibraryNotFoundError, \"Could not find libui shared library: #{LibUI.ffi_lib}\"\n    else\n      begin\n        dlload LibUI.ffi_lib\n      rescue LoadError\n        raise LibraryLoadError, \"Could not load libui shared library: #{LibUI.ffi_lib}\"\n      end\n    end\n\n    class << self\n      attr_reader :func_map\n\n      def try_extern(signature, *opts)\n        extern(signature, *opts)\n      rescue StandardError => e\n        # Do not raise error when the function is not found\n        # because some functions may not be available on older versions of libui.\n        warn \"#{e.class.name}: #{e.message}\"\n      end\n\n      def ffi_methods\n        @ffi_methods ||= func_map.each_key.to_a\n      end\n    end\n\n    typealias('uint32_t', 'unsigned int')\n    typealias('uint64_t', 'unsigned long long')\n\n    InitOptions = struct [\n      'size_t Size'\n    ]\n\n    # https://github.com/andlabs/libui/blob/master/ui.h\n    # keep same order\n\n    try_extern 'const char *uiInit(uiInitOptions *options)'\n    try_extern 'void uiUninit(void)'\n    try_extern 'void uiFreeInitError(const char *err)'\n\n    try_extern 'void uiMain(void)'\n    try_extern 'void uiMainSteps(void)'\n    try_extern 'int uiMainStep(int wait)'\n    try_extern 'void uiQuit(void)'\n    try_extern 'void uiQueueMain(void (*f)(void *data), void *data)'\n    try_extern 'void uiTimer(int milliseconds, int (*f)(void *data), void *data)'\n    try_extern 'void uiOnShouldQuit(int (*f)(void *data), void *data)'\n    try_extern 'void uiFreeText(char *text)'\n\n    Control = struct [\n      'uint32_t Signature',\n      'uint32_t OSSignature',\n      'uint32_t TypeSignature',\n      'void (*Destroy)(uiControl *)',\n      'uintptr_t (*Handle)(uiControl *)',\n      'uiControl *(*Parent)(uiControl *)',\n      'void (*SetParent)(uiControl *, uiControl *)',\n      'int (*Toplevel)(uiControl *)',\n      'int (*Visible)(uiControl *)',\n      'void (*Show)(uiControl *)',\n      'void (*Hide)(uiControl *)',\n      'int (*Enabled)(uiControl *)',\n      'void (*Enable)(uiControl *)',\n      'void (*Disable)(uiControl *)'\n    ]\n\n    try_extern 'void uiControlDestroy(uiControl *c)'\n    try_extern 'uintptr_t uiControlHandle(uiControl *c)'\n    try_extern 'uiControl *uiControlParent(uiControl *c)'\n    try_extern 'void uiControlSetParent(uiControl *c, uiControl *parent)'\n    try_extern 'int uiControlToplevel(uiControl *c)'\n    try_extern 'int uiControlVisible(uiControl *c)'\n    try_extern 'void uiControlShow(uiControl *c)'\n    try_extern 'void uiControlHide(uiControl *c)'\n    try_extern 'int uiControlEnabled(uiControl *c)'\n    try_extern 'void uiControlEnable(uiControl *c)'\n    try_extern 'void uiControlDisable(uiControl *c)'\n\n    try_extern 'uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, const char *typenamestr)'\n    try_extern 'void uiFreeControl(uiControl *c)'\n\n    try_extern 'void uiControlVerifySetParent(uiControl *c, uiControl *parent)'\n    try_extern 'int uiControlEnabledToUser(uiControl *c)'\n\n    try_extern 'void uiUserBugCannotSetParentOnToplevel(const char *type)'\n\n    # uiWindow\n\n    try_extern 'char *uiWindowTitle(uiWindow *w)'\n    try_extern 'void uiWindowSetTitle(uiWindow *w, const char *title)'\n    try_extern 'void uiWindowPosition(uiWindow *w, int *x, int *y)'\n    try_extern 'void uiWindowSetPosition(uiWindow *w, int x, int y)'\n    try_extern 'void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'\n    try_extern 'void uiWindowContentSize(uiWindow *w, int *width, int *height)'\n    try_extern 'void uiWindowSetContentSize(uiWindow *w, int width, int height)'\n    try_extern 'int uiWindowFullscreen(uiWindow *w)'\n    try_extern 'void uiWindowSetFullscreen(uiWindow *w, int fullscreen)'\n    try_extern 'void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'\n    try_extern 'void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *sender, void *senderData), void *data)'\n    try_extern 'void uiWindowOnFocusChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'\n    try_extern 'int uiWindowFocused(uiWindow *w)'\n    try_extern 'int uiWindowBorderless(uiWindow *w)'\n    try_extern 'void uiWindowSetBorderless(uiWindow *w, int borderless)'\n    try_extern 'void uiWindowSetChild(uiWindow *w, uiControl *child)'\n    try_extern 'int uiWindowMargined(uiWindow *w)'\n    try_extern 'void uiWindowSetMargined(uiWindow *w, int margined)'\n    try_extern 'int uiWindowResizeable(uiWindow *w)'\n    try_extern 'void uiWindowSetResizeable(uiWindow *w, int resizeable)'\n    try_extern 'uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)'\n\n    # uiButton\n\n    try_extern 'char *uiButtonText(uiButton *b)'\n    try_extern 'void uiButtonSetText(uiButton *b, const char *text)'\n    try_extern 'void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *sender, void *senderData), void *data)'\n    try_extern 'uiButton *uiNewButton(const char *text)'\n\n    # uiBox\n\n    try_extern 'void uiBoxAppend(uiBox *b, uiControl *child, int stretchy)'\n    try_extern 'int uiBoxNumChildren(uiBox *b)'\n    try_extern 'void uiBoxDelete(uiBox *b, int index)'\n    try_extern 'int uiBoxPadded(uiBox *b)'\n    try_extern 'void uiBoxSetPadded(uiBox *b, int padded)'\n    try_extern 'uiBox *uiNewHorizontalBox(void)'\n    try_extern 'uiBox *uiNewVerticalBox(void)'\n\n    # uiCheckbox\n\n    try_extern 'char *uiCheckboxText(uiCheckbox *c)'\n    try_extern 'void uiCheckboxSetText(uiCheckbox *c, const char *text)'\n    try_extern 'void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *sender, void *senderData), void *data)'\n    try_extern 'int uiCheckboxChecked(uiCheckbox *c)'\n    try_extern 'void uiCheckboxSetChecked(uiCheckbox *c, int checked)'\n    try_extern 'uiCheckbox *uiNewCheckbox(const char *text)'\n\n    # uiEntry\n\n    try_extern 'char *uiEntryText(uiEntry *e)'\n    try_extern 'void uiEntrySetText(uiEntry *e, const char *text)'\n    try_extern 'void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *sender, void *senderData), void *data)'\n    try_extern 'int uiEntryReadOnly(uiEntry *e)'\n    try_extern 'void uiEntrySetReadOnly(uiEntry *e, int readonly)'\n    try_extern 'uiEntry *uiNewEntry(void)'\n    try_extern 'uiEntry *uiNewPasswordEntry(void)'\n    try_extern 'uiEntry *uiNewSearchEntry(void)'\n\n    # uiLabel\n\n    try_extern 'char *uiLabelText(uiLabel *l)'\n    try_extern 'void uiLabelSetText(uiLabel *l, const char *text)'\n    try_extern 'uiLabel *uiNewLabel(const char *text)'\n\n    # uiTab\n\n    try_extern 'void uiTabAppend(uiTab *t, const char *name, uiControl *c)'\n    try_extern 'void uiTabInsertAt(uiTab *t, const char *name, int index, uiControl *c)'\n    try_extern 'void uiTabDelete(uiTab *t, int index)'\n    try_extern 'int uiTabNumPages(uiTab *t)'\n    try_extern 'int uiTabMargined(uiTab *t, int index)'\n    try_extern 'void uiTabSetMargined(uiTab *t, int index, int margined)'\n    try_extern 'uiTab *uiNewTab(void)'\n\n    # uiGroup\n\n    try_extern 'char *uiGroupTitle(uiGroup *g)'\n    try_extern 'void uiGroupSetTitle(uiGroup *g, const char *title)'\n    try_extern 'void uiGroupSetChild(uiGroup *g, uiControl *c)'\n    try_extern 'int uiGroupMargined(uiGroup *g)'\n    try_extern 'void uiGroupSetMargined(uiGroup *g, int margined)'\n    try_extern 'uiGroup *uiNewGroup(const char *title)'\n\n    # uiSpinbox\n\n    try_extern 'int uiSpinboxValue(uiSpinbox *s)'\n    try_extern 'void uiSpinboxSetValue(uiSpinbox *s, int value)'\n    try_extern 'void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *sender, void *senderData), void *data)'\n    try_extern 'uiSpinbox *uiNewSpinbox(int min, int max)'\n\n    # uiSlider\n\n    try_extern 'int uiSliderValue(uiSlider *s)'\n    try_extern 'void uiSliderSetValue(uiSlider *s, int value)'\n    try_extern 'int uiSliderHasToolTip(uiSlider *s)'\n    try_extern 'void uiSliderSetHasToolTip(uiSlider *s, int hasToolTip)'\n    try_extern 'void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *sender, void *senderData), void *data)'\n    try_extern 'void uiSliderOnReleased(uiSlider *s, void (*f)(uiSlider *sender, void *senderData), void *data)'\n    try_extern 'void uiSliderSetRange(uiSlider *s, int min, int max)'\n    try_extern 'uiSlider *uiNewSlider(int min, int max)'\n\n    # uiProgressBar\n\n    try_extern 'int uiProgressBarValue(uiProgressBar *p)'\n    try_extern 'void uiProgressBarSetValue(uiProgressBar *p, int n)'\n    try_extern 'uiProgressBar *uiNewProgressBar(void)'\n\n    # uiSeparator\n\n    try_extern 'uiSeparator *uiNewHorizontalSeparator(void)'\n    try_extern 'uiSeparator *uiNewVerticalSeparator(void)'\n\n    # uiCombobox\n\n    try_extern 'void uiComboboxAppend(uiCombobox *c, const char *text)'\n    try_extern 'void uiComboboxInsertAt(uiCombobox *c, int index, const char *text)'\n    try_extern 'void uiComboboxDelete(uiCombobox *c, int index)'\n    try_extern 'void uiComboboxClear(uiCombobox *c)'\n    try_extern 'int uiComboboxNumItems(uiCombobox *c)'\n    try_extern 'int uiComboboxSelected(uiCombobox *c)'\n    try_extern 'void uiComboboxSetSelected(uiCombobox *c, int index)'\n    try_extern 'void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *sender, void *senderData), void *data)'\n    try_extern 'uiCombobox *uiNewCombobox(void)'\n\n    # uiEditableCombobox\n\n    try_extern 'void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)'\n    try_extern 'char *uiEditableComboboxText(uiEditableCombobox *c)'\n    try_extern 'void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)'\n    try_extern 'void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *sender, void *senderData), void *data)'\n    try_extern 'uiEditableCombobox *uiNewEditableCombobox(void)'\n\n    # uiRadioButtons\n\n    try_extern 'void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)'\n    try_extern 'int uiRadioButtonsSelected(uiRadioButtons *r)'\n    try_extern 'void uiRadioButtonsSetSelected(uiRadioButtons *r, int index)'\n    try_extern 'void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *sender, void *senderData), void *data)'\n    try_extern 'uiRadioButtons *uiNewRadioButtons(void)'\n\n    # uiDateTimePicker\n\n    # time.h\n    TM = if Fiddle::WINDOWS\n           struct [\n             'int tm_sec',\n             'int tm_min',\n             'int tm_hour',\n             'int tm_mday',\n             'int tm_mon',\n             'int tm_year',\n             'int tm_wday',\n             'int tm_yday',\n             'int tm_isdst'\n           ]\n         else # The GNU C Library (glibc)\n           struct [\n             'int tm_sec',\n             'int tm_min',\n             'int tm_hour',\n             'int tm_mday',\n             'int tm_mon',\n             'int tm_year',\n             'int tm_wday',\n             'int tm_yday',\n             'int tm_isdst',\n             'long tm_gmtoff',\n             'const char *tm_zone'\n           ]\n         end\n\n    try_extern 'void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)'\n    try_extern 'void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)'\n    try_extern 'void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *sender, void *senderData), void *data)'\n    try_extern 'uiDateTimePicker *uiNewDateTimePicker(void)'\n    try_extern 'uiDateTimePicker *uiNewDatePicker(void)'\n    try_extern 'uiDateTimePicker *uiNewTimePicker(void)'\n\n    # uiMultilineEntry\n\n    try_extern 'char *uiMultilineEntryText(uiMultilineEntry *e)'\n    try_extern 'void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)'\n    try_extern 'void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)'\n    try_extern 'void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *sender, void *senderData), void *data)'\n    try_extern 'int uiMultilineEntryReadOnly(uiMultilineEntry *e)'\n    try_extern 'void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)'\n    try_extern 'uiMultilineEntry *uiNewMultilineEntry(void)'\n    try_extern 'uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)'\n\n    # uiMenuItem\n\n    try_extern 'void uiMenuItemEnable(uiMenuItem *m)'\n    try_extern 'void uiMenuItemDisable(uiMenuItem *m)'\n    try_extern 'void uiMenuItemOnClicked(uiMenuItem *m, void (*f)(uiMenuItem *sender, uiWindow *window, void *senderData), void *data)'\n    try_extern 'int uiMenuItemChecked(uiMenuItem *m)'\n    try_extern 'void uiMenuItemSetChecked(uiMenuItem *m, int checked)'\n\n    # uiMenu\n\n    try_extern 'uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)'\n    try_extern 'uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)'\n    try_extern 'uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)'\n    try_extern 'uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)'\n    try_extern 'uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)'\n    try_extern 'void uiMenuAppendSeparator(uiMenu *m)'\n    try_extern 'uiMenu *uiNewMenu(const char *name)'\n\n    try_extern 'char *uiOpenFile(uiWindow *parent)'\n    try_extern 'char *uiOpenFolder(uiWindow *parent)'\n    try_extern 'char *uiSaveFile(uiWindow *parent)'\n    try_extern 'void uiMsgBox(uiWindow *parent, const char *title, const char *description)'\n    try_extern 'void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)'\n\n    # uiArea\n\n    AreaHandler = struct [\n      'void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *)',\n      'void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *)',\n      'void (*MouseCrossed)(uiAreaHandler *, uiArea *, int left)',\n      'void (*DragBroken)(uiAreaHandler *, uiArea *)',\n      'int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *)'\n    ]\n\n    typealias 'uiWindowResizeEdge', 'int'\n\n    try_extern 'void uiAreaSetSize(uiArea *a, int width, int height)'\n    try_extern 'void uiAreaQueueRedrawAll(uiArea *a)'\n    try_extern 'void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)'\n    try_extern 'void uiAreaBeginUserWindowMove(uiArea *a)'\n    try_extern 'void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)'\n    try_extern 'uiArea *uiNewArea(uiAreaHandler *ah)'\n    try_extern 'uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)'\n\n    AreaDrawParams = struct [\n      'uiDrawContext *Context',\n      'double AreaWidth',\n      'double AreaHeight',\n      'double ClipX',\n      'double ClipY',\n      'double ClipWidth',\n      'double ClipHeight'\n    ]\n    typealias 'uiDrawBrushType', 'int'\n    typealias 'uiDrawLineCap', 'int'\n    typealias 'uiDrawLineJoin', 'int'\n    typealias 'uiDrawFillMode', 'int'\n\n    DrawMatrix = struct [\n      'double M11',\n      'double M12',\n      'double M21',\n      'double M22',\n      'double M31',\n      'double M32'\n    ]\n\n    DrawBrush = struct [\n      'uiDrawBrushType Type',\n      'double R',\n      'double G',\n      'double B',\n      'double A',\n      'double X0',\n      'double Y0',\n      'double X1',\n      'double Y1',\n      'double OuterRadius',\n      'uiDrawBrushGradientStop *Stops',\n      'size_t NumStops'\n    ]\n\n    DrawBrushGradientStop = struct [\n      'double Pos',\n      'double R',\n      'double G',\n      'double B',\n      'double A'\n    ]\n\n    DrawStrokeParams = struct [\n      'uiDrawLineCap Cap',\n      'uiDrawLineJoin Join',\n      'double Thickness',\n      'double MiterLimit',\n      'double *Dashes',\n      'size_t NumDashes',\n      'double DashPhase'\n    ]\n\n    # uiDrawPath\n    try_extern 'uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode)'\n    try_extern 'void uiDrawFreePath(uiDrawPath *p)'\n    try_extern 'void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)'\n    try_extern 'void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)'\n    try_extern 'void uiDrawPathLineTo(uiDrawPath *p, double x, double y)'\n    try_extern 'void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)'\n    try_extern 'void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)'\n    try_extern 'void uiDrawPathCloseFigure(uiDrawPath *p)'\n    try_extern 'void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)'\n    try_extern 'int uiDrawPathEnded(uiDrawPath *p)'\n    try_extern 'void uiDrawPathEnd(uiDrawPath *p)'\n    try_extern 'void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)'\n    try_extern 'void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)'\n\n    # uiDrawMatrix\n    try_extern 'void uiDrawMatrixSetIdentity(uiDrawMatrix *m)'\n    try_extern 'void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)'\n    try_extern 'void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)'\n    try_extern 'void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)'\n    try_extern 'void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)'\n    try_extern 'void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)'\n    try_extern 'int uiDrawMatrixInvertible(uiDrawMatrix *m)'\n    try_extern 'int uiDrawMatrixInvert(uiDrawMatrix *m)'\n    try_extern 'void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)'\n    try_extern 'void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)'\n\n    try_extern 'void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)'\n    try_extern 'void uiDrawClip(uiDrawContext *c, uiDrawPath *path)'\n    try_extern 'void uiDrawSave(uiDrawContext *c)'\n    try_extern 'void uiDrawRestore(uiDrawContext *c)'\n\n    # uiAttribute\n    try_extern 'void uiFreeAttribute(uiAttribute *a)'\n\n    typealias 'uiAttributeType', 'int'\n\n    try_extern 'uiAttributeType uiAttributeGetType(const uiAttribute *a)'\n    try_extern 'uiAttribute *uiNewFamilyAttribute(const char *family)'\n    try_extern 'const char *uiAttributeFamily(const uiAttribute *a)'\n    try_extern 'uiAttribute *uiNewSizeAttribute(double size)'\n    try_extern 'double uiAttributeSize(const uiAttribute *a)'\n\n    typealias 'uiTextWeight', 'int'\n\n    try_extern 'uiAttribute *uiNewWeightAttribute(uiTextWeight weight)'\n    try_extern 'uiTextWeight uiAttributeWeight(const uiAttribute *a)'\n\n    typealias 'uiTextItalic', 'int'\n\n    try_extern 'uiAttribute *uiNewItalicAttribute(uiTextItalic italic)'\n    try_extern 'uiTextItalic uiAttributeItalic(const uiAttribute *a)'\n\n    typealias 'uiTextStretch', 'int'\n\n    try_extern 'uiAttribute *uiNewStretchAttribute(uiTextStretch stretch)'\n    try_extern 'uiTextStretch uiAttributeStretch(const uiAttribute *a)'\n    try_extern 'uiAttribute *uiNewColorAttribute(double r, double g, double b, double a)'\n    try_extern 'void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha)'\n    try_extern 'uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a)'\n\n    typealias 'uiUnderline', 'int'\n\n    try_extern 'uiAttribute *uiNewUnderlineAttribute(uiUnderline u)'\n    try_extern 'uiUnderline uiAttributeUnderline(const uiAttribute *a)'\n\n    typealias 'uiUnderlineColor', 'int'\n\n    try_extern 'uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a)'\n    try_extern 'void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha)'\n\n    # uiOpenTypeFeatures\n\n    typealias 'uiOpenTypeFeaturesForEachFunc', 'void*'\n\n    try_extern 'uiOpenTypeFeatures *uiNewOpenTypeFeatures(void)'\n    try_extern 'void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf)'\n    try_extern 'uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf)'\n    try_extern 'void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value)'\n    try_extern 'void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d)'\n    try_extern 'int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value)'\n    try_extern 'void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data)'\n    try_extern 'uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf)'\n    try_extern 'const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a)'\n\n    # uiAttributedString\n\n    typealias 'uiAttributedStringForEachAttributeFunc', 'void*'\n\n    try_extern 'uiAttributedString *uiNewAttributedString(const char *initialString)'\n    try_extern 'void uiFreeAttributedString(uiAttributedString *s)'\n    try_extern 'const char *uiAttributedStringString(const uiAttributedString *s)'\n    try_extern 'size_t uiAttributedStringLen(const uiAttributedString *s)'\n    try_extern 'void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str)'\n    try_extern 'void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at)'\n    try_extern 'void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end)'\n    try_extern 'void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end)'\n    try_extern 'void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)'\n    try_extern 'size_t uiAttributedStringNumGraphemes(uiAttributedString *s)'\n    try_extern 'size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos)'\n    try_extern 'size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos)'\n\n    # uiFont\n\n    FontDescriptor = struct [\n      'char *Family',\n      'double Size',\n      'uiTextWeight Weight',\n      'uiTextItalic Italic',\n      'uiTextStretch Stretch'\n    ]\n\n    try_extern 'void uiLoadControlFont(uiFontDescriptor *f)'\n    try_extern 'void uiFreeFontDescriptor(uiFontDescriptor *desc)'\n\n    typealias 'uiDrawTextAlign', 'int'\n\n    DrawTextLayoutParams = struct [\n      'uiAttributedString *String',\n      'uiFontDescriptor *DefaultFont',\n      'double Width',\n      'uiDrawTextAlign Align'\n    ]\n\n    try_extern 'uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *params)'\n    try_extern 'void uiDrawFreeTextLayout(uiDrawTextLayout *tl)'\n    try_extern 'void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)'\n    try_extern 'void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)'\n\n    # uiFontButton\n\n    try_extern 'void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)'\n    try_extern 'void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *sender, void *senderData), void *data)'\n    try_extern 'uiFontButton *uiNewFontButton(void)'\n    try_extern 'void uiFreeFontButtonFont(uiFontDescriptor *desc)'\n\n    typealias 'uiModifiers', 'int'\n\n    AreaMouseEvent = struct [\n      'double X',\n      'double Y',\n      'double AreaWidth',\n      'double AreaHeight',\n      'int Down',\n      'int Up',\n      'int Count',\n      'uiModifiers Modifiers',\n      'uint64_t Held1To64'\n    ]\n\n    typealias 'uiExtKey', 'int'\n\n    AreaKeyEvent = struct [\n      'char Key',\n      'uiExtKey ExtKey',\n      'uiModifiers Modifier',\n      'uiModifiers Modifiers',\n      'int Up'\n    ]\n\n    # uiColorButton\n\n    try_extern 'void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)'\n    try_extern 'void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)'\n    try_extern 'void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *sender, void *senderData), void *data)'\n    try_extern 'uiColorButton *uiNewColorButton(void)'\n\n    # uiForm\n\n    try_extern 'void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)'\n    try_extern 'int uiFormNumChildren(uiForm *f)'\n    try_extern 'void uiFormDelete(uiForm *f, int index)'\n    try_extern 'int uiFormPadded(uiForm *f)'\n    try_extern 'void uiFormSetPadded(uiForm *f, int padded)'\n    try_extern 'uiForm *uiNewForm(void)'\n\n    typealias 'uiAlign', 'int'\n\n    typealias 'uiAt', 'int'\n\n    # uiGrid\n\n    try_extern 'void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)'\n    try_extern 'void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)'\n    try_extern 'int uiGridPadded(uiGrid *g)'\n    try_extern 'void uiGridSetPadded(uiGrid *g, int padded)'\n    try_extern 'uiGrid *uiNewGrid(void)'\n\n    # uiImage\n\n    try_extern 'uiImage *uiNewImage(double width, double height)'\n    try_extern 'void uiFreeImage(uiImage *i)'\n    try_extern 'void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)'\n\n    # uiTable\n    try_extern 'void uiFreeTableValue(uiTableValue *v)'\n\n    typealias 'uiTableValueType', 'int'\n\n    try_extern 'uiTableValueType uiTableValueGetType(const uiTableValue *v)'\n    try_extern 'uiTableValue *uiNewTableValueString(const char *str)'\n    try_extern 'const char *uiTableValueString(const uiTableValue *v)'\n    try_extern 'uiTableValue *uiNewTableValueImage(uiImage *img)'\n    try_extern 'uiImage *uiTableValueImage(const uiTableValue *v)'\n    try_extern 'uiTableValue *uiNewTableValueInt(int i)'\n    try_extern 'int uiTableValueInt(const uiTableValue *v)'\n    try_extern 'uiTableValue *uiNewTableValueColor(double r, double g, double b, double a)'\n    try_extern 'void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a)'\n\n    TableModelHandler = struct [\n      'int (*NumColumns)(uiTableModelHandler *, uiTableModel *)',\n      'uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int)',\n      'int (*NumRows)(uiTableModelHandler *, uiTableModel *)',\n      'uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column)',\n      'void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *)'\n    ]\n\n    try_extern 'uiTableModel *uiNewTableModel(uiTableModelHandler *mh)'\n    try_extern 'void uiFreeTableModel(uiTableModel *m)'\n    try_extern 'void uiTableModelRowInserted(uiTableModel *m, int newIndex)'\n    try_extern 'void uiTableModelRowChanged(uiTableModel *m, int index)'\n    try_extern 'void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)'\n\n    TableTextColumnOptionalParams = struct [\n      'int ColorModelColumn'\n    ]\n\n    TableParams = struct [\n      'uiTableModel *Model',\n      'int RowBackgroundColorModelColumn'\n    ]\n\n    try_extern 'void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'\n    try_extern 'void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)'\n    try_extern 'void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'\n    try_extern 'void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)'\n    try_extern 'void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'\n    try_extern 'void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)'\n    try_extern 'void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)'\n    try_extern 'int uiTableHeaderVisible(uiTable *t)'\n    try_extern 'void uiTableHeaderSetVisible(uiTable *t, int visible)'\n    try_extern 'uiTable *uiNewTable(uiTableParams *params)'\n    try_extern 'void uiTableOnRowClicked(uiTable *t, void (*f)(uiTable *t, int row, void *data), void *data)'\n    try_extern 'void uiTableOnRowDoubleClicked(uiTable *t, void (*f)(uiTable *t, int row, void *data), void *data)'\n\n    typealias 'uiSortIndicator', 'int'\n\n    try_extern 'void uiTableHeaderSetSortIndicator(uiTable *t, int column, uiSortIndicator indicator)'\n    try_extern 'uiSortIndicator uiTableHeaderSortIndicator(uiTable *t, int column)'\n    try_extern 'void uiTableHeaderOnClicked(uiTable *t, void (*f)(uiTable *sender, int column, void *senderData), void *data)'\n    try_extern 'int uiTableColumnWidth(uiTable *t, int column)'\n    try_extern 'void uiTableColumnSetWidth(uiTable *t, int column, int width)'\n\n    typealias 'uiTableSelectionMode', 'int'\n\n    try_extern 'uiTableSelectionMode uiTableGetSelectionMode(uiTable *t)'\n    try_extern 'void uiTableSetSelectionMode(uiTable *t, uiTableSelectionMode mode)'\n    try_extern 'void uiTableOnSelectionChanged(uiTable *t, void (*f)(uiTable *t, void *data), void *data)'\n    try_extern 'uiTableSelection* uiTableGetSelection(uiTable *t)'\n    try_extern 'void uiTableSetSelection(uiTable *t, uiTableSelection *sel)'\n    try_extern 'void uiFreeTableSelection(uiTableSelection* s)'\n\n    TableSelection = struct [\n      'int NumRows',\n      'int *Rows'\n    ]\n  end\nend\n"
  },
  {
    "path": "lib/libui/fiddle_patch.rb",
    "content": "module LibUI\n  # This module overrides Fiddle's mtehods\n  # - Fiddle::Importer#extern\n  # - Fiddle::CParser#parse_signature\n  # Original methods are in\n  # - https://github.com/ruby/fiddle/blob/master/lib/fiddle/import.rb\n  # - https://github.com/ruby/fiddle/blob/master/lib/fiddle/cparser.rb\n  # These changes add the ability to parse the signatures of functions given as arguments.\n\n  module FiddlePatch\n    def parse_signature(signature, tymap = nil)\n      tymap ||= {}\n      ctype, func, args = case compact(signature)\n                          when /^(?:[\\w\\*\\s]+)\\(\\*(\\w+)\\((.*?)\\)\\)(?:\\[\\w*\\]|\\(.*?\\));?$/\n                            [TYPE_VOIDP, Regexp.last_match(1), Regexp.last_match(2)]\n                          when /^([\\w\\*\\s]+[*\\s])(\\w+)\\((.*?)\\);?$/\n                            [parse_ctype(Regexp.last_match(1).strip, tymap), Regexp.last_match(2), Regexp.last_match(3)]\n                          else\n                            raise(\"can't parse the function prototype: #{signature}\")\n                          end\n      symname = func\n      callback_argument_types = {}                                              # Added\n      argtype = split_arguments(args).collect.with_index do |arg, idx|          # Added with_index\n        # Check if it is a function pointer or not\n        if arg =~ /\\(\\*.*\\)\\(.*\\)/                                              # Added\n          # From the arguments, create a notation that looks like a function declaration\n          # int(*f)(int *, void *) -> int f(int *, void *)\n          func_arg = arg.sub('(*', ' ').sub(')', '')                            # Added\n          # Use Fiddle's parse_signature method again.\n          callback_argument_types[idx] = parse_signature(func_arg)              # Added\n        end\n        parse_ctype(arg, tymap)\n      end\n      # Added callback_argument_types. Original method return only 3 values.\n      [symname, ctype, argtype, callback_argument_types]\n    end\n\n    def extern(signature, *opts)\n      symname, ctype, argtype, callback_argument_types = parse_signature(signature, type_alias)\n      opt = parse_bind_options(opts)\n      func = import_function(symname, ctype, argtype, opt[:call_type])\n\n      # callback_argument_types\n      func.instance_variable_set(:@callback_argument_types, \n                                   callback_argument_types) # Added\n      # attr_reader\n      def func.callback_argument_types\n        @callback_argument_types\n      end\n\n      # argument_types\n      # Ruby 2.7 Fiddle::Function dose not have @argument_types\n      # Ruby 3.0 Fiddle::Function has @argument_types\n      if func.instance_variable_defined?(:@argument_types)\n        # check if @argument_types are the same\n        if func.instance_variable_get(:@argument_types) != argtype\n          warn \"#{symname} func.argument_types:#{func.argument_types} != argtype #{argtype}\"\n        end\n      else\n        func.instance_variable_set(:@argument_types, argtype)\n      end\n      # attr_reader\n      def func.argument_types\n        @argument_types\n      end\n\n      name = symname.gsub(/@.+/, '')\n      @func_map[name] = func\n      # define_method(name){|*args,&block| f.call(*args,&block)}\n      begin\n        /^(.+?):(\\d+)/ =~ caller.first\n        file = Regexp.last_match(1)\n        line = Regexp.last_match(2).to_i\n      rescue StandardError\n        file, line = __FILE__, __LINE__ + 3\n      end\n      module_eval(<<-EOS, file, line)\n        def #{name}(*args, &block)\n          @func_map['#{name}'].call(*args,&block)\n        end\n      EOS\n      module_function(name)\n      func\n    end\n  end\n  private_constant :FiddlePatch\nend\n"
  },
  {
    "path": "lib/libui/libui_base.rb",
    "content": "module LibUI\n  module LibUIBase\n    FFI.func_map.each_key do |original_method_name|\n      name = Utils.convert_to_ruby_method(original_method_name)\n      func = FFI.func_map[original_method_name]\n\n      define_method(name) do |*args, &blk|\n        # Assume that block is the last argument.\n        args << blk if blk\n\n        # The proc object is converted to a Closure::BlockCaller object.\n        args.map!.with_index do |arg, idx|\n          next arg unless arg.is_a?(Proc)\n\n          # now arg must be Proc\n\n          # The types of the function arguments are stored in advance.\n          # See the monkey patch in ffi.rb.\n          _f, ret_type, arg_types = func.callback_argument_types[idx]\n          # TODO: raise some nice error if _f is nil.\n\n          callback = Fiddle::Closure::BlockCaller.new(\n            ret_type, arg_types, &arg\n          )\n          # Protect from GC\n          # by giving the owner object a reference to the callback.\n          # See https://github.com/kojix2/LibUI/issues/8\n          receiver = args.first\n          owner = if idx == 0 || # UI.queue_main{}\n                     receiver.nil? ||\n                     (receiver.respond_to?(:frozen?) && receiver.frozen?) # UI.timer(100) {}\n                    LibUIBase # keep a reference on an internal module to avoid GC\n                  else\n                    receiver # receiver object holds the callback\n                  end\n          if owner.instance_variable_defined?(:@callbacks)\n            owner.instance_variable_get(:@callbacks) << callback\n          else\n            owner.instance_variable_set(:@callbacks, [callback])\n          end\n          callback\n        end\n\n        # Make it possible to omit the last nil. This may be an over-optimization.\n        siz = func.argument_types.size - 1\n        args[siz] = nil if args.size == siz\n\n        FFI.public_send(original_method_name, *args)\n      end\n    end\n  end\n\n  private_constant :LibUIBase\nend\n"
  },
  {
    "path": "lib/libui/utils.rb",
    "content": "module LibUI\n  module Utils\n    class << self\n      def convert_to_ruby_method(original_method_name)\n        underscore(original_method_name.delete_prefix('ui'))\n      end\n\n      # Converting camel case to underscore case in ruby\n      # https://stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby#1509939\n      def underscore(str)\n        str.gsub(/::/, '/') # Maybe we don't need it.\n           .gsub(/([A-Z]+)([A-Z][a-z])/, '\\1_\\2')\n           .gsub(/([a-z\\d])([A-Z])/, '\\1_\\2')\n           .tr('-', '_')\n           .downcase\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/libui/version.rb",
    "content": "module LibUI\n  VERSION = '0.2.0'\nend\n"
  },
  {
    "path": "lib/libui.rb",
    "content": "require_relative 'libui/version'\nrequire_relative 'libui/utils'\nrequire_relative 'libui/error'\nrequire 'rbconfig'\n\nmodule LibUI\n  class << self\n    attr_accessor :ffi_lib\n  end\n\n  host_cpu = case RbConfig::CONFIG['host_cpu']\n             when /i\\d86/\n               'x86'\n             else\n               RbConfig::CONFIG['host_cpu']\n             end\n\n  lib_name = [\n    # For libui-ng shared libraries compiled with (rake vendor:build)\n    \"libui.#{RbConfig::CONFIG['host_cpu']}.#{RbConfig::CONFIG['SOEXT']}\",\n    # For libui-ng shared library downloaded from RubyGems.org\n    \"libui.#{host_cpu}.#{RbConfig::CONFIG['SOEXT']}\",\n    # For backward compatibility or manual compilation of libui-ng\n    \"libui.#{RbConfig::CONFIG['SOEXT']}\"\n  ]\n\n  self.ffi_lib = \\\n    if ENV['LIBUIDIR'] && !ENV['LIBUIDIR'].empty?\n      lib_name.lazy\n              .map { |name| File.expand_path(name, ENV['LIBUIDIR']) }\n              .find { |path| File.exist?(path) }\n    else\n      lib_name.lazy\n              .map { |name| File.expand_path(\"../vendor/#{name}\", __dir__) }\n              .find { |path| File.exist?(path) }\n    end\n\n  require_relative 'libui/ffi'\n  require_relative 'libui/libui_base'\n\n  extend LibUIBase\n\n  class << self\n    def init(opt = nil)\n      # Allocate uiInitOptions if not provided\n      unless opt\n        opt = FFI::InitOptions.malloc\n        opt.to_ptr.free = Fiddle::RUBY_FREE\n        opt.Size = FFI::InitOptions.size\n      end\n\n      err_ptr = super(opt) # uiInit returns const char* error or NULL on success\n      return nil if err_ptr.null?\n\n      # Convert C string to Ruby string and free the error string per API contract\n      err_msg = err_ptr.to_s\n      free_init_error(err_ptr)\n      warn err_msg\n      nil\n    end\n\n    # Gets the window position.\n    # Coordinates are measured from the top left corner of the screen.\n    # @param w [Fiddle::Pointer] Pointer of uiWindow instance.\n    # @return [Array] position of the window. [x, y]\n    # @note This method may return inaccurate or dummy values on Unix platforms.\n\n    def window_position(w)\n      x_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE)\n      y_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE)\n      super(w, x_ptr, y_ptr)\n      x = x_ptr[0, Fiddle::SIZEOF_INT].unpack1('i*')\n      y = y_ptr[0, Fiddle::SIZEOF_INT].unpack1('i*')\n      [x, y]\n    end\n\n    # FIXME: This is a workaround for a old version of Fiddle.\n    # Fiddle 1.1.2 and above should be able to handle one character string.\n    # See https://github.com/ruby/fiddle/issues/96\n\n    def open_type_features_add(otf, a, b, c, d, value)\n      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }\n      super(otf, a, b, c, d, value)\n    end\n\n    def open_type_features_remove(otf, a, b, c, d)\n      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }\n      super(otf, a, b, c, d)\n    end\n\n    def open_type_features_get(otf, a, b, c, d, value)\n      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }\n      super(otf, a, b, c, d, value)\n    end\n  end\n\n  ## UI_ENUM https://github.com/libui-ng/libui-ng/blob/master/ui.h\n\n  # ForEach\n  ForEachContinue    =    0\n  ForEachStop        =    1\n\n  # WindowResizeEdge\n  WindowResizeEdgeLeft        = 0\n  WindowResizeEdgeTop         = 1\n  WindowResizeEdgeRight       = 2\n  WindowResizeEdgeBottom      = 3\n  WindowResizeEdgeTopLeft     = 4\n  WindowResizeEdgeTopRight    = 5\n  WindowResizeEdgeBottomLeft  = 6\n  WindowResizeEdgeBottomRight = 7\n\n  # DrawBrushType\n  DrawBrushTypeSolid          = 0\n  DrawBrushTypeLinearGradient = 1\n  DrawBrushTypeRadialGradient = 2\n  DrawBrushTypeImage          = 3\n\n  # DrawLineCap\n  DrawLineCapFlat   = 0\n  DrawLineCapRound  = 1\n  DrawLineCapSquare = 2\n\n  # DrawLineJoin\n  DrawLineJoinMiter = 0\n  DrawLineJoinRound = 1\n  DrawLineJoinBevel = 2\n\n  DrawDefaultMiterLimit = 10.0\n\n  # DrawFillMode\n  DrawFillModeWinding   = 0\n  DrawFillModeAlternate = 1\n\n  # AttributeType\n  AttributeTypeFamily         = 0\n  AttributeTypeSize           = 1\n  AttributeTypeWeight         = 2\n  AttributeTypeItalic         = 3\n  AttributeTypeStretch        = 4\n  AttributeTypeColor          = 5\n  AttributeTypeBackground     = 6\n  AttributeTypeUnderline      = 7\n  AttributeTypeUnderlineColor = 8\n  AttributeTypeFeatures       = 9\n\n  # TextWeight\n  TextWeightMinimum    = 0\n  TextWeightThin       = 100\n  TextWeightUltraLight = 200\n  TextWeightLight      = 300\n  TextWeightBook       = 350\n  TextWeightNormal     = 400\n  TextWeightMedium     = 500\n  TextWeightSemiBold   = 600\n  TextWeightBold       = 700\n  TextWeightUltraBold  = 800\n  TextWeightHeavy      = 900\n  TextWeightUltraHeavy = 950\n  TextWeightMaximum    = 1000\n\n  # TextItalic\n  TextItalicNormal  = 0\n  TextItalicOblique = 1\n  TextItalicItalic  = 2\n\n  # TextStretch\n  TextStretchUltraCondensed = 0\n  TextStretchExtraCondensed = 1\n  TextStretchCondensed      = 2\n  TextStretchSemiCondensed  = 3\n  TextStretchNormal         = 4\n  TextStretchSemiExpanded   = 5\n  TextStretchExpanded       = 6\n  TextStretchExtraExpanded  = 7\n  TextStretchUltraExpanded  = 8\n\n  # Underline\n  UnderlineNone       = 0\n  UnderlineSingle     = 1\n  UnderlineDouble     = 2\n  UnderlineSuggestion = 3\n\n  # UnderlineColor\n  UnderlineColorCustom    = 0\n  UnderlineColorSpelling  = 1\n  UnderlineColorGrammar   = 2\n  UnderlineColorAuxiliary = 3\n\n  # DrawTextAlign\n  DrawTextAlignLeft   = 0\n  DrawTextAlignCenter = 1\n  DrawTextAlignRight  = 2\n\n  # Modifiers\n  ModifierCtrl  = (1 << 0)\n  ModifierAlt   = (1 << 1)\n  ModifierShift = (1 << 2)\n  ModifierSuper = (1 << 3)\n\n  # ExtKey\n  ExtKeyEscape    = 1\n  ExtKeyInsert    = 2\n  ExtKeyDelete    = 3\n  ExtKeyHome      = 4\n  ExtKeyEnd       = 5\n  ExtKeyPageUp    = 6\n  ExtKeyPageDown  = 7\n  ExtKeyUp        = 8\n  ExtKeyDown      = 9\n  ExtKeyLeft      = 10\n  ExtKeyRight     = 11\n  ExtKeyF1        = 12\n  ExtKeyF2        = 13\n  ExtKeyF3        = 14\n  ExtKeyF4        = 15\n  ExtKeyF5        = 16\n  ExtKeyF6        = 17\n  ExtKeyF7        = 18\n  ExtKeyF8        = 19\n  ExtKeyF9        = 20\n  ExtKeyF10       = 21\n  ExtKeyF11       = 22\n  ExtKeyF12       = 23\n  ExtKeyN0        = 24\n  ExtKeyN1        = 25\n  ExtKeyN2        = 26\n  ExtKeyN3        = 27\n  ExtKeyN4        = 28\n  ExtKeyN5        = 29\n  ExtKeyN6        = 30\n  ExtKeyN7        = 31\n  ExtKeyN8        = 32\n  ExtKeyN9        = 33\n  ExtKeyNDot      = 34\n  ExtKeyNEnter    = 35\n  ExtKeyNAdd      = 36\n  ExtKeyNSubtract = 37\n  ExtKeyNMultiply = 38\n  ExtKeyNDivide   = 39\n\n  # Align\n  AlignFill   = 0\n  AlignStart  = 1\n  AlignCenter = 2\n  AlignEnd    = 3\n\n  # At\n  AtLeading  = 0\n  AtTop      = 1\n  AtTrailing = 2\n  AtBottom   = 3\n\n  # TableValueType\n  TableValueTypeString = 0\n  TableValueTypeImage  = 1\n  TableValueTypeInt    = 2\n  TableValueTypeColor  = 3\n\n  # SortIndicator\n  SortIndicatorNone       = 0\n  SortIndicatorAscending  = 1\n  SortIndicatorDescending = 2\n\n  # editable\n  TableModelColumnNeverEditable  = -1\n  TableModelColumnAlwaysEditable = -2\n\n  # TableSelectionMode\n  TableSelectionModeNone       = 0\n  TableSelectionModeZeroOrOne  = 1\n  TableSelectionModeOne        = 2\n  TableSelectionModeZeroOrMany = 3\nend\n"
  },
  {
    "path": "libui.gemspec",
    "content": "require_relative 'lib/libui/version'\n\nGem::Specification.new do |spec|\n  spec.name          = 'libui'\n  spec.version       = LibUI::VERSION\n  spec.summary       = 'Ruby bindings to libui'\n  spec.homepage      = 'https://github.com/kojix2/libui'\n  spec.license       = 'MIT'\n\n  spec.authors       = ['kojix2']\n  spec.email         = ['2xijok@gmail.com']\n\n  spec.files = Dir['*.{md,txt}', '{lib}/**/*', 'vendor/{LICENSE,README}.md']\n  spec.require_paths = 'lib'\n\n  spec.required_ruby_version = '>= 2.6'\n\n  # Use the GEM_PLATFORM environment variable if specified\n  gem_platform = ENV['GEM_PLATFORM']\n  spec.platform = gem_platform if gem_platform && !gem_platform.empty? && gem_platform != 'ruby'\n\n  # See `gem help platform` for information on platform matching.\n  case spec.platform.to_s\n  when 'x86_64-linux'\n    spec.files << 'vendor/libui.x86_64.so'\n  when 'aarch64-linux'\n    spec.files << 'vendor/libui.aarch64.so'\n  when 'x86_64-darwin'\n    spec.files << 'vendor/libui.x86_64.dylib'\n  when 'arm64-darwin'\n    spec.files << 'vendor/libui.arm64.dylib'\n  when 'x64-mingw32', 'x64-mingw-ucrt'\n    spec.files << 'vendor/libui.x64.dll'\n  when 'x86-mingw32'\n    spec.files << 'vendor/libui.x86.dll'\n  else\n    spec.files.concat(Dir['vendor/*.{dll,dylib,so}']) # all\n  end\n\n  spec.add_dependency 'fiddle'\nend\n"
  },
  {
    "path": "scripts/README.md",
    "content": "# Scripts to check for changes in ui.h\n\nHelper scripts for the developer.\n\n## Usage\n\n```sh\nbash ui_diff.sh\n```\n## Requirement\n\n* [delta](https://github.com/dandavison/delta)\n* gcc - [remove comments from C/C++ code](https://stackoverflow.com/questions/2394017/remove-comments-from-c-c-code)\n"
  },
  {
    "path": "scripts/ui_diff.sh",
    "content": "#!/usr/bin/env bash\n\ncd \"$(dirname \"$0\")\"\n\ndelta <(./ui_ffi.rb) <(./ui_h.rb)\n# diff <(./ui_ffi.rb) <(./ui_h.rb)\n"
  },
  {
    "path": "scripts/ui_ffi.rb",
    "content": "#!/usr/bin/env ruby\n\nlibui_ffi_path = File.expand_path('../lib/libui/ffi.rb', __dir__)\nlibui_path = File.expand_path('../lib/libui.rb', __dir__)\n\n# Read the files into arrays by each line\nffi_lines = File.readlines(libui_ffi_path).map(&:strip)\nlibui_lines = File.readlines(libui_path)\n\n# Count try_extern\nmatches = ffi_lines.select { _1.start_with?('try_extern') }\nputs 'count try_extern'\nputs matches.count\n\n# Print try_extern calls\nputs(matches.map { _1.delete_prefix(\"try_extern '\").delete_suffix(\"'\") })\n\n# Print enum names\nputs(libui_lines.select { _1.start_with?('  # ') }\n                .map { _1.delete_prefix('  # ').strip })\n"
  },
  {
    "path": "scripts/ui_h.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire 'open-uri'\nrequire 'tempfile'\n\nUI_H_URL = 'https://raw.githubusercontent.com/libui-ng/libui-ng/master/ui.h'.freeze\n\nui_h = []\nTempfile.open(['ui', '.h']) do |tf|\n  tf.write URI.open(UI_H_URL).read\n  tf.close\n\n  cmd_gcc = 'gcc -fpreprocessed -P -dD -E'\n\n  ui_h = `#{cmd_gcc} #{tf.path}` # Remove comments\n         .split(\"\\n\").select { !_1.strip.start_with?('#') }.join(\"\\n\") # Remove macros\n         .split(';').map do\n           _1.split(\"\\n\").map(&:strip).join(' ')\n             .strip.squeeze(' ')\n         end\nend\n# Count\n# - Count the occurrences of '_UI_EXTERN'\n\nputs 'count _UI_EXTERN'\nputs(ui_h.count { _1.start_with?('_UI_EXTERN') })\n\n# Functions\n# - Print the definitions of functions marked with '_UI_EXTERN'\n\nputs(ui_h.select { _1.start_with?('_UI_EXTERN') }\n         .map { _1.delete_prefix('_UI_EXTERN ').squeeze(' ') })\n\n# Enums\n# - Print the names of enums marked with '_UI_ENUM'\n\nputs(ui_h.select { _1.include?('_UI_ENUM') }\n         .map { _1.match(/_UI_ENUM\\(ui(\\w+)/)[1] })\n"
  },
  {
    "path": "test/libui_test.rb",
    "content": "require 'test_helper'\n\nclass LibUITest < Minitest::Test\n  def test_that_it_has_a_version_number\n    refute_nil ::LibUI::VERSION\n  end\n\n  def test_ffi_method_call\n    pt = LibUI::FFI::InitOptions.malloc\n    pt.to_ptr.free = Fiddle::RUBY_FREE\n    assert_kind_of Fiddle::Pointer, LibUI::FFI.uiInit(pt)\n    assert_nil LibUI::FFI.uiQuit\n  end\n\n  def test_method_call\n    assert_nil LibUI.init\n    assert_nil LibUI.quit\n  end\n\n  def test_basic_window\n    assert_nil LibUI.init\n    assert_kind_of Fiddle::Pointer, (\n      main_window = LibUI.new_window('hello world', 300, 200, 1)\n    )\n    assert_nil LibUI.control_show(main_window)\n    assert_nil(\n      LibUI.window_on_closing(main_window) do\n        LibUI.control_destroy(main_window)\n        LibUI.quit\n        0\n      end\n    )\n    # LibUI.main\n    assert_nil LibUI.quit\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "$LOAD_PATH.unshift File.expand_path('../lib', __dir__)\nrequire 'libui'\n\nrequire 'minitest/autorun'\nrequire 'minitest/pride'\n"
  },
  {
    "path": "test/utils_test.rb",
    "content": "require 'test_helper'\n\nclass LibUIUtilsTest < Minitest::Test\n  def test_convert_to_ruby_method\n    rbmethod1 = LibUI::Utils.convert_to_ruby_method('uiNewMatz')\n    rbmethod2 = LibUI::Utils.convert_to_ruby_method('AINewMatz')\n    assert_equal 'new_matz', rbmethod1\n    assert_equal 'ai_new_matz', rbmethod2\n  end\n\n  def test_underscore\n    assert_equal '3v3_v_ap_f2_er7s@_f_d/d_sc', LibUI::Utils.underscore('3v3VApF2Er7s@-fD::DSc')\n  end\nend\n"
  },
  {
    "path": "vendor/LICENSE.md",
    "content": "Copyright (c) 2022 libui-ng authors\n\nCopyright (c) 2014 Pietro Gagliardi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "vendor/README.md",
    "content": "# libui-ng: a portable GUI library for C\n\nFork of [andlabs/libui](https://github.com/andlabs/libui). This README is being written.<br>\n[![Build Status, GitHub Actions](https://github.com/libui-ng/libui-ng/actions/workflows/build.yml/badge.svg)](https://github.com/libui-ng/libui-ng/actions/workflows/build.yml)\n[![GitLab](https://img.shields.io/badge/gitlab-%23181717.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/libui-ng/libui-ng)\n[![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/libui-ng/libui-ng)\n\n## About\n\nSimple and portable (but not inflexible) GUI library in C that uses the native GUI technologies of each platform it supports.\n\n## Status\n\nlibui-ng is currently **mid-alpha** software.\n\nSee [CHANGELOG.md](CHANGELOG.md)\n\n*Old announcements can be found in the [news.md](old/news.md) file.*\n\n## Runtime Requirements\n\n* Windows: Windows Vista SP2 with Platform Update or newer\n* Unix: GTK+ 3.10 or newer\n* Mac OS X: OS X 10.8 or newer\n\n## Build Requirements\n\n* All platforms:\n\t* [Meson](https://mesonbuild.com/) 0.58.0 or newer\n\t* Any of Meson's backends; this section assumes you are using [Ninja](https://ninja-build.org/), but there is no reason the other backends shouldn't work.\n* Windows: either\n\t* Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) — you can build either a static or a shared library\n\t* MinGW-w64 (other flavors of MinGW may not work) — **you can only build a static library**; shared library support will be re-added once the following features come in:\n\t\t* [Isolation awareness](https://msdn.microsoft.com/en-us/library/aa375197%28v=vs.85%29.aspx), which is how you get themed controls from a DLL without needing a manifest\n* Unix: nothing else specific\n* Mac OS X: nothing else specific, so long as you can build Cocoa programs\n\n## Building\n\nlibui-ng mainly uses [the standard Meson build options](https://mesonbuild.com/Builtin-options.html).\n\n```\n$ # in the top-level libui-ng directory run:\n$ meson setup build [options]\n$ ninja -C build\n```\n\nOnce this completes, everything will be under `build/meson-out/`.\n\nlibui-ng specific options:\n\n- `-Dtests=(true|false)` controls whether tests are built; defaults to `true`\n- `-Dexamples=(true|false)` controls whether examples are built; defaults to `true`\n\nMost important Meson options:\n\n* `--buildtype=(debug|release|...)` controls the type of build made; the default is `debug`. For a full list of valid values, consult [the Meson documentation](https://mesonbuild.com/Running-Meson.html).\n* `--default-library=(shared|static)` controls whether libui is built as a shared library or a static library; the default is `shared`. You currently cannot specify `both`, as the build process changes depending on the target type (though I am willing to look into changing things if at all possible).\n* `-Db_sanitize=which` allows enabling the chosen [sanitizer](https://github.com/google/sanitizers) on a system that supports sanitizers. The list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#base-options).\n* `--backend=backend` allows using the specified `backend` for builds instead of `ninja` (the default). A list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options).\n* `--wrap-mode=(forcefallback|nofallback|nodownload|...)` controls which cmocka library version to use in test enabled builds. The default is `forcefallback` to pull and build a local copy. Package maintainers may wish to choose `nofallback` to use the system's library and declare `cmocka` a build time dependency or `nodownload`, see [the Meson documentation](https://mesonbuild.com/Subprojects.html#commandline-options) for more details.\n\nMost other built-in options will work, though keep in mind there are a handful of options that cannot be overridden because libui depends on them holding a specific value; if you do override these, though, libui will warn you when you run `meson`.\n\nThe Meson website and documentation has more in-depth usage instructions.\n\nFor the sake of completeness, I should note that the default value of `--layout` is `flat`, not the usual `mirror`. This is done both to make creating the release archives easier as well as to reduce the chance that shared library builds will fail to start on Windows because the DLL is in another directory. You can always specify this manually if you want.\n\nBackends other than `ninja` should work, but are untested by me.\n\n## Testing\n\n### Automated Unit Tests\n\nRun the included unit tests via `meson test -C build`. Alternatively you can also run the `unit` executable manually.\n\n### Manual Testing Suite\n\nRun the manual quality assurance test suite via `qa` and follow the instructions laid out within.\n\n## Installation\n\nMeson also supports installing from source; if you use Ninja, just do\n\n```\n$ ninja -C build install\n```\n\nWhen running `meson`, the `--prefix` option will set the installation prefix. [The Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options) has more information, and even lists more fine-grained options that you can use to control the installation.\n\n#### Arch Linux\n\nCan be built from AUR: https://aur.archlinux.org/packages/libui-ng-git/\n\n## Documentation [WIP]\n\n[API](https://libui-ng.github.io/libui-ng/), check the [modules](https://libui-ng.github.io/libui-ng/modules.html) section for an overview of (nearly all) uiControls.\n\nConsult the `ui.h` comments for the uiControls missing in the docs.\n\nCheck the `examples` directory for fully fledged examples. Check out the `tests` directory and subdirectories for more real world usage.\n\n## Language Bindings\n\nlibui was originally written as part of my [package ui for Go](https://github.com/andlabs/ui). Now that libui is separate, package ui has become a binding to libui. As such, package ui is the only official binding.\n\nOther people have made bindings to other languages:\n\nLanguage | Bindings\n--- | ---\nC++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike)\nC# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding)\nC# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/)\nCHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui)\nCommon Lisp | [jinwoo/cl-ui](https://github.com/jinwoo/cl-ui)\nCrystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron), [iu](https://github.com/grkek/iu)\nD | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid)\nEuphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria)\nHarbour | [hbui](https://github.com/rjopek/hbui)\nHaskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui)\nJanet | [JanetUI](https://github.com/janet-lang/janetui)\nJavaScript/Node.js | [libui-node](https://github.com/parro-it/libui-node), [libui.js (merged into libui-node?)](https://github.com/mavenave/libui.js), [proton-native](https://github.com/kusti8/proton-native), [vuido](https://github.com/mimecorg/vuido)\nJulia | [Libui.jl](https://github.com/joa-quim/Libui.jl)\nKotlin | [kotlin-libui](https://github.com/msink/kotlin-libui)\nLua | [libuilua](https://github.com/zevv/libuilua), [libui-lua](https://github.com/mdombroski/libui-lua), [lui](http://tset.de/lui/index.html), [lui](https://github.com/zhaozg/lui)\nNim | [ui](https://github.com/nim-lang/ui), [uing](https://github.com/neroist/uing)\nPerl6 | [perl6-libui](https://github.com/Garland-g/perl6-libui)\nPHP | [ui](https://github.com/krakjoe/ui), [Ardillo](https://github.com/ardillo-php/ext)\nPython | [pylibui](https://github.com/joaoventura/pylibui)\nRing | [RingLibui](https://github.com/ring-lang/ring/tree/master/extensions/ringlibui)\nRuby | [libui-ruby](https://github.com/jamescook/libui-ruby), [LibUI](https://github.com/kojix2/libui), [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui)\nRust | [libui-ng-sys](https://github.com/norepimorphism/libui-ng-sys), [boing](https://github.com/norepimorphism/boing), [libui-rs](https://github.com/rust-native-ui/libui-rs), [libui](https://github.com/libui-rs/libui)\nScala | [scalaui](https://github.com/lolgab/scalaui)\nSwift | [libui-swift](https://github.com/sclukey/libui-swift)\n\n## Frequently Asked Questions\n\n### Why does my program start in the background on OS X if I run from the command line?\nOS X normally does not start program executables directly; instead, it uses [Launch Services](https://developer.apple.com/reference/coreservices/1658613-launch_services?language=objc) to coordinate the launching of the program between the various parts of the system and the loading of info from an .app bundle. One of these coordination tasks is responsible for bringing a newly launched app into the foreground. This is called \"activation\".\n\nWhen you run a binary directly from the Terminal, however, you are running it directly, not through Launch Services. Therefore, the program starts in the background, because no one told it to activate! Now, it turns out [there is an API](https://developer.apple.com/reference/appkit/nsapplication/1428468-activateignoringotherapps) that we can use to force our app to be activated. But if we use it, then we'd be trampling over Launch Services, which already knows whether it should activate or not. Therefore, libui does not step over Launch Services, at the cost of requiring an extra user step if running directly from the command line.\n\nSee also [this](https://github.com/andlabs/libui/pull/20#issuecomment-211381971) and [this](http://stackoverflow.com/questions/25318524/what-exactly-should-i-pass-to-nsapp-activateignoringotherapps-to-get-my-appl).\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md)\n\n## Screenshots\n\nFrom examples/controlgallery:\n\n![Windows](examples/controlgallery/windows.png)\n\n![Unix](examples/controlgallery/unix.png)\n\n![OS X](examples/controlgallery/darwin.png)\n"
  }
]