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) glimmer-dsl-libui [![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 | | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | | | | 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) # # 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.
[![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)