Project

Ruiby

0.01
No commit activity in last 3 years
No release in over 3 years
A DSL for building simple GUI ruby application, based on Gtk.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

Ruiby

Build Status Gem Version AppVeyor

A DSL for building simple GUI ruby application. Based on gtk.

Resources

Code: http://github.com/glurp/Ruiby

Doc: Reference+Exemples.

Gem : https://rubygems.org/gems/Ruiby

Based on Ruby-Gmome2 :

Status

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

Installation

  1. system

Install Ruby 2.x # x>1

  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

Usage

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

Require

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.

Threading

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 }

Observed Object/Variable

Dynamic variable

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,...

Dynamic Object

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} }
  ....

Dynamic Stock Object

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  
  ....

Component

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...

License

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

Exemples

See samples in "./samples" directory (run all.rb) See at end of Doc reference : Ex.