We are steering toward a graphical user interface. In this article we lay out the firt visual stone, the Widget or more precisely the class WidgetBase.
A major cleanup of the code written so far is already planned and notice that these minor changes will be reflected only on github and no article will be posted until Inox becomes useable/stable.
What we have so far
This article is a follow up of my series discussing Inox. The last article defined a general, not visual, reusable component. It has a parent, and may have children, which is already a sane base for a visual component.
For now we made no assumption on the visual part; if we will be using native wigets/controller of a native toolkit, or perform the drawing part ourself. So we need to split up the Widget code in two parts: 1) WidgetBase, which is responsible for laying out the basic features of a Widget and 2) Widget, the real code that will pup-up the widget on screen.
WidgetBase
First let’s take a look at a simplified code for WidgetBase, and let me explain line by line what is going on.
class WidgetBase < Component
properties {
frame { type Rect; readwrite; default [32,32,32,32].to_rect }
visible { type Object; readwrite; default false }
}
actions :update, :parent_frame_changed
def show; self.visible = true; end
def hide; self.visible = false; end
def bounds
[0,0,frame.size.width, frame.size.height].to_rect
end
protected
def set_frame(value)
assert(value).respond_to?(:to_rect).end!
property_set!(:frame, value.to_rect)
end
def set_visible(value)
assert(value).kind_of?(TrueClass).or.kind_of?(FalseClass).end!
property_set!(:visible, value)
end
end
Line: 1
We define the class WidgetBase and we inherit all the gudd code from Component: Properties, Actions, a Parent. WidgetBase doesn't include the Container module because the container module can be included later by any subclass and so make the Widget a container.
Let supose you later want to override the class Button, to make it display subitems in a contextual menu. And the items will be made children of the Button. Well you can, do something like that later by simply including the Container module.
Line: 2..5
Frame is the location, a Rect on the parent, where a widget is placed and drawn. Nothing fancy but the bare minimum.
The second evident property that all widgets needs, defines if the widget is visible or not.
From the previous articles we get all the features of Properties:
wgt = WidgetBase.new
# a setter
wgt.visible = true
# a setter by call
wgt.visible(true)
# a getter
wgt.visible => true
# an action changed
wgt.on_visible_changed { puts "Visibility changed to #{self.visible} }
# a subclass friendly method for overriding
class MyButton < WidgetBase
protected
def visible_changed!
puts "Visibility changed to #{self.visible} from subclass"
end
end
btn = MyButton.new
btn.visible = false => call to visible_changed!
A lot of functionality in a few line of code! That's the amazing world of Ruby, which really helps you to code faster, nearly and the speed of thought.
Line: 7
Defines an action update, will later be used by parents container for triggering and update of the widget. So subclasses can override update! to perform their tasks.
Parent_frame_changed will be called by a container widget to inform its children that they have to readjust their frame.
Lines: 9..10
These two methods makes the source code shorter and more understandable. Internally the property is what matters and not the methods which changes the properties. The subclass has only to implement the property_changed method
Lines: 12..13
frame has the current location of the widget looking from the parent perspective. For it's children their parents has a location of [0,0] and they are only interested in the [width, height] of the parent. So bounds represent the parent from inside and frame represent the parent from the outside.
Lines: 19..24
Override the two setters to assert that we pass in the values of the expected type. TODO this feature has to be moved to the Properties module, but since ruby Doesn't have a Boolean class we still need to perform a manual assert.
ContainerBase
We want to regroup the functionality of common widgets into Base classes. The role of a whole genre of Widgets is to include and displays other widgets, so we will dispense this import task with its owen class.
class ContainerBase < WidgetBase
include Container
protected
def frame_changed!
children.each { |c| c.parent_frame_changed }
end
def update!
children.each { |c| c.update }
end
end
We include the Container module and propagate our changes the children.
Default Widgets
In the process of creating widgets the first widget that we need is a representation of the Screen( Desktop), where the application can draws its Windows. Note that the role of representing the Screen and that of containing the windows is split between two Widgets.
Screen really only represents the desktop area, and Application is the actual container for the Windows.
class ScreenBase < WidgetBase include Singleton end
For practical purpose at the time of this writing Application is still defined as singleton, one application per ruby interpreter. This will be changed in the near future to accommodate multiple applications per ruby interpreter.
class ApplicationBase < ContainerBase
include Singleton
properties {
title { type String; readwrite; default 'Inox Application' }
}
actions :run
def initialize(*args, &block)
super(*args, &block)
self.parent = Screen.instance()
self.frame = self.parent.frame
end
def child_removed!(obj)
dispose if children.length == 0
end
end
An application has always Screen.instance as parent and the application frame is the Screen frame. When there are any children left the application is closed.
The Window Widget is also self explanatory:
class WindowBase < ContainerBase
properties {
title { type String; default "Inox Window" }
}
def initialize(*args, &block)
super(*args, &block)
self.frame = [0,0,640,480]
self.frame { |f|
f.center = $parent.frame.center
}
end
end
We set a default size for the window and a default position and we are ready to go. Here is a list of avaible actions for WindowBase:
- create
- dispose
- parent_changed
- frame_changed
- visible_changed
- parent_frame_changed
- child_added
- child_removed
- title_changed
Yes, we can take advantage of these actions in our code f.ex we want to perform something when a windows is closed/destroyed, we add a handler to dispose:
win.on_dispose {
puts "This window was closed"
}
win.on_visible_changed {
puts "This window is now hidden" unless self.visible
}
The last piece of Widget we need to get an example working is a button:
class ButtonBase < WidgetBase
properties {
title { type String; default 'Button' }
}
actions :clicked
def initialize(*args, &block)
super(*args, &block)
self.frame = [0,0,96,32]
end
end
Pretty the same logic as behind the window widget. We already have a default title and we set a new default frame size. We this four Widgets defined we can start implementing the native widgets.
To be continued
Here I present you the roadmap for the next few days. Yes, it is now a mater of days to get the first dialog showing up. Preliminary tests with Cocoa, and QT were very encouraging. The next article will handle the implementation of Screen, Application, Window, Button for Cocoa and QT.
The short term goals(for 1 August 2009 1am GMT +2):
- Inox packaged as gem
- Inox Components alpha-ready: Screen, Application, Window, Button, Label, TextField, and Progressbar
- Targeted toolkits: Cocoa, QT, GTK, Ncurse
- Define the long term roadmap
I don't know if I can meet the deadline for Ncurse, but Ncurses gives us a way to keep Inox balanced, I mean if it doesn't make sense to be done in Ncurse than perhaps it is more suited to place that feature as an external plugin for Inox; or some kind of extention. So I think that the Project will split up, one branch as 'Inox the core' with a fixed list of features that should be universally accessible on all platform and a well designed plugin architecture taking advantage of ruby's flexibility, to promote code reuse and share. Let say that for now I continue working on the core of Inox.
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 7: Native Originally this article formed one article with Ruby Inox Part...
- 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...
- Ruby Inox Part 3.1: Actions Here is a short update for the Ruby Inox Part3:...
Tags: Component, GUI, Inox, OO, Part 5, Ruby, Ruby Inox, Toolkit