A DSL for building simple GUI ruby application. Based on gtk.
Code: http://github.com/glurp/Ruiby
Doc: Reference+Exemples.
Gem : https://rubygems.org/gems/Ruiby
Based on Ruby-Gmome2 :
NEW : 3.24.0 !! 28-11-2017 : Linear Gradient on canvas draw surfaces primitives
Now, plotters and dashboard can have beautiful background :)
TO DO :
- improve graphics quality on canvas : linear gradient
- improve graphics quality on canvas : radial gradient
- improve graphics quality on canvas : transparency
- refactoring samples demos with last improve: dynvar/autoslot...
- resolve 100% gtk3 deprecated warning
- complete rspec => 99% coverage ?
Abandoned :
- gadget API
- system
Install Ruby 2.x # x>1
- install Ruiby
(
gem install Ruiby
will install ruby-gtk3 which install gtk3 libs)
> gem install Ruiby
Test it:
> ruiby_demo # check good installation with gtk3 (default)
> ruiby_sketchi # write and test ruiby code
Here a working gem config on windows (25-Sept-2016, Ruby 2.3.3p222) :
did_you_mean-1.0.0
pkg-config-1.2.3
native-package-installer-1.0.4
cairo-1.15.9
glib2-3.1.8
gobject-introspection-3.1.8
gio2-3.1.8
atk-3.1.8
cairo-gobject-3.1.8
pango-3.1.8
gdk_pixbuf2-3.1.8
gdk3-3.1.8
rsvg2-3.1.8
gtk3-3.1.8
Ruiby-3.23.0
gtksourceview3-3.1.8
DSL is usable via inherit, include, Ruiby.app bloc, or one-liner command.
By inherit:
class Application < Ruiby_gtk
def initialize(t,w,h)
super(t,w,h)
end
def component()
stack do
...
end
end
.....your code....
end
Ruiby.start { Win.new("application title",350,10) }
By include, calling ruiby-component() :
class Win < Gtk::Window
include Ruiby
def initialize(t,w,h)
super()
add(@vb=VBox.new(false, 2))
....
end
def add_a_ruiby_button()
ruiby_component do
append_to(@vb) do
button("Hello Word #{@vb.children.size}") {
add_a_ruiby_button()
}
end
end
end
end
Ruiby.start { Win.new("application title",350,10) }
Autonomous DSL, for little application (most of demo in samples/ are done with this pattern) :
require 'Ruiby'
Ruiby.app do
stack do
. . .
end
end
And, for very little application ('~' are replaced by guillemet):
> ruiby button(~Continue ? ~) "{ exit!(0) }"
> ruiby fields([%w{a b},%w{b c},%w{c d}]) { "|a,b,c|" p [a,b,c] if a; exit!(a ?0:1) }
> ruiby -width 100 -height 300 -title "Please, select a file" \
l=list(~Files :~);l.set_data Dir.glob(~*~) ; \
buttoni(~Selected~) { puts l.selection ; exit!(0) } ;\
buttoni(~Annul~) { exit!(1) }
Simple usage with gtk3 :
require 'Ruiby'
Usage with Event Machine: load event-machine before Ruiby :
require 'em-proxy'
require 'Ruiby'
Warning : EM.run is done when starting mainloop, after creation of window(s). So, if you need initialization of event-machine callback, do it in component(), in a after(0):
Ruiby.app do
....
after(0) { EventMachine::start_server().. { ... } }
end
See samples/spygui.rb, for example of GUI with EM.
Ruiby does not have confidence in gtk multi threading, so all Ruiby commands must be done in main thread context. A Ruiby delegate is provided in Kernel module for support multi-threading
A Queue is polled by main-window thread :
- main window poll Queue , messages are proc to be instance_eval() in the main window context
- everywhere, a thread can invoke
invoke_gui {ruiby code}
. this send to the main queue the proc, which will be evaluated asynchronously
instance_eval is avoided in ruiby. He is used only for thread invoker : gui_invoke().
require_relative '../lib/Ruiby'
class App < Ruiby_gtk
def initialize
super("Testing Ruiby for Threading",150,0)
threader(10)
Thread.new { A.new.run }
end
def component()
stack do
sloti(label("Hello, this is Thread test !"))
stack { @lab=stacki { } }
end
end # endcomponent
end
class A
def run
loop do
sleep(1) # thread...
there=self
gui_invoke { append_to(@lab) { sloti(
label( there.aaa ) # ! instance_eval on main window
) } }
end
end
def aaa() Time.now.to_s end
end
Ruiby.start { App.new }
Often, a widget (an entry, a label, a slider...) show the value of a ruby variable. each time a code modify this variable, it must modify the widget, and vice-versa... This is very tiring :)
With data binding, this notifications are done by the framework
So DynVar
can be used for representing a value variable which is dynamics, ie.
which must notify widgets which show the variable state.
So we can do :
foo=DynVar.new(0)
entry(foo)
islider(foo)
....
foo.value=43
....
That works ! the entry and the slider will be updated.
A move on slider will update foo.value and the entry. Idem for a key in the entry : slider and foo.value will be updated.
if you want to be notified for your own treatment, you can observe a DynVar :
foo.observ { |v| @socket.puts(v.to_s) rescue nil }
Here, a modification of foo variable will be send on the network...
Warning !! the block will always be executed in the main thread context (mainloop gtk context). So DynVar is a resource internal to Ruiby framework.
Widget which accept DynVar are : entry, ientry, islider, label, check_button,
must be extend to button, togglebutton, combo, radio_button ... list, grid,...
Often, this kind of Dyn variables are members of a 'record', which should be organized by an Ruby Object (a Struct...)
So DynObject
create a class, which is organized by a hash :
- packet of variable name
- put initial value for each
- each variable will be a DynVar
FooClass=make_DynClass("v1" => 1 , "v2" => 2, "s1" => 'Hello...')
foo=FooClass.new( "s1" => Time.now.to_s ) # default value of s1 variable is replaced
...
label(" foo: ") ; entry(foo.s1)
islider(foo.v1)
islider(foo.v2)
....
button("4x33") { Thread.new { foo.s1.value="s4e33" ; foo.v2.value=33 ; foo.v1.value=4} }
....
DynObject can be persisted to file system : use make_StockDynObject
, and
instantiate with an object persistent ID
FooClass=make_StockDynClass("v1"=> 1 , "v2" => 2, "s1" => 'Hello...')
foo1=FooClass.new( "foo1" , "s1" => Time.now.to_s )
foo2=FooClass.new( "foo2" , "s1" => (Time.now+10).to_s )
....
button("Exit") { ruiby_exit} # on exit, foo1 and foo2 will been saved to {tmpdir}/<$0>.storage
....
make_StockDynObject
do both : Class creation and class instantiation.
foo=make_StockDynObject("v1"=> 1 , "v2" => 2, "s1" => 'Hello...')
....
button(foo.s1) { foo.s1.value= prompt("new S1 value ?")}
button("Exit") { ruiby_exit} # on exit, foo1 and foo2 will been saved to {tmpdir}/<$0>.storage
....
Ruiby is not really object-orented : most of DSL words are simple method in Ruby_dsl module.
Sometime, this is not good enough :
- when a component must have many specific methods
- when component have (model) state : variable member must be used
So Component concept has been added (Fev 2016).It authorize to define a class, child of AbstractComponent, which can be used by a DSL Word.
Components code seem very close to a Ruiby window : free constructor,
define component()
method for draw the widgets
Create a component:
class AAA < AbstractComposant
def initialize(name)
@name= name
@state=1
end
def component()
framei("Component Comp:#{@name}") do
label_clickable("B#{@name}...") { @state=2 }
entry(@name,4)
end
end
def get_state() @state end
end
Define a word which instantiate a component of class AAA:
module Ruiby_dsl
def aaa(*args)
c=install_composant(self,AAA.new(*args))
end
end
Use the component:
c=nil
stack {
c=aaa "foo"
flowi { aaa 1; aaa 2 }
}
button("?") { alert( c.get_state() ) }
A demo is at samples/composant.rb
.
TO-DO:
- Canvas and Plot must be converted to Component, soon :)
- Define
destroy()
- Hook for auto-generate DSL word
- Test Stock, Dynvar, threading,
- Tests, tests, test...
Ruiby : LGPL, CC BY-SA
fafamfam rasters images : CC Attribution 4.0 http://www.famfamfam.com/
Crystal Clear icon set : LGPL
Farm Fresh icon set : CC Attribution 3.0 License http://www.fatcow.com/free-icons
See samples in "./samples" directory (run all.rb) See at end of Doc reference : Ex.