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