!Electronic || !Bionic || !Ultrasonic

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):

  1. Inox packaged as gem
  2. Inox Components alpha-ready: Screen, Application, Window, Button, Label, TextField, and Progressbar
  3. Targeted toolkits: Cocoa, QT, GTK, Ncurse
  4. 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:
  1. Ruby Inox Part 7: Native Originally this article formed one article with Ruby Inox Part...
  2. Ruby Inox Part 5: Component Something promised is something due. Only two days have passed...
  3. Ruby Inox Part 4.1: Property In Ruby Inox Part 4: Property I described I would...
  4. Ruby Inox Part 4: Property Last time, we took a look at Actions. A standard...
  5. Ruby Inox Part 3.1: Actions Here is a short update for the Ruby Inox Part3:...
  6. Unified Ruby GUI Ruby is incredibly adaptable to a multitude of environments, operating...
Email Icon Facebook Icon Twitter Icon Share this IconShareThis Follow Ironicwolf on Twitter