Full Code of kojix2/LibUI for AI

main 1442dcb96d4c cached
60 files
170.3 KB
49.0k tokens
80 symbols
1 requests
Download .txt
Repository: kojix2/LibUI
Branch: main
Commit: 1442dcb96d4c
Files: 60
Total size: 170.3 KB

Directory structure:
gitextract_3lw4y_4b/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── doc.yml
│       ├── release-rubygems.yml
│       └── test.yml
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── examples/
│   ├── basic_area.rb
│   ├── basic_button.rb
│   ├── basic_draw_text.rb
│   ├── basic_entry.rb
│   ├── basic_table.rb
│   ├── basic_table_image.rb
│   ├── basic_window.rb
│   ├── control_gallery.rb
│   ├── date_time_picker.rb
│   ├── draw_text.rb
│   ├── font_button.rb
│   ├── gpt2_notepad.rb
│   ├── histogram.rb
│   ├── midi_player.rb
│   ├── simple_notepad.rb
│   ├── spectrum.rb
│   └── turing_pattern.rb
├── examples2/
│   ├── README.md
│   ├── button.rb
│   ├── checkbox.rb
│   ├── color_button.rb
│   ├── combobox.rb
│   ├── date_picker.rb
│   ├── editable_combobox.rb
│   ├── entry.rb
│   ├── font_button.rb
│   ├── grid.rb
│   ├── multiline_entry.rb
│   ├── password_entry.rb
│   ├── progress_bar.rb
│   ├── search_entry.rb
│   ├── slider.rb
│   ├── todo/
│   │   └── todo.md
│   └── window.rb
├── lib/
│   ├── libui/
│   │   ├── error.rb
│   │   ├── ffi.rb
│   │   ├── fiddle_patch.rb
│   │   ├── libui_base.rb
│   │   ├── utils.rb
│   │   └── version.rb
│   └── libui.rb
├── libui.gemspec
├── scripts/
│   ├── README.md
│   ├── ui_diff.sh
│   ├── ui_ffi.rb
│   └── ui_h.rb
├── test/
│   ├── libui_test.rb
│   ├── test_helper.rb
│   └── utils_test.rb
└── vendor/
    ├── LICENSE.md
    └── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
  directory: "/"
  schedule:
    interval: "weekly"


================================================
FILE: .github/workflows/doc.yml
================================================
name: doc

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ruby
      - name: Generate document
        run: gem install -N yard && yard doc
      - name: Publish Documentation on GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./doc


================================================
FILE: .github/workflows/release-rubygems.yml
================================================
name: Release libui to RubyGems

on:
  push:
    tags:
      - "v*.*.*"
  workflow_dispatch:

jobs:
  push:
    environment: release-rubygems
    strategy:
      fail-fast: false
      matrix:
        # Default Ruby version: 3.4 (used for all platforms except x64-mingw32)
        include:
          - os: ubuntu-latest
            ruby-version: "3.4"
            gem_platform: x86_64-linux
          - os: ubuntu-24.04-arm
            ruby-version: "3.4"
            gem_platform: aarch64-linux
          - os: macos-13 # Intel
            ruby-version: "3.4"
            gem_platform: x86_64-darwin
          - os: macos-14 # Apple Silicon
            ruby-version: "3.4"
            gem_platform: arm64-darwin
          - os: windows-latest
            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
            gem_platform: x64-mingw32
          - os: windows-latest
            ruby-version: "3.4"
            gem_platform: x64-mingw-ucrt

    runs-on: ${{ matrix.os }}

    permissions:
      id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
      contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag

    env:
      GEM_PLATFORM: ${{ matrix.gem_platform }}

    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}
          bundler-cache: true

      - name: Show versions
        run: |
          ruby -v
          gem -v
          bundle -v
        shell: bash

      - name: Update RubyGems for Windows Ruby 2.7 (enable 'gem exec')
        if: ${{ matrix.gem_platform == 'x64-mingw32' }}
        run: |
          gem update --system 3.4.22
          gem --version
        shell: bash

      - name: Prepare vendored binary (bundle exec rake vendor:auto)
        run: |
          bundle exec rake vendor:auto
        shell: bash

      - name: Release gem to RubyGems.org
        uses: rubygems/release-gem@v1


================================================
FILE: .github/workflows/test.yml
================================================
name: test
on:
  - push
  - pull_request
  - workflow_dispatch
jobs:
  build:
    name: ${{ matrix.runner }} Ruby ${{ matrix.ruby }}
    strategy:
      fail-fast: false
      matrix:
        runner:
          - ubuntu-latest
          - ubuntu-24.04-arm
          - macos-15-intel
          - macos-latest
          - windows-latest
        ruby: ["2.7", "3.2", "3.3", "3.4", "4.0"]
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
      - name: Install dependencies
        run: bundle install
      - name: Download libui shared libraries
        run: bundle exec rake vendor:auto
      - name: Rake test (XVFB)
        uses: coactions/setup-xvfb@v1
        with:
          run: bundle exec rake test


================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.lock
/vendor/bundle
/vendor/*libui*
build.log

# gpt2 exmaple
*.onnx
gpt2.bin
gpt2.i2w

================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

# Specify your gem's dependencies in libui.gemspec
gemspec

gem 'minitest'
gem 'rake'
gem 'rubyzip'

# group :development do
#   gem 'chunky_png'
#   gem 'numo-narray'
# end


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2020-present kojix2

Permission 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:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE 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.


================================================
FILE: README.md
================================================
# LibUI

[![test](https://github.com/kojix2/LibUI/actions/workflows/test.yml/badge.svg)](https://github.com/kojix2/LibUI/actions/workflows/test.yml)
[![Gem Version](https://badge.fury.io/rb/libui.svg)](https://badge.fury.io/rb/libui)
<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>
[![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)
[![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)

LibUI is a Ruby wrapper for libui family.

:rocket: [libui-ng](https://github.com/libui-ng/libui-ng) - A cross-platform portable GUI library

:wrench: [libui-dev](https://github.com/petabyt/libui-dev) - Native UI library for C - with some extras

:radio_button: [libui](https://github.com/andlabs/libui) - Original version by andlabs.

## Installation

```sh
gem install libui # --pre
```

- The gem package includes the libui-ng shared library for Windows, Mac, and Linux.
  - Namely `libui.x64.dll`/`libui.x86.dll`, `libui.x86_64.dylib`/`libui.arm64.dylib`, or `libui.x86_64.so`/`libui.aarch64.so`.
- No dependencies required.
  - The libui gem uses the standard Ruby library [Fiddle](https://github.com/ruby/fiddle) to call C functions.

| Windows                                                                                                          | Mac                                                                                                              | Linux                                                                                                            |
| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| <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"> |

Notes:

- 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.
- 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))
- Users with [Raspberry Pi](https://www.raspberrypi.com/) or other platforms will need to compile the C libui library. See the [Development](#development) section.

## Usage

```ruby
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('hello world', 200, 100, 1)

button = UI.new_button('Button')

UI.button_on_clicked(button) do
  UI.msg_box(main_window, 'Information', 'You clicked the button')
end

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.window_set_child(main_window, button)
UI.control_show(main_window)

UI.main
UI.quit
```

For more examples, see the [examples](https://github.com/kojix2/libui/tree/main/examples) directory.

### General Rules

Compared to the original libui library written in C:

- Method names use snake_case.
- The last argument can be omitted if it's nil.
- A block can be passed as a callback.
  - The block will be converted to a Proc object and added as the last argument.
  - The last argument can still be omitted when nil.

You can use [the libui-ng API documentation](https://libui-ng.github.io/libui-ng/) as a reference.

### DSLs for LibUI

LibUI is not object-oriented because it is a thin Ruby wrapper (binding) for the procedural C libui library, mirroring its API structure.

To 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):

- [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui)
- [libui_paradise](https://rubygems.org/gems/libui_paradise)

### Working with fiddle pointers

```ruby
require 'libui'
UI = LibUI
UI.init
```

To convert a pointer to a string:

```ruby
label = UI.new_label("Ruby")
p pointer = UI.label_text(label) # #<Fiddle::Pointer>
p pointer.to_s # Ruby
```

If you need to use C structs, you can do the following:

```ruby
font_button = UI.new_font_button

# Allocate memory
font_descriptor = UI::FFI::FontDescriptor.malloc
font_descriptor.to_ptr.free = Fiddle::RUBY_FREE
# font_descriptor = UI::FFI::FontDescriptor.malloc(Fiddle::RUBY_FREE) # fiddle 1.0.1 or higher

UI.font_button_on_changed(font_button) do
  UI.font_button_font(font_button, font_descriptor)
  p family:  font_descriptor.Family.to_s,
    size:    font_descriptor.Size,
    weight:  font_descriptor.Weight,
    italic:  font_descriptor.Italic,
    stretch: font_descriptor.Stretch
end
```

- Callbacks
  - In Ruby/Fiddle, a C callback function is written as an object of
    `Fiddle::Closure::BlockCaller` or `Fiddle::Closure`.
    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.

```ruby
# Assign to a local variable to prevent it from being collected by GC.
handler.MouseEvent   = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.DragBroken   = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
```

### Creating a Windows executable (.exe) with OCRA

OCRA (One-Click Ruby Application) builds Windows executables from Ruby source code.

- https://github.com/larsch/ocra/

To build an exe with Ocra, include 3 DLLs from the ruby_builtin_dlls folder:

```sh
ocra examples/control_gallery.rb        ^
  --dll ruby_builtin_dlls/libssp-0.dll  ^
  --dll ruby_builtin_dlls/libgmp-10.dll ^
  --dll ruby_builtin_dlls/libffi-7.dll  ^
  --gem-all=fiddle                      ^
```

Add additional options below if necessary:

```sh
  --window                              ^
  --add-all-core                        ^
  --chdir-first                         ^
  --icon assets\app.ico                 ^
  --verbose                             ^
  --output out\gallery.exe
```

## Development

```sh
git clone https://github.com/kojix2/libui
cd libui
bundle install
bundle exec rake vendor:auto
bundle exec rake test
```

### Pre-built shared libraries for libui-ng

Download pre-built libui-ng shared libraries (per your current platform):

```sh
bundle exec rake vendor:auto
```

Clean downloaded vendor files (keeps `vendor/{LICENSE,README}.md`):

```sh
bundle exec rake vendor:clean
```

### Using your own libui build

If 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).

### Publishing gems

#### Automated Publishing

Push a version tag to automatically publish platform-specific gems:

```sh
git tag v0.2.0
git push origin v0.2.0
```

Requires `RUBYGEMS_API_KEY` repository secret with scoped API key.

#### Manual Publishing

```sh
ls vendor             # check the vendor directory
rm -rf pkg            # remove previously built gems
rake build_platform   # build gems

# Check the contents of the gem
find pkg -name *.gem -exec sh -c "echo; echo \# {}; tar -O -f {} -x data.tar.gz | tar zt" \;

rake release_platform # publish gems
```

Windows Ruby (x64-mingw32 or x64-mingw-ucrt)

```sh
gem install rake rubyzip
GEM_PLATFORM=x64-mingw32 rake vendor:clean
GEM_PLATFORM=x64-mingw32 rake vendor:auto
GEM_PLATFORM=x64-mingw32 gem build libui.gemspec
gem push libui-0.2.0-x64-mingw32.gem
```

### libui or libui-ng

- From version 0.1.X, LibUI supports only libui-ng.
- Version 0.0.X only supports andlabs/libui.

## Contributing

Would you like to contribute to LibUI?

- Please feel free to send us your [pull requests](https://github.com/kojix2/libui/pulls).
  - Small corrections, such as typo fixes, are appreciated.
- Did you find any bugs? Submit them in the [issues](https://github.com/kojix2/LibUI/issues) section!

Do you need commit rights?

- 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.
- Many OSS projects become abandoned because only the founder has commit rights to the original repository.

Support libui-ng development

- Contributing to the development of libui-ng is a contribution to the entire libui community, including Ruby's LibUI.
- 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.

## Acknowledgements

This project is inspired by libui-ruby.

- https://github.com/jamescook/libui-ruby

While libui-ruby uses [Ruby-FFI](https://github.com/ffi/ffi), this gem uses [Fiddle](https://github.com/ruby/fiddle).

## License

[MIT License](https://opensource.org/licenses/MIT).


================================================
FILE: Rakefile
================================================
# frozen_string_literal: true

require 'rake/testtask'
require 'rbconfig'
require 'fileutils'
require 'zip'
require 'bundler/gem_tasks'

require_relative 'lib/libui/version'

# Configuration
COMMIT_HASH = ENV['LIBUI_NG_COMMIT_HASH'] || 'd37a4d8'

# Path constants
BUILD_DIR = 'builddir'
MESON_OUT_DIR = "#{BUILD_DIR}/meson-out"
DEBUG_DIR = 'libui/debug'

# Platform-specific configuration for shared libraries
PLATFORM_CONFIG = {
  'arm64-darwin' => [
    { zip: 'macOS-arm64-shared-release.zip', src: 'builddir/meson-out/libui.dylib', dest: 'vendor/libui.arm64.dylib' }
  ],
  'x86_64-darwin' => [
    { zip: 'macOS-x64-shared-release.zip', src: 'builddir/meson-out/libui.dylib', dest: 'vendor/libui.x86_64.dylib' }
  ],
  'x86_64-linux' => [
    { zip: 'Ubuntu-x64-shared-release.zip', src: 'builddir/meson-out/libui.so', dest: 'vendor/libui.x86_64.so' }
  ],
  'aarch64-linux' => [
    { zip: 'Ubuntu-arm64-shared-release.zip', src: 'builddir/meson-out/libui.so', dest: 'vendor/libui.aarch64.so' }
  ],
  'x64-mingw32' => [
    { zip: 'Windows-x64-msvc-shared-release.zip', src: 'builddir/meson-out/libui.dll', dest: 'vendor/libui.x64.dll' }
  ],
  'x86-mingw32' => [
    { zip: 'Windows-x86-msvc-shared-release.zip', src: 'builddir/meson-out/libui.dll', dest: 'vendor/libui.x86.dll' }
  ]
}.freeze

# Test configuration
Rake::TestTask.new(:test) do |t|
  t.libs << 'test'
  t.libs << 'lib'
  t.test_files = FileList['test/**/*_test.rb']
end

task default: :test

# Utility functions
def log_message(message)
  puts "[Rake] #{message}"
end

def detect_platform_config_key
  # Environment variable override
  if ENV['GEM_PLATFORM']
    platform_str = ENV['GEM_PLATFORM']
    return platform_str if PLATFORM_CONFIG.key?(platform_str)
  end

  current_platform = Gem::Platform.local

  # Try exact match first
  return current_platform.to_s if PLATFORM_CONFIG.key?(current_platform.to_s)

  # Use RubyGems matching with custom Windows mingw support
  PLATFORM_CONFIG.keys.find do |config_key|
    config_platform = Gem::Platform.new(config_key)

    # Standard RubyGems matching
    if Gem::Platform.send(:match_platforms?, current_platform, [config_platform])
      true
    # Custom Windows mingw matching for x64 variants
    elsif config_key == 'x64-mingw32' &&
          current_platform.os.start_with?('mingw') &&
          %w[x64 x86_64].include?(current_platform.cpu)
      true
    # Custom Windows mingw matching for x86 variants
    elsif config_key == 'x86-mingw32' &&
          current_platform.os.start_with?('mingw') &&
          %w[x86 i386 i686].include?(current_platform.cpu)
      true
    else
      false
    end
  end
end

def url_for_libui_ng_commit(file_name)
  "https://github.com/kojix2/libui-ng/releases/download/commit-#{COMMIT_HASH}/#{file_name}"
end

def download_file(file_name, url)
  log_message "Running: curl -L -o #{file_name} #{url}"
  curl_status = system("curl -L -o #{file_name} #{url}")
  return true if curl_status && File.exist?(file_name)

  warn "Warning: Failed to download #{file_name} from #{url}"
  false
end

def extract_zip_files(file_name, lib_paths)
  return unless file_name.end_with?('.zip')

  Zip::File.open(file_name) do |zip_file|
    zip_file.each do |entry|
      # Extract only exact matches for shared libraries
      next unless lib_paths.include?(entry.name)

      print "Extracting #{entry.name} from #{file_name}..."

      # Preserve complete directory structure
      target_path = entry.name
      FileUtils.mkdir_p(File.dirname(target_path)) unless entry.directory?

      unless entry.directory?
        entry.extract(target_path) { true } # Overwrite if exists
      end
      puts 'done'
    end
  end
end

def download_from_url(lib_paths, file_name, url)
  log_message "Downloading #{lib_paths} from #{url}"

  download_file(file_name, url)
  extract_zip_files(file_name, lib_paths)
ensure
  File.delete(file_name) if File.exist?(file_name)
end

# Mid-level functions
def download_libui_ng_nightly(lib_paths, file_name)
  url = url_for_libui_ng_commit(file_name)
  download_from_url(lib_paths, file_name, url)
end

def download_and_place(zip_name, src, dest)
  url = url_for_libui_ng_commit(zip_name)
  success = download_file(zip_name, url)

  unless success
    warn "Error: Failed to download #{zip_name}"
    exit 1
  end

  extract_zip_files(zip_name, [src])
  FileUtils.mkdir_p File.dirname(dest)
  FileUtils.cp src, dest
ensure
  File.delete(zip_name) if File.exist?(zip_name)
end

# High-level processing functions
def process_config_entry(entry)
  # Standard download and place for shared libraries only
  download_and_place(entry[:zip], entry[:src], entry[:dest])
end

def process_platform(platform_entries)
  platform_entries.each do |entry|
    process_config_entry(entry)
  end
end

# Platform gem building
platforms = %w[
  x86_64-linux
  aarch64-linux
  x86_64-darwin
  arm64-darwin
  x64-mingw32
  x86-mingw32
]

task :build_platform do
  platforms.each do |platform|
    sh 'gem', 'build', '--platform', platform
  end

  FileUtils.mkdir_p('pkg')
  Dir['*.gem'].each do |file|
    FileUtils.move(file, 'pkg')
  end
end

task :release_platform do
  Dir["pkg/libui-#{LibUI::VERSION}-*.gem"].each do |file|
    sh 'gem', 'push', file
  end
end

# Vendor tasks
namespace 'vendor' do
  desc 'Download pre-built libraries for current platform'
  task :auto do
    platform_key = detect_platform_config_key
    if platform_key && PLATFORM_CONFIG[platform_key]
      log_message "Processing platform: #{platform_key}"
      process_platform(PLATFORM_CONFIG[platform_key])
    else
      current_platform = Gem::Platform.local
      log_message "No configuration found for current platform: #{current_platform}"
      log_message "Available platforms: #{PLATFORM_CONFIG.keys.join(', ')}"
      exit 1
    end
  ensure
    # Clean up temporary directory after processing
    Rake::Task['vendor:cleanup'].invoke
  end

  desc 'Clean vendor directory'
  task :clean do
    vendor_files = Dir['vendor/*'] - Dir['vendor/{LICENSE,README}.md']
    vendor_files.each do |f|
      FileUtils.rm_rf(f)
      log_message "Removed #{f}"
    end
  end

  # Clean up temporary directory
  task :cleanup do
    if Dir.exist?(BUILD_DIR)
      FileUtils.rm_rf BUILD_DIR
      log_message "Cleaned up #{BUILD_DIR}"
    end
  end
end


================================================
FILE: examples/basic_area.rb
================================================
require 'libui'

UI = LibUI

UI.init

handler = UI::FFI::AreaHandler.malloc
handler.to_ptr.free = Fiddle::RUBY_FREE
area    = UI.new_area(handler)
brush   = UI::FFI::DrawBrush.malloc
brush.to_ptr.free = Fiddle::RUBY_FREE

handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|
  path = UI.draw_new_path(0)
  UI.draw_path_add_rectangle(path, 0, 0, 400, 400)
  UI.draw_path_end(path)
  brush.Type = 0
  brush.R = 0.4
  brush.G = 0.4
  brush.B = 0.8
  brush.A = 1.0
  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
  UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)
  UI.draw_free_path(path)
end

do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }

handler.Draw         = handler_draw_event
handler.MouseEvent   = do_nothing
handler.MouseCrossed = do_nothing
handler.DragBroken   = do_nothing
handler.KeyEvent     = key_event

box = UI.new_vertical_box
UI.box_set_padded(box, 1)
UI.box_append(box, area, 1)

main_window = UI.new_window('Basic Area', 400, 400, 1)
UI.window_set_margined(main_window, 1)
UI.window_set_child(main_window, box)

UI.window_on_closing(main_window) do
  UI.quit
  1
end
UI.control_show(main_window)

UI.main
UI.uninit


================================================
FILE: examples/basic_button.rb
================================================
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('hello world', 300, 200, 1)

button = UI.new_button('Button')

UI.button_on_clicked(button) do
  UI.msg_box(main_window, 'Information', 'You clicked the button')
end

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.window_set_child(main_window, button)
UI.control_show(main_window)

UI.main
UI.uninit


================================================
FILE: examples/basic_draw_text.rb
================================================
require 'libui'

UI = LibUI

UI.init

handler = UI::FFI::AreaHandler.malloc
handler.to_ptr.free = Fiddle::RUBY_FREE
area    = UI.new_area(handler)

# Michael Ende (1929-1995)
# The Neverending Story is a fantasy novel by German writer Michael Ende,
# The English version, translated by Ralph Manheim, was published in 1983.

TITLE = 'Michael Ende (1929-1995) The Neverending Story'

str1 = \
  '  At last Ygramul sensed that something was coming toward ' \
  'her. With the speed of lightning, she turned about, confronting ' \
  'Atreyu with an enormous steel-blue face. Her single eye had a ' \
  'vertical pupil, which stared at Atreyu with inconceivable malignancy. '

str2 = \
  '  A cry of fear escaped Bastian. '

str3 = \
  '  A cry of terror passed through the ravine and echoed from ' \
  'side to side. Ygramul turned her eye to left and right, to see if ' \
  'someone else had arrived, for that sound could not have been ' \
  'made by the boy who stood there as though paralyzed with ' \
  'horror. '

str4 = \
  '  Could she have heard my cry? Bastion wondered in alarm. ' \
  "But that's not possible. "

str5 = \
  '  And then Atreyu heard Ygramuls voice. It was very high ' \
  'and slightly hoarse, not at all the right kind of voice for that ' \
  'enormous face. Her lips did not move as she spoke. It was the ' \
  'buzzing of a great swarm of hornets that shaped itself into ' \
  'words. '

str = ''
attr_str = UI.new_attributed_string(str)

def attr_str.append(what, color)
  c = case color
      when :red
        [0.0, 0.5, 0.0, 0.7]
      when :green
        [0.5, 0.0, 0.25, 0.7]
      end
  color_attribute = UI.new_color_attribute(*c)
  start = UI.attributed_string_len(self)
  UI.attributed_string_append_unattributed(self, what)
  UI.attributed_string_set_attribute(self, color_attribute, start, start + what.size)
  UI.attributed_string_append_unattributed(self, "\n\n")
end

attr_str.append(str1, :green)
attr_str.append(str2, :red)
attr_str.append(str3, :green)
attr_str.append(str4, :red)
attr_str.append(str5, :green)

Georgia = 'Georgia'

handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, adp|
  area_draw_params = UI::FFI::AreaDrawParams.new(adp)
  default_font = UI::FFI::FontDescriptor.malloc
  default_font.to_ptr.free = Fiddle::RUBY_FREE
  default_font.Family = Georgia
  default_font.Size = 13
  default_font.Weight = 500
  default_font.Italic = 0
  default_font.Stretch = 4
  params = UI::FFI::DrawTextLayoutParams.malloc
  params.to_ptr.free = Fiddle::RUBY_FREE

  # UI.font_button_font(font_button, default_font)
  params.String = attr_str
  params.DefaultFont = default_font
  params.Width = area_draw_params.AreaWidth
  params.Align = 0
  text_layout = UI.draw_new_text_layout(params)
  UI.draw_text(area_draw_params.Context, text_layout, 0, 0)
  UI.draw_free_text_layout(text_layout)
end

handler.Draw = handler_draw_event

# Assigning to local variables
# This is intended to protect Fiddle::Closure from garbage collection.
do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }
handler.MouseEvent   = do_nothing
handler.MouseCrossed = do_nothing
handler.DragBroken   = do_nothing
handler.KeyEvent     = key_event

box = UI.new_vertical_box
UI.box_set_padded(box, 1)
UI.box_append(box, area, 1)

main_window = UI.new_window(TITLE, 600, 400, 1)
UI.window_set_margined(main_window, 1)
UI.window_set_child(main_window, box)

UI.window_on_closing(main_window) do
  UI.quit
  1
end
UI.control_show(main_window)

UI.main
UI.uninit


================================================
FILE: examples/basic_entry.rb
================================================
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('Basic Entry', 300, 50, 1)
UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)

entry = UI.new_entry
UI.entry_on_changed(entry) do
  puts UI.entry_text(entry).to_s
  $stdout.flush # For Windows
end
UI.box_append(hbox, entry, 1)

button = UI.new_button('Button')
UI.button_on_clicked(button) do
  text = UI.entry_text(entry).to_s
  UI.msg_box(main_window, 'You entered', text)
  0
end

UI.box_append(hbox, button, 0)

UI.control_show(main_window)
UI.main
UI.uninit


================================================
FILE: examples/basic_table.rb
================================================
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('Animal sounds', 300, 200, 1)

hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)

data = [
  %w[cat meow],
  %w[dog woof],
  %w[chicken cock-a-doodle-doo],
  %w[horse neigh],
  %w[cow moo]
]

# Protects BlockCaller objects from garbage collection.
@block_callers = []
def rbcallback(*args, &block)
  args << [0] if args.size == 1 # Argument types are omitted
  block_caller = Fiddle::Closure::BlockCaller.new(*args, &block)
  @block_callers << block_caller
  block_caller
end

model_handler = UI::FFI::TableModelHandler.malloc
model_handler.to_ptr.free = Fiddle::RUBY_FREE
model_handler.NumColumns   = rbcallback(4) { 2 }
model_handler.ColumnType   = rbcallback(4) { 0 }
model_handler.NumRows      = rbcallback(4) { 5 }
model_handler.CellValue    = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, column|
  UI.new_table_value_string(data[row][column])
end
model_handler.SetCellValue = rbcallback(0, [0]) {}

model = UI.new_table_model(model_handler)

table_params = UI::FFI::TableParams.malloc
table_params.to_ptr.free = Fiddle::RUBY_FREE
table_params.Model = model
table_params.RowBackgroundColorModelColumn = -1

table = UI.new_table(table_params)
UI.table_append_text_column(table, 'Animal', 0, -1)
UI.table_append_text_column(table, 'Description', 1, -1)

UI.box_append(hbox, table, 1)
UI.control_show(main_window)

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.main
UI.free_table_model(model)
UI.uninit


================================================
FILE: examples/basic_table_image.rb
================================================
# NOTE:
# This example displays images that can be freely downloaded from the Studio Ghibli website.
# https://www.ghibli.jp/works/red-turtle/
# "Please feel free to use them within the scope of common sense." Toshio Suzuki (producer)

require 'libui'
require 'chunky_png'
require 'open-uri'

UI = LibUI

UI.init

main_window = UI.new_window('The Red Turtle (2016)', 310, 350, 0)

hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)

IMAGES = Array.new(50) do |i|
  url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', i + 1)
  f = URI.open(url)
  canvas = ChunkyPNG::Canvas.from_io(f)
  f.close
  data = canvas.to_rgba_stream
  width = canvas.width
  height = canvas.height
  image = UI.new_image(width, height)
  UI.image_append(image, data, width, height, width * 4)
  image
rescue StandardError => e
  warn url, e.message
end

# Protects BlockCaller objects from garbage collection.
@block_callers = []
def rbcallback(*args, &block)
  args << [0] if args.size == 1 # Argument types are omitted
  block_caller = Fiddle::Closure::BlockCaller.new(*args, &block)
  @block_callers << block_caller
  block_caller
end

model_handler = UI::FFI::TableModelHandler.malloc
model_handler.to_ptr.free = Fiddle::RUBY_FREE
model_handler.NumColumns   = rbcallback(4) { 1 }
model_handler.ColumnType   = rbcallback(4) { 1 } # Image
model_handler.NumRows      = rbcallback(4) { IMAGES.size }
model_handler.CellValue    = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, _column|
  UI.new_table_value_image(IMAGES[row])
end
model_handler.SetCellValue = rbcallback(0, [0]) {}

model = UI.new_table_model(model_handler)

table_params = UI::FFI::TableParams.malloc
table_params.to_ptr.free = Fiddle::RUBY_FREE
table_params.Model = model
table_params.RowBackgroundColorModelColumn = -1

table = UI.new_table(table_params)
UI.table_append_image_column(table, 'Directed by Michaël Dudok de Wit', -1)

UI.box_append(hbox, table, 1)
UI.control_show(main_window)

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.main
UI.free_table_model(model)
IMAGES.each { |i| UI.free_image(i) }
UI.uninit


================================================
FILE: examples/basic_window.rb
================================================
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('hello world', 300, 200, 1)

UI.control_show(main_window)

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.main
UI.uninit


================================================
FILE: examples/control_gallery.rb
================================================
require 'libui'
UI = LibUI

UI.init

# File menu
menu = UI.new_menu('File')
open_menu_item = UI.menu_append_item(menu, 'Open')
UI.menu_item_on_clicked(open_menu_item) do
  pt = UI.open_file(MAIN_WINDOW)
  puts pt unless pt.null?
end
save_menu_item = UI.menu_append_item(menu, 'Save')
UI.menu_item_on_clicked(save_menu_item) do
  pt = UI.save_file(MAIN_WINDOW)
  puts pt unless pt.null?
end
UI.menu_append_separator(menu)
should_quit_item = UI.menu_append_check_item(menu, 'Should Quit_')
UI.menu_item_set_checked(should_quit_item, 1)
UI.menu_append_quit_item(menu)
# onShouldQuit callback is called when the user presses the quit menu item.
UI.on_should_quit do
  if UI.menu_item_checked(should_quit_item) == 1
    puts 'Bye Bye (on_should_quit)'
    UI.control_destroy(MAIN_WINDOW) # You have to destroy the window manually.
    1 # UI.quit is automatically called in the C function onQuitClicked().
  else
    UI.msg_box(MAIN_WINDOW, 'Warning', 'Please check "Should Quit"')
    0 # Don't quit
  end
end

# Edit menu
edit_menu = UI.new_menu('Edit')
UI.menu_append_check_item(edit_menu, 'Checkable Item_')
UI.menu_append_separator(edit_menu)
disabled_item = UI.menu_append_item(edit_menu, 'Disabled Item_')
UI.menu_item_disable(disabled_item)

UI.menu_append_preferences_item(menu)

# Help menu
help_menu = UI.new_menu('Help')
UI.menu_append_item(help_menu, 'Help')
UI.menu_append_about_item(help_menu)

# Main Window
MAIN_WINDOW = UI.new_window('Control Gallery', 600, 500, 1)
UI.window_set_margined(MAIN_WINDOW, 1)
UI.window_on_closing(MAIN_WINDOW) do
  puts 'Bye Bye'
  UI.quit
  # return 1 to destroys the window automatically.
  # return 0 to keep the window. (You can destroy it manually.)
  1
end

vbox = UI.new_vertical_box
UI.window_set_child(MAIN_WINDOW, vbox)
hbox = UI.new_horizontal_box
UI.box_set_padded(vbox, 1)
UI.box_set_padded(hbox, 1)

UI.box_append(vbox, hbox, 1)

# Group - Basic Controls
group = UI.new_group('Basic Controls')
UI.group_set_margined(group, 1)
UI.box_append(hbox, group, 1) # OSX bug?

inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)

# Button
button = UI.new_button('Button')
UI.button_on_clicked(button) do
  UI.msg_box(MAIN_WINDOW, 'Information', 'You clicked the button')
end
UI.box_append(inner, button, 0)

# Checkbox
checkbox = UI.new_checkbox('Checkbox')
UI.checkbox_on_toggled(checkbox) do |ptr|
  checked = UI.checkbox_checked(ptr) == 1
  UI.window_set_title(MAIN_WINDOW, "Checkbox is #{checked}")
  UI.checkbox_set_text(ptr, "I am the checkbox (#{checked})")
end
UI.box_append(inner, checkbox, 0)

# Label
UI.box_append(inner, UI.new_label('Label'), 0)

# Separator
UI.box_append(inner, UI.new_horizontal_separator, 0)

# Date Picker
UI.box_append(inner, UI.new_date_picker, 0)

# Time Picker
UI.box_append(inner, UI.new_time_picker, 0)

# Date Time Picker
UI.box_append(inner, UI.new_date_time_picker, 0)

# Font Button
UI.box_append(inner, UI.new_font_button, 0)

# Color Button
UI.box_append(inner, UI.new_color_button, 0)

inner2 = UI.new_vertical_box
UI.box_set_padded(inner2, 1)
UI.box_append(hbox, inner2, 1)

# Group - Numbers
group = UI.new_group('Numbers')
UI.group_set_margined(group, 1)
UI.box_append(inner2, group, 0)

inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)

# Spinbox
spinbox = UI.new_spinbox(0, 100)
UI.spinbox_set_value(spinbox, 42)
UI.spinbox_on_changed(spinbox) do |ptr|
  puts "New Spinbox value: #{UI.spinbox_value(ptr)}"
end
UI.box_append(inner, spinbox, 0)

# Slider
slider = UI.new_slider(0, 100)
UI.box_append(inner, slider, 0)

# Progressbar
progressbar = UI.new_progress_bar
UI.box_append(inner, progressbar, 0)

UI.slider_on_changed(slider) do |ptr|
  v = UI.slider_value(ptr)
  puts "New Slider value: #{v}"
  UI.progress_bar_set_value(progressbar, v)
end

# Group - Lists
group = UI.new_group('Lists')
UI.group_set_margined(group, 1)
UI.box_append(inner2, group, 0)

inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)

# Combobox
cbox = UI.new_combobox
UI.combobox_append(cbox, 'combobox Item 1')
UI.combobox_append(cbox, 'combobox Item 2')
UI.combobox_append(cbox, 'combobox Item 3')
UI.box_append(inner, cbox, 0)
UI.combobox_on_selected(cbox) do |ptr|
  puts "New combobox selection: #{UI.combobox_selected(ptr)}"
end

# Editable Combobox
ebox = UI.new_editable_combobox
UI.editable_combobox_append(ebox, 'Editable Item 1')
UI.editable_combobox_append(ebox, 'Editable Item 2')
UI.editable_combobox_append(ebox, 'Editable Item 3')
UI.box_append(inner, ebox, 0)

# Radio Buttons
rb = UI.new_radio_buttons
UI.radio_buttons_append(rb, 'Radio Button 1')
UI.radio_buttons_append(rb, 'Radio Button 2')
UI.radio_buttons_append(rb, 'Radio Button 3')
UI.box_append(inner, rb, 1)

# Tab
tab = UI.new_tab
hbox1 = UI.new_horizontal_box
hbox2 = UI.new_horizontal_box
UI.tab_append(tab, 'Page 1', hbox1)
UI.tab_append(tab, 'Page 2', hbox2)
UI.tab_append(tab, 'Page 3', UI.new_horizontal_box)
UI.box_append(inner2, tab, 1)

# Text Entry
text_entry = UI.new_entry
UI.entry_set_text text_entry, 'Please enter your feelings'
UI.entry_on_changed(text_entry) do |ptr|
  puts "Current textbox data: '#{UI.entry_text(ptr)}'"
end
UI.box_append(hbox1, text_entry, 1)

UI.control_show(MAIN_WINDOW)

UI.main
UI.uninit


================================================
FILE: examples/date_time_picker.rb
================================================
require 'libui'

UI = LibUI

UI.init

vbox = UI.new_vertical_box

date_time_picker = UI.new_date_time_picker

time = UI::FFI::TM.malloc
time.to_ptr.free = Fiddle::RUBY_FREE

UI.date_time_picker_on_changed(date_time_picker) do
  UI.date_time_picker_time(date_time_picker, time)
  p sec: time.tm_sec,
    min: time.tm_min,
    hour: time.tm_hour,
    mday: time.tm_mday,
    mon: time.tm_mon,
    year: time.tm_year,
    wday: time.tm_wday,
    yday: time.tm_yday,
    isdst: time.tm_isdst
end
UI.box_append(vbox, date_time_picker, 1)

main_window = UI.new_window('Date Time Pickers', 300, 200, 1)
UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end
UI.window_set_child(main_window, vbox)
UI.control_show(main_window)

UI.main
UI.uninit


================================================
FILE: examples/draw_text.rb
================================================
require 'libui'

UI = LibUI

def append_with_attribute(attr_str, what, attr1, attr2)
  start_pos = UI.attributed_string_len(attr_str)
  end_pos = start_pos + what.length
  UI.attributed_string_append_unattributed(attr_str, what)
  UI.attributed_string_set_attribute(attr_str, attr1, start_pos, end_pos)
  UI.attributed_string_set_attribute(attr_str, attr2, start_pos, end_pos) if attr2
end

def make_attribute_string
  attr_str = UI.new_attributed_string(
    "Drawing strings with libui is done with the uiAttributedString and uiDrawTextLayout objects.\n" \
     'uiAttributedString lets you have a variety of attributes: '
  )

  attr1 = UI.new_family_attribute('Courier New')
  append_with_attribute(attr_str, 'font family', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_size_attribute(18)
  append_with_attribute(attr_str, 'font size', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_weight_attribute(UI::TextWeightBold)
  append_with_attribute(attr_str, 'font weight', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_italic_attribute(UI::TextItalicItalic)
  append_with_attribute(attr_str, 'font italicness', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_stretch_attribute(UI::TextStretchCondensed)
  append_with_attribute(attr_str, 'font stretch', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_color_attribute(0.75, 0.25, 0.5, 0.75)
  append_with_attribute(attr_str, 'text color', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_background_attribute(0.5, 0.5, 0.25, 0.5)
  append_with_attribute(attr_str, 'text background color', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  attr1 = UI.new_underline_attribute(UI::UnderlineSingle)
  append_with_attribute(attr_str, 'underline style', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ', ')

  UI.attributed_string_append_unattributed(attr_str, 'and ')
  attr1 = UI.new_underline_attribute(UI::UnderlineDouble)
  attr2 = UI.new_underline_color_attribute(UI::UnderlineColorCustom, 1.0, 0.0, 0.5, 1.0)
  append_with_attribute(attr_str, 'underline color', attr1, attr2)
  UI.attributed_string_append_unattributed(attr_str, '. ')

  UI.attributed_string_append_unattributed(attr_str, 'Furthermore, there are attributes allowing for ')
  attr1 = UI.new_underline_attribute(UI::UnderlineSuggestion)
  attr2 = UI.new_underline_color_attribute(UI::UnderlineColorSpelling, 0, 0, 0, 0)
  append_with_attribute(attr_str, 'special underlines for indicating spelling errors', attr1, attr2)
  UI.attributed_string_append_unattributed(attr_str, ' (and other types of errors) ')

  UI.attributed_string_append_unattributed(attr_str,
                                           'and control over OpenType features such as ligatures (for instance, ')
  otf = UI.new_open_type_features
  UI.open_type_features_add(otf, 'l', 'i', 'g', 'a', 0)
  attr1 = UI.new_features_attribute(otf)
  append_with_attribute(attr_str, 'afford', attr1, nil)
  UI.attributed_string_append_unattributed(attr_str, ' vs. ')
  UI.open_type_features_add(otf, 'l', 'i', 'g', 'a', 1)
  attr1 = UI.new_features_attribute(otf)
  append_with_attribute(attr_str, 'afford', attr1, nil)
  UI.free_open_type_features(otf)
  UI.attributed_string_append_unattributed(attr_str, ").\n")

  UI.attributed_string_append_unattributed(attr_str,
                                           'Use the controls opposite to the text to control properties of the text.')
  attr_str
end

def on_font_changed(area)
  UI.area_queue_redraw_all(area)
end

def on_combobox_selected(area)
  UI.area_queue_redraw_all(area)
end

def draw_event(adp, attr_str, font_button, alignment)
  area_draw_params = UI::FFI::AreaDrawParams.new(adp)
  default_font = UI::FFI::FontDescriptor.malloc
  default_font.to_ptr.free = Fiddle::RUBY_FREE
  params = UI::FFI::DrawTextLayoutParams.malloc
  params.to_ptr.free = Fiddle::RUBY_FREE

  params.String = attr_str
  UI.font_button_font(font_button, default_font)
  params.DefaultFont = default_font
  params.Width = area_draw_params.AreaWidth
  params.Align = UI.combobox_selected(alignment)
  text_layout = UI.draw_new_text_layout(params)
  UI.draw_text(area_draw_params.Context, text_layout, 0, 0)
  UI.draw_free_text_layout(text_layout)
  UI.free_font_button_font(default_font)
end

UI.init

handler = UI::FFI::AreaHandler.malloc
handler.to_ptr.free = Fiddle::RUBY_FREE

handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _area, adp|
  draw_event(adp, @attr_str, @font_button, @alignment)
end

handler.Draw = handler_draw_event

do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }
handler.MouseEvent   = do_nothing
handler.MouseCrossed = do_nothing
handler.DragBroken   = do_nothing
handler.KeyEvent     = key_event

@attr_str = make_attribute_string

main_window = UI.new_window('Text-Drawing Example', 640, 480, 1)
UI.window_set_margined(main_window, 1)
UI.window_on_closing(main_window) do
  UI.quit
  1
end

hbox = UI.new_horizontal_box
UI.box_set_padded(hbox, 1)
UI.window_set_child(main_window, hbox)

vbox = UI.new_vertical_box
UI.box_set_padded(vbox, 1)
UI.box_append(hbox, vbox, 0)

@font_button = UI.new_font_button
UI.font_button_on_changed(@font_button) { on_font_changed(@area) }
UI.box_append(vbox, @font_button, 0)

form = UI.new_form
UI.form_set_padded(form, 1)
UI.box_append(vbox, form, 0)

@alignment = UI.new_combobox
UI.combobox_append(@alignment, 'Left')
UI.combobox_append(@alignment, 'Center')
UI.combobox_append(@alignment, 'Right')
UI.combobox_set_selected(@alignment, 0)
UI.combobox_on_selected(@alignment) { on_combobox_selected(@area) }
UI.form_append(form, 'Alignment', @alignment, 0)

@area = UI.new_area(handler)
UI.box_append(hbox, @area, 1)

UI.control_show(main_window)
UI.main

UI.free_attributed_string(@attr_str)
UI.uninit


================================================
FILE: examples/font_button.rb
================================================
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('hello world', 300, 200, 1)

font_button = UI.new_font_button
font_descriptor = UI::FFI::FontDescriptor.malloc
font_descriptor.to_ptr.free = Fiddle::RUBY_FREE
UI.font_button_on_changed(font_button) do
  UI.font_button_font(font_button, font_descriptor)
  p family: font_descriptor.Family.to_s,
    size: font_descriptor.Size,
    weight: font_descriptor.Weight,
    italic: font_descriptor.Italic,
    stretch: font_descriptor.Stretch
end

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.window_set_child(main_window, font_button)
UI.control_show(main_window)

UI.main
UI.uninit


================================================
FILE: examples/gpt2_notepad.rb
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'libui'
require 'onnxruntime'
require 'blingfire'
require 'numo/narray'

# GPT-2 model
# Transformer-based language model for text generation.
# https://github.com/onnx/models/tree/main/text/machine_comprehension/gpt-2

Dir.chdir(__dir__) do
  %w[
    https://github.com/microsoft/BlingFire/raw/master/dist-pypi/blingfire/gpt2.bin
    https://github.com/microsoft/BlingFire/raw/master/dist-pypi/blingfire/gpt2.i2w
    https://github.com/onnx/models/raw/main/text/machine_comprehension/gpt-2/model/gpt2-lm-head-10.onnx
  ].each do |url|
    fname = File.basename(url)
    next if File.exist?(fname)

    print "Downloading #{fname}..."
    require 'open-uri'
    File.binwrite(fname, URI.open(url).read)
    puts 'done'
  end
  @encoder = BlingFire.load_model('gpt2.bin')
  @decoder = BlingFire.load_model('gpt2.i2w')
  @model = OnnxRuntime::Model.new('gpt2-lm-head-10.onnx')
end

def softmax(y)
  Numo::NMath.exp(y) / Numo::NMath.exp(y).sum(1, keepdims: true)
end

def predict(a, prob: true)
  outputs = @model.predict({ input1: [[a]] })
  logits = Numo::DFloat.cast(outputs['output1'][0])
  logits = logits[true, -1, true]
  return logits.argmax unless prob

  log_probs = softmax(logits)
  cum_probs = log_probs.cumsum(1)
  r = rand(0..cum_probs[-1]) # 0..1
  (cum_probs < r).count
end

def predict_text(s, max = 30)
  a = @encoder.text_to_ids(s)
  max.times do
    id = predict(a)
    a << id
    break if id == 13 # .
  end
  @decoder.ids_to_text(a)
end

# GUI
UI = LibUI

UI.init

main_window = UI.new_window('GPT-2 Notepad', 500, 300, 1)
UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

hbox = UI.new_vertical_box
UI.box_set_padded(hbox, 1)
UI.window_set_child(main_window, hbox)

bbox = UI.new_horizontal_box
UI.box_append(hbox, bbox, 0)
clear_button = UI.new_button('Clear')
write_button = UI.new_button('Continue the sentence(s)')
UI.box_append(bbox, clear_button, 1)
UI.box_append(bbox, write_button, 1)

entry = UI.new_multiline_entry
UI.box_append(hbox, entry, 1)

UI.button_on_clicked(clear_button) do
  UI.multiline_entry_set_text(entry, '')
end

UI.button_on_clicked(write_button) do
  s = UI.multiline_entry_text(entry).to_s
  if s.empty?
    UI.msg_box(main_window, 'Empty!', 'Please enter some text first.')
  else
    s2 = predict_text(s)
    UI.multiline_entry_set_text(entry, s2)
  end
end

UI.control_show(main_window)
UI.main
UI.uninit


================================================
FILE: examples/histogram.rb
================================================
# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb

require 'libui'

UI = LibUI

X_OFF_LEFT   = 20
Y_OFF_TOP    = 20
X_OFF_RIGHT  = 20
Y_OFF_BOTTOM = 20
POINT_RADIUS = 5

init         = UI.init
handler      = UI::FFI::AreaHandler.malloc
handler.to_ptr.free = Fiddle::RUBY_FREE
histogram    = UI.new_area(handler)
brush        = UI::FFI::DrawBrush.malloc
brush.to_ptr.free = Fiddle::RUBY_FREE
color_button = UI.new_color_button
blue         = 0x1E90FF
datapoints   = []

def graph_size(area_width, area_height)
  graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
  graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
  [graph_width, graph_height]
end

matrix = UI::FFI::DrawMatrix.malloc
matrix.to_ptr.free = Fiddle::RUBY_FREE

def point_locations(datapoints, width, height)
  xincr = width / 9.0 # 10 - 1 to make the last point be at the end
  yincr = height / 100.0

  data = []
  datapoints.each_with_index do |dp, i|
    val = 100 - UI.spinbox_value(dp)
    data << [xincr * i, yincr * val]
  end

  data
end

def construct_graph(datapoints, width, height, should_extend)
  locations = point_locations(datapoints, width, height)
  path = UI.draw_new_path(0) # winding
  first_location = locations[0] # x and y
  UI.draw_path_new_figure(path, first_location[0], first_location[1])
  locations.each do |loc|
    UI.draw_path_line_to(path, loc[0], loc[1])
  end

  if should_extend
    UI.draw_path_line_to(path, width, height)
    UI.draw_path_line_to(path, 0, height)
    UI.draw_path_close_figure(path)
  end

  UI.draw_path_end(path)

  path
end

handler_draw_event = Fiddle::Closure::BlockCaller.new(
  0, [1, 1, 1]
) do |_area_handler, _area, area_draw_params|
  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
  path = UI.draw_new_path(0) # winding
  UI.draw_path_add_rectangle(path, 0, 0, area_draw_params.AreaWidth, area_draw_params.AreaHeight)
  UI.draw_path_end(path)
  set_solid_brush(brush, 0xFFFFFF, 1.0) # white
  UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)
  UI.draw_free_path(path)
  dsp = UI::FFI::DrawStrokeParams.malloc
  dsp.to_ptr.free = Fiddle::RUBY_FREE
  dsp.Cap = 0 # flat
  dsp.Join = 0 # miter
  dsp.Thickness = 2
  dsp.MiterLimit = 10 # DEFAULT_MITER_LIMIT
  dashes = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
  dsp.Dashes = dashes
  dsp.NumDashes = 0
  dsp.DashPhase = 0

  # draw axes
  set_solid_brush(brush, 0x000000, 1.0) # black
  graph_width, graph_height = *graph_size(area_draw_params.AreaWidth, area_draw_params.AreaHeight)

  path = UI.draw_new_path(0) # winding
  UI.draw_path_new_figure(path, X_OFF_LEFT, Y_OFF_TOP)
  UI.draw_path_line_to(path, X_OFF_LEFT, Y_OFF_TOP + graph_height)
  UI.draw_path_line_to(path, X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
  UI.draw_path_end(path)
  UI.draw_stroke(area_draw_params.Context, path, brush, dsp)
  UI.draw_free_path(path)

  # now transform the coordinate space so (0, 0) is the top-left corner of the graph
  UI.draw_matrix_set_identity(matrix)
  UI.draw_matrix_translate(matrix, X_OFF_LEFT, Y_OFF_TOP)
  UI.draw_transform(area_draw_params.Context, matrix)

  # now get the color for the graph itself and set up the brush
  # uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA)
  graph_r = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
  graph_g = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
  graph_b = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
  graph_a = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double

  UI.color_button_color(color_button, graph_r, graph_g, graph_b, graph_a)
  brush.Type = 0 # solid
  brush.R = graph_r[0, 8].unpack1('d')
  brush.G = graph_g[0, 8].unpack1('d')
  brush.B = graph_b[0, 8].unpack1('d')

  # now create the fill for the graph below the graph line
  path = construct_graph(datapoints, graph_width, graph_height, true)
  brush.A = graph_a[0, 8].unpack1('d') / 2.0
  UI.draw_fill(area_draw_params.Context, path, brush)
  UI.draw_free_path(path)

  # now draw the histogram line
  path = construct_graph(datapoints, graph_width, graph_height, false)
  brush.A = graph_a[0, 8].unpack1('d')
  UI.draw_stroke(area_draw_params.Context, path, brush, dsp)
  UI.draw_free_path(path)
end

# Assigning to local variables
# This is intended to protect Fiddle::Closure from garbage collection.
# See https://github.com/kojix2/LibUI/issues/8
do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }
handler.Draw         = handler_draw_event
handler.MouseEvent   = do_nothing
handler.MouseCrossed = do_nothing
handler.DragBroken   = do_nothing
handler.KeyEvent     = key_event

UI.freeInitError(init) unless init.nil?

hbox = UI.new_horizontal_box
UI.box_set_padded(hbox, 1)

vbox = UI.new_vertical_box
UI.box_set_padded(vbox, 1)
UI.box_append(hbox, vbox, 0)
UI.box_append(hbox, histogram, 1)

datapoints = Array.new(10) do
  UI.new_spinbox(0, 100).tap do |datapoint|
    UI.spinbox_set_value(datapoint, Random.new.rand(90))
    UI.spinbox_on_changed(datapoint) do
      UI.area_queue_redraw_all(histogram)
    end
    UI.box_append(vbox, datapoint, 0)
  end
end

def set_solid_brush(brush, color, alpha)
  brush.Type = 0 # solid
  brush.R = ((color >> 16) & 0xFF) / 255.0
  brush.G = ((color >> 8) & 0xFF) / 255.0
  brush.B = (color & 0xFF) / 255.0
  brush.A = alpha
  brush
end

set_solid_brush(brush, blue, 1.0)
UI.color_button_set_color(color_button, brush.R, brush.G, brush.B, brush.A)

UI.color_button_on_changed(color_button) do
  UI.area_queue_redraw_all(histogram)
end

UI.box_append(vbox, color_button, 0)

MAIN_WINDOW = UI.new_window('histogram example', 640, 480, 1)
UI.window_set_margined(MAIN_WINDOW, 1)
UI.window_set_child(MAIN_WINDOW, hbox)

UI.window_on_closing(MAIN_WINDOW) do
  puts 'Bye Bye'
  UI.quit
  1
end

UI.control_show(MAIN_WINDOW)

UI.main
UI.uninit


================================================
FILE: examples/midi_player.rb
================================================
#!/usr/bin/env ruby

require 'libui'
UI = LibUI

class TinyMidiPlayer
  VERSION = '0.0.1'

  def initialize
    UI.init
    @pid = nil
    @music_directory = File.expand_path(ARGV[0] || '~/Music/')
    @midi_files      = Dir.glob(File.join(@music_directory, '**/*.mid'))
                          .sort_by { |path| File.basename(path) }
    at_exit { stop_midi }
    create_gui
  end

  def stop_midi
    if @pid
      Process.kill(:SIGKILL, @pid) if @th.alive?
      @pid = nil
    end
  end

  def play_midi
    stop_midi
    if @pid.nil? && @selected_file
      begin
        @pid = spawn "timidity #{@selected_file}"
        @th = Process.detach @pid
      rescue Errno::ENOENT
        warn 'Timidty++ not found. Please install Timidity++.'
        warn 'https://sourceforge.net/projects/timidity/'
      end
    end
  end

  def show_version(main_window)
    UI.msg_box(main_window,
               'Tiny Midi Player',
               "Written in Ruby\n" \
               "https://github.com/kojix2/libui\n" \
               "Version #{VERSION}")
  end

  def create_gui
    # loop_menu = UI.new_menu('Repeat')
    # items = %w[Off One].map do |item_name|
    #   item = UI.menu_append_check_item(loop_menu, item_name)
    # end
    # items.each_with_index do |item, idx|
    #   UI.menu_item_on_clicked(item) do
    #     @repeat = idx
    #     (items - [item]).each do |i|
    #       UI.menu_item_set_checked(i, 0)
    #     end
    #     0
    #   end
    # end

    help_menu = UI.new_menu('Help')
    version_item = UI.menu_append_item(help_menu, 'Version')

    UI.new_window('Tiny Midi Player', 200, 50, 1).tap do |main_window|
      UI.menu_item_on_clicked(version_item) { show_version(main_window) }

      UI.window_on_closing(main_window) do
        UI.quit
        1
      end

      UI.new_horizontal_box.tap do |hbox|
        UI.new_vertical_box.tap do |vbox|
          UI.new_button('▶').tap do |button1|
            UI.button_on_clicked(button1) { play_midi }
            UI.box_append(vbox, button1, 1)
          end
          UI.new_button('■').tap do |button2|
            UI.button_on_clicked(button2) { stop_midi }
            UI.box_append(vbox, button2, 1)
          end
          UI.box_append(hbox, vbox, 0)
        end
        UI.window_set_child(main_window, hbox)

        UI.new_combobox.tap do |cbox|
          @midi_files.each do |path|
            name = File.basename(path)
            UI.combobox_append(cbox, name)
          end
          UI.combobox_on_selected(cbox) do |ptr|
            @selected_file = @midi_files[UI.combobox_selected(ptr)]
            play_midi if @th&.alive?
            0
          end
          UI.box_append(hbox, cbox, 1)
        end
      end
      UI.control_show(main_window)
    end
    UI.main
    UI.uninit
  end
end

TinyMidiPlayer.new


================================================
FILE: examples/simple_notepad.rb
================================================
#!/usr/bin/env ruby

require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('Notepad', 500, 300, 1)
UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.quit
  1
end

vbox = UI.new_vertical_box
UI.window_set_child(main_window, vbox)

entry = UI.new_non_wrapping_multiline_entry
UI.box_append(vbox, entry, 1)

UI.control_show(main_window)
UI.main
UI.uninit


================================================
FILE: examples/spectrum.rb
================================================
#!/usr/bin/env ruby

# Please play your favorite music or video on your computer
# when running this spectrum example.

require 'libui'
require 'ffi-portaudio'  # https://github.com/nanki/ffi-portaudio
require 'numo/pocketfft' # https://github.com/yoshoku/numo-pocketfft

# ---------------------------------------------------------------------------- #

class FFTStream < FFI::PortAudio::Stream
  def process(input, _output, frame_count, _time_info, _status_flags, _user_data)
    i = Numo::Int16.cast(input.read_array_of_int16(frame_count))
    @spec = (Numo::Pocketfft.rfft(i)[0..511].abs / 1000.0).to_a
    :paContinue
  end

  def spec
    @spec || [0] * 512
  end
end

FFI::PortAudio::API.Pa_Initialize

input = FFI::PortAudio::API::PaStreamParameters.new
input[:device] = FFI::PortAudio::API.Pa_GetDefaultInputDevice
input[:channelCount] = 1
input[:sampleFormat] = FFI::PortAudio::API::Int16
input[:suggestedLatency] = 0
input[:hostApiSpecificStreamInfo] = nil
stream = FFTStream.new
stream.open(input, nil, 44_100, 1024)
stream.start

# ---------------------------------------------------------------------------- #

UI = LibUI

UI.init

handler = UI::FFI::AreaHandler.malloc
area    = UI.new_area(handler)

brush = UI::FFI::DrawBrush.malloc.tap do |b|
  b.Type = 0
  b.R = 0.9
  b.G = 0.2
  b.B = 0.6
  b.A = 1.0
end

dashes = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
stroke_params = UI::FFI::DrawStrokeParams.malloc.tap do |sp|
  sp.Cap = UI::DrawLineCapFlat
  sp.Join = UI::DrawLineJoinMiter
  sp.MiterLimit = 10
  sp.Dashes = dashes
  sp.NumDashes = 0
  sp.DashPhase = 0
  sp.Thickness = 1.0
end

handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|
  UI.draw_new_path(UI::DrawFillModeWinding).then do |path|
    stream.spec.each.with_index do |i, j|
      UI.draw_path_new_figure(path, 10 + j, 121)
      UI.draw_path_line_to(path, 10 + j, 120 - [i, 120].min)
    end
    UI.draw_path_end(path)

    area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
    UI.draw_stroke(area_draw_params.Context, path, brush.to_ptr, stroke_params)
    UI.draw_free_path(path)
  end
end

handler.Draw = handler_draw_event
do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }
handler.MouseEvent   = do_nothing
handler.MouseCrossed = do_nothing
handler.DragBroken   = do_nothing
handler.KeyEvent     = key_event

box = UI.new_vertical_box
UI.box_set_padded(box, 1)
UI.box_append(box, area, 1)

main_window = UI.new_window('SPECTRUM', 560, 150, 1)
UI.window_set_margined(main_window, 1)
UI.window_set_child(main_window, box)

UI.window_on_closing(main_window) do
  UI.quit
  stream.close
  ::FFI::PortAudio::API.Pa_Terminate
  1
end
UI.control_show(main_window)

UI.queue_main do
  UI.timer(100) do
    UI.area_queue_redraw_all(area)
    1
  end
end
UI.main
UI.uninit


================================================
FILE: examples/turing_pattern.rb
================================================
#!/usr/bin/env ruby

# https://en.wikipedia.org/wiki/Turing_pattern
#
# > The Turing pattern is a concept introduced by English mathematician Alan Turing
# in a 1952 paper titled "The Chemical Basis of Morphogenesis" which describes
# how patterns in nature, such as stripes and spots, can arise naturally and
# autonomously from a homogeneous, uniform state.
#
# > In his classic paper, Turing examined the behaviour of a system in which two
# diffusible substances interact with each other, and found that such a system
# is able to generate a spatially periodic pattern even from a random or almost
# uniform initial condition. Turing hypothesized that the resulting wavelike
# patterns are the chemical basis of morphogenesis.

# Studies of Turing pattern formation in zebrafish skin (2021)
# https://doi.org/10.1098/rsta.2020.0274
#
# > An unexpected discovery was made regarding the mechanism responsible for
# cell–cell interactions. When isolated melanophores and xanthophores were
# co-cultured in vitro, the cells were found to repel each other, but the signal
# was transmitted by short protrusion extending from the xanthophores. Regarding
# distant interactions, Hamada et al. found that long cell protrusions extending
# from the melanophores were involved (figure 3a–c). Interestingly, neither
# short- nor long-range interactions are mediated by ‘diffusion’. Therefore,
# strictly speaking, pattern formation does not occur by a reaction–diffusion
# system. However, mathematically, it can be considered as a homologous
# phenomenon, because the protrusions with two different lengths mimic the role
# of two different molecules with different diffusion coefficients in a
# reaction–diffusion system.
#
# > What established the pattern is not the shading of the chemicals, but the
# distribution of cells that behave autonomously. Another major difference is
# that long-distance signalling is conveyed directly by cell protrusions rather
# than by molecular diffusion.

# Perhaps Turing and his contemporaries were not right about the precise
# mechanism of pattern formation. It may be that the propagation of waves of
# cell signaling through direct contact, rather than the diffusion of morphogens,
# forms the pattern of the organism.
# However, the patterns generated by the diffusion reaction system are very
# impressive...

WAIT_TIME = 200 # Increase this number if you cannot redraw in time.
NUM_STEPS = 50  # Number of steps before being redrawn.

require 'libui'
# A matrix calculation library for Ruby like NumPy.
require 'numo/narray'

# generate png image from narray(faster)
# require 'magro'
require 'chunky_png'

module GrayScott
  # shorthand
  SFloat = Numo::SFloat
  UInt8  = Numo::UInt8

  class SFloat
    alias _ inplace
  end

  module Utils
    # To avoid mistakes
    A = (1..-1).freeze
    B = (0..-2).freeze
    T = true

    def self.laplacian2d(uv, dx)
      l_uv = uv.new_zeros
      l_uv[A, T]._ + uv[B, T]
      l_uv[T, A]._ + uv[T, B]

      l_uv[0, T]._ + uv[-1, T]
      l_uv[T, 0]._ + uv[T, -1]

      l_uv[B, T]._ + uv[A, T]
      l_uv[T, B]._ + uv[T, A]

      l_uv[-1, T]._ + uv[0, T]
      l_uv[T, -1]._ + uv[T, 0]

      l_uv._ - (uv * 4)
      l_uv._ / (dx * dx)
      l_uv
    end
  end

  # Gray-Scott model
  class Model
    Dx = 0.01

    # Delta t is the change in time for each iteration
    Dt = 1

    # diffusion rate for U
    Du = 2e-5

    # diffusion rate for V
    Dv = 1e-5

    attr_accessor :f, :k, :u, :v
    attr_reader :width, :height

    def initialize(width: 256, height: 256)
      @width  = width
      @height = height

      # Feed rate
      @f = 0.04

      # Kill rate
      @k = 0.06

      # concentration of U
      @u = SFloat.ones height, width

      # concentration of V
      @v = SFloat.zeros height, width
    end

    def clear
      u.fill 1.0
      v.fill 0.0
    end

    def step
      l_u = Utils.laplacian2d u, Dx
      l_v = Utils.laplacian2d v, Dx

      uvv = u * v * v
      dudt = Du * l_u - uvv + f * (1.0 - u)
      dvdt = Dv * l_v + uvv - (f + k) * v
      u._ + (Dt * dudt)
      v._ + (Dt * dvdt)

      # clip is better.
      @u[@u.lt 0.00001] = 0.00001
      @u[@u.gt 1] = 1
      @v[@v.lt 0.00001] = 0.00001
      @v[@v.gt 1] = 1
    end
  end

  module Color
    module_function

    def colorize(ar, color_type)
      case color_type
      when 'colorful'
        hsv2rgb(ar)
      when 'reverse-colorful'
        hsv2rgb(1.0 - ar)
      when 'red'
        red(ar)
      when 'green'
        green(ar)
      when 'blue'
        blue(ar)
      when 'reverse-red'
        reverse_red(ar)
      when 'reverse-green'
        reverse_green(ar)
      when 'reverse-blue'
        reverse_blue(ar)
      when 'grayscale'
        grayscale(ar)
      end
    end

    # speed
    def uInt8_dstack(ar)
      x = UInt8.zeros(*ar[0].shape, 3)
      x[true, true, 0] = ar[0]
      x[true, true, 1] = ar[1]
      x[true, true, 2] = ar[2]
      x
    end

    def hsv2rgb(h)
      i = UInt8.cast(h * 6)
      f = (h * 6.0) - i
      p = UInt8.zeros(*h.shape)
      v = UInt8.new(*h.shape).fill 255
      q = (1.0 - f) * 256
      t = f * 256
      rgb = UInt8.zeros(*h.shape, 3)
      t = UInt8.cast(t)
      i = uInt8_dstack([i, i, i])
      rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]
      rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]
      rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]
      rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]
      rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]
      rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]
      rgb
    end

    def red(ar)
      uint8_zeros_256(0, ar)
    end

    def green(ar)
      uint8_zeros_256(1, ar)
    end

    def blue(ar)
      uint8_zeros_256(2, ar)
    end

    def reverse_red(ar)
      uint8_zeros_256(0, (1.0 - ar))
    end

    def reverse_green(ar)
      uint8_zeros_256(1, (1.0 - ar))
    end

    def reverse_blue(ar)
      uint8_zeros_256(2, (1.0 - ar))
    end

    def grayscale(ar)
      d = ar * 255
      uInt8_dstack([d, d, d])
    end

    def uint8_zeros_256(ch, ar)
      d = UInt8.zeros(*ar.shape, 3)
      d[true, true, ch] = UInt8.cast(ar * 256)
      d
    end
  end
end

UI = LibUI
UI.init

width        = 100
height       = 100
ratio        = 2
pix_size     = 4
pointer_size = 5
model_width  = width * ratio
model_height = height * ratio

@model = GrayScott::Model.new(width: model_width, height: model_height)
@model.clear
@color_type = 'colorful'
@uv = 'v'
@running = false

# menu File

menu_file = UI.new_menu('File')
menu_file_new = UI.menu_append_item(menu_file, 'New')
UI.menu_item_on_clicked(menu_file_new) do
  @model.clear
  UI.area_queue_redraw_all(@area)
end

# menu File Open

menu_file_open = UI.menu_append_item(menu_file, 'Open Model')
UI.menu_item_on_clicked(menu_file_open) do
  pt = UI.open_file(@main_window)
  unless pt.null?
    file_path = pt.to_s
    begin
      model = Marshal.load(File.binread(file_path))
    rescue StandardError => e
      UI.msg_box_error(
        @main_window, '⚠️ Error',
        "Failed to open file.\n" \
        "#{file_path}\n" \
        "#{e.message}"
      )
      next
    end
    if model.width == @model.width &&
       model.height == @model.height
      @model = model
      UI.area_queue_redraw_all(@area)
    else
      UI.msg_box_error(
        @main_window, '⚠️ Error',
        "File shape is different.\n" \
        "file: width #{model.width} height #{model.height}\n" \
        "model: width #{@model.width} height #{@model.height}"
      )
    end
  end
end

@save_file_path = nil

save_model_as_proc = proc do
  pt = UI.save_file(@main_window)
  unless pt.null?
    @save_file_path = pt.to_s
    Marshal.dump(@model, File.open(@save_file_path, 'wb'))
  end
end

# menu File Save

menu_file_save = UI.menu_append_item(menu_file, 'Save Model')
UI.menu_item_on_clicked(menu_file_save) do
  if @save_file_path
    Marshal.dump(@model, File.open(@save_file_path, 'wb'))
  else
    save_model_as_proc.call
  end
end

# menu File Save As

menu_file_save_as = UI.menu_append_item(menu_file, 'Save Model As')
UI.menu_item_on_clicked(menu_file_save_as, save_model_as_proc)

# menu File Quit

menu_file_quit = UI.menu_append_quit_item(menu_file)
UI.on_should_quit do
  UI.control_destroy(@main_window)
  1
end

# menu Help

menu_help = UI.new_menu('Help')
menu_help_about = UI.menu_append_item(menu_help, 'About')
UI.menu_item_on_clicked(menu_help_about) do
  UI.msg_box(
    @main_window,
    '🦓 Turing Pattern 🐠',
    <<~HELP_MESSAGE
      How to use

      (1) Click on the red area several times. Blue dots will show up.
      (2) Press the "▶ START" button.
      (3) Try out different parameters.

      Written in Ruby
      https://github.com/kojix2/LibUI
    HELP_MESSAGE
  )
end

# area

area_handler = UI::FFI::AreaHandler.malloc
area_handler.to_ptr.free = Fiddle::RUBY_FREE
@area = UI.new_area(area_handler)
brush = UI::FFI::DrawBrush.malloc
brush.to_ptr.free = Fiddle::RUBY_FREE

handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|
  area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
  rgb = (GrayScott::Color.colorize(@model.public_send(@uv.to_sym), @color_type)
                         .cast_to(Numo::SFloat)
                         .inplace / 255.0)
        .reshape!(height, ratio, width, ratio, 3).sum(1, 3) # Resize
        .inplace / (ratio**2)
  # 200 x 200 => 100 x 100 because LibUI is slow...
  height.times do |y|
    width.times do |x|
      path = UI.draw_new_path(UI::DrawFillModeWinding)
      UI.draw_path_add_rectangle(path,
                                 pix_size * (x + 1), pix_size * (y + 1),
                                 pix_size, pix_size)
      UI.draw_path_end(path)
      brush.Type = 0
      brush.R = rgb[y, x, 0]
      brush.G = rgb[y, x, 1]
      brush.B = rgb[y, x, 2]
      brush.A = 1.0
      UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)
      UI.draw_free_path(path)
    end
  end
end

do_nothing = Fiddle::Closure::BlockCaller.new(0, [0]) {}
key_event  = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 }

handler_mouse_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, e|
  e = UI::FFI::AreaMouseEvent.new(e)
  if e.Down == 1
    x = e.X * (ratio / pix_size.to_f)
    y = e.Y * (ratio / pix_size.to_f)
    next if x >= model_width + pointer_size ||
            y >= model_height + pointer_size

    yrange = ([(y - pointer_size), 0].max)..([y, (model_height - 1)].min)
    xrange = ([(x - pointer_size), 0].max)..([x, (model_width - 1)].min)
    @model.u[yrange, xrange] = 0.5
    @model.v[yrange, xrange] = 0.5
    UI.area_queue_redraw_all(@area)
  end
end

area_handler.Draw         = handler_draw_event
area_handler.MouseEvent   = handler_mouse_event
area_handler.MouseCrossed = do_nothing
area_handler.DragBroken   = do_nothing
area_handler.KeyEvent     = key_event

# slide_feed

label_f = UI.new_label('f')
slider_feed = UI.new_slider(0, 100)
UI.slider_set_value(slider_feed, @model.f * 1000)
UI.slider_on_changed(slider_feed) do |ptr|
  @model.f = UI.slider_value(ptr) / 1000.0
end

# slider_kill

label_k = UI.new_label('k')
slider_kill = UI.new_slider(0, 100)
UI.slider_set_value(slider_kill, @model.k * 1000)
UI.slider_on_changed(slider_kill) do |ptr|
  @model.k = UI.slider_value(ptr) / 1000.0
end

# combobox preset

# The presets are taken from the following implementations:
# https://github.com/pmneila/jsexp

presets = [
  {
    name: 'Default',
    feed: 0.037,
    kill: 0.06
  },  {
    name: 'Solitons',
    feed: 0.03,
    kill: 0.062
  },  {
    name: 'Pulsating Solitons',
    feed: 0.025,
    kill: 0.06
  },  {
    name: 'Worms',
    feed: 0.078,
    kill: 0.061
  },  {
    name: 'Mazes',
    feed: 0.029,
    kill: 0.057
  },  {
    name: 'Holes',
    feed: 0.039,
    kill: 0.058
  },  {
    name: 'Chaos',
    feed: 0.026,
    kill: 0.051
  },  {
    name: 'Chaos and holes',
    feed: 0.034,
    kill: 0.056
  },  {
    name: 'Moving spots',
    feed: 0.014,
    kill: 0.054
  },  {
    name: 'Spots and loops',
    feed: 0.018,
    kill: 0.051
  },  {
    name: 'Waves',
    feed: 0.014,
    kill: 0.045
  },  {
    name: 'The U-Skate World',
    feed: 0.062,
    kill: 0.06093
  }
]

cbox_presets = UI.new_combobox
presets.each do |preset|
  UI.combobox_append(cbox_presets, preset[:name])
end
UI.combobox_set_selected(cbox_presets, 0)
UI.combobox_on_selected(cbox_presets) do |ptr|
  preset = presets[UI.combobox_selected(ptr)]
  @model.f = preset[:feed]
  @model.k = preset[:kill]
  UI.slider_set_value(slider_feed, preset[:feed] * 1000)
  UI.slider_set_value(slider_kill, preset[:kill] * 1000)
end

# combobox u/v

uv = %w[u v]
cbox_uv = UI.new_combobox
uv.each do |s|
  UI.combobox_append(cbox_uv, s)
end
UI.combobox_set_selected(cbox_uv, 1)
UI.combobox_on_selected(cbox_uv) do |ptr|
  @uv = uv[UI.combobox_selected(ptr)]
  UI.area_queue_redraw_all(@area) unless @running
end

# combobox color

color_type_list = %w[
  colorful
  reverse-colorful
  red
  green
  blue
  reverse-red
  reverse-green
  reverse-blue
  grayscale
]

cbox_color = UI.new_combobox
color_type_list.each do |s|
  UI.combobox_append(cbox_color, s)
end
UI.combobox_set_selected(cbox_color, 0)
UI.combobox_on_selected(cbox_color) do |ptr|
  @color_type = color_type_list[UI.combobox_selected(ptr)]
  UI.area_queue_redraw_all(@area) unless @running
end

# button start/stop

button_start = UI.new_button('▶️ START')
UI.button_on_clicked(button_start) do
  @running = !@running
  UI.button_set_text(button_start,
                     @running ? '🛑 STOP' : '▶️ START')
end

# button capture

button_capture = UI.new_button('📷')
UI.button_on_clicked(button_capture) do
  image = GrayScott::Color.colorize(@model.public_send(@uv.to_sym), @color_type)

  pt = UI.save_file(@main_window)
  unless pt.null?
    file_path = pt.to_s
    if defined?(Magro::IO)
      Magro::IO.imsave(file_path, image)
    else
      img = ChunkyPNG::Image.from_rgb_stream(model_width, model_height, image.to_string)
      img.save(file_path)
    end
  end
end

# hbox

hbox1 = UI.new_horizontal_box
UI.box_set_padded(hbox1, 1)
UI.box_append(hbox1, cbox_presets, 0)
UI.box_append(hbox1, label_f, 0)
UI.box_append(hbox1, slider_feed, 1)
UI.box_append(hbox1, label_k, 0)
UI.box_append(hbox1, slider_kill, 1)

hbox2 = UI.new_horizontal_box
UI.box_set_padded(hbox2, 1)
UI.box_append(hbox2, cbox_uv, 0)
UI.box_append(hbox2, cbox_color, 0)
UI.box_append(hbox2, button_start, 1)
UI.box_append(hbox2, button_capture, 0)

# vbox

vbox = UI.new_vertical_box
UI.box_set_padded(vbox, 1)
UI.box_append(vbox, hbox1, 0)
UI.box_append(vbox, hbox2, 0)
UI.box_append(vbox, @area, 1)

# main window

@main_window = UI.new_window('Turing Pattern', 440, 560, 1)
UI.window_set_margined(@main_window, 1)
UI.window_set_child(@main_window, vbox)

UI.window_on_closing(@main_window) do
  UI.quit
  1
end
UI.control_show(@main_window)

# queue

UI.queue_main do
  UI.timer(WAIT_TIME) do
    next 1 unless @running # do nothing

    NUM_STEPS.times do
      @model.step
    end
    UI.area_queue_redraw_all(@area)
    1 # continue
  end
end

UI.main
UI.uninit


================================================
FILE: examples2/README.md
================================================
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).

The rationale (and objective) for this directory here serves at the least the following purposes:

- Provide standalone (working) .rb files that test individual components of
the ruby-libui suite, such as the various widgets that are part of (and supported by)
libui. For instance, button.rb should contain all code that relates to buttons,
including functionality to test the on-clicked event. Same for combobox.rb, which
should include all code specific to the combobox-widget, and so forth. Code used
for this purpose should be contained within a single .rb file only, as well as the
libui-bindings necessary to demonstrate its functionality (e. g. toplevel LibUI or
UI, the main window, usually a box to contain the widget at hand, and so forth). This
way a new user can look at the included functionality, learn from it, and quickly
adapt it to his or her use case.

- Provide explanations to any other methods and functions that are offered
by this project, even if it may not be directly related to a specific widget.
For instance, querying the current libui-version, if that is made available
by upstream code, and similar functionality that new users may find useful,
or old users may have forgotten - thus, this also serves as a quick refresher
for remembering how to use something, with a focus on specific widgets.

- Documentation and explanations within those individual .rb files. That way
new users of this project may learn the bindings made available by kojix2
more rapidly so.

Stay tuned for more updates in this regard in the long run. Right now nine
widgets have been added; expect more code in this regard over the next weeks
or months. \o/

I also invite other people to contribute changes, including documentation. Let's improve
the default experience of ruby + libui for new users, as well as provide working reference
implementations for all functionality made available in ruby-libui.

So far (this update) almost 82 "components" are verified by examples in the subdirectory examples2/ here.
I think we are close to 50% in total now or almost at 50%.

Widgets that have been added to this subdirectory include, as standalone files, in alphabetical
order:

    button.rb
    checkbox.rb
    color_button.rb
    combobox.rb
    date_picker.rb
    editable_combobox.rb
    entry.rb
    grid.rb
    multiline_entry.rb
    password_entry.rb
    progress_bar.rb
    search_entry.rb
    slider.rb
    window.rb

Note 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).

Available new-entries in regards to LibUI include:

- new_area
- new_attributed_string
- new_background_attribute
- new_button
- new_checkbox
- new_color_attribute
- new_color_button
- new_combobox
- new_date_picker
- new_date_time_picker
- new_editable_combobox
- new_entry
- new_family_attribute
- new_features_attribute
- new_font_button
- new_form
- new_grid
- new_group
- new_horizontal_box
- new_horizontal_separator
- new_image
- new_italic_attribute
- new_label
- new_menu
- new_multiline_entry
- new_non_wrapping_multiline_entry
- new_open_type_features
- new_password_entry
- new_progress_bar
- new_radio_buttons
- new_scrolling_area
- new_search_entry
- new_size_attribute
- new_slider
- new_spinbox
- new_stretch_attribute
- new_tab
- new_table
- new_table_model
- new_table_value_color
- new_table_value_image
- new_table_value_int
- new_table_value_string
- new_time_picker
- new_underline_attribute
- new_underline_color_attribute
- new_vertical_box
- new_vertical_separator
- new_weight_attribute
- new_window



================================================
FILE: examples2/button.rb
================================================
# ============================================================================ #
# This example (button.rb) shall demonstrate the following functionality
# (4 components), as well as their implementation-status in regards to
# this file:
#
#   :new_button                         # [DONE]
#   :button_on_clicked                  # [DONE]
#   :button_set_text                    # [DONE]
#   :button_text                        # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('button.rb', 400, 240, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_button # Create a new button here.
LibUI.box_append(hbox, _, 1) # Add the button here.
LibUI.button_set_text(_, 'This is a generic text for the button.')

puts 'The text for our button is as follows (obtained via LibUI.button_text():'
puts
puts "  #{LibUI.button_text(_)}"
puts

callback_for_the_button = proc {
  puts 'I was clicked.'
  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.
}

LibUI.button_on_clicked(_, callback_for_the_button)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/checkbox.rb
================================================
# ============================================================================ #
# This example (checkbox.rb) shall demonstrate the following functionality
# (6 components), as well as their implementation-status in regards to
# this file:
#
#   :new_checkbox              # [DONE]
#   :checkbox_checked          # [DONE]
#   :checkbox_on_toggled       # [DONE]
#   :checkbox_set_checked      # [DONE]
#   :checkbox_set_text         # [DONE]
#   :checkbox_text             # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('checkbox.rb', 400, 240, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_checkbox # Create a new checkbox here.
LibUI.box_append(hbox, _, 1) # Add it to a box.
LibUI.checkbox_set_text(_, 'This is a generic text for the checkbox.')

puts 'The text for our checkbox follows (obtained via LibUI.checkbox_text():'
puts
puts "  #{LibUI.checkbox_text(_)}"
puts

callback_for_the_checkbox = proc {
  puts 'I was toggled. My state is now:'
  case LibUI.checkbox_checked(_)
  when 1
    puts '  checked (active)'
  when 0
    puts '  unchecked (inactive)'
  end
  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.
}

LibUI.checkbox_on_toggled(_, callback_for_the_checkbox)

puts 'Setting the checkbox to checked (is-selected) next.'
LibUI.checkbox_set_checked(_, 1)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/color_button.rb
================================================
# ============================================================================ #
# This example (color_button.rb) shall demonstrate the following
# functionality (4 components), as well as their implementation-status
# in regards to this file:
#
#   :new_color_button              # [DONE]
#   :color_button_color            # [DONE]
#   :color_button_on_changed       # [DONE]
#   :color_button_set_color        # [DONE]
#
# See an API reference here:
#
#   https://libui-ng.github.io/libui-ng/structui_editable_combobox.html
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('color_button.rb', 640, 240, 1)

# ============================================================================ #
# Get the colours and set up the brush
# uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA)
# ============================================================================ #
graph_r = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
graph_g = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
graph_b = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double
graph_a = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE) # double

vbox = LibUI.new_vertical_box
LibUI.box_set_padded(vbox, 1)
_ = LibUI.new_color_button # Create a new color-button here.
LibUI.box_append(vbox, _, 0) # Add the combobox here. Right now the combobox is empty.

LibUI.color_button_on_changed(_) {
  puts 'The colour button was changed.'
}

BRUSH             = LibUI::FFI::DrawBrush.malloc
BRUSH.to_ptr.free = Fiddle::RUBY_FREE

# === set_solid_brush
def set_solid_brush(
    brush = BRUSH,
    color = 0x1E90FF,
    alpha
  )
  BRUSH.Type = 0 # solid
  BRUSH.R = ((color >> 16) & 0xFF) / 255.0
  BRUSH.G = ((color >>  8) & 0xFF) / 255.0
  BRUSH.B = (color & 0xFF) / 255.0
  BRUSH.A = alpha
  BRUSH
end

puts 'Set to a blue colour next.'
set_solid_brush(BRUSH, 0x1E90FF, 1.0)

LibUI.color_button_set_color(_, BRUSH.R, BRUSH.G, BRUSH.B, BRUSH.A)

LibUI.color_button_color(_, graph_r, graph_g, graph_b, graph_a) # Use LibUI.color_button_color() here.

LibUI.window_set_child(main_window, vbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/combobox.rb
================================================
# ============================================================================ #
# This example (combobox.rb) shall demonstrate the following functionality
# (9 components), as well as their implementation-status in regards to
# this file:
#
#   :new_combobox                       [DONE]
#   :combobox_append                    [DONE]
#   :combobox_clear                     [DONE]
#   :combobox_delete                    [DONE]          
#   :combobox_insert_at                 [DONE]
#   :combobox_num_items                 [DONE]
#   :combobox_on_selected               [DONE]
#   :combobox_selected                  [DONE]
#   :combobox_set_selected              [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

# ============================================================================ #
# === populate_the_combobox_with_this_array
#
# This method is used as a helper-method, to populate the combobox we use
# here with data (an Array).
# ============================================================================ #
def populate_the_combobox_with_this_array(
    the_combobox,
    this_array
  )
  this_array.each {|this_entry|
    LibUI.combobox_append(the_combobox, this_entry) # Here we add elements to the combobox.
  }
  LibUI.combobox_set_selected(the_combobox, 0) # The first element is now the default selected entry.
end

main_window = LibUI.new_window('combobox.rb', 400, 240, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_  = LibUI.new_combobox # Create a new combobox here.
LibUI.box_append(hbox, _, 1) # Add the combobox here. Right now the combobox is empty.

# ============================================================================ #
# Let's add data to the combobox, as an Array:
# ============================================================================ #
array = %w( matz created ruby as efficient alternative to perl )
populate_the_combobox_with_this_array(_, array)

# ============================================================================ #
# Next showing how to clear it:
# ============================================================================ #
LibUI.combobox_clear(_)
populate_the_combobox_with_this_array(_, array) # And re-populate it here.

# ============================================================================ #
# Next we delete the third entry; and then insert two new elements
# at that former position, to showcase the functionality
# combobox_delete, as well as combobox_insert_at. Note that combobox_delete
# takes two arguments, whereas combobox_insert_at takes three arguments.
# ============================================================================ #
LibUI.combobox_delete(_, 2)
LibUI.combobox_insert_at(_, 2, 'ruby')
LibUI.combobox_insert_at(_, 2, 'awesome')

# ============================================================================ #
# Show how many elements are in that combobox:
# ============================================================================ #
puts "The combobox we are using here has a total "\
     "of #{LibUI.combobox_num_items(_)} elements."

LibUI.combobox_set_selected(_, 3) # Change the selected element next, to item 4.

# ============================================================================ #
# Show the selected entry next:
# ============================================================================ #
puts "The presently selected entry in our combobox is element number "\
     "#{LibUI.combobox_selected(_)}."

puts 'Last but not least, try to change the combobox to a new value.'
puts 'This will trigger LibUI.combobox_on_selected().'

LibUI.combobox_on_selected(_) { |pointer|
  selected = LibUI.combobox_selected(pointer)
  puts "The new selection is element number `#{selected}`."
}

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/date_picker.rb
================================================
# ============================================================================ #
# === DatePicker - a widget to allow the user to enter a date
#
# This example (date_picker.rb) shall demonstrate the following functionality
# (3 components), as well as their implementation-status in regards to
# this file:
#
#   :new_date_picker             # [DONE]
#   :date_time_picker_on_changed # [DONE]
#   :date_time_picker_set_time   # [NOT YET IMPLEMENTED]
#
# Documentation for the perl-API, for libui, can be seen here:
#
#   https://metacpan.org/pod/LibUI::DatePicker
#
# While this is not necessarily 1:1 the ruby-API, for the most part it
# is quite similar.
#
# Note that not all functionality related to date_picker is tested for
# yet in this file. Patches to enhance functionality as well as the
# documentation are welcome.
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('date_picker.rb', 640, 240, 1)

vbox = LibUI.new_vertical_box
LibUI.box_set_padded(vbox, 1)
_ = LibUI.new_date_picker # Create a date-picker widget here.
LibUI.box_append(vbox, _, 0) # Add the font-button here.

callback_on_changed = proc { |pointer|
  puts 'The time was changed.'
  #puts LibUI.date_time_picker_time(pointer) 
}
LibUI.date_time_picker_on_changed(_, callback_on_changed)

# uiDateTimePickerSetTime(d : UI::DateTimePicker*, tm : LibC::Tm*)
# LibUI.date_time_picker_set_time(_, Time.now)

LibUI.window_set_child(main_window, vbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/editable_combobox.rb
================================================
# ============================================================================ #
# This example (editable_combobox.rb) shall demonstrate the following
# functionality (5 components), as well as their implementation-status in
# regards to this file:
#
#   :new_editable_combobox                 # [DONE]
#   :editable_combobox_append              # [DONE]
#   :editable_combobox_on_changed          # [DONE]
#   :editable_combobox_set_text            # [DONE]
#   :editable_combobox_text                # [DONE]
#
# See an API reference here:
#
#   https://libui-ng.github.io/libui-ng/structui_editable_combobox.html
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

# ============================================================================ #
# === populate_the_combobox_with_this_array
#
# This method is used as a helper-method, to populate the combobox we use
# here with data (an Array).
# ============================================================================ #
def populate_the_combobox_with_this_array(
    the_combobox,
    this_array
  )
  this_array.each {|this_entry|
    LibUI.editable_combobox_append(the_combobox, this_entry) # Here we add elements to the combobox.
  }
  LibUI.combobox_set_selected(the_combobox, 0) # The first element is now the default selected entry.
end

main_window = LibUI.new_window('combobox.rb', 640, 480, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_  = LibUI.new_editable_combobox # Create a new combobox here.
LibUI.box_append(hbox, _, 1) # Add the combobox here. Right now the combobox is empty.

# ============================================================================ #
# Let's add data to the combobox, as an Array:
# ============================================================================ #
array = %w( matz created ruby as efficient alternative to perl )
populate_the_combobox_with_this_array(_, array)

# ============================================================================ #
# Next showing how to clear it:
# ============================================================================ #
LibUI.combobox_clear(_)
populate_the_combobox_with_this_array(_, array) # And re-populate it here.

# ============================================================================ #
# Next we delete the third entry; and then insert two new elements
# at that former position, to showcase the functionality
# combobox_delete, as well as combobox_insert_at. Note that combobox_delete
# takes two arguments, whereas combobox_insert_at takes three arguments.
# ============================================================================ #
LibUI.combobox_delete(_, 2)
LibUI.combobox_insert_at(_, 2, 'ruby')

# ============================================================================ #
# Show how many elements are in that combobox:
# ============================================================================ #
puts "The combobox we are using here has a total "\
     "of #{LibUI.combobox_num_items(_)} elements."

LibUI.combobox_set_selected(_, 3) # Change the selected element next, to item 4.

# ============================================================================ #
# Show the selected entry next:
# ============================================================================ #
puts "The presently selected entry in our combobox is element number "\
     "#{LibUI.combobox_selected(_)}."

puts 'Last but not least, try to change the combobox to a new value.'
puts 'This will trigger LibUI.combobox_on_selected().'

# ============================================================================ #
# Testing support for :editable_combobox_on_changed next.
# ============================================================================ #
LibUI.editable_combobox_on_changed(_) { |pointer|
  selected = LibUI.combobox_selected(pointer)
  puts "The new selection is element number `#{selected}`."
  puts "The currently selected text is: `#{LibUI.editable_combobox_text(pointer)}`"
}

# ============================================================================ #
# As explained by kojix2, libui-ng intentionally limits editable comboboxes
# to simple text get/set functionality.
#
# Since users can freely input text in an editable combobox, the concept
# of "which item is selected" becomes ambiguous. This is why rather
# than using LibUI.combobox_set_selected(), LibUI.editable_combobox_set_text()
# is used next. 
# ============================================================================ #
LibUI.editable_combobox_set_text(_, 'Testing a new default text. This will appear first.')

LibUI.editable_combobox_append(_, 'This is a black cat.') # Here we add elements to the combobox.

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/entry.rb
================================================
# ============================================================================ #
# This example (entry.rb) shall demonstrate the following functionality
# (6 components), as well as their implementation-status in regards to
# this file:
#
#   :new_entry                   # [DONE]
#   :entry_on_changed            # [DONE]
#   :entry_read_only             # [DONE]
#   :entry_set_read_only         # [DONE]
#   :entry_set_text              # [DONE]
#   :entry_text                  # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('entry.rb', 800, 440, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_entry # Create a new entry here.
LibUI.box_append(hbox, _, 1) # Add the entry here.
@old_entry_text = 'This is a generic text for the entry.'
LibUI.entry_set_text(_, @old_entry_text)

puts 'The entry will be set to read-only next, via '\
     'LibUI.entry_set_read_only().'

LibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.
puts
puts "Is this entry read-only? #{LibUI.entry_read_only(_)}"\
     " # note that a 1 here means yes/true"
puts
puts 'The text for the current entry in use is as follows:'
puts
puts "  → #{LibUI.entry_text(_)}"
puts
puts 'Making the entry no longer read-only next:'
puts
LibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.

callback_proc = proc { |pointer|
  new_text = LibUI.entry_text(pointer).to_s
  puts
  puts "The old entry-text was: '#{@old_entry_text}'"
  puts "The new entry-text is:  '#{new_text}'"
  @old_entry_text = new_text 
}
LibUI.entry_on_changed(_, callback_proc)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/font_button.rb
================================================
# ============================================================================ #
# This example (font_button.rb) shall demonstrate the following
# functionality (5 components), as well as their implementation-status
# in regards to this file:
#
#   :new_font_button              # [DONE]
#   :font_button_font             # [NOT YET ADDED]
#   :font_button_on_changed       # [DONE]
#   :free_font_button_font        # [NOT YET ADDED]
#   :free_font_descriptor         #[NOT YET ADDED]
#
# Unsure what ":load_control_ font" is.
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('font_button.rb', 640, 240, 1)

vbox = LibUI.new_vertical_box
LibUI.box_set_padded(vbox, 1)
_ = LibUI.new_font_button # Create a new font-button here.
LibUI.box_append(vbox, _, 0) # Add the font-button here.

LibUI.font_button_on_changed(_) {|entry|
  puts 'The font was changed. (class '+entry.class.to_s+')'
}

LibUI.window_set_child(main_window, vbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/grid.rb
================================================
# ============================================================================ #
# This example (grid.rb) shall demonstrate the following functionality
# (8 components), as well as their implementation-status in regards to
# this file:
#
#   :new_grid                                   # [DONE]
#   :grid_append                                # [DONE]
#   :grid_insert_at                             # Unsure how to do this.
#   :grid_padded                                # [DONE]
#   :grid_set_padded                            # [DONE]
#
# API documentation can be seen here:
#
#   https://libui.dev/structui_grid.html
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('grid.rb', 800, 250, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_grid # Create a new grid here.
LibUI.box_append(hbox, _, 1) # Add the grid here.
LibUI.grid_set_padded(_,  0) # Here we could toggle the padded-status.
puts 'Is the grid padded? '+LibUI.grid_padded(_).to_s+
     ' (1 means yes)'

button1 = LibUI.new_button('Test-Button #1')
button2 = LibUI.new_button('Test-Button #2')
button3 = LibUI.new_button('Test-Button #3')
button4 = LibUI.new_button('Test-Button #4')

left    = 0
top     = 0
xspan   = 1
yspan   = 1
hexpand = 1
halign  = 1
vexpand = 1
valign  = 1

# ======================================================================== #
# left, top, xspan, yspan, hexpand, halign, vexpand, valign
#  0,    0,    2,     1,      0,      1,       1,      1
# ======================================================================== #
LibUI.grid_append(
  _,
  button1, # This is the widget that will be added (appended) onto the grid-widget.
  left,
  top,
  xspan,
  yspan,
  hexpand,
  halign,
  vexpand,
  valign
)


left    = 1
top     = 0
xspan   = 1
yspan   = 1
hexpand = 1
halign  = 1
vexpand = 1
valign  = 1

# ======================================================================== #
# left, top, xspan, yspan, hexpand, halign, vexpand, valign
#  0,    0,    2,     1,      0,      1,       1,      1
# ======================================================================== #
LibUI.grid_append(
  _,
  button2, # This is the widget that will be added (appended) onto the grid-widget.
  left,
  top,
  xspan,
  yspan,
  hexpand,
  halign,
  vexpand,
  valign
)

left    = 0
top     = 1
xspan   = 1
yspan   = 1
hexpand = 1
halign  = 1
vexpand = 1
valign  = 1

# ======================================================================== #
# left, top, xspan, yspan, hexpand, halign, vexpand, valign
#  0,    1,    2,     1,      0,      1,       1,      1
# ======================================================================== #
LibUI.grid_append(
  _,
  button3, # This is the widget that will be added (appended) onto the grid-widget.
  left,
  top,
  xspan,
  yspan,
  hexpand,
  halign,
  vexpand,
  valign
)

left    = 1
top     = 1
xspan   = 1
yspan   = 1
hexpand = 1
halign  = 1
vexpand = 1
valign  = 1

# ======================================================================== #
# left, top, xspan, yspan, hexpand, halign, vexpand, valign
#  1,    1,    2,     1,      0,      1,       1,      1
# ======================================================================== #
LibUI.grid_append(
  _,
  button4, # This is the widget that will be added (appended) onto the grid-widget.
  left,
  top,
  xspan,
  yspan,
  hexpand,
  halign,
  vexpand,
  valign
)

if false # The next clause does not work correctly yet.
entry1  = LibUI.new_entry
# ============================================================================ #
# See: https://libui.dev/structui_grid.html#ad282fc62adbaed067699f949d619899c
#
# Arguments to LibUI.grid_insert_at() are:
#
#   void uiGridInsertAt	(	uiGrid *	g,
#   uiControl *	c,
#   uiControl *	existing,
#   uiAt	at,
#   int	xspan,
#   int	yspan,
#   int	hexpand,
#   uiAlign	halign,
#   int	vexpand,
#   uiAlign	valign )
#
# ============================================================================ #
LibUI.grid_insert_at(
  _,
  entry1,  # The widget to insert.
  button3, # Our relative widget.
  LibUI::AtTrailing, # at: Placement specifier in relation to existing control.
  0, # xspan
  1, # yspan
  1,
  1,
  1,
  1
)
end

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/multiline_entry.rb
================================================
# ============================================================================ #
# This example (multiline_entry.rb) shall demonstrate the following functionality
# (6 components), as well as their implementation-status in regards to
# this file:
#
#   :multiline_entry_append        # [DONE]
#   :multiline_entry_on_changed
#   :multiline_entry_read_only     # [DONE]
#   :multiline_entry_set_read_only # [DONE]
#   :multiline_entry_set_text      # [DONE]
#   :multiline_entry_text
#   :new_multiline_entry           # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('entry.rb', 800, 440, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_multiline_entry # Create a new entry here.
LibUI.box_append(hbox, _, 1) # Add the entry here.
@old_entry_text = 'This is a generic text for the entry.'
LibUI.multiline_entry_set_text(_, @old_entry_text)
puts 'Appending something next.'
LibUI.multiline_entry_append(_, ' More content.')

callback_proc = proc { |pointer|
  new_text = LibUI.multiline_entry_text(pointer).to_s
  puts
  puts "The old entry-text was: '#{@old_entry_text}'"
  puts "The new entry-text is:  '#{new_text}'"
  @old_entry_text = new_text 
}
LibUI.multiline_entry_on_changed(_, callback_proc)

# LibUI.multiline_entry_set_read_only(_, 1) # Set it read-only here.
# LibUI.multiline_entry_read_only(_) # Query the read-only way here.

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/password_entry.rb
================================================
# ============================================================================ #
# This example (password_entry.rb) shall demonstrate the following functionality
# (6 components), as well as their implementation-status in regards to
# this file:
#
#   :new_entry                   # [DONE]
#   :entry_on_changed            # [DONE]
#   :entry_read_only             # [DONE]
#   :entry_set_read_only         # [DONE]
#   :entry_set_text              # [DONE]
#   :entry_text                  # [DONE]
#
# Note that password-entry is a subclass of uiEntry.
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('password_entry.rb', 800, 440, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_password_entry # Create a new password-entry here.
LibUI.box_append(hbox, _, 1) # Add the password-entry here.
@old_entry_text = 'foobar' # Use a very short password here.
LibUI.entry_set_text(_, @old_entry_text)

puts 'The password-entry will be set to read-onlyn ext, via '\
     'LibUI.entry_set_read_only().'

LibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.
puts
puts "Is this entry read-only? #{LibUI.entry_read_only(_)}"\
     " # note that a 1 here means yes/true"
puts
puts 'The text for the current entry in use is as follows:'
puts
puts "  → #{LibUI.entry_text(_)}"
puts
puts 'Making the entry no longer read-only next:'
puts
LibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.

callback_proc = proc { |pointer|
  new_text = LibUI.entry_text(pointer).to_s
  puts
  puts "The old entry-text was: '#{@old_entry_text}'"
  puts "The new entry-text is:  '#{new_text}'"
  @old_entry_text = new_text
  puts
  puts 'Note that this callback can be modified to'
  puts 'allow for the search functionality'
  puts
  puts 'Interestingly the output shows the real password,'
  puts 'so be careful with this.'
}
LibUI.entry_on_changed(_, callback_proc)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/progress_bar.rb
================================================
# ============================================================================ #
# This example (progress_bar.rb) shall demonstrate the following
# functionality (2 components), as well as their implementation-status
# in regards to this file:
#
#   :progress_bar_set_value       # [DONE]
#   :progress_bar_value           # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('ProgressBar', 640, 240, 1)

vbox = LibUI.new_vertical_box
LibUI.box_set_padded(vbox, 1)
_ = LibUI.new_progress_bar # Create a new progressbar here.
LibUI.progress_bar_set_value(_, 42) # Show how to set a value to a progress bar.
LibUI.box_append(vbox, _, 0.5) # Add the progressbar here.

puts 'The current value of the progress bar is: '+
      LibUI.progress_bar_value(_).to_s

LibUI.window_set_child(main_window, vbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/search_entry.rb
================================================
# ============================================================================ #
# This example (search_entry.rb) shall demonstrate the following functionality
# (6 components), as well as their implementation-status in regards to
# this file:
#
#   :new_entry                   # [DONE]
#   :entry_on_changed            # [DONE]
#   :entry_read_only             # [DONE]
#   :entry_set_read_only         # [DONE]
#   :entry_set_text              # [DONE]
#   :entry_text                  # [DONE]
#
# Note that search-entry is a subclass of uiEntry.
#
# See also rust-documentation here:
#
#   https://docs.rs/libui/latest/libui/controls/struct.SearchEntry.html
#
# Or Go documentation here:
#
#   https://pkg.go.dev/github.com/andlabs/ui#NewSearchEntry
# 
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('search_entry.rb', 800, 440, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

_ = LibUI.new_search_entry # Create a new search-entry here.
LibUI.box_append(hbox, _, 1) # Add the search-entry here.
@old_entry_text = 'This is a generic text for the search-entry.'
LibUI.entry_set_text(_, @old_entry_text)

puts 'The entry will be set to read-onlyn ext, via '\
     'LibUI.entry_set_read_only().'

LibUI.entry_set_read_only(_, 1) # We have to use 1 rather than true here, unfortunately.
puts
puts "Is this entry read-only? #{LibUI.entry_read_only(_)}"\
     " # note that a 1 here means yes/true"
puts
puts 'The text for the current entry in use is as follows:'
puts
puts "  → #{LibUI.entry_text(_)}"
puts
puts 'Making the entry no longer read-only next:'
puts
LibUI.entry_set_read_only(_, 0) # We have to use 1 rather than true here, unfortunately.

callback_proc = proc { |pointer|
  new_text = LibUI.entry_text(pointer).to_s
  puts
  puts "The old entry-text was: '#{@old_entry_text}'"
  puts "The new entry-text is:  '#{new_text}'"
  @old_entry_text = new_text
  puts
  puts 'Note that this callback can be modified to'
  puts 'allow for the search functionality'
}
LibUI.entry_on_changed(_, callback_proc)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/slider.rb
================================================
# ============================================================================ #
# This example (slider.rb) shall demonstrate the following functionality
# (8 components), as well as their implementation-status in regards to
# this file:
#
#   :new_slider                                 # [DONE]
#   :slider_has_tool_tip                        # [DONE]
#   :slider_on_changed                          # [DONE]
#   :slider_on_released                         # [DONE]
#   :slider_set_has_tool_tip                    # [DONE]
#   :slider_set_range                           # [DONE]
#   :slider_set_value                           # [DONE]
#   :slider_value                               # [DONE]
#
# API documentation can be seen here:
#
#   https://libui.dev/structui_slider.html
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('slider.rb', 800, 440, 1)

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

# new_slider() wants: uiNewSlider (int min, int max)
_ = LibUI.new_slider(1, 100) # Create a new slider here.
LibUI.box_append(hbox, _, 1) # Add the slider here.

# ============================================================================ #
# The default "tooltip" is the current value of the slider at hand.
# ============================================================================ #
puts 'Does this slider haver a tooltip? '+
     LibUI.slider_has_tool_tip(_).to_s

callback_proc_on_changed = proc {|entry| # entry is a Fiddle::Pointer
  new_value = LibUI.slider_value(entry) # Obtain the current value of the slider here.
  puts 'The slider was changed. The new value is: '+new_value.to_s
}
LibUI.slider_set_has_tool_tip(_, 1) # 1 means true here

puts 'Modifying the range now from -200 to +200.'
LibUI.slider_set_range(_, -200, 200)

puts 'Setting the value of the slider to 42 now, as a new default.'
LibUI.slider_set_value(_, 42)

LibUI.slider_on_changed(_, callback_proc_on_changed)

callback_proc_on_released = proc {|entry| # entry is a Fiddle::Pointer
  puts 'The slider was released.'
}

LibUI.slider_on_released(_, callback_proc_on_released)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)

LibUI.window_on_closing(main_window) {
  LibUI.quit
  1
}

LibUI.main
LibUI.uninit


================================================
FILE: examples2/todo/todo.md
================================================
This file is no longer necessary - the parent directory keeps track of what is finished and what is yet-to-be-done.


================================================
FILE: examples2/window.rb
================================================
# ============================================================================ #
# This example (window.rb) shall demonstrate the following functionality
# (18 components), as well as their implementation-status in regards to
# this file:
#
#  :new_window                                   # [DONE]
#  :window_borderless                            # [DONE]
#  :window_content_size                          # Unsure how to use this
#  :window_focused                               # [DONE] - returns whether or not the window is focused.
#  :window_fullscreen                            # [DONE]
#  :window_margined                              # [DONE]
#  :window_on_closing                            # [DONE]
#  :window_on_content_size_changed               # [DONE]
#  :window_on_focus_changed                      # [DONE]
#  :window_resizeable                            # [DONE]
#  :window_set_borderless                        # [DONE]
#  :window_set_child                             # [DONE]
#  :window_set_content_size                      # [DONE]
#  :window_set_fullscreen                        # [DONE]
#  :window_set_margined                          # [DONE]
#  :window_set_resizeable                        # [DONE]
#  :window_set_title                             # [DONE]
#  :window_title                                 # [DONE]
#
# ============================================================================ #
require 'libui'
LibUI.init # Initialize LibUI.

main_window = LibUI.new_window('window.rb', 880, 640, 1)
LibUI.window_set_title(main_window, 'TEST TITLE')
puts 'The temporary title of this window is: '+
      LibUI.window_title(main_window)
LibUI.window_set_title(main_window, 'window.rb') # And restore the title here again.
LibUI.window_set_resizeable(main_window, 1) # Making it resizeable - is better, in my opinion.
LibUI.window_set_margined(main_window, 1)
puts 'Is the window margined? '+
      LibUI.window_margined(main_window).to_s

puts 'The main-window will have a margin, thanks to LibUI.window_set_margined()'

puts 'Can this window be resized? '+
      LibUI.window_resizeable(main_window).to_s

puts 'Setting this window to fullscreen next, by default.'
puts '(Actually, no, because this is annoying; the code for this'
puts 'is LibUI.window_set_fullscreen(main_window, 1), though.'
# LibUI.window_set_fullscreen(main_window, 1)
puts 'You can also query it via LibUI.window_fullscreen(main_window)'

hbox = LibUI.new_horizontal_box
LibUI.box_set_padded(hbox, 1)

# ============================================================================ #
# LibUI.window_set_content_size() is actually
# LibUI::FFI.uiWindowSetContentSize
#
# The code for this is:
#
#   try_extern 'void uiWindowSetContentSize(uiWindow *w, int width, int height)'
#
# So it needs two arguments. Note that this can be ignored by the system
# though.
#
# More documentation for this method can be seen here:
#
#   https://libui.dev/structui_window.html#a1f33b8462a999bdaf276bcdca07dfe28
#
# ============================================================================ #
_ = LibUI.new_label(
  "Just testing the window-widget here.\n\n"\
  "For testing-purposes this window can not be resized, "\
  "which is\nNOT recommended. See LibUI.window_set_resizeable()"
  ) # Create a new help-text. here.
LibUI.box_append(hbox, _, 1) # Add it to a box.

puts
puts 'The window will be borderless, thanks to LibUI.window_set_borderless()'
puts 'Actually, that is not extremely useful, so we do not use it.'
LibUI.window_set_borderless(main_window, 0)
puts 'You can test whether it is borderless via LibUI.borderless()'

callback_proc = proc { |pointer|
  puts '_'*80
  puts 'The focus changed. This happens when the main-window'
  puts 'is dragged to a new position, for instance, as well as'
  puts 'on startup.'
  # === window_content_size
  #
  # try_extern 'void uiWindowContentSize(uiWindow *w, int *width, int *height)'
  #
  # puts 'The window-content-size is '+
  #       LibUI.window_content_size(main_window, 15,15).to_s
  puts '_'*80
}

LibUI.window_on_focus_changed(main_window, callback_proc)
puts 'Is the window focused? '+LibUI.window_focused(main_window).to_s

callback_on_content_size_changed = proc { |pointer|
  puts 'The content-size of the main window was changed.'
}
LibUI.window_on_content_size_changed(main_window, callback_on_content_size_changed)

# ============================================================================ #
# Moves the window to the specified position.
# ============================================================================ #
# puts 'Set to a position:'
# LibUI.window_set_position(main_window, 2, 2)

LibUI.window_set_child(main_window, hbox)
LibUI.control_show(main_window)
LibUI.window_on_closing(main_window) {
  # Do on-closing actions here.
  LibUI.quit
  1 # An Integer must be returned by this block.
}

puts 'Setting the content-size of the main window to a value of 2200, 500 next,'
puts 'to test the method called LibUI.window_set_content_size():'
LibUI.window_set_content_size(main_window, 2200, 500) # This actually works, I tested it recently.

LibUI.main
LibUI.uninit


================================================
FILE: lib/libui/error.rb
================================================
module LibUI
  # base error class
  class Error < StandardError; end

  # LibUI shared library not found error
  class LibraryNotFoundError < Error; end

  # LibUI shared library load error
  class LibraryLoadError < Error; end
end


================================================
FILE: lib/libui/ffi.rb
================================================
require 'fiddle/import'
require_relative 'fiddle_patch'
require_relative 'error'

module LibUI

  module FFI
    extend Fiddle::Importer
    extend FiddlePatch

    if LibUI.ffi_lib.nil?
      raise LibraryNotFoundError, 'Could not find libui shared library. LibUI.ffi_lib is nil.'
    elsif !File.exist?(LibUI.ffi_lib)
      raise LibraryNotFoundError, "Could not find libui shared library: #{LibUI.ffi_lib}"
    else
      begin
        dlload LibUI.ffi_lib
      rescue LoadError
        raise LibraryLoadError, "Could not load libui shared library: #{LibUI.ffi_lib}"
      end
    end

    class << self
      attr_reader :func_map

      def try_extern(signature, *opts)
        extern(signature, *opts)
      rescue StandardError => e
        # Do not raise error when the function is not found
        # because some functions may not be available on older versions of libui.
        warn "#{e.class.name}: #{e.message}"
      end

      def ffi_methods
        @ffi_methods ||= func_map.each_key.to_a
      end
    end

    typealias('uint32_t', 'unsigned int')
    typealias('uint64_t', 'unsigned long long')

    InitOptions = struct [
      'size_t Size'
    ]

    # https://github.com/andlabs/libui/blob/master/ui.h
    # keep same order

    try_extern 'const char *uiInit(uiInitOptions *options)'
    try_extern 'void uiUninit(void)'
    try_extern 'void uiFreeInitError(const char *err)'

    try_extern 'void uiMain(void)'
    try_extern 'void uiMainSteps(void)'
    try_extern 'int uiMainStep(int wait)'
    try_extern 'void uiQuit(void)'
    try_extern 'void uiQueueMain(void (*f)(void *data), void *data)'
    try_extern 'void uiTimer(int milliseconds, int (*f)(void *data), void *data)'
    try_extern 'void uiOnShouldQuit(int (*f)(void *data), void *data)'
    try_extern 'void uiFreeText(char *text)'

    Control = struct [
      'uint32_t Signature',
      'uint32_t OSSignature',
      'uint32_t TypeSignature',
      'void (*Destroy)(uiControl *)',
      'uintptr_t (*Handle)(uiControl *)',
      'uiControl *(*Parent)(uiControl *)',
      'void (*SetParent)(uiControl *, uiControl *)',
      'int (*Toplevel)(uiControl *)',
      'int (*Visible)(uiControl *)',
      'void (*Show)(uiControl *)',
      'void (*Hide)(uiControl *)',
      'int (*Enabled)(uiControl *)',
      'void (*Enable)(uiControl *)',
      'void (*Disable)(uiControl *)'
    ]

    try_extern 'void uiControlDestroy(uiControl *c)'
    try_extern 'uintptr_t uiControlHandle(uiControl *c)'
    try_extern 'uiControl *uiControlParent(uiControl *c)'
    try_extern 'void uiControlSetParent(uiControl *c, uiControl *parent)'
    try_extern 'int uiControlToplevel(uiControl *c)'
    try_extern 'int uiControlVisible(uiControl *c)'
    try_extern 'void uiControlShow(uiControl *c)'
    try_extern 'void uiControlHide(uiControl *c)'
    try_extern 'int uiControlEnabled(uiControl *c)'
    try_extern 'void uiControlEnable(uiControl *c)'
    try_extern 'void uiControlDisable(uiControl *c)'

    try_extern 'uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, const char *typenamestr)'
    try_extern 'void uiFreeControl(uiControl *c)'

    try_extern 'void uiControlVerifySetParent(uiControl *c, uiControl *parent)'
    try_extern 'int uiControlEnabledToUser(uiControl *c)'

    try_extern 'void uiUserBugCannotSetParentOnToplevel(const char *type)'

    # uiWindow

    try_extern 'char *uiWindowTitle(uiWindow *w)'
    try_extern 'void uiWindowSetTitle(uiWindow *w, const char *title)'
    try_extern 'void uiWindowPosition(uiWindow *w, int *x, int *y)'
    try_extern 'void uiWindowSetPosition(uiWindow *w, int x, int y)'
    try_extern 'void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'
    try_extern 'void uiWindowContentSize(uiWindow *w, int *width, int *height)'
    try_extern 'void uiWindowSetContentSize(uiWindow *w, int width, int height)'
    try_extern 'int uiWindowFullscreen(uiWindow *w)'
    try_extern 'void uiWindowSetFullscreen(uiWindow *w, int fullscreen)'
    try_extern 'void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'
    try_extern 'void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *sender, void *senderData), void *data)'
    try_extern 'void uiWindowOnFocusChanged(uiWindow *w, void (*f)(uiWindow *sender, void *senderData), void *data)'
    try_extern 'int uiWindowFocused(uiWindow *w)'
    try_extern 'int uiWindowBorderless(uiWindow *w)'
    try_extern 'void uiWindowSetBorderless(uiWindow *w, int borderless)'
    try_extern 'void uiWindowSetChild(uiWindow *w, uiControl *child)'
    try_extern 'int uiWindowMargined(uiWindow *w)'
    try_extern 'void uiWindowSetMargined(uiWindow *w, int margined)'
    try_extern 'int uiWindowResizeable(uiWindow *w)'
    try_extern 'void uiWindowSetResizeable(uiWindow *w, int resizeable)'
    try_extern 'uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)'

    # uiButton

    try_extern 'char *uiButtonText(uiButton *b)'
    try_extern 'void uiButtonSetText(uiButton *b, const char *text)'
    try_extern 'void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *sender, void *senderData), void *data)'
    try_extern 'uiButton *uiNewButton(const char *text)'

    # uiBox

    try_extern 'void uiBoxAppend(uiBox *b, uiControl *child, int stretchy)'
    try_extern 'int uiBoxNumChildren(uiBox *b)'
    try_extern 'void uiBoxDelete(uiBox *b, int index)'
    try_extern 'int uiBoxPadded(uiBox *b)'
    try_extern 'void uiBoxSetPadded(uiBox *b, int padded)'
    try_extern 'uiBox *uiNewHorizontalBox(void)'
    try_extern 'uiBox *uiNewVerticalBox(void)'

    # uiCheckbox

    try_extern 'char *uiCheckboxText(uiCheckbox *c)'
    try_extern 'void uiCheckboxSetText(uiCheckbox *c, const char *text)'
    try_extern 'void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *sender, void *senderData), void *data)'
    try_extern 'int uiCheckboxChecked(uiCheckbox *c)'
    try_extern 'void uiCheckboxSetChecked(uiCheckbox *c, int checked)'
    try_extern 'uiCheckbox *uiNewCheckbox(const char *text)'

    # uiEntry

    try_extern 'char *uiEntryText(uiEntry *e)'
    try_extern 'void uiEntrySetText(uiEntry *e, const char *text)'
    try_extern 'void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *sender, void *senderData), void *data)'
    try_extern 'int uiEntryReadOnly(uiEntry *e)'
    try_extern 'void uiEntrySetReadOnly(uiEntry *e, int readonly)'
    try_extern 'uiEntry *uiNewEntry(void)'
    try_extern 'uiEntry *uiNewPasswordEntry(void)'
    try_extern 'uiEntry *uiNewSearchEntry(void)'

    # uiLabel

    try_extern 'char *uiLabelText(uiLabel *l)'
    try_extern 'void uiLabelSetText(uiLabel *l, const char *text)'
    try_extern 'uiLabel *uiNewLabel(const char *text)'

    # uiTab

    try_extern 'void uiTabAppend(uiTab *t, const char *name, uiControl *c)'
    try_extern 'void uiTabInsertAt(uiTab *t, const char *name, int index, uiControl *c)'
    try_extern 'void uiTabDelete(uiTab *t, int index)'
    try_extern 'int uiTabNumPages(uiTab *t)'
    try_extern 'int uiTabMargined(uiTab *t, int index)'
    try_extern 'void uiTabSetMargined(uiTab *t, int index, int margined)'
    try_extern 'uiTab *uiNewTab(void)'

    # uiGroup

    try_extern 'char *uiGroupTitle(uiGroup *g)'
    try_extern 'void uiGroupSetTitle(uiGroup *g, const char *title)'
    try_extern 'void uiGroupSetChild(uiGroup *g, uiControl *c)'
    try_extern 'int uiGroupMargined(uiGroup *g)'
    try_extern 'void uiGroupSetMargined(uiGroup *g, int margined)'
    try_extern 'uiGroup *uiNewGroup(const char *title)'

    # uiSpinbox

    try_extern 'int uiSpinboxValue(uiSpinbox *s)'
    try_extern 'void uiSpinboxSetValue(uiSpinbox *s, int value)'
    try_extern 'void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *sender, void *senderData), void *data)'
    try_extern 'uiSpinbox *uiNewSpinbox(int min, int max)'

    # uiSlider

    try_extern 'int uiSliderValue(uiSlider *s)'
    try_extern 'void uiSliderSetValue(uiSlider *s, int value)'
    try_extern 'int uiSliderHasToolTip(uiSlider *s)'
    try_extern 'void uiSliderSetHasToolTip(uiSlider *s, int hasToolTip)'
    try_extern 'void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *sender, void *senderData), void *data)'
    try_extern 'void uiSliderOnReleased(uiSlider *s, void (*f)(uiSlider *sender, void *senderData), void *data)'
    try_extern 'void uiSliderSetRange(uiSlider *s, int min, int max)'
    try_extern 'uiSlider *uiNewSlider(int min, int max)'

    # uiProgressBar

    try_extern 'int uiProgressBarValue(uiProgressBar *p)'
    try_extern 'void uiProgressBarSetValue(uiProgressBar *p, int n)'
    try_extern 'uiProgressBar *uiNewProgressBar(void)'

    # uiSeparator

    try_extern 'uiSeparator *uiNewHorizontalSeparator(void)'
    try_extern 'uiSeparator *uiNewVerticalSeparator(void)'

    # uiCombobox

    try_extern 'void uiComboboxAppend(uiCombobox *c, const char *text)'
    try_extern 'void uiComboboxInsertAt(uiCombobox *c, int index, const char *text)'
    try_extern 'void uiComboboxDelete(uiCombobox *c, int index)'
    try_extern 'void uiComboboxClear(uiCombobox *c)'
    try_extern 'int uiComboboxNumItems(uiCombobox *c)'
    try_extern 'int uiComboboxSelected(uiCombobox *c)'
    try_extern 'void uiComboboxSetSelected(uiCombobox *c, int index)'
    try_extern 'void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *sender, void *senderData), void *data)'
    try_extern 'uiCombobox *uiNewCombobox(void)'

    # uiEditableCombobox

    try_extern 'void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)'
    try_extern 'char *uiEditableComboboxText(uiEditableCombobox *c)'
    try_extern 'void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)'
    try_extern 'void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *sender, void *senderData), void *data)'
    try_extern 'uiEditableCombobox *uiNewEditableCombobox(void)'

    # uiRadioButtons

    try_extern 'void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)'
    try_extern 'int uiRadioButtonsSelected(uiRadioButtons *r)'
    try_extern 'void uiRadioButtonsSetSelected(uiRadioButtons *r, int index)'
    try_extern 'void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *sender, void *senderData), void *data)'
    try_extern 'uiRadioButtons *uiNewRadioButtons(void)'

    # uiDateTimePicker

    # time.h
    TM = if Fiddle::WINDOWS
           struct [
             'int tm_sec',
             'int tm_min',
             'int tm_hour',
             'int tm_mday',
             'int tm_mon',
             'int tm_year',
             'int tm_wday',
             'int tm_yday',
             'int tm_isdst'
           ]
         else # The GNU C Library (glibc)
           struct [
             'int tm_sec',
             'int tm_min',
             'int tm_hour',
             'int tm_mday',
             'int tm_mon',
             'int tm_year',
             'int tm_wday',
             'int tm_yday',
             'int tm_isdst',
             'long tm_gmtoff',
             'const char *tm_zone'
           ]
         end

    try_extern 'void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)'
    try_extern 'void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)'
    try_extern 'void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *sender, void *senderData), void *data)'
    try_extern 'uiDateTimePicker *uiNewDateTimePicker(void)'
    try_extern 'uiDateTimePicker *uiNewDatePicker(void)'
    try_extern 'uiDateTimePicker *uiNewTimePicker(void)'

    # uiMultilineEntry

    try_extern 'char *uiMultilineEntryText(uiMultilineEntry *e)'
    try_extern 'void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)'
    try_extern 'void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)'
    try_extern 'void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *sender, void *senderData), void *data)'
    try_extern 'int uiMultilineEntryReadOnly(uiMultilineEntry *e)'
    try_extern 'void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)'
    try_extern 'uiMultilineEntry *uiNewMultilineEntry(void)'
    try_extern 'uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)'

    # uiMenuItem

    try_extern 'void uiMenuItemEnable(uiMenuItem *m)'
    try_extern 'void uiMenuItemDisable(uiMenuItem *m)'
    try_extern 'void uiMenuItemOnClicked(uiMenuItem *m, void (*f)(uiMenuItem *sender, uiWindow *window, void *senderData), void *data)'
    try_extern 'int uiMenuItemChecked(uiMenuItem *m)'
    try_extern 'void uiMenuItemSetChecked(uiMenuItem *m, int checked)'

    # uiMenu

    try_extern 'uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)'
    try_extern 'uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)'
    try_extern 'uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)'
    try_extern 'uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)'
    try_extern 'uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)'
    try_extern 'void uiMenuAppendSeparator(uiMenu *m)'
    try_extern 'uiMenu *uiNewMenu(const char *name)'

    try_extern 'char *uiOpenFile(uiWindow *parent)'
    try_extern 'char *uiOpenFolder(uiWindow *parent)'
    try_extern 'char *uiSaveFile(uiWindow *parent)'
    try_extern 'void uiMsgBox(uiWindow *parent, const char *title, const char *description)'
    try_extern 'void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)'

    # uiArea

    AreaHandler = struct [
      'void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *)',
      'void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *)',
      'void (*MouseCrossed)(uiAreaHandler *, uiArea *, int left)',
      'void (*DragBroken)(uiAreaHandler *, uiArea *)',
      'int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *)'
    ]

    typealias 'uiWindowResizeEdge', 'int'

    try_extern 'void uiAreaSetSize(uiArea *a, int width, int height)'
    try_extern 'void uiAreaQueueRedrawAll(uiArea *a)'
    try_extern 'void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)'
    try_extern 'void uiAreaBeginUserWindowMove(uiArea *a)'
    try_extern 'void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)'
    try_extern 'uiArea *uiNewArea(uiAreaHandler *ah)'
    try_extern 'uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)'

    AreaDrawParams = struct [
      'uiDrawContext *Context',
      'double AreaWidth',
      'double AreaHeight',
      'double ClipX',
      'double ClipY',
      'double ClipWidth',
      'double ClipHeight'
    ]
    typealias 'uiDrawBrushType', 'int'
    typealias 'uiDrawLineCap', 'int'
    typealias 'uiDrawLineJoin', 'int'
    typealias 'uiDrawFillMode', 'int'

    DrawMatrix = struct [
      'double M11',
      'double M12',
      'double M21',
      'double M22',
      'double M31',
      'double M32'
    ]

    DrawBrush = struct [
      'uiDrawBrushType Type',
      'double R',
      'double G',
      'double B',
      'double A',
      'double X0',
      'double Y0',
      'double X1',
      'double Y1',
      'double OuterRadius',
      'uiDrawBrushGradientStop *Stops',
      'size_t NumStops'
    ]

    DrawBrushGradientStop = struct [
      'double Pos',
      'double R',
      'double G',
      'double B',
      'double A'
    ]

    DrawStrokeParams = struct [
      'uiDrawLineCap Cap',
      'uiDrawLineJoin Join',
      'double Thickness',
      'double MiterLimit',
      'double *Dashes',
      'size_t NumDashes',
      'double DashPhase'
    ]

    # uiDrawPath
    try_extern 'uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode)'
    try_extern 'void uiDrawFreePath(uiDrawPath *p)'
    try_extern 'void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)'
    try_extern 'void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)'
    try_extern 'void uiDrawPathLineTo(uiDrawPath *p, double x, double y)'
    try_extern 'void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)'
    try_extern 'void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)'
    try_extern 'void uiDrawPathCloseFigure(uiDrawPath *p)'
    try_extern 'void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)'
    try_extern 'int uiDrawPathEnded(uiDrawPath *p)'
    try_extern 'void uiDrawPathEnd(uiDrawPath *p)'
    try_extern 'void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)'
    try_extern 'void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)'

    # uiDrawMatrix
    try_extern 'void uiDrawMatrixSetIdentity(uiDrawMatrix *m)'
    try_extern 'void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)'
    try_extern 'void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)'
    try_extern 'void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)'
    try_extern 'void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)'
    try_extern 'void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)'
    try_extern 'int uiDrawMatrixInvertible(uiDrawMatrix *m)'
    try_extern 'int uiDrawMatrixInvert(uiDrawMatrix *m)'
    try_extern 'void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)'
    try_extern 'void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)'

    try_extern 'void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)'
    try_extern 'void uiDrawClip(uiDrawContext *c, uiDrawPath *path)'
    try_extern 'void uiDrawSave(uiDrawContext *c)'
    try_extern 'void uiDrawRestore(uiDrawContext *c)'

    # uiAttribute
    try_extern 'void uiFreeAttribute(uiAttribute *a)'

    typealias 'uiAttributeType', 'int'

    try_extern 'uiAttributeType uiAttributeGetType(const uiAttribute *a)'
    try_extern 'uiAttribute *uiNewFamilyAttribute(const char *family)'
    try_extern 'const char *uiAttributeFamily(const uiAttribute *a)'
    try_extern 'uiAttribute *uiNewSizeAttribute(double size)'
    try_extern 'double uiAttributeSize(const uiAttribute *a)'

    typealias 'uiTextWeight', 'int'

    try_extern 'uiAttribute *uiNewWeightAttribute(uiTextWeight weight)'
    try_extern 'uiTextWeight uiAttributeWeight(const uiAttribute *a)'

    typealias 'uiTextItalic', 'int'

    try_extern 'uiAttribute *uiNewItalicAttribute(uiTextItalic italic)'
    try_extern 'uiTextItalic uiAttributeItalic(const uiAttribute *a)'

    typealias 'uiTextStretch', 'int'

    try_extern 'uiAttribute *uiNewStretchAttribute(uiTextStretch stretch)'
    try_extern 'uiTextStretch uiAttributeStretch(const uiAttribute *a)'
    try_extern 'uiAttribute *uiNewColorAttribute(double r, double g, double b, double a)'
    try_extern 'void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha)'
    try_extern 'uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a)'

    typealias 'uiUnderline', 'int'

    try_extern 'uiAttribute *uiNewUnderlineAttribute(uiUnderline u)'
    try_extern 'uiUnderline uiAttributeUnderline(const uiAttribute *a)'

    typealias 'uiUnderlineColor', 'int'

    try_extern 'uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a)'
    try_extern 'void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha)'

    # uiOpenTypeFeatures

    typealias 'uiOpenTypeFeaturesForEachFunc', 'void*'

    try_extern 'uiOpenTypeFeatures *uiNewOpenTypeFeatures(void)'
    try_extern 'void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf)'
    try_extern 'uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf)'
    try_extern 'void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value)'
    try_extern 'void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d)'
    try_extern 'int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value)'
    try_extern 'void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data)'
    try_extern 'uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf)'
    try_extern 'const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a)'

    # uiAttributedString

    typealias 'uiAttributedStringForEachAttributeFunc', 'void*'

    try_extern 'uiAttributedString *uiNewAttributedString(const char *initialString)'
    try_extern 'void uiFreeAttributedString(uiAttributedString *s)'
    try_extern 'const char *uiAttributedStringString(const uiAttributedString *s)'
    try_extern 'size_t uiAttributedStringLen(const uiAttributedString *s)'
    try_extern 'void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str)'
    try_extern 'void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at)'
    try_extern 'void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end)'
    try_extern 'void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end)'
    try_extern 'void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)'
    try_extern 'size_t uiAttributedStringNumGraphemes(uiAttributedString *s)'
    try_extern 'size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos)'
    try_extern 'size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos)'

    # uiFont

    FontDescriptor = struct [
      'char *Family',
      'double Size',
      'uiTextWeight Weight',
      'uiTextItalic Italic',
      'uiTextStretch Stretch'
    ]

    try_extern 'void uiLoadControlFont(uiFontDescriptor *f)'
    try_extern 'void uiFreeFontDescriptor(uiFontDescriptor *desc)'

    typealias 'uiDrawTextAlign', 'int'

    DrawTextLayoutParams = struct [
      'uiAttributedString *String',
      'uiFontDescriptor *DefaultFont',
      'double Width',
      'uiDrawTextAlign Align'
    ]

    try_extern 'uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *params)'
    try_extern 'void uiDrawFreeTextLayout(uiDrawTextLayout *tl)'
    try_extern 'void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)'
    try_extern 'void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)'

    # uiFontButton

    try_extern 'void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)'
    try_extern 'void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *sender, void *senderData), void *data)'
    try_extern 'uiFontButton *uiNewFontButton(void)'
    try_extern 'void uiFreeFontButtonFont(uiFontDescriptor *desc)'

    typealias 'uiModifiers', 'int'

    AreaMouseEvent = struct [
      'double X',
      'double Y',
      'double AreaWidth',
      'double AreaHeight',
      'int Down',
      'int Up',
      'int Count',
      'uiModifiers Modifiers',
      'uint64_t Held1To64'
    ]

    typealias 'uiExtKey', 'int'

    AreaKeyEvent = struct [
      'char Key',
      'uiExtKey ExtKey',
      'uiModifiers Modifier',
      'uiModifiers Modifiers',
      'int Up'
    ]

    # uiColorButton

    try_extern 'void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)'
    try_extern 'void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)'
    try_extern 'void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *sender, void *senderData), void *data)'
    try_extern 'uiColorButton *uiNewColorButton(void)'

    # uiForm

    try_extern 'void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)'
    try_extern 'int uiFormNumChildren(uiForm *f)'
    try_extern 'void uiFormDelete(uiForm *f, int index)'
    try_extern 'int uiFormPadded(uiForm *f)'
    try_extern 'void uiFormSetPadded(uiForm *f, int padded)'
    try_extern 'uiForm *uiNewForm(void)'

    typealias 'uiAlign', 'int'

    typealias 'uiAt', 'int'

    # uiGrid

    try_extern 'void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)'
    try_extern 'void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)'
    try_extern 'int uiGridPadded(uiGrid *g)'
    try_extern 'void uiGridSetPadded(uiGrid *g, int padded)'
    try_extern 'uiGrid *uiNewGrid(void)'

    # uiImage

    try_extern 'uiImage *uiNewImage(double width, double height)'
    try_extern 'void uiFreeImage(uiImage *i)'
    try_extern 'void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)'

    # uiTable
    try_extern 'void uiFreeTableValue(uiTableValue *v)'

    typealias 'uiTableValueType', 'int'

    try_extern 'uiTableValueType uiTableValueGetType(const uiTableValue *v)'
    try_extern 'uiTableValue *uiNewTableValueString(const char *str)'
    try_extern 'const char *uiTableValueString(const uiTableValue *v)'
    try_extern 'uiTableValue *uiNewTableValueImage(uiImage *img)'
    try_extern 'uiImage *uiTableValueImage(const uiTableValue *v)'
    try_extern 'uiTableValue *uiNewTableValueInt(int i)'
    try_extern 'int uiTableValueInt(const uiTableValue *v)'
    try_extern 'uiTableValue *uiNewTableValueColor(double r, double g, double b, double a)'
    try_extern 'void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a)'

    TableModelHandler = struct [
      'int (*NumColumns)(uiTableModelHandler *, uiTableModel *)',
      'uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int)',
      'int (*NumRows)(uiTableModelHandler *, uiTableModel *)',
      'uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column)',
      'void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *)'
    ]

    try_extern 'uiTableModel *uiNewTableModel(uiTableModelHandler *mh)'
    try_extern 'void uiFreeTableModel(uiTableModel *m)'
    try_extern 'void uiTableModelRowInserted(uiTableModel *m, int newIndex)'
    try_extern 'void uiTableModelRowChanged(uiTableModel *m, int index)'
    try_extern 'void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)'

    TableTextColumnOptionalParams = struct [
      'int ColorModelColumn'
    ]

    TableParams = struct [
      'uiTableModel *Model',
      'int RowBackgroundColorModelColumn'
    ]

    try_extern 'void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'
    try_extern 'void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)'
    try_extern 'void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'
    try_extern 'void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)'
    try_extern 'void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)'
    try_extern 'void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)'
    try_extern 'void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)'
    try_extern 'int uiTableHeaderVisible(uiTable *t)'
    try_extern 'void uiTableHeaderSetVisible(uiTable *t, int visible)'
    try_extern 'uiTable *uiNewTable(uiTableParams *params)'
    try_extern 'void uiTableOnRowClicked(uiTable *t, void (*f)(uiTable *t, int row, void *data), void *data)'
    try_extern 'void uiTableOnRowDoubleClicked(uiTable *t, void (*f)(uiTable *t, int row, void *data), void *data)'

    typealias 'uiSortIndicator', 'int'

    try_extern 'void uiTableHeaderSetSortIndicator(uiTable *t, int column, uiSortIndicator indicator)'
    try_extern 'uiSortIndicator uiTableHeaderSortIndicator(uiTable *t, int column)'
    try_extern 'void uiTableHeaderOnClicked(uiTable *t, void (*f)(uiTable *sender, int column, void *senderData), void *data)'
    try_extern 'int uiTableColumnWidth(uiTable *t, int column)'
    try_extern 'void uiTableColumnSetWidth(uiTable *t, int column, int width)'

    typealias 'uiTableSelectionMode', 'int'

    try_extern 'uiTableSelectionMode uiTableGetSelectionMode(uiTable *t)'
    try_extern 'void uiTableSetSelectionMode(uiTable *t, uiTableSelectionMode mode)'
    try_extern 'void uiTableOnSelectionChanged(uiTable *t, void (*f)(uiTable *t, void *data), void *data)'
    try_extern 'uiTableSelection* uiTableGetSelection(uiTable *t)'
    try_extern 'void uiTableSetSelection(uiTable *t, uiTableSelection *sel)'
    try_extern 'void uiFreeTableSelection(uiTableSelection* s)'

    TableSelection = struct [
      'int NumRows',
      'int *Rows'
    ]
  end
end


================================================
FILE: lib/libui/fiddle_patch.rb
================================================
module LibUI
  # This module overrides Fiddle's mtehods
  # - Fiddle::Importer#extern
  # - Fiddle::CParser#parse_signature
  # Original methods are in
  # - https://github.com/ruby/fiddle/blob/master/lib/fiddle/import.rb
  # - https://github.com/ruby/fiddle/blob/master/lib/fiddle/cparser.rb
  # These changes add the ability to parse the signatures of functions given as arguments.

  module FiddlePatch
    def parse_signature(signature, tymap = nil)
      tymap ||= {}
      ctype, func, args = case compact(signature)
                          when /^(?:[\w\*\s]+)\(\*(\w+)\((.*?)\)\)(?:\[\w*\]|\(.*?\));?$/
                            [TYPE_VOIDP, Regexp.last_match(1), Regexp.last_match(2)]
                          when /^([\w\*\s]+[*\s])(\w+)\((.*?)\);?$/
                            [parse_ctype(Regexp.last_match(1).strip, tymap), Regexp.last_match(2), Regexp.last_match(3)]
                          else
                            raise("can't parse the function prototype: #{signature}")
                          end
      symname = func
      callback_argument_types = {}                                              # Added
      argtype = split_arguments(args).collect.with_index do |arg, idx|          # Added with_index
        # Check if it is a function pointer or not
        if arg =~ /\(\*.*\)\(.*\)/                                              # Added
          # From the arguments, create a notation that looks like a function declaration
          # int(*f)(int *, void *) -> int f(int *, void *)
          func_arg = arg.sub('(*', ' ').sub(')', '')                            # Added
          # Use Fiddle's parse_signature method again.
          callback_argument_types[idx] = parse_signature(func_arg)              # Added
        end
        parse_ctype(arg, tymap)
      end
      # Added callback_argument_types. Original method return only 3 values.
      [symname, ctype, argtype, callback_argument_types]
    end

    def extern(signature, *opts)
      symname, ctype, argtype, callback_argument_types = parse_signature(signature, type_alias)
      opt = parse_bind_options(opts)
      func = import_function(symname, ctype, argtype, opt[:call_type])

      # callback_argument_types
      func.instance_variable_set(:@callback_argument_types, 
                                   callback_argument_types) # Added
      # attr_reader
      def func.callback_argument_types
        @callback_argument_types
      end

      # argument_types
      # Ruby 2.7 Fiddle::Function dose not have @argument_types
      # Ruby 3.0 Fiddle::Function has @argument_types
      if func.instance_variable_defined?(:@argument_types)
        # check if @argument_types are the same
        if func.instance_variable_get(:@argument_types) != argtype
          warn "#{symname} func.argument_types:#{func.argument_types} != argtype #{argtype}"
        end
      else
        func.instance_variable_set(:@argument_types, argtype)
      end
      # attr_reader
      def func.argument_types
        @argument_types
      end

      name = symname.gsub(/@.+/, '')
      @func_map[name] = func
      # define_method(name){|*args,&block| f.call(*args,&block)}
      begin
        /^(.+?):(\d+)/ =~ caller.first
        file = Regexp.last_match(1)
        line = Regexp.last_match(2).to_i
      rescue StandardError
        file, line = __FILE__, __LINE__ + 3
      end
      module_eval(<<-EOS, file, line)
        def #{name}(*args, &block)
          @func_map['#{name}'].call(*args,&block)
        end
      EOS
      module_function(name)
      func
    end
  end
  private_constant :FiddlePatch
end


================================================
FILE: lib/libui/libui_base.rb
================================================
module LibUI
  module LibUIBase
    FFI.func_map.each_key do |original_method_name|
      name = Utils.convert_to_ruby_method(original_method_name)
      func = FFI.func_map[original_method_name]

      define_method(name) do |*args, &blk|
        # Assume that block is the last argument.
        args << blk if blk

        # The proc object is converted to a Closure::BlockCaller object.
        args.map!.with_index do |arg, idx|
          next arg unless arg.is_a?(Proc)

          # now arg must be Proc

          # The types of the function arguments are stored in advance.
          # See the monkey patch in ffi.rb.
          _f, ret_type, arg_types = func.callback_argument_types[idx]
          # TODO: raise some nice error if _f is nil.

          callback = Fiddle::Closure::BlockCaller.new(
            ret_type, arg_types, &arg
          )
          # Protect from GC
          # by giving the owner object a reference to the callback.
          # See https://github.com/kojix2/LibUI/issues/8
          receiver = args.first
          owner = if idx == 0 || # UI.queue_main{}
                     receiver.nil? ||
                     (receiver.respond_to?(:frozen?) && receiver.frozen?) # UI.timer(100) {}
                    LibUIBase # keep a reference on an internal module to avoid GC
                  else
                    receiver # receiver object holds the callback
                  end
          if owner.instance_variable_defined?(:@callbacks)
            owner.instance_variable_get(:@callbacks) << callback
          else
            owner.instance_variable_set(:@callbacks, [callback])
          end
          callback
        end

        # Make it possible to omit the last nil. This may be an over-optimization.
        siz = func.argument_types.size - 1
        args[siz] = nil if args.size == siz

        FFI.public_send(original_method_name, *args)
      end
    end
  end

  private_constant :LibUIBase
end


================================================
FILE: lib/libui/utils.rb
================================================
module LibUI
  module Utils
    class << self
      def convert_to_ruby_method(original_method_name)
        underscore(original_method_name.delete_prefix('ui'))
      end

      # Converting camel case to underscore case in ruby
      # https://stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby#1509939
      def underscore(str)
        str.gsub(/::/, '/') # Maybe we don't need it.
           .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
           .gsub(/([a-z\d])([A-Z])/, '\1_\2')
           .tr('-', '_')
           .downcase
      end
    end
  end
end


================================================
FILE: lib/libui/version.rb
================================================
module LibUI
  VERSION = '0.2.0'
end


================================================
FILE: lib/libui.rb
================================================
require_relative 'libui/version'
require_relative 'libui/utils'
require_relative 'libui/error'
require 'rbconfig'

module LibUI
  class << self
    attr_accessor :ffi_lib
  end

  host_cpu = case RbConfig::CONFIG['host_cpu']
             when /i\d86/
               'x86'
             else
               RbConfig::CONFIG['host_cpu']
             end

  lib_name = [
    # For libui-ng shared libraries compiled with (rake vendor:build)
    "libui.#{RbConfig::CONFIG['host_cpu']}.#{RbConfig::CONFIG['SOEXT']}",
    # For libui-ng shared library downloaded from RubyGems.org
    "libui.#{host_cpu}.#{RbConfig::CONFIG['SOEXT']}",
    # For backward compatibility or manual compilation of libui-ng
    "libui.#{RbConfig::CONFIG['SOEXT']}"
  ]

  self.ffi_lib = \
    if ENV['LIBUIDIR'] && !ENV['LIBUIDIR'].empty?
      lib_name.lazy
              .map { |name| File.expand_path(name, ENV['LIBUIDIR']) }
              .find { |path| File.exist?(path) }
    else
      lib_name.lazy
              .map { |name| File.expand_path("../vendor/#{name}", __dir__) }
              .find { |path| File.exist?(path) }
    end

  require_relative 'libui/ffi'
  require_relative 'libui/libui_base'

  extend LibUIBase

  class << self
    def init(opt = nil)
      # Allocate uiInitOptions if not provided
      unless opt
        opt = FFI::InitOptions.malloc
        opt.to_ptr.free = Fiddle::RUBY_FREE
        opt.Size = FFI::InitOptions.size
      end

      err_ptr = super(opt) # uiInit returns const char* error or NULL on success
      return nil if err_ptr.null?

      # Convert C string to Ruby string and free the error string per API contract
      err_msg = err_ptr.to_s
      free_init_error(err_ptr)
      warn err_msg
      nil
    end

    # Gets the window position.
    # Coordinates are measured from the top left corner of the screen.
    # @param w [Fiddle::Pointer] Pointer of uiWindow instance.
    # @return [Array] position of the window. [x, y]
    # @note This method may return inaccurate or dummy values on Unix platforms.

    def window_position(w)
      x_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE)
      y_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE)
      super(w, x_ptr, y_ptr)
      x = x_ptr[0, Fiddle::SIZEOF_INT].unpack1('i*')
      y = y_ptr[0, Fiddle::SIZEOF_INT].unpack1('i*')
      [x, y]
    end

    # FIXME: This is a workaround for a old version of Fiddle.
    # Fiddle 1.1.2 and above should be able to handle one character string.
    # See https://github.com/ruby/fiddle/issues/96

    def open_type_features_add(otf, a, b, c, d, value)
      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }
      super(otf, a, b, c, d, value)
    end

    def open_type_features_remove(otf, a, b, c, d)
      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }
      super(otf, a, b, c, d)
    end

    def open_type_features_get(otf, a, b, c, d, value)
      a, b, c, d = [a, b, c, d].map { |s| s.is_a?(String) ? s.ord : s }
      super(otf, a, b, c, d, value)
    end
  end

  ## UI_ENUM https://github.com/libui-ng/libui-ng/blob/master/ui.h

  # ForEach
  ForEachContinue    =    0
  ForEachStop        =    1

  # WindowResizeEdge
  WindowResizeEdgeLeft        = 0
  WindowResizeEdgeTop         = 1
  WindowResizeEdgeRight       = 2
  WindowResizeEdgeBottom      = 3
  WindowResizeEdgeTopLeft     = 4
  WindowResizeEdgeTopRight    = 5
  WindowResizeEdgeBottomLeft  = 6
  WindowResizeEdgeBottomRight = 7

  # DrawBrushType
  DrawBrushTypeSolid          = 0
  DrawBrushTypeLinearGradient = 1
  DrawBrushTypeRadialGradient = 2
  DrawBrushTypeImage          = 3

  # DrawLineCap
  DrawLineCapFlat   = 0
  DrawLineCapRound  = 1
  DrawLineCapSquare = 2

  # DrawLineJoin
  DrawLineJoinMiter = 0
  DrawLineJoinRound = 1
  DrawLineJoinBevel = 2

  DrawDefaultMiterLimit = 10.0

  # DrawFillMode
  DrawFillModeWinding   = 0
  DrawFillModeAlternate = 1

  # AttributeType
  AttributeTypeFamily         = 0
  AttributeTypeSize           = 1
  AttributeTypeWeight         = 2
  AttributeTypeItalic         = 3
  AttributeTypeStretch        = 4
  AttributeTypeColor          = 5
  AttributeTypeBackground     = 6
  AttributeTypeUnderline      = 7
  AttributeTypeUnderlineColor = 8
  AttributeTypeFeatures       = 9

  # TextWeight
  TextWeightMinimum    = 0
  TextWeightThin       = 100
  TextWeightUltraLight = 200
  TextWeightLight      = 300
  TextWeightBook       = 350
  TextWeightNormal     = 400
  TextWeightMedium     = 500
  TextWeightSemiBold   = 600
  TextWeightBold       = 700
  TextWeightUltraBold  = 800
  TextWeightHeavy      = 900
  TextWeightUltraHeavy = 950
  TextWeightMaximum    = 1000

  # TextItalic
  TextItalicNormal  = 0
  TextItalicOblique = 1
  TextItalicItalic  = 2

  # TextStretch
  TextStretchUltraCondensed = 0
  TextStretchExtraCondensed = 1
  TextStretchCondensed      = 2
  TextStretchSemiCondensed  = 3
  TextStretchNormal         = 4
  TextStretchSemiExpanded   = 5
  TextStretchExpanded       = 6
  TextStretchExtraExpanded  = 7
  TextStretchUltraExpanded  = 8

  # Underline
  UnderlineNone       = 0
  UnderlineSingle     = 1
  UnderlineDouble     = 2
  UnderlineSuggestion = 3

  # UnderlineColor
  UnderlineColorCustom    = 0
  UnderlineColorSpelling  = 1
  UnderlineColorGrammar   = 2
  UnderlineColorAuxiliary = 3

  # DrawTextAlign
  DrawTextAlignLeft   = 0
  DrawTextAlignCenter = 1
  DrawTextAlignRight  = 2

  # Modifiers
  ModifierCtrl  = (1 << 0)
  ModifierAlt   = (1 << 1)
  ModifierShift = (1 << 2)
  ModifierSuper = (1 << 3)

  # ExtKey
  ExtKeyEscape    = 1
  ExtKeyInsert    = 2
  ExtKeyDelete    = 3
  ExtKeyHome      = 4
  ExtKeyEnd       = 5
  ExtKeyPageUp    = 6
  ExtKeyPageDown  = 7
  ExtKeyUp        = 8
  ExtKeyDown      = 9
  ExtKeyLeft      = 10
  ExtKeyRight     = 11
  ExtKeyF1        = 12
  ExtKeyF2        = 13
  ExtKeyF3        = 14
  ExtKeyF4        = 15
  ExtKeyF5        = 16
  ExtKeyF6        = 17
  ExtKeyF7        = 18
  ExtKeyF8        = 19
  ExtKeyF9        = 20
  ExtKeyF10       = 21
  ExtKeyF11       = 22
  ExtKeyF12       = 23
  ExtKeyN0        = 24
  ExtKeyN1        = 25
  ExtKeyN2        = 26
  ExtKeyN3        = 27
  ExtKeyN4        = 28
  ExtKeyN5        = 29
  ExtKeyN6        = 30
  ExtKeyN7        = 31
  ExtKeyN8        = 32
  ExtKeyN9        = 33
  ExtKeyNDot      = 34
  ExtKeyNEnter    = 35
  ExtKeyNAdd      = 36
  ExtKeyNSubtract = 37
  ExtKeyNMultiply = 38
  ExtKeyNDivide   = 39

  # Align
  AlignFill   = 0
  AlignStart  = 1
  AlignCenter = 2
  AlignEnd    = 3

  # At
  AtLeading  = 0
  AtTop      = 1
  AtTrailing = 2
  AtBottom   = 3

  # TableValueType
  TableValueTypeString = 0
  TableValueTypeImage  = 1
  TableValueTypeInt    = 2
  TableValueTypeColor  = 3

  # SortIndicator
  SortIndicatorNone       = 0
  SortIndicatorAscending  = 1
  SortIndicatorDescending = 2

  # editable
  TableModelColumnNeverEditable  = -1
  TableModelColumnAlwaysEditable = -2

  # TableSelectionMode
  TableSelectionModeNone       = 0
  TableSelectionModeZeroOrOne  = 1
  TableSelectionModeOne        = 2
  TableSelectionModeZeroOrMany = 3
end


================================================
FILE: libui.gemspec
================================================
require_relative 'lib/libui/version'

Gem::Specification.new do |spec|
  spec.name          = 'libui'
  spec.version       = LibUI::VERSION
  spec.summary       = 'Ruby bindings to libui'
  spec.homepage      = 'https://github.com/kojix2/libui'
  spec.license       = 'MIT'

  spec.authors       = ['kojix2']
  spec.email         = ['2xijok@gmail.com']

  spec.files = Dir['*.{md,txt}', '{lib}/**/*', 'vendor/{LICENSE,README}.md']
  spec.require_paths = 'lib'

  spec.required_ruby_version = '>= 2.6'

  # Use the GEM_PLATFORM environment variable if specified
  gem_platform = ENV['GEM_PLATFORM']
  spec.platform = gem_platform if gem_platform && !gem_platform.empty? && gem_platform != 'ruby'

  # See `gem help platform` for information on platform matching.
  case spec.platform.to_s
  when 'x86_64-linux'
    spec.files << 'vendor/libui.x86_64.so'
  when 'aarch64-linux'
    spec.files << 'vendor/libui.aarch64.so'
  when 'x86_64-darwin'
    spec.files << 'vendor/libui.x86_64.dylib'
  when 'arm64-darwin'
    spec.files << 'vendor/libui.arm64.dylib'
  when 'x64-mingw32', 'x64-mingw-ucrt'
    spec.files << 'vendor/libui.x64.dll'
  when 'x86-mingw32'
    spec.files << 'vendor/libui.x86.dll'
  else
    spec.files.concat(Dir['vendor/*.{dll,dylib,so}']) # all
  end

  spec.add_dependency 'fiddle'
end


================================================
FILE: scripts/README.md
================================================
# Scripts to check for changes in ui.h

Helper scripts for the developer.

## Usage

```sh
bash ui_diff.sh
```
## Requirement

* [delta](https://github.com/dandavison/delta)
* gcc - [remove comments from C/C++ code](https://stackoverflow.com/questions/2394017/remove-comments-from-c-c-code)


================================================
FILE: scripts/ui_diff.sh
================================================
#!/usr/bin/env bash

cd "$(dirname "$0")"

delta <(./ui_ffi.rb) <(./ui_h.rb)
# diff <(./ui_ffi.rb) <(./ui_h.rb)


================================================
FILE: scripts/ui_ffi.rb
================================================
#!/usr/bin/env ruby

libui_ffi_path = File.expand_path('../lib/libui/ffi.rb', __dir__)
libui_path = File.expand_path('../lib/libui.rb', __dir__)

# Read the files into arrays by each line
ffi_lines = File.readlines(libui_ffi_path).map(&:strip)
libui_lines = File.readlines(libui_path)

# Count try_extern
matches = ffi_lines.select { _1.start_with?('try_extern') }
puts 'count try_extern'
puts matches.count

# Print try_extern calls
puts(matches.map { _1.delete_prefix("try_extern '").delete_suffix("'") })

# Print enum names
puts(libui_lines.select { _1.start_with?('  # ') }
                .map { _1.delete_prefix('  # ').strip })


================================================
FILE: scripts/ui_h.rb
================================================
#!/usr/bin/env ruby

require 'open-uri'
require 'tempfile'

UI_H_URL = 'https://raw.githubusercontent.com/libui-ng/libui-ng/master/ui.h'.freeze

ui_h = []
Tempfile.open(['ui', '.h']) do |tf|
  tf.write URI.open(UI_H_URL).read
  tf.close

  cmd_gcc = 'gcc -fpreprocessed -P -dD -E'

  ui_h = `#{cmd_gcc} #{tf.path}` # Remove comments
         .split("\n").select { !_1.strip.start_with?('#') }.join("\n") # Remove macros
         .split(';').map do
           _1.split("\n").map(&:strip).join(' ')
             .strip.squeeze(' ')
         end
end
# Count
# - Count the occurrences of '_UI_EXTERN'

puts 'count _UI_EXTERN'
puts(ui_h.count { _1.start_with?('_UI_EXTERN') })

# Functions
# - Print the definitions of functions marked with '_UI_EXTERN'

puts(ui_h.select { _1.start_with?('_UI_EXTERN') }
         .map { _1.delete_prefix('_UI_EXTERN ').squeeze(' ') })

# Enums
# - Print the names of enums marked with '_UI_ENUM'

puts(ui_h.select { _1.include?('_UI_ENUM') }
         .map { _1.match(/_UI_ENUM\(ui(\w+)/)[1] })


================================================
FILE: test/libui_test.rb
================================================
require 'test_helper'

class LibUITest < Minitest::Test
  def test_that_it_has_a_version_number
    refute_nil ::LibUI::VERSION
  end

  def test_ffi_method_call
    pt = LibUI::FFI::InitOptions.malloc
    pt.to_ptr.free = Fiddle::RUBY_FREE
    assert_kind_of Fiddle::Pointer, LibUI::FFI.uiInit(pt)
    assert_nil LibUI::FFI.uiQuit
  end

  def test_method_call
    assert_nil LibUI.init
    assert_nil LibUI.quit
  end

  def test_basic_window
    assert_nil LibUI.init
    assert_kind_of Fiddle::Pointer, (
      main_window = LibUI.new_window('hello world', 300, 200, 1)
    )
    assert_nil LibUI.control_show(main_window)
    assert_nil(
      LibUI.window_on_closing(main_window) do
        LibUI.control_destroy(main_window)
        LibUI.quit
        0
      end
    )
    # LibUI.main
    assert_nil LibUI.quit
  end
end


================================================
FILE: test/test_helper.rb
================================================
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'libui'

require 'minitest/autorun'
require 'minitest/pride'


================================================
FILE: test/utils_test.rb
================================================
require 'test_helper'

class LibUIUtilsTest < Minitest::Test
  def test_convert_to_ruby_method
    rbmethod1 = LibUI::Utils.convert_to_ruby_method('uiNewMatz')
    rbmethod2 = LibUI::Utils.convert_to_ruby_method('AINewMatz')
    assert_equal 'new_matz', rbmethod1
    assert_equal 'ai_new_matz', rbmethod2
  end

  def test_underscore
    assert_equal '3v3_v_ap_f2_er7s@_f_d/d_sc', LibUI::Utils.underscore('3v3VApF2Er7s@-fD::DSc')
  end
end


================================================
FILE: vendor/LICENSE.md
================================================
Copyright (c) 2022 libui-ng authors

Copyright (c) 2014 Pietro Gagliardi

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.



================================================
FILE: vendor/README.md
================================================
# libui-ng: a portable GUI library for C

Fork of [andlabs/libui](https://github.com/andlabs/libui). This README is being written.<br>
[![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)
[![GitLab](https://img.shields.io/badge/gitlab-%23181717.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/libui-ng/libui-ng)
[![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/libui-ng/libui-ng)

## About

Simple and portable (but not inflexible) GUI library in C that uses the native GUI technologies of each platform it supports.

## Status

libui-ng is currently **mid-alpha** software.

See [CHANGELOG.md](CHANGELOG.md)

*Old announcements can be found in the [news.md](old/news.md) file.*

## Runtime Requirements

* Windows: Windows Vista SP2 with Platform Update or newer
* Unix: GTK+ 3.10 or newer
* Mac OS X: OS X 10.8 or newer

## Build Requirements

* All platforms:
	* [Meson](https://mesonbuild.com/) 0.58.0 or newer
	* 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.
* Windows: either
	* Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) — you can build either a static or a shared library
	* 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:
		* [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
* Unix: nothing else specific
* Mac OS X: nothing else specific, so long as you can build Cocoa programs

## Building

libui-ng mainly uses [the standard Meson build options](https://mesonbuild.com/Builtin-options.html).

```
$ # in the top-level libui-ng directory run:
$ meson setup build [options]
$ ninja -C build
```

Once this completes, everything will be under `build/meson-out/`.

libui-ng specific options:

- `-Dtests=(true|false)` controls whether tests are built; defaults to `true`
- `-Dexamples=(true|false)` controls whether examples are built; defaults to `true`

Most important Meson options:

* `--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).
* `--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).
* `-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).
* `--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).
* `--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.

Most 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`.

The Meson website and documentation has more in-depth usage instructions.

For 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.

Backends other than `ninja` should work, but are untested by me.

## Testing

### Automated Unit Tests

Run the included unit tests via `meson test -C build`. Alternatively you can also run the `unit` executable manually.

### Manual Testing Suite

Run the manual quality assurance test suite via `qa` and follow the instructions laid out within.

## Installation

Meson also supports installing from source; if you use Ninja, just do

```
$ ninja -C build install
```

When 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.

#### Arch Linux

Can be built from AUR: https://aur.archlinux.org/packages/libui-ng-git/

## Documentation [WIP]

[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.

Consult the `ui.h` comments for the uiControls missing in the docs.

Check the `examples` directory for fully fledged examples. Check out the `tests` directory and subdirectories for more real world usage.

## Language Bindings

libui 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.

Other people have made bindings to other languages:

Language | Bindings
--- | ---
C++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike)
C# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding)
C# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/)
CHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui)
Common Lisp | [jinwoo/cl-ui](https://github.com/jinwoo/cl-ui)
Crystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron), [iu](https://github.com/grkek/iu)
D | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid)
Euphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria)
Harbour | [hbui](https://github.com/rjopek/hbui)
Haskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui)
Janet | [JanetUI](https://github.com/janet-lang/janetui)
JavaScript/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)
Julia | [Libui.jl](https://github.com/joa-quim/Libui.jl)
Kotlin | [kotlin-libui](https://github.com/msink/kotlin-libui)
Lua | [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)
Nim | [ui](https://github.com/nim-lang/ui), [uing](https://github.com/neroist/uing)
Perl6 | [perl6-libui](https://github.com/Garland-g/perl6-libui)
PHP | [ui](https://github.com/krakjoe/ui), [Ardillo](https://github.com/ardillo-php/ext)
Python | [pylibui](https://github.com/joaoventura/pylibui)
Ring | [RingLibui](https://github.com/ring-lang/ring/tree/master/extensions/ringlibui)
Ruby | [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)
Rust | [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)
Scala | [scalaui](https://github.com/lolgab/scalaui)
Swift | [libui-swift](https://github.com/sclukey/libui-swift)

## Frequently Asked Questions

### Why does my program start in the background on OS X if I run from the command line?
OS 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".

When 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.

See 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).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)

## Screenshots

From examples/controlgallery:

![Windows](examples/controlgallery/windows.png)

![Unix](examples/controlgallery/unix.png)

![OS X](examples/controlgallery/darwin.png)
Download .txt
gitextract_3lw4y_4b/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── doc.yml
│       ├── release-rubygems.yml
│       └── test.yml
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── examples/
│   ├── basic_area.rb
│   ├── basic_button.rb
│   ├── basic_draw_text.rb
│   ├── basic_entry.rb
│   ├── basic_table.rb
│   ├── basic_table_image.rb
│   ├── basic_window.rb
│   ├── control_gallery.rb
│   ├── date_time_picker.rb
│   ├── draw_text.rb
│   ├── font_button.rb
│   ├── gpt2_notepad.rb
│   ├── histogram.rb
│   ├── midi_player.rb
│   ├── simple_notepad.rb
│   ├── spectrum.rb
│   └── turing_pattern.rb
├── examples2/
│   ├── README.md
│   ├── button.rb
│   ├── checkbox.rb
│   ├── color_button.rb
│   ├── combobox.rb
│   ├── date_picker.rb
│   ├── editable_combobox.rb
│   ├── entry.rb
│   ├── font_button.rb
│   ├── grid.rb
│   ├── multiline_entry.rb
│   ├── password_entry.rb
│   ├── progress_bar.rb
│   ├── search_entry.rb
│   ├── slider.rb
│   ├── todo/
│   │   └── todo.md
│   └── window.rb
├── lib/
│   ├── libui/
│   │   ├── error.rb
│   │   ├── ffi.rb
│   │   ├── fiddle_patch.rb
│   │   ├── libui_base.rb
│   │   ├── utils.rb
│   │   └── version.rb
│   └── libui.rb
├── libui.gemspec
├── scripts/
│   ├── README.md
│   ├── ui_diff.sh
│   ├── ui_ffi.rb
│   └── ui_h.rb
├── test/
│   ├── libui_test.rb
│   ├── test_helper.rb
│   └── utils_test.rb
└── vendor/
    ├── LICENSE.md
    └── README.md
Download .txt
SYMBOL INDEX (80 symbols across 21 files)

FILE: examples/basic_draw_text.rb
  function append (line 47) | def attr_str.append(what, color)

FILE: examples/basic_table.rb
  function rbcallback (line 22) | def rbcallback(*args, &block)

FILE: examples/basic_table_image.rb
  function rbcallback (line 36) | def rbcallback(*args, &block)

FILE: examples/draw_text.rb
  function append_with_attribute (line 5) | def append_with_attribute(attr_str, what, attr1, attr2)
  function make_attribute_string (line 13) | def make_attribute_string
  function on_font_changed (line 81) | def on_font_changed(area)
  function on_combobox_selected (line 85) | def on_combobox_selected(area)
  function draw_event (line 89) | def draw_event(adp, attr_str, font_button, alignment)

FILE: examples/gpt2_notepad.rb
  function softmax (line 32) | def softmax(y)
  function predict (line 36) | def predict(a, prob: true)
  function predict_text (line 48) | def predict_text(s, max = 30)

FILE: examples/histogram.rb
  function graph_size (line 23) | def graph_size(area_width, area_height)
  function point_locations (line 32) | def point_locations(datapoints, width, height)
  function construct_graph (line 45) | def construct_graph(datapoints, width, height, should_extend)
  function set_solid_brush (line 160) | def set_solid_brush(brush, color, alpha)

FILE: examples/midi_player.rb
  class TinyMidiPlayer (line 6) | class TinyMidiPlayer
    method initialize (line 9) | def initialize
    method stop_midi (line 19) | def stop_midi
    method play_midi (line 26) | def play_midi
    method show_version (line 39) | def show_version(main_window)
    method create_gui (line 47) | def create_gui

FILE: examples/spectrum.rb
  class FFTStream (line 12) | class FFTStream < FFI::PortAudio::Stream
    method process (line 13) | def process(input, _output, frame_count, _time_info, _status_flags, _u...
    method spec (line 19) | def spec

FILE: examples/turing_pattern.rb
  type GrayScott (line 55) | module GrayScott
    class SFloat (line 60) | class SFloat
    type Utils (line 64) | module Utils
      function laplacian2d (line 70) | def self.laplacian2d(uv, dx)
    class Model (line 91) | class Model
      method initialize (line 106) | def initialize(width: 256, height: 256)
      method clear (line 123) | def clear
      method step (line 128) | def step
    type Color (line 146) | module Color
      function colorize (line 149) | def colorize(ar, color_type)
      function uInt8_dstack (line 173) | def uInt8_dstack(ar)
      function hsv2rgb (line 181) | def hsv2rgb(h)
      function red (line 200) | def red(ar)
      function green (line 204) | def green(ar)
      function blue (line 208) | def blue(ar)
      function reverse_red (line 212) | def reverse_red(ar)
      function reverse_green (line 216) | def reverse_green(ar)
      function reverse_blue (line 220) | def reverse_blue(ar)
      function grayscale (line 224) | def grayscale(ar)
      function uint8_zeros_256 (line 229) | def uint8_zeros_256(ch, ar)

FILE: examples2/color_button.rb
  function set_solid_brush (line 43) | def set_solid_brush(

FILE: examples2/combobox.rb
  function populate_the_combobox_with_this_array (line 26) | def populate_the_combobox_with_this_array(

FILE: examples2/editable_combobox.rb
  function populate_the_combobox_with_this_array (line 26) | def populate_the_combobox_with_this_array(

FILE: lib/libui.rb
  type LibUI (line 6) | module LibUI
    function init (line 44) | def init(opt = nil)
    function window_position (line 68) | def window_position(w)
    function open_type_features_add (line 81) | def open_type_features_add(otf, a, b, c, d, value)
    function open_type_features_remove (line 86) | def open_type_features_remove(otf, a, b, c, d)
    function open_type_features_get (line 91) | def open_type_features_get(otf, a, b, c, d, value)

FILE: lib/libui/error.rb
  type LibUI (line 1) | module LibUI
    class Error (line 3) | class Error < StandardError; end
    class LibraryNotFoundError (line 6) | class LibraryNotFoundError < Error; end
    class LibraryLoadError (line 9) | class LibraryLoadError < Error; end

FILE: lib/libui/ffi.rb
  type LibUI (line 5) | module LibUI
    type FFI (line 7) | module FFI
      function try_extern (line 26) | def try_extern(signature, *opts)
      function ffi_methods (line 34) | def ffi_methods

FILE: lib/libui/fiddle_patch.rb
  type LibUI (line 1) | module LibUI
    type FiddlePatch (line 10) | module FiddlePatch
      function parse_signature (line 11) | def parse_signature(signature, tymap = nil)
      function extern (line 38) | def extern(signature, *opts)

FILE: lib/libui/libui_base.rb
  type LibUI (line 1) | module LibUI
    type LibUIBase (line 2) | module LibUIBase

FILE: lib/libui/utils.rb
  type LibUI (line 1) | module LibUI
    type Utils (line 2) | module Utils
      function convert_to_ruby_method (line 4) | def convert_to_ruby_method(original_method_name)
      function underscore (line 10) | def underscore(str)

FILE: lib/libui/version.rb
  type LibUI (line 1) | module LibUI

FILE: test/libui_test.rb
  class LibUITest (line 3) | class LibUITest < Minitest::Test
    method test_that_it_has_a_version_number (line 4) | def test_that_it_has_a_version_number
    method test_ffi_method_call (line 8) | def test_ffi_method_call
    method test_method_call (line 15) | def test_method_call
    method test_basic_window (line 20) | def test_basic_window

FILE: test/utils_test.rb
  class LibUIUtilsTest (line 3) | class LibUIUtilsTest < Minitest::Test
    method test_convert_to_ruby_method (line 4) | def test_convert_to_ruby_method
    method test_underscore (line 11) | def test_underscore
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (183K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 110,
    "preview": "version: 2\nupdates:\n- package-ecosystem: \"github-actions\"\n  directory: \"/\"\n  schedule:\n    interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/doc.yml",
    "chars": 482,
    "preview": "name: doc\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: ac"
  },
  {
    "path": ".github/workflows/release-rubygems.yml",
    "chars": 2204,
    "preview": "name: Release libui to RubyGems\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n  workflow_dispatch:\n\njobs:\n  push:\n    environm"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 824,
    "preview": "name: test\non:\n  - push\n  - pull_request\n  - workflow_dispatch\njobs:\n  build:\n    name: ${{ matrix.runner }} Ruby ${{ ma"
  },
  {
    "path": ".gitignore",
    "chars": 161,
    "preview": "/.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"
  },
  {
    "path": "Gemfile",
    "chars": 205,
    "preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in libui.gemspec\ngemspec\n\ngem 'minitest'\ngem 'rake'\ngem"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2020-present kojix2\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "README.md",
    "chars": 9623,
    "preview": "# LibUI\n\n[![test](https://github.com/kojix2/LibUI/actions/workflows/test.yml/badge.svg)](https://github.com/kojix2/LibUI"
  },
  {
    "path": "Rakefile",
    "chars": 6298,
    "preview": "# frozen_string_literal: true\n\nrequire 'rake/testtask'\nrequire 'rbconfig'\nrequire 'fileutils'\nrequire 'zip'\nrequire 'bun"
  },
  {
    "path": "examples/basic_area.rb",
    "chars": 1276,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\narea"
  },
  {
    "path": "examples/basic_button.rb",
    "chars": 395,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nbutton = UI.new_button('B"
  },
  {
    "path": "examples/basic_draw_text.rb",
    "chars": 3568,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nhandler = UI::FFI::AreaHandler.malloc\nhandler.to_ptr.free = Fiddle::RUBY_FREE\narea"
  },
  {
    "path": "examples/basic_entry.rb",
    "chars": 619,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('Basic Entry', 300, 50, 1)\nUI.window_on_closing(main_w"
  },
  {
    "path": "examples/basic_table.rb",
    "chars": 1517,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('Animal sounds', 300, 200, 1)\n\nhbox = UI.new_horizonta"
  },
  {
    "path": "examples/basic_table_image.rb",
    "chars": 2121,
    "preview": "# NOTE:\n# This example displays images that can be freely downloaded from the Studio Ghibli website.\n# https://www.ghibl"
  },
  {
    "path": "examples/basic_window.rb",
    "chars": 216,
    "preview": "require 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nUI.control_show(main_wind"
  },
  {
    "path": "examples/control_gallery.rb",
    "chars": 5300,
    "preview": "require 'libui'\nUI = LibUI\n\nUI.init\n\n# File menu\nmenu = UI.new_menu('File')\nopen_menu_item = UI.menu_append_item(menu, '"
  },
  {
    "path": "examples/date_time_picker.rb",
    "chars": 755,
    "preview": "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:"
  },
  {
    "path": "examples/draw_text.rb",
    "chars": 6060,
    "preview": "require 'libui'\n\nUI = LibUI\n\ndef append_with_attribute(attr_str, what, attr1, attr2)\n  start_pos = UI.attributed_string_"
  },
  {
    "path": "examples/font_button.rb",
    "chars": 673,
    "preview": "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"
  },
  {
    "path": "examples/gpt2_notepad.rb",
    "chars": 2442,
    "preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'libui'\nrequire 'onnxruntime'\nrequire 'blingfire'\nrequire 'nu"
  },
  {
    "path": "examples/histogram.rb",
    "chars": 5870,
    "preview": "# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb\n\nrequire 'libui'\n\nUI = LibUI\n\nX_OFF_LEFT   = "
  },
  {
    "path": "examples/midi_player.rb",
    "chars": 2811,
    "preview": "#!/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"
  },
  {
    "path": "examples/simple_notepad.rb",
    "chars": 373,
    "preview": "#!/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"
  },
  {
    "path": "examples/spectrum.rb",
    "chars": 2884,
    "preview": "#!/usr/bin/env ruby\n\n# Please play your favorite music or video on your computer\n# when running this spectrum example.\n\n"
  },
  {
    "path": "examples/turing_pattern.rb",
    "chars": 15116,
    "preview": "#!/usr/bin/env ruby\n\n# https://en.wikipedia.org/wiki/Turing_pattern\n#\n# > The Turing pattern is a concept introduced by "
  },
  {
    "path": "examples2/README.md",
    "chars": 4035,
    "preview": "This directory (examples2/) contains code that refers to widgets and functions made available via the official libui-ng "
  },
  {
    "path": "examples2/button.rb",
    "chars": 1376,
    "preview": "# ============================================================================ #\n# This example (button.rb) shall demons"
  },
  {
    "path": "examples2/checkbox.rb",
    "chars": 1665,
    "preview": "# ============================================================================ #\n# This example (checkbox.rb) shall demo"
  },
  {
    "path": "examples2/color_button.rb",
    "chars": 2304,
    "preview": "# ============================================================================ #\n# This example (color_button.rb) shall "
  },
  {
    "path": "examples2/combobox.rb",
    "chars": 3994,
    "preview": "# ============================================================================ #\n# This example (combobox.rb) shall demo"
  },
  {
    "path": "examples2/date_picker.rb",
    "chars": 1649,
    "preview": "# ============================================================================ #\n# === DatePicker - a widget to allow th"
  },
  {
    "path": "examples2/editable_combobox.rb",
    "chars": 4931,
    "preview": "# ============================================================================ #\n# This example (editable_combobox.rb) s"
  },
  {
    "path": "examples2/entry.rb",
    "chars": 1901,
    "preview": "# ============================================================================ #\n# This example (entry.rb) shall demonst"
  },
  {
    "path": "examples2/font_button.rb",
    "chars": 1159,
    "preview": "# ============================================================================ #\n# This example (font_button.rb) shall d"
  },
  {
    "path": "examples2/grid.rb",
    "chars": 4468,
    "preview": "# ============================================================================ #\n# This example (grid.rb) shall demonstr"
  },
  {
    "path": "examples2/multiline_entry.rb",
    "chars": 1655,
    "preview": "# ============================================================================ #\n# This example (multiline_entry.rb) sha"
  },
  {
    "path": "examples2/password_entry.rb",
    "chars": 2213,
    "preview": "# ============================================================================ #\n# This example (password_entry.rb) shal"
  },
  {
    "path": "examples2/progress_bar.rb",
    "chars": 1033,
    "preview": "# ============================================================================ #\n# This example (progress_bar.rb) shall "
  },
  {
    "path": "examples2/search_entry.rb",
    "chars": 2303,
    "preview": "# ============================================================================ #\n# This example (search_entry.rb) shall "
  },
  {
    "path": "examples2/slider.rb",
    "chars": 2355,
    "preview": "# ============================================================================ #\n# This example (slider.rb) shall demons"
  },
  {
    "path": "examples2/todo/todo.md",
    "chars": 116,
    "preview": "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",
    "chars": 5146,
    "preview": "# ============================================================================ #\n# This example (window.rb) shall demons"
  },
  {
    "path": "lib/libui/error.rb",
    "chars": 232,
    "preview": "module LibUI\n  # base error class\n  class Error < StandardError; end\n\n  # LibUI shared library not found error\n  class L"
  },
  {
    "path": "lib/libui/ffi.rb",
    "chars": 29529,
    "preview": "require 'fiddle/import'\nrequire_relative 'fiddle_patch'\nrequire_relative 'error'\n\nmodule LibUI\n\n  module FFI\n    extend "
  },
  {
    "path": "lib/libui/fiddle_patch.rb",
    "chars": 3621,
    "preview": "module LibUI\n  # This module overrides Fiddle's mtehods\n  # - Fiddle::Importer#extern\n  # - Fiddle::CParser#parse_signat"
  },
  {
    "path": "lib/libui/libui_base.rb",
    "chars": 1950,
    "preview": "module LibUI\n  module LibUIBase\n    FFI.func_map.each_key do |original_method_name|\n      name = Utils.convert_to_ruby_m"
  },
  {
    "path": "lib/libui/utils.rb",
    "chars": 589,
    "preview": "module LibUI\n  module Utils\n    class << self\n      def convert_to_ruby_method(original_method_name)\n        underscore("
  },
  {
    "path": "lib/libui/version.rb",
    "chars": 37,
    "preview": "module LibUI\n  VERSION = '0.2.0'\nend\n"
  },
  {
    "path": "lib/libui.rb",
    "chars": 7124,
    "preview": "require_relative 'libui/version'\nrequire_relative 'libui/utils'\nrequire_relative 'libui/error'\nrequire 'rbconfig'\n\nmodul"
  },
  {
    "path": "libui.gemspec",
    "chars": 1307,
    "preview": "require_relative 'lib/libui/version'\n\nGem::Specification.new do |spec|\n  spec.name          = 'libui'\n  spec.version    "
  },
  {
    "path": "scripts/README.md",
    "chars": 291,
    "preview": "# 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## Requir"
  },
  {
    "path": "scripts/ui_diff.sh",
    "chars": 112,
    "preview": "#!/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",
    "chars": 636,
    "preview": "#!/usr/bin/env ruby\n\nlibui_ffi_path = File.expand_path('../lib/libui/ffi.rb', __dir__)\nlibui_path = File.expand_path('.."
  },
  {
    "path": "scripts/ui_h.rb",
    "chars": 1023,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'open-uri'\nrequire 'tempfile'\n\nUI_H_URL = 'https://raw.githubusercontent.com/libui-ng/libui"
  },
  {
    "path": "test/libui_test.rb",
    "chars": 830,
    "preview": "require 'test_helper'\n\nclass LibUITest < Minitest::Test\n  def test_that_it_has_a_version_number\n    refute_nil ::LibUI::"
  },
  {
    "path": "test/test_helper.rb",
    "chars": 124,
    "preview": "$LOAD_PATH.unshift File.expand_path('../lib', __dir__)\nrequire 'libui'\n\nrequire 'minitest/autorun'\nrequire 'minitest/pri"
  },
  {
    "path": "test/utils_test.rb",
    "chars": 441,
    "preview": "require 'test_helper'\n\nclass LibUIUtilsTest < Minitest::Test\n  def test_convert_to_ruby_method\n    rbmethod1 = LibUI::Ut"
  },
  {
    "path": "vendor/LICENSE.md",
    "chars": 1098,
    "preview": "Copyright (c) 2022 libui-ng authors\n\nCopyright (c) 2014 Pietro Gagliardi\n\nPermission is hereby granted, free of charge, "
  },
  {
    "path": "vendor/README.md",
    "chars": 10158,
    "preview": "# libui-ng: a portable GUI library for C\n\nFork of [andlabs/libui](https://github.com/andlabs/libui). This README is bein"
  }
]

About this extraction

This page contains the full source code of the kojix2/LibUI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (170.3 KB), approximately 49.0k tokens, and a symbol index with 80 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!