lib/ruiby_gtk/dsl/layouts.rb
# Creative Commons BY-SA : Regis d'Aubarede <regis.aubarede@gmail.com>
# LGPL
module Ruiby_dsl
############################ Slot : H/V Box or Frame
# container : vertical box, take all space available, sloted in parent by default
def stack(config={},add1=true,&b) _cbox(true,Box.new(:vertical, 2),config,add1,&b) end
# container : horizontal box, take all space available, sloted in parent by default
def flow(config={},add1=true,&b) _cbox(true,Box.new(:horizontal, 2),config,add1,&b) end
# container : vertical or horizontal box (stack/flow, choice by first argument),
# sloted in parent by default
def var_box(sens,config={},add1=true,&b) _cbox(true,Box.new(sens, 2),config,add1,&b) end
# container : vertical box, take only necessary space , sloted in parent
def stacki(config={},add1=true,&b) _cbox(false,Box.new(:vertical, 2),config,add1,&b) end
# container : horizontal box, take only necessary space , sloted in parent
def flowi(config={},add1=true,&b) _cbox(false,Box.new(:horizontal, 2),config,add1,&b) end
# container : vertical or horizontal box (stacki/flowi, choice by first argument),
# sloted in parent by default
def var_boxi(sens,config={},add1=true,&b) _cbox(false,Box.new(sens, 2),config,add1,&b) end
# container manage children has sentence flow widgets
def sentence(config={},add=true,&b) _cbox(true,FlowBox.new(),config,add,&b) end
def sentenci(config={},add=true,&b) _cbox(false,FlowBox.new(),config,add,&b) end
# box { } container which manage children widget without slot (pack())
# in parent container.
# Use it for cell in table, notebook : table { row { cell(box { });... }; ... }
def box(sens=:vertical)
box=Gtk::Box.new(sens,2)
_set_accepter(box,:layout,:widget)
@lcur << box
yield
autoslot()
@lcur.pop
end
def current_layout() @lcur.last end
# mock class which can be push to layout stack : they accept some
# specific type of commands
class HandlerContainer
def accept?(t)
raise("no widget accepted here : #{self.class}") unless t==:handler
end
end
# add a accept?() method to a layout (box). so children
# will check if they can be added to current layout by invoke
# current_layout=@lcur.last
# current_layout.accept?( <type-children> )
def _set_accepter(layout,*types)
if types.size==1
layout.define_singleton_method(:accept?) do |type|
raise("No command #{type} accepted here, accept=#{types}/") unless types.first==type
end
elsif types.size==2
layout.define_singleton_method(:accept?) do |type|
raise("No command #{type} accepted here, accept=#{types}/") unless types.first==type || types.last==type
end
else
layout.define_singleton_method(:accept?) do |type|
raise("No command #{type} accepted here, accept=#{types}/") unless types.any? { |a| a==type }
end
end
end
def _accept?(type)
w=@lcur.last
w.respond_to?(:accept?) ? w.accept?(type) : true
end
################################ Some other layouts
# set homogeneous contrainte on current container :
# all chidren whill have same size
# * stack : children will have same height
# * flow : children will have same width
def regular(on=true) @lcur.last.homogeneous=on end
# set space between each chidren of current box
def spacing(npixels=0) @lcur.last.spacing=npixels end
# center { } container which center his content (auto-sloted)
# TODO : tested!
def center()
autoslot()
valign = Gtk::Alignment.new(0,0,0,0)
@lcur.last.pack_start(valign, :expand => true, :fill => false, :padding => 0)
vbox=Box.new(:vertical, 0)
_set_accepter(vbox,:layout,:widget)
valign.add(vbox)
@lcur << vbox
yield
autoslot()
@lcur.pop
end
# TODO : not tested!
def left(&blk)
autoslot()
w=yield
halign = Gtk::Alignment.new(0,0,0,0)
halign.add(w)
@lcur.last.pack_start(halign, :expand => false, :fill => false, :padding => 3)
razslot()
end
# TODO : not tested!
def right(&blk)
autoslot()
w=yield
halign = Gtk::Alignment.new(1,0,0,0)
halign.add(w)
@lcur.last.pack_start(halign, :expand => false, :fill => false, :padding => 3)
razslot()
end
def update() Ruiby.update() end
# a box with border and texte title, take all space
def frame(t="",config={},add1=true,&b)
w=_cbox(true,Frame.new(t),config,add1) { s=stack { b.call } ; s.set_border_width(5) }
end
# a box with border and texte title, take only necessary space
def framei(t="",config={},add1=true,&b)
_cbox(false,Frame.new(t),config,add1) { s=stacki { b.call } ; s.set_border_width(5) }
end
# private: generic packer
def _cbox(expand,box,config,add1)
autoslot() # pack last widget before append new bow
parent=@lcur.last
if add1
_pack(parent,box,expand)
end
_set_accepter(box,:layout,:widget)
@lcur << box
yield
autoslot() # pack last widget before closing box
@lcur.pop
apply_options(box,config)
end
def _pack(parent,box,expand)
parent.respond_to?(:pack_start) ?
parent.pack_start(box, :expand => expand, :fill => true):
parent.add(box)
end
# pack widget in parameter, share space with prother widget
# this is the default: all widget will be sloted if they are not slotied
# this is done by attribs(w) which is call after construction of almost all widget
def slot(w) @current_widget=nil; _pack(@lcur.last,w,true) ; w end
# pack widget in parameter, take only necessary space
def sloti(w) @current_widget=nil; @lcur.last.pack_start(w, :expand => false, :fill => false, :padding => 0) ; w end
# slot() precedently created widget if not sloted.
# this is done by attribs(w) which is call after construction of almost all widget
def autoslot(w=nil)
(slot(@current_widget)) if @current_widget!=nil
@current_widget=w
end
# forget precedent widget oconstructed
def razslot() @current_widget=nil; end
# set a background color to current container
# Usage : stack { background("#FF0000") { flow { ...} } }
def background(color,options={},&b)
_accept?(:layout)
eventbox = Gtk::EventBox.new
ret=_cbox(true,eventbox,{},true,&b)
apply_options(eventbox,{bg: color}.merge(options))
ret
end
def backgroundi(color,options={},&b)
_accept?(:layout)
eventbox = Gtk::EventBox.new
ret=_cbox(false,eventbox,{},true,&b)
apply_options(eventbox,{bg: color}.merge(options))
ret
end
######### Scrollable stack container
# create a Scrolled widget with a autobuild stack in it
# stack can be populated
# respond to : scroll_to_top; scroll_to_bottom,
def scrolled(width,height,&b) vbox_scrolled(width,height,&b) end
def vbox_scrolled(width,height,&b)
sw=ScrolledWindow.new()
slot(sw)
sw.set_width_request(width) if width>0
sw.set_height_request(height) if height>0
sw.set_policy(:automatic, :always)
ret= box(&b) if block_given?
sw.add_with_viewport(ret)
class << sw
; def scroll_to_top() vadjustment.set_value( 0 ) ; vadjustment.value_changed ; end
; def scroll_to_bottom() vadjustment.set_value( vadjustment.upper - 100); vadjustment.value_changed ; end
#def scroll_to_left() hadjustment.set_value( 0 ) end
#def scroll_to_right() hadjustment.set_value( hadjustment.upper-1 ) end
end
attribs(sw,{})
end
###################################### notebooks
# create a notebook widget. it must contain page() widgets
# notebook { page("first") { ... } ; ... }
# nb.page=<no page> => active no page
def notebook()
nb = Notebook.new()
slot(nb)
_set_accepter(nb,:tab)
@lcur << nb
yield
@lcur.pop
nb
end
# a page widget. only for notebook container.
# button can be text or icone (if startin by '#', as label)
def page(title,icon=nil)
_accept?(:tab)
l=Image.new(pixbuf: get_pixmap(icon[1..-1])) if icon
l=Image.new(pixbuf: get_pixmap(title[1..-1])) if title && title[0,1]=="#"
l=Label.new(title) if title.size>1 && title[0,1]!="#"
@lcur.last.append_page( box { yield }, l )
end
############################## Accordion
# create a accordion menu.
# must contain aitem() which must containe alabel() :
# accordion { aitem(txt) { alabel(lib) { code }; ...} ... }
def accordion()
_accept?(:layout)
@slot_accordion_active=nil #only one accordion active by window!
w=stack { stacki {
_set_accepter(@lcur.last,:aitem,:layout,:widget)
yield
}}
separator
w
end
# create a horizontral accordion menu.
# must contain aitem() which must containe alabel() :
# accordion { aitem(txt) { alabel(lib) { code }; ...} ... }
def haccordion()
_accept?(:layout)
@slot_accordion_active=nil #only one accordion active by window!
w=flow { flowi {
_set_accepter(@lcur.last,:aitem,:layout,:widget)
yield
}}
separator
w
end
# a button menu in accordion
# bloc is evaluate for create/view a list of alabel :
# aitem(txt) { alabel(lib) { code }; ...}
def aitem(txt,&blk)
_accept?(:aitem)
b2=Gtk::Box.new(:vertical,2)
_set_accepter(b2,:alabel,:layout,:widget)
b=button(txt) {
clear_append_to(@slot_accordion_active) {} if @slot_accordion_active
@slot_accordion_active=b2
clear_append_to(b2) { blk.call() }
slot_append_after(b2,b)
}
end
# create a button-entry in a accordion menu
# bloc is evaluate on user click. must be in aitem() bloc :
# accordion { aitem(txt) { alabel(lib) { code }; ...} ... }
def alabel(txt,&blk)
_accept?(:alabel)
l=nil
pclickable(proc { blk.call(l) if blk} ) { l=label(txt) }
end
############################## Panned :
# create a container which can containe 2 widgets, separated by movable bar
# block invoked must create 2 widgets, vertivaly disposed
def stack_paned(size,fragment,&blk) _paned(false,size,fragment,&blk) end
# create a container which can containe 2 widgets, separated by movable bar
# block invoked must create 2 widgets,horizonaly disposed
def flow_paned(size,fragment,&blk) _paned(true,size,fragment,&blk) end
def _paned(horizontal,size,fragment)
s=stack {} # create a temporary container for inner widgets
@lcur << s
yield()
autoslot
@lcur.pop
raise("panned : must contain only 2 children") if s.children.size!=2
frame1,frame2=*s.children
(frame1.shadow_type = :in) rescue nil
(frame2.shadow_type = :in) rescue nil
paned = Paned.new(horizontal ? :horizontal : :vertical)
paned.position=size*fragment
horizontal ? paned.set_size_request(size, -1) : paned.set_size_request(-1,size)
s.remove(frame1)
s.remove(frame2)
s.parent.remove(s)
paned.pack1(frame1, :resize => true, :shrink => false)
paned.pack2(frame2, :resize => true, :shrink => false)
slot(paned)
end
#a button which show a sub-frame on action
def button_expand(text,initiale_state=false,options={},&b)
expander = Gtk::Expander.new(text)
expander.expanded = initiale_state
frame=box(&b)
expander.add(frame)
attribs(expander,options)
end
######################## Dialog ##################
# Dialog content is build with bloc parameter.
# Action on Ok/Nok/delete button make a call to :response bloc.
# dialog is destoy if return value of :response is true
#
# dialog_async("title",:response=> bloc {|dia,e| }) {
# flow { button("dd") ... }
# }
def dialog_async(title,config={},&b)
dialog = Dialog.new(
title: title,
parent: self,
buttons: [[Gtk::Stock::OK, :accept],
[Gtk::Stock::CANCEL, :reject]]
)
dialog.set_window_position(:center) if ! config[:position]
@lcur << dialog.child
hbox=stack { yield(dialog) }
@lcur.pop
Ruiby.apply_provider(dialog.child)
if config[:response]
dialog.signal_connect('response') do |w,response_id|
rep=if response_id == Gtk::ResponseType::ACCEPT
config[:response].call(dialog,response_id)
else
true
end
dialog.destroy if rep
end
else
dialog.signal_connect('response') { |w,response_id| dialog.destroy }
end
dialog.show_all
end
# panel_async: a dialog without button
def panel_async(title)
dialog = Dialog.new(
title: title,
parent: self,
buttons: []
)
dialog.set_window_position(:center)
@lcur << dialog.child
stack { yield(dialog) }
@lcur.pop
Ruiby.apply_provider(dialog.child)
dialog.show_all
end
# Arm a callback on window/dialog closed
# must return false for close the dialog
# dialog { stack {.... ; on_delete { @isclosed=true ; false} } }
def on_delete(&b)
@lcur.last.toplevel.signal_connect("delete_event") { |w,e| b.call }
end
# Dialog contents is build with bloc parameter.
# call is bloced until action on Ok/Nok/delete button
# return true if dialog quit is done by action on OK button
#
# dialog("title") {
# flow { button("dd") ... }
# }
def dialog(title="")
dialog = Dialog.new(
title: title,
parent: self,
buttons: [[Gtk::Stock::OK, :accept],
[Gtk::Stock::CANCEL, :reject]]
)
@lcur << dialog.child
hbox=stack { yield(dialog) }
@lcur.pop
dialog.set_window_position(:center)
Ruiby.apply_provider(dialog.child)
dialog.show_all
rep=dialog.run # blocked
dialog.destroy
rep==-3
end
def panel(title="")
dialog = Dialog.new(
title: title,
parent: self,
buttons: []
)
ctx={}
@lcur << dialog.child
hbox=stack { yield(dialog,ctx) }
@lcur.pop
dialog.set_window_position(:center)
Ruiby.apply_provider(dialog.child)
dialog.show_all
rep=dialog.run # blocked
dialog.destroy
ctx
end
# a dialog without default buttons
# can be synchrone (block the caller until wndow destroyed)
def window(title="",sync=false)
dialog = Dialog.new(
title: title,
parent: self,
buttons: []
)
@lcur << dialog.child
hbox=stack { yield(dialog) }
@lcur.pop
dialog.set_window_position(:center)
Ruiby.apply_provider(dialog.child)
dialog.show_all
if sync
rep=dialog.run # blocked
dialog.destroy
end
end
end