Originally this article formed one article with Ruby Inox Part 6: Widget as everything had to evolve concurrently of each other. But decided later to split them up into separated article to underline the importance of the xxxBase line of classes and there major difference to the xxxNative line of classes.
xxxBase
The priority of these classes is to be as general and uniform as possible so they can be all used in much the same way and give a great relieve to the developer that will use them. That’s one major factor for speeding up the development process.
By general I mean that every class should solve one specific problem and introduce as few properties and actions as required to solve that problem.
Uniform a widget should behave like his nearest existing cousin. This is enforced by the code reuse within the xxxBase class hierarchy, but still to keep in mind.
xxxNative
A small subset of general, uniform, reusable widgets is better than haveing to write 10 different classes for 10 different toolkits 10 times for every project your write.
Instead you get a small general toolkit to give face to your ruby script, on every supported toolkit.
So these xxxNative classes focus on the end user experience. These classes implements the xxxBase and should produce exactly the same behavior across different native solutions. Hereby we limit our choice of native Controls/Widgets to a small subset which are well understood and supported on every native toolkit.
In this article I present the implementation of ScreenBase, ApplicationBase, WindowBase and ButtonBase for Cocoa, Qt.
Screen
For Cocoa:
class Screen < ScreenBase def native OSX::NSScreen.mainScreen end protected def create! self.frame = native.frame end end
For Qt:
class Screen < ScreenBase def native Qt::Application.desktop end protected def create self.frame = native.screenGeometry end end
That’s the whole magic for now. We have a Native Screen class with the size of the screen we will output our windows. For now this is a simplified approch that we need to expand to acknowledge multiple displays, multiple desktops and so on.
Screen.instance returns the singleton instance for a screen and the frame is the size of the screen. So we respect the uniform way of WidgetBase.
Application
The implementation for Cocoa is straightforward. As we find a one to one mapping of the functionality we are expecting. Cocoa expects application to be a singleton, and in the future we have to adapt this behavior of cocoa to the behavior we want of multiple application per interpreter.
class Application < ApplicationBase def native OSX::NSApplication.sharedApplication() end protected def create!; native.activateIgnoringOtherApps(true); def run!; native.run; end def dispose! # exception to the rule, terminate first the children an than the parent. super self.native.terminate(nil) end end
Qt gives us much the same idioms as Cocoa and doesn’t limit us to one instance of application. Here the Qt code:
class Application < ApplicationBase def native; @app; end protected def create! @app = Qt::Application.new(ARGV) end def run!; native.exec(); end def dispose! # exception to the rule, terminate first the children an than the parent. super self.native.quit() end end
Window
Let’s start with the window code for Cocoa:
# We reverse Cocoa's inverse coordinate style class FlippedView < OSX::NSView def isFlipped true end end # responds to action of NSWindow class WindowDelegate < OSX::NSObject def setTarget(target) @target = target end def windowWillClose(notification) @target.send :dispose end end class Window < WindowBase def create! styleMask = OSX::NSTitledWindowMask + OSX::NSClosableWindowMask + OSX::NSMiniaturizableWindowMask + OSX::NSResizableWindowMask @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer( self.frame.to_a, styleMask, OSX::NSBackingStoreBuffered, false) self.tile = self.class.properties[:title].default delegate = WindowDelegate.alloc.init delegate.setTarget self @window.setDelegate(delegate) @window.setContentView(FlippedView.alloc.initWithFrame(@window.contentView.frame)) end def native @window end protected def child_added!(obj) super(obj) @window.contentView.addSubview(obj.native) end def get_title; @window.title.to_s; end def set_title(v); @window.setTitle(v); end def visible_changed! if self.visible then @window.makeKeyAndOrderFront(@window) else @window.orderOut(nil) end end def frame_changed! @window.setFrame_display(self.frame.to_a,true) super end end
TODO create a more general approach for wrapping Cocoa’s voodoo delegates call and event notifications. But beside that this code is working. And behaves like we want a window to behave.
I won’t go into details for Cocoa as this code will hopefully change and get cleaner. So next is the Qt class:
class Window < WindowBase def native; @window; end protected def create! @window = Qt::Widget.new(nil) t = self.class.properties[:title].default @window.setGeometry(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) @window.setWindowTitle(t) end def dispose!; @window.delete; super; end def child_added!(obj) super(obj) obj.native.setParent(@window) end def get_title; @window.windowTitle.to_s; end def set_title(v); @window.setWindowTitle(v); end def visible_changed! if self.visible then @window.show() else @window.hide() end end def frame_changed! @window.setGeometry(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) super end end
Perhaps it is only me but I find that Qt still reflects better our intentions and deliver a more compact code than Cocoa. Whatever the two implmentations above works.
Button
First cocoa:
class ButtonDelegate < OSX::NSObject def setTarget(target) @target = target end def clicked(sender) @target.send :clicked end end class Button < ButtonBase def native; @ button; end def create! @button = OSX::NSButton.alloc.initWithFrame(self.frame.to_a) del = ButtonDelegate.alloc.init del.setTarget self with @button do setAction :clicked setTarget del setBezelStyle OSX::NSRoundedBezelStyle end end def get_title; native.title.to_s; end def set_title(v); native.setTitle(v); end def dispose!; native.release; super; end def visible_changed!(ov, v) native.setHidden(v) if ov != v end def frame_changed! native.setFrame(self.frame.to_a) end end
Slowly we see a pattern emerge how to wrap Cocoa Objects into Widgets and this pattern will be catched in metaprogramming in a following article, but for now this code works as expected and produces the same result as the Qt code belove:
class ButtonDelegate < Qt::Widget slots 'perform_click()' def setTarget(obj); @target = obj; end def perform_click; @target.clicked; end end class Button < ButtonBase def native; @button; end protected def create! @button = Qt::PushButton.new('Button', nil) delegate = ButtonDelegate.new delegate.setTarget(self) Qt::Object.connect @button, SIGNAL('clicked()'), delegate, SLOT('perform_click()') end def get_title; native.title.to_s; end def set_title(v); native.setWindowTitle(v); end def dispose!; native.delete; super; end def visible_changed!(ov, v); native.setHidden(v) if ov != v; end def frame_changed!; native.setGeometry(*self.frame.to_a); end end
The goal we set out to reach is attained. An with a little bit of sugar,
# special case for application as it doe # def application(&block) app = Application.instance with(app, &block) app end def self.define_sugar_for(*syms) syms.each do |sym| class_eval %{ def #{sym.to_s.downcase.to_sym}(&block) obj = #{sym}.new(self) with(obj, &block) self.add_child(obj) if self.container? obj end } end end define_sugar_for :Window, :Button # :Label, :Edit
we can already do amazing constructs.
First working example
The Hello World example with a button:
require 'inox' include Inox # Evaluates the block within the singleton instance Application application { # creates a window and evalutes the block within its instance window { # set the title by method calling # setting the title by assignment not possible due # to the duality of local variables title 'Hello World' # evaluates the block within the instance of the property frame # equivalent of doing something like: # # f = self.frame # f.size = [200, 120] # f.center = parent.bounds.center # self.frame = f # # because f is of class Rect we need to get or assign an whole Rect # for the frame property to get updated. frame { # assignement by method call not possible since size/center # are not properties self.size = [200, 120] self.center = $parent.bounds.center } # create a button and evaluates within its instance button { title 'Quit' frame { self.center = $parent.bounds.center } # callback called when button is clicked on_clicked { Application.instance.dispose } }.show }.show }.run
To be continued
Hope this article has show the fun we are heading to. But there is still a lot of work to be done before Inox reaches a useable state. It reaches completeness when it supports at least: Cocoa, Qt, Kde, Gtk2, Windows(somehow), Agnostic(general doit ourself way), basic dialogs support for Ncurse.
The first targeted application written with Inox should be a GUI editor for Inox, which can produce and manage Inox code, in script style and MVC style.
But for the next article there is still a more humble step to be taken first. A great deal of code cleanup and re-factoring to settle down on a definitive core code so that you can download and tryout the first examples.
Ruby Inox on GitHub
This file is part of the Ruby Inox project. Inox is a work in progress and this file might not be up to date. The status and latest files are available on GitHub
Related posts:
- Ruby Inox Part 6: Widget We are steering toward a graphical user interface. In this...
- Ruby Inox Part 5: Component Something promised is something due. Only two days have passed...
- Ruby Inox Part 4.1: Property In Ruby Inox Part 4: Property I described I would...
- Ruby Inox Part 4: Property Last time, we took a look at Actions. A standard...
- Unified Ruby GUI Ruby is incredibly adaptable to a multitude of environments, operating...
- Ruby Inox Part 3.1: Actions Here is a short update for the Ruby Inox Part3:...



ShareThis