Skip to content
Patrick Plenefisch edited this page Apr 16, 2022 · 3 revisions

JRubyFX Design

JRubyFX is effectively three projects in one: a JavaFX DSL + builder, patches for JavaFX <=> JRuby interop, and FXML integration, each building on the prior.

The DSL + Builders

The DSL and builders is the first "pillar" of JRubyFX. It comprises roughly of the following files:

  • jrubyfx/module.rb
  • jrubyfx/dsl.rb
  • jrubyfx/utils/common_utils.rb

module.rb: build and with

module.rb is where the JRubyFX module is defined and contains two very important methods: build and with. Whenever a class imports the JRubyFX module, they gain these two methods.

with takes an object, a list of properties, and an optional block. It first applies the properties to the object, then calls the block in the context of the object. This enables you to avoid having to type the object's name each time you set a property. On the downside, local instance variables and methods are inaccessable and must be accessed from local closure variables.

build is similar, but instead of giving it an object, you give it a type, which it then constructs and calls with, returning the finished object.

dsl.rb: NAME_TO_CLASSES and method_missing

dsl.rb is exactly what it sounds like: the heart of the DSL (Domain Specific Language, in this case the domain is JavaFX, hence we have a JavaFX Specific "Language"). NAME_TO_CLASSES is the first half of the DSL; it maps DSL methods to JavaFX classes. Most of it is generated from the imports list (see Random Other Stuff section at the bottom) and thus appears very small. When it is generated, it will for example map "tree_view" to the javafx.scene.controls.TreeView class. This information is then used by the method_missing function (a ruby metaprogramming construct). The method_missing function is called when an unknown method is called in a class that has included the DSL. It first checks to see if the method name is in the NAME_TO_CLASSES map, and if it is not, it "throws" the missing method back up to see if somebody else knows about it. On the other hand, if it does find it in the map, it calls build with the mapped class, which in turn creates the object and calls with, which in turn sets properties and executes a block in the scope of the object so you can easily set stuff on the new object.

common_utils.rb

Both the previous two files use this file to split, set, and convert properties on objects.

These three files allow you to instead of saying the java-ish

btn = Java::javafx.scene.controls.Button.new
btn.text = "Good Evening, Jeeves!"
btn.set_on_action do
  ...
end

to say this:

button(text: "Good Evening, Jeeves!") do
  set_on_action do
    ...
  end
end

The core_ext converters

The core converters "pillar" is composed of three parts: the actual converters, static class references to the converters, and descriptors to build more class converters. This section is contained in the following files:

  • jrubyfx/utils/common_converters.rb
  • jrubyfx/core_ext/*.rb
  • jrubyfx/core_ext/exts.yml
  • jrubyfx/dsl.rb
  • jrubyfx/core_ext/precompiled.rb

common_converters.rb

This file contains several bits: metaprogramming helpers for the core_ext/*.rb files and actual converters. Most of the operations it does are pretty standard so I won't go into much detail.

core_ext static overrides

This folder contains lots of static overrides and converter declarations that are not supported (yet) in the yaml file or which are just plain methods like FileChooser.add_extension_filter.

exts.yml

This YAML file declaratively declares what extra DSL methods should be appended to precompiled.rb for given classes.

dsl.rb & precompiled.rb

This is the heart of this stage. When you either directly or indirectly run rake reflect this code runs to generate precompiled.rb from exts.yml. write_enum_converter is the prime bit of code and does three things:

  • Reflexively find all methods with enums and add symbol => enum converters on them
  • Reflexively find all methods with colors and add symbol => Color converters on them
  • Read exts.yml and add specified converters/dsl methods on them

All this new information is then massaged into precompiled.rb, which is then much faster to load each time than re-computing it every load as it rarely changes.

The FXML support

As of 2.0, FXML support uses the native JavaFX FXMLLoader. The old JRubyFX-FxmlLoader is no longer needed and can be removed

  • fxml_helper.rb
  • controller.rb
  • module.rb

controller.rb

Aka where the action is. All FXML controlers must include JRubyFX::Controller to work properly. Once it has been included, a class gains several methods and metaprogramming constructs. The most important ones are probbably:

  • fxml & fxml_root
  • load_into
  • on
  • css

fxml & fxml_root

fxml enables a controller to specify the default fxml file it is a controller for if the filename is not the same as its class name.

Example:

fxml_root File.dirname(__FILE__)
# only need this once. Says that all fxml files are in the same dir as this file

class MyObject
  include JRubyFX::Controller
  fxml "MyObject.fxml"
end

load_into

This is the primary way to create the main scene's controller (use new for custom control controllers). The only required argument is the scene to load it into, but it has more options such as the filename to load, scene properties, initialization arguments, etc.

Example:

MyController.load_into(stage)

on

If you want to have one method for multiple events of different names, use on instead of def. The reason for it to exist was because of original limitations in the 0.9.x series where you needed to use it instead of def, but that requirement has vanished now that we have JRubyFX-FxmlLoader`.

Example:

class MyController
  inclue JRubyFX::Controller
  def normal_event_handler(event)
    # ...
  end

  on :on_style_event_handler do |event|
    # ...
  end

  on [:on, :multiple, :handler, :names] do |event|
    # this block is defined for #on, #multiple, #handler, and #names callbacks
    # ...
  end
end

css and friends

These are conveinence methods for calling scene.lookup and friends

module.rb

At the end of this file are several class methods that enable you to easily define FXML-exportable properties. Since FXML is all strings, these define a propertynameGetType method that returns the ruby class that corresponds to the type of the properties

Random Other Stuff

Astute readers will notice there is a small bit I have not talked about yet. Rake tasks, binaries, and the imports & plumbing.

Binaries

jrubyfx-generator

This binary allows you to give it an fxml file and it outputs a controller skeleton, similar to SceneBuilder's one for Java.

jrubyfx-jarify

This binary allows you to generate a jar file from the jruby-complete.jar. This is not a full soloution and needs a bit of improvement.

Rake tasks

Several things like jarification are in jrubyfx_tasks.rb

application.rb and java_fx_impl.rb

This is a launcher that uses java_fx_impl.rb to properly launch javafx from ruby.

part_imports.rb / imports.rb

This imports all JavaFX classes so we can use them and creates the javaFX tree that several other classes use.