has anyone gotten a chance to take a look at the GUtopIa code yet?
there’s something very strange about it. something i can’t figure out. i
mean i wrote it and all, but i never explictly told it how to update the
widget when the object changes. yet it does! how? i keep coming back to
this and looking for some place in the code that does it, but i can’t
find it. very spooky.
~transami
On Tue, 2002-07-23 at 18:31, Tom Sawyer wrote:
well, i just read over my last post. sorry about all the typos. i needed
a break and didn’t bother to proofread. but i’m sure you can make out
what i was trying to say where i blurbed. okay, now to the good stuff!
i spent the last few days hacking out the first steps in the creation of
a super-duper ruby gui api, which i have dubbed GUtopIa (thanks rich).
just to kick things off here’s a small clip of one of my examples (note
MyModels is drawn from an unshown require):
INTERFACE = ‘PDA’
fruitapp = FruitApp.new
mygui = GUtopIa::GUI.new(‘Fruity Fun Time’)
names = {
‘awindow’ => fruitapp.appname,
‘aradio’ => fruitapp.fruitbasket.name
}
bindings = {
fruitapp.fruitbasket.name => {
:value => Proc.new { fruitapp.fruit.isa },
:value= => Proc.new { |x| fruitapp.fruit.isa = x },
:items => Proc.new { fruitapp.fruitbasket.contains },
:event_change => Proc.new { fruitapp.pickafruit }
}
}
model = MyModels.pick_model(mygui, INTERFACE)
mygui.transaction(bindings, names, &model)
mygui.build
the idea is that you take an instance of your application (FruitApp in
this case), completely untouched --you don’t have to add or change a
single line of code (unless you want too you then create a model. a
model (not shown in example) is an executable description of the fixed
aspects of your gui. it has no references to any part of your app
(although you can do so if you don’t care about SOC) it is a completely
independent entity and as such can be reused in other fitting contexts.
you can also draw on multiple sub-models. then you create a binding (as
seen above). this describes how an instance of your application
“interfaces” with your model. and then presto out comes you gui-ized
app.
also note that GUtopIa uses a transaction mechinism. this effectivly
bundles an executable model into a single execution. not only does this
increase speed, but also offers a means to rollback the effects of a
transaction should there be an error, adding much greater stability to
your gui-ized application.
some other things to note: bindings are actually integrated directly
into the class models of the widgets. when a widget is created it is
subclassed into a specialized class of that widget which includes
methods to react to the bindings described.
i’ve attached my work thus far so you can checkout all of this in
detail. be sure to run example.rb (the snipet above) and check out
orig-example.rb, which is a non-SOC version of the original Rouge/Utopia
example. then check out gutopia.rb which is the heart of the matter.
so that’s the the word on implementation. i’ve taken the advice of
others and i have worked out what i think is shaping up to be the best
gui api design in existence. Curt Hibbs has offered me a wiki for this
project. (thanks curt) i will take him up on it. now i hope i can get
YOU enthused enough to help. there’s real potential here, to not only
create an awsome gui api for ruby that is unique and powerful, but also
create a full-fledge game sdk! this thing looks to be awesome indeed.
i need people to take on a few tasks. a ruby wrapper needs to be created
for ClanLib and we need to build a Themes and Behaviors manager. someone
needs to hack out the communications layer (i’ve tried it with DRb and
ROMP but they bomb). and i could use some help finishing off the main
api. if we can pull together on this, ruby, our favorite little
scripting language, will have its very own GUI to rival all others!
as you might be able to tell, i’m very excited! i hope you are too!
thanks for your time and hope to hear from you,
~transami
GUtopIa Example - Application Layer
Copyright (c) 2002 Thomas Sawyer, Ruby License
class FruitApp
attr_accessor :appname
attr_reader :fruitbasket, :fruit, :basketname
def initialize
@appname = ‘Fruity Fun’
@fruitbasket = FruitBasket.new
@fruit = Fruit.new
end
def pickafruit
puts “You got a #{@fruit.isa}!”
end
end
class FruitBasket
attr_accessor :name, :contains
def initialize
@name = ‘Fruit Basket’
@contains = [“None”, “Apple”, “Orange”, “Banana”]
end
end
class Fruit
attr_accessor :isa
def initialize
@isa = “None”
end
end
GUtopIa Daemon Example - Client
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘drb’
require ‘example-app’
require ‘example-pre’
Configure interface
INTERFACE = ‘PDA’
puts “\nINTERFACE: #{INTERFACE}\n”
Make GUI App
fruitapp = FruitApp.new
DRb.start_service()
mygui = DRbObject.new(nil, ‘druby://localhost:8080’)
names = {
‘awindow’ => fruitapp.appname,
‘aradio’ => fruitapp.fruitbasket.name
}
bindings = {
fruitapp.fruitbasket.name => {
:value => Proc.new { fruitapp.fruit.isa },
:value= => Proc.new { |x| fruitapp.fruit.isa = x },
:items => Proc.new { fruitapp.fruitbasket.contains },
:event_change => Proc.new { fruitapp.pickafruit }
}
}
model = MyModels.pick_model(mygui, INTERFACE)
mygui.transaction(bindings, names, &model)
mygui.build
Run some tests
puts
puts "MYGUI "
p mygui
puts
puts “MYGUI WIDGETS”
mygui.widgets.each_key do |k|
puts k
end
puts
puts “THE WINDOW”
p mygui.widgets[fruitapp.appname].class.superclass
puts “name: #{fruitapp.appname}”
puts “width: #{mygui.widgets[fruitapp.appname].width}”
puts “height: #{mygui.widgets[fruitapp.appname].height}”
puts
puts “THE RADIO”
p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
puts “name: #{fruitapp.fruitbasket.name}”
puts “items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}”
puts “value: #{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts
puts “TRY OUT!”
puts
puts “Current state of fruit and its widget:”
puts “fruit=#{fruitapp.fruit.isa}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Orange’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Trigger event_change event:”
mygui.widgets[fruitapp.fruitbasket.name].event_change
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Apple’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
GUtopIa Example - Models
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘gutopia’
module MyModels
def MyModels.pick_model(gui, which)
case which
when ‘PC’
return MyModels.pc_model(gui)
when ‘PDA’
return MyModels.pda_model(gui)
end
end
private
PC model using serial notatation (slower)
def MyModels.pc_model(gui)
Proc.new {
# RadioBox
r = gui.widgetFactory(:RadioBox, 'aradio')
# Window
w = gui.widgetFactory(:Window, 'awindow')
w.width = 640
w.height = 400
w.grid = [ [ r ] ]
}
end
PDA model using parallel notation (faster)
def MyModels.pda_model(gui)
Proc.new {
# RadioBox
r = gui.widgetFactory(:RadioBox, 'aradio')
# Window
w = gui.widgetFactory(:Window, 'awindow',
:width => 320,
:height => 240,
:grid => [ [ r ] ]
)
}
end
end # MyModels
GUtopIa Daemon Example - Client
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘romp/romp’
require ‘example-app’
require ‘example-pre’
Configure interface
INTERFACE = ‘PDA’
puts “\nINTERFACE: #{INTERFACE}\n”
Make GUI App
fruitapp = FruitApp.new
client = ROMP::Client.new(‘tcpromp://localhost:8080’)
mygui = client.resolve(‘gutopia-romp’)
names = {
‘awindow’ => fruitapp.appname,
‘aradio’ => fruitapp.fruitbasket.name
}
bindings = {
fruitapp.fruitbasket.name => {
:value => Proc.new { fruitapp.fruit.isa },
:value= => Proc.new { |x| fruitapp.fruit.isa = x },
:items => Proc.new { fruitapp.fruitbasket.contains },
:event_change => Proc.new { fruitapp.pickafruit }
}
}
model = MyModels.pick_model(mygui, INTERFACE)
mygui.transaction(bindings, names, &model)
mygui.build
Run some tests
puts
puts "MYGUI "
p mygui
puts
puts “MYGUI WIDGETS”
mygui.widgets.each_key do |k|
puts k
end
puts
puts “THE WINDOW”
p mygui.widgets[fruitapp.appname].class.superclass
puts “name: #{fruitapp.appname}”
puts “width: #{mygui.widgets[fruitapp.appname].width}”
puts “height: #{mygui.widgets[fruitapp.appname].height}”
puts
puts “THE RADIO”
p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
puts “name: #{fruitapp.fruitbasket.name}”
puts “items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}”
puts “value: #{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts
puts “TRY OUT!”
puts
puts “Current state of fruit and its widget:”
puts “fruit=#{fruitapp.fruit.isa}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Orange’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Trigger event_change event:”
mygui.widgets[fruitapp.fruitbasket.name].event_change
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Apple’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
GUtopIa Example - Presentation
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘gutopia’
require ‘example-app’
require ‘example-pre’
Configure interface
INTERFACE = ‘PDA’
puts “\nINTERFACE: #{INTERFACE}\n”
Make GUI App
fruitapp = FruitApp.new
mygui = GUtopIa::GUI.new(‘Fruity Fun Time’)
names = {
‘awindow’ => fruitapp.appname,
‘aradio’ => fruitapp.fruitbasket.name
}
bindings = {
fruitapp.fruitbasket.name => {
:value => Proc.new { fruitapp.fruit.isa },
:value= => Proc.new { |x| fruitapp.fruit.isa = x },
:items => Proc.new { fruitapp.fruitbasket.contains },
:event_change => Proc.new { fruitapp.pickafruit }
}
}
model = MyModels.pick_model(mygui, INTERFACE)
mygui.transaction(bindings, names, &model)
mygui.build
Run some tests
puts
puts "MYGUI "
p mygui
puts
puts “MYGUI WIDGETS”
mygui.widgets.each_key do |k|
puts k
end
puts
puts “THE WINDOW”
p mygui.widgets[fruitapp.appname].class.superclass
puts “name: #{fruitapp.appname}”
puts “width: #{mygui.widgets[fruitapp.appname].width}”
puts “height: #{mygui.widgets[fruitapp.appname].height}”
puts
puts “THE RADIO”
p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
puts “name: #{fruitapp.fruitbasket.name}”
puts “items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}”
puts “value: #{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts
puts “TRY OUT!”
puts
puts “Current state of fruit and its widget:”
puts “fruit=#{fruitapp.fruit.isa}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Orange’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Trigger event_change event:”
mygui.widgets[fruitapp.fruitbasket.name].event_change
puts
puts “Changing fruit to: #{fruitapp.fruit.isa = ‘Apple’}”
puts “widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}”
puts
puts “Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = ‘Banana’}”
puts “fruit=#{fruitapp.fruit.isa}”
puts
GUtopIa - ROMP Daemon
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘drb’
require ‘gutopia’
gutopia_drb = GUtopIa::GUI.new(‘DRb’)
DRb.start_service(‘druby://localhost:8080’, gutopia_drb)
DRb.thread.join
GUtopIa - ROMP Daemon
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘romp/romp’
require ‘gutopia’
gutopiad = GUtopIa::GUI.new(‘Romp’)
server = ROMP::Server.new(‘tcpromp://localhost:8080’, nil, true)
server.bind(gutopiad, ‘gutopia-romp’)
server.thread.join
GUtopIa API
Copyright (c)2002 Thomas Sawyer, Ruby License
module GUtopIa
Returns a GUIFactory object
class GUI
attr_reader :name, :widgets
def initialize(name)
@name = name
#
@widgets = {}
@names = {}
@bindings = {}
@transactions = []
end
# Method used to prefab GUI
def transaction(bindings={}, names={}, &model)
@names = names
@bindings = bindings
@transactions << Proc.new {
#commit
#begin
model.call #(self)
#commit
#rescue ScriptError
#rollback
#rescue StandardError
#rollback
#end
}
end
# Method used to build GUI
# This runs the all pending transactions
def build
@transactions.each do |transaction|
transaction.call(@bindings)
end
@transactions.clear # all done with those
end
# Widget Factory
# Returns a new widget object of specialized widget class
def widgetFactory(widget, name, attributes={})
# a widget string name will work too
widget = widget.intern if widget.is_a?(String)
# makes an anoynomous class as subclass of desired widget
widgetClass = Class.new(GUtopIa.const_get(widget))
# specialize class via bindings
if @bindings[@names[name] || name]
@bindings[@names[name] || name].each do |k, v|
widgetClass.class_eval {
define_method(k, v)
}
end
end
w = widgetClass.new(attributes)
w.name = @names[name] || name
@widgets[w.name] = w
return w
end
#
def alert(msg)
puts msg
end
#
def stop
puts "stopping...#{@name}"
exit
end
# Useful Class Methods?
def GUI.screen_realestate
# { :height => , :width => , :depth => }
end
end # GUI
Widgets
ToDo: add a whole bunch more super widgets
attributes require validation
class Widget
attr_accessor :name
end
class Window < Widget
attr_accessor :height, :width, :colordepth, :flags
attr_accessor :caption, :icon, :background, :grid
def initialize(attributes={})
@height = attributes[:height] ? attributes[:height] : 200
@width = attributes[:width] ? attributes[:width] : 200
@colordepth = attributes[:colordepth] ? attributes[:colordepth] : 16
@flags = attributes[:flags] ? attributes[:flags] : 0
@caption = attributes[:caption] ? attributes[:caption] : nil
@icon = attributes[:icon] ? attributes[:icon] : nil
@background = attributes[:background] ? attributes[:background] : nil
@grid = attributes[:grid] ? attributes[:grid] : []
#
super()
end
def show
puts "showing window....#{@caption}"
end
def hide
puts "hiding window...#{@caption}"
end
end # Window
class Panel < Widget
attr_accessor :height, :width
attr_accessor :background, :grid
def initialize(attributes={})
@height = attributes[:height] ? attributes[:height] : 200
@width = attributes[:width] ? attributes[:width] : 200
@background = attributes[:background] ? attributes[:background] : nil
@grid = attributes[:grid] ? attributes[:grid] : []
#
super()
end
end # Panel
class Label < Widget
attr_accessor :text
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
#
super()
end
end # Label
class TextField < Widget
attr_accessor :text, :value
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
@value = attributes[:value] ? attributes[:value] : nil
#
super()
end
end # TextField
class TextArea < Widget
attr_accessor :text, :value, :cols, :lines
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
@value = attributes[:value] ? attributes[:value] : nil
@cols = attributes[:cols] ? attributes[:cols] : nil
@lines = attributes[:lines] ? attributes[:lines] : nil
#
super()
end
end # TextArea
class Button < Widget
attr_accessor :text
attr_accessor :event_pressed
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
# events
@event_pressed = attributes[:event_pressed] ? attributes[:event_pressed] : nil
#
super()
end
end # Button
class RadioBox < Widget
attr_accessor :value, :items
attr_accessor :event_change
def initialize(attributes={})
@value = attributes[:value] ? attributes[:value] : nil
@items = attributes[:content] ? attributes[:content] : nil
# events
@event_change = attributes[:event_change] ? attributes[:event_change] : nil
#
super()
end
end # RadioBox
class MenuBar < Widget
attr_accessor :items
def initialize(attributes={})
@items = attributes[:items] ? attributes[:items] : nil
#
super()
end
end # MenuBar
class Menu < Widget
attr_accessor :text, :items
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
@items = attributes[:items] ? attributes[:items] : nil
#
super()
end
end # Menu
class MenuItem < Widget
attr_accessor :text
attr_accessor :event_selected
def initialize(attributes={})
@text = attributes[:text] ? attributes[:text] : nil
# events
@event_selected = attributes[:event_selected] ? attributes[:event_selected] : nil
#
super()
end
end # MenuItem
end # GUtopIa
GUtopIa Example - Presentation
Copyright (c) 2002 Thomas Sawyer, Ruby License
require ‘gutopia’
app = GUtopIa::GUI.new(‘My Application’)
here you must refer to widgets with app.widgets[‘widget’s name’]
bindings = {
‘quit’ => {
:event_selected => Proc.new {
app.stop
}
},
‘about’ => {
:event_selected => Proc.new {
app.widgets[‘aboutWindow’].show
}
},
‘submit’ => {
:event_pressed => Proc.new {
app.alert(“And the fruit is…#{app.widgets[‘favoriteFruit’].text}”)
app.widgets[‘mainWindow’].hide
app.stop
}
},
‘clear’ => {
:event_pressed => Proc.new {
app.widgets[‘favoriteFruit’].text = nil
}
},
‘close’ => {
:event_pressed => Proc.new {
app.widgets[‘aboutWindow’].hide
}
}
}
here you can refer to widgets with local name
or the app.widgets[‘widget’s name’]
app.transaction(bindings) {
quit = app.widgetFactory(:MenuItem, ‘quit’,
:text => ‘Quit’
)
about = app.widgetFactory(:MenuItem, ‘about’,
:text => ‘About’
)
file = app.widgetFactory(:Menu, ‘file’,
:text => ‘File’,
:items => [ app.widgets[‘quit’] ]
)
help = app.widgetFactory(:Menu, ‘help’,
:text => ‘Help’,
:items => [ about ]
)
menu = app.widgetFactory(:MenuBar, ‘menu’,
:items => [ file, help ]
)
firstName = app.widgetFactory(:TextField, ‘firstName’,
:label => ‘First Name’,
:label_position => :left
)
lastName = app.widgetFactory(:TextField, ‘lastName’,
:label => ‘Last Name’,
:label_position => :left
)
favoriteFruit = app.widgetFactory(:TextArea, ‘favoriteFruit’,
:text => ‘Banana’,
:cols => 5,
:lines => 4,
:readonly => false,
:label => ‘Favorite Fruit’,
:label_position => :top
)
submit = app.widgetFactory(:Button, ‘submit’,
:text => ‘Update’,
:image => ‘check.png’
)
clear = app.widgetFactory(:Button, ‘clear’,
:text => ‘Clear’
)
fruit_panel = app.widgetFactory(:Panel, ‘fruit_panel’,
:grid => [ [ firstName ],
[ lastName ],
[ favoriteFruit ],
[ submit ],
[ clear ] ]
)
mainWindow = app.widgetFactory(:Window, ‘mainWindow’,
:caption => ‘My Cool Window’,
:icon => ‘myIcon.ico’,
:grid => [ [ menu ],
[ fruit_panel ] ]
)
aboutLbl = app.widgetFactory(:Label, ‘aboutLbl’,
:text => <<-DATA
This is a very cool application
that I created using the GUtopIa
GUI API.
DATA
)
close = app.widgetFactory(:Button, ‘close’,
:text => ‘Close’
)
aboutWindow = app.widgetFactory(:Window, ‘aboutWindow’,
:caption => ‘About My Cool App’,
:grid => [ [ aboutLbl ],
[ close ] ]
)
}
app.build
puts
puts “select about from menu”
app.widgets[‘about’].event_selected
puts “click on close”
app.widgets[‘close’].event_pressed
puts “click on submit”
app.widgets[‘submit’].event_pressed
puts
–
~transami