Skip to content

knb/ruby-qml

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ruby-qml Gem Version

ruby-qml is a QML / Qt Quick wrapper for Ruby. It provides bindings between QML and Ruby and enables you to use Qt Quick-based GUI from Ruby.

Dependency Status Build Status Coverage Status Inline docs

What you can do with ruby-qml

  • Develop desktop GUI applications only with Ruby and QML
  • Easily combine codes written in C++ and Qt with your Ruby code

Gallery

Screenshot

Screenshot

Installation

Requirements

  • Ruby 1.9 or later
  • OS X or Linux
  • pkg-config
  • libffi
  • Qt 5.2 or later

OS X with Homebrew

To install ruby-qml on OS X with Homebrew, run the following commands:

$ brew install pkg-config libffi qt5
$ gem install qml -- --with-libffi-dir=$(brew --prefix libffi) --with-qt-dir=$(brew --prefix qt5)

Both libffi and Qt5 are keg-only in Homebrew, so you must specify their paths explicitly (or force linking).

If you use official Qt installation, for example:

$ brew install pkg-config libffi
$ gem install qml -- --with-libffi-dir=$(brew --prefix libffi) --with-qt-dir=$HOME/Qt/5.3/clang_64

The Qt installation path ($HOME/Qt/5.3/clang_64 in this example) depends on your Qt installation configuration and Qt version.

General (OSX and Linux)

$ gem install qml

Options

  • --with-libffi-dir=[dir]
    • libffi installation directory (optional).
  • --with-qt-dir=[dir]
    • Qt installation directory (optional).

Use Gemfile

Add this line to your Gemfile:

gem 'qml'

And then execute:

$ bundle install

To pass build options, use bundle config. For example:

$ bundle config build.qml --with-libffi-dir=$(brew --prefix libffi) --with-qt-dir=$(brew --prefix qt5)

The configuration will be saved in ~/.bundle/config.

Usage

Load QML file

The following code loads a QML file and shows an application window titled "Hello, world!".

require 'qml'

QML.run do |app|
  app.load_path Pathname(__FILE__) + '../main.qml'
end
// main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1

ApplicationWindow {
    visible: true
    width: 200
    height: 100
    title: "Hello, world!"
}

Use Ruby class in QML

To make your class available to QML, include QML::Access and call register_to_qml.

By including QML::Access, you can also define properties and signals in Ruby classes like in QML.

Properties are used to bind data between QML and Ruby. Signals are used to provide the observer pattern-like notification from Ruby to QML.

Screenshot

# Ruby
class FizzBuzz
  include QML::Access
  register_to_qml under: "Example", version: "1.0"

  property :input, '0'
  property :result , ''
  signal :inputWasFizzBuzz, []

  on_changed :input do
    i = input.to_i
    self.result = case
    when i % 15 == 0
      inputWasFizzBuzz.emit
      "FizzBuzz"
    when i % 3 == 0
      "Fizz"
    when i % 5 == 0
      "Buzz"
    else
      i.to_s
    end
  end

  def quit
    puts "quitting..."
    QML.application.quit
  end
end
// QML - main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import Example 1.0

ApplicationWindow {
    visible: true
    width: 200
    height: 200
    title: "FizzBuzz"

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10
        TextField {
            placeholderText: "Input"
            text: "0"
            id: textField
        }
        Text {
            id: text
            text: fizzBuzz.result
        }
        Button {
            text: 'Quit'
            onClicked: fizzBuzz.quit()
        }
        Text {
            id: lastFizzBuzz
        }
    }
    FizzBuzz {
        id: fizzBuzz
        input: textField.text
        onInputWasFizzBuzz: lastFizzBuzz.text = "Last FizzBuzz: " + textField.text
    }
}

You can omit arguments of register_to_qml if they are obvious:

module Example
  VERSION = '1.0.0'

  class FizzBuzz
    include QML::Access
    register_to_qml

    ...
  end
end

If the Ruby object is singleton, you can use the root context to make it available to QML. In this case, you don't have to use register_to_qml.

class Foo
  include QML::Access
  def foo
    puts "foo"
  end
end

QML.run do |app|
  app.context[:foo] = Foo.new
  app.load_path Pathname(__FILE__) + '../main.qml'
end

Pass data to QML ListModels

To bind list data between QML ListView and Ruby, you can use ListModels.

  • QML::Data::ListModel - the base class for ruby-qml list models.

  • QML::Data::ArrayModel - provides a simple list model implementation using Array.

  • QML::Data::QueryModel - for databases (like ActiveRecord, Sequel or something)

This example uses ArrayModel to provide list data for a QML ListView. When the content of the ArrayModel is changed, the list view is also automatically updated.

Examples

# Ruby
class TodoController
  include QML::Access
  register_to_qml under: "Example", version: "1.0"

  property :model, QML::Data::ArrayModel.new(:title, :description, :due_date)

  def add(title, description, due_date)
    # Items of list models must be "Hash-like" (have #[] method to get columns)
    item = {
      title: title,
      description: description,
      due_date: due_date
    }
    p item
    model << item
  end
end
// QML
ListView {
    model: todo.model
    delegate: Text {
        text: "Title: " + title + ",  Description: " + description + ", Due date: " + due_date
    }
}
TodoController {
  id: todo
}

Combile asynchronous operations

In QML, all UI-related operations are done synchronously in the event loop. To set result of asynchronous operations to the UI, use QML.later or QML::Dispatchable#later.

Examples

# Ruby
class HeavyTaskController
  # QML::Access includes QML::Dispathable
  include QML::Access
  register_to_qml under: "Example", version: "1.0"

  property :result, ''

  def set_result(result)
    self.result = result
  end

  def start_heavy_task
    Thread.new do
      self.later.set_result do_heavy_task() # #set_result is called in the main thread in the next event loop
      # or
      QML.later do
        set_result do_heavy_task()
      end
    end
  end
end
// QML
Text {
  text: controller.result
}
Button {
  text: "Start!!"
  onClicked: controller.start_heavy_task()
}
HeavyTaskController {
  id: controller
}

Use Qt objects in Ruby

In ruby-qml, Qt objects (QObject-derived C++ objects and QML objects) can be accessed from Ruby via the meta-object system of Qt.

You can access:

  • Properties
  • Signals
  • Slots (as methods), Q_INVOKAVLE methods, QML methods

You cannot access:

  • Normal C++ member functions

If their names are camelCase in Qt, ruby-qml aliases them as underscore_case.

# QML::Application is a wrapper for QApplication
app = QML.application

# set property
app.applicationName = "Test"
app.application_name = "Test" # aliased version

# connect to signal
app.aboutToQuit.connect do # "about_to_quit" is also OK
  puts "quitting..."
end

# call method (slot)
app.quit

Value conversions

The following types are automatically converted between Ruby and QML:

  • Integer
  • Double
  • String
  • Time
  • Date
  • DateTime
  • Array
  • Hash
  • QML::Geometry::Point (QPoint, QPointF)
  • QML::Geometry::Size (QSize, QSizeF)
  • QML::Geometry::Rectangle (QRect, QRectF)
  • QML::QtObjectBase (Qt objects)
  • QML::Access
  • QML::Data::ListModel

Load and use Qt C++ plugins

PluginLoader loads Qt C++ plugins. It enables you to use your Qt C++ codes from Ruby easily.

// C++ - plugin example
class MyPlugin : public QObject
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.myplugin.MyPlugin")
signals:
    void added(int value);

public slots:
    int add(int x, int y) {
        int result = x + y;
        emit added(result);
        return result;
    }
};
# Ruby
plugin = QML::PluginLoader.new(directory, "myplugin").instance

plugin.added.connect do |value|
  puts "added value: #{value}"
end

plugin.add(1, 2) #=> 3

Garbage collection

To support garbage collection of Qt objects used in ruby-qml, #managed? attribute of each Qt object wrappr determines its memory management status.

Managed objects

Manged objects are managed by Ruby and QML and garbage collected when no longer reachable. All objects created inside QML and objects returned from C++ methods will be managed by default.

Unmanaged objects

Unmanaged objects are not managed and never garbage collected. Objects that have parents or that obtained from properties of other Qt objects will be unmanaged by default.

Specify management status explicitly

The #managed? method returns whether the object is managed or not. The #prefer_managed methods sets management status safely (e.g., objects that are created by QML will remain managed and objects that have parents will remain unmanaged).

plugin = PluginLoader.new(path).instance
obj = plugin.create_object
obj.prefer_managed false

Use with EventMachine

You can use EventMachine with ruby-qml. It is more powerful than the default ruby-qml event loop.

Instead of using QML.run, start an EventMachine event loop by EM.run and process QML events periodically by QML::Application#process_events.

require 'qml'
require 'eventmachine'

EM.run do
  QML.init
  EM.add_periodic_timer(0.01) { QML.application.process_events }
  QML.application.load_path(Pathname.pwd + 'main.qml')
end

You can also use em-synchrony to write callback-free asynchronous operation for ruby-qml.

require 'qml'
require 'eventmachine'
require 'em-synchrony'
require 'em-http-request'

class Controller
  include QML::Access
  property :result, ''

  def get
    EM.synchrony do
      content = EM::Synchrony.sync EM::HttpRequest.new('http://www.example.com/').get
      self.result = content.response
    end
  end

  def quit
    EM.stop
  end

  register_to_qml under: 'Example', version: '0.1'
end

EM.run do
  QML.init
  EM.add_periodic_timer(0.01) { QML.application.process_events }
  QML.application.load_path(Pathname.pwd + 'main.qml')
end

Contributing

Install dependencies

$ bundle install

Build native extension

Before running ruby-qml in development, the native extension of ruby-qml needs to have been built. To build it, run the following commands:

$ cd ext/qml
$ bundle exec ruby extconf.rb --with-qt-dir=/path/to/qt --with-libffi-dir=/path/to/libffi
$ make -j4

Run tests

Tests for ruby-qml is written in RSpec. To run tests, do:

$ bundle exec rspec

Run examples

$ bundle exec ruby examples/fizzbuzz/fizzbuzz.rb

Send pull requests

  1. Fork it ( https://github.com/seanchas116/ruby-qml/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Write some tests
  5. Push to the branch (git push origin my-new-feature)
  6. Create new Pull Request

About

A QML / Qt Quick bindings for Ruby

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C++ 49.7%
  • Ruby 47.0%
  • QML 2.9%
  • QMake 0.4%