Layout management in Ruby GTK

In this chapter, we show how to lay out our widgets on windows or dialogs.

When we design the GUI of our application, we decide what widgets we use and how we organise those widgets in the application. To organise our widgets, we use specialised non-visible widgets called layout containers. In this chapter, we mention Gtk::Alignment, Gtk::Fixed, Gtk::VBox and Gtk::Grid.

Gtk::Fixed

The Gtk::Fixed container places child widgets at fixed positions and with fixed sizes. This container performs no automatic layout management. In most applications, we do not use this container. There are some specialised areas where we use it, for example games, applications that work with diagrams, resizable components that can be moved (like a chart in a spreadsheet application), small educational examples.

#!/usr/bin/ruby

'''
ZetCode Ruby GTK tutorial

In this program, we lay out widgets
using absolute positioning.

Author: Jan Bodnar
Website: www.zetcode.com
Last modified: May 2014
'''

require 'gtk3'

class RubyApp < Gtk::Window

    def initialize
        super
    
        init_ui
    end
    
    def init_ui

        override_background_color :normal, Gdk::RGBA::new(0.2, 0.2, 0.2, 1)
               
        begin       
            bardejov = Gdk::Pixbuf.new :file => "bardejov.jpg"
            rotunda = Gdk::Pixbuf.new :file => "rotunda.jpg"
            mincol = Gdk::Pixbuf.new :file => "mincol.jpg"
        rescue IOError => e
            puts e
            puts "cannot load images"
            exit
        end

        image1 = Gtk::Image.new :pixbuf => bardejov 
        image2 = Gtk::Image.new :pixbuf => rotunda 
        image3 = Gtk::Image.new :pixbuf => mincol 
        
        fixed = Gtk::Fixed.new
        
        fixed.put image1, 20, 20
        fixed.put image2, 40, 160
        fixed.put image3, 170, 50
  
        add fixed
        
        set_title "Fixed"
        signal_connect "destroy" do 
            Gtk.main_quit 
        end        
        
        set_default_size 300, 280
        window_position = :center
        
        show_all        
    end
end

Gtk.init
    window = RubyApp.new
Gtk.main

In our example, we show three small images on the window. We explicitly specify the x, y coordinates at which we place these images.

override_background_color :normal, Gdk::RGBA::new(0.2, 0.2, 0.2, 1)

For better visual experience, we change the background colour to dark gray.

bardejov = Gdk::Pixbuf.new :file => "bardejov.jpg"

We load the image from the disk to the Gtk::Pixbuf object.

image1 = Gtk::Image.new :pixbuf => bardejov 
image2 = Gtk::Image.new :pixbuf => rotunda 
image3 = Gtk::Image.new :pixbuf => mincol 

The Gtk::Image is a widget that is used to display images. It takes a Gdk::Pixbuf object in the constructor.

fixed = Gtk::Fixed.new

We create the Gtk::Fixed container.

fixed.put image1, 20, 20

We place the first image at x=20, y=20 coordinates.

add fixed

Finally, we add the Gtk::Fixed container to the window.

Gtk::Fixed
Figure: Gtk::Fixed

Buttons

The Gtk::Alignment container controls the alignment and the size of its child widget.

#!/usr/bin/ruby

'''
ZetCode Ruby GTK tutorial

In this program, we position two buttons
in the bottom right corner of the window.
We use horizontal and vertical boxes.

Author: Jan Bodnar
Website: www.zetcode.com
Last modified: May 2014
'''

require 'gtk3'


class RubyApp < Gtk::Window

    def initialize
        super
    
        init_ui
    end
    
    def init_ui
        
        set_border_width 10
        
        vbox = Gtk::Box.new :vertical, 0
        hbox = Gtk::Box.new :horizontal, 5
        
        e_space = Gtk::Alignment.new 0, 0, 0, 0
        vbox.pack_start e_space, :expand => true
        
        ok_btn = Gtk::Button.new :label => "OK"
        ok_btn.set_size_request 70, 30
        close_btn = Gtk::Button.new :label => "Close"
        close_btn.set_size_request 70, 30
        
        hbox.add ok_btn
        hbox.add close_btn 
        
        halign = Gtk::Alignment.new 1, 0, 0, 0
        halign.add hbox
        
        vbox.pack_start halign, :expand => false, 
            :fill => false, :padding => 5

        add vbox
        
        set_title "Buttons"
        signal_connect "destroy" do 
            Gtk.main_quit 
        end        
        
        set_default_size 260, 150
        set_window_position :center
        
        show_all        
    end
end

Gtk.init
    window = RubyApp.new
Gtk.main

In the code example, we place two buttons into the right bottom corner of the window. To accomplish this, we use one horizontal box, one vertical box and two alignment containers.

set_border_width 10

The set_border_width sets some empty space around the borders of the Gtk::Window container widget. It is important for our example because the close button will not be too close to the right edge of the window.

vbox = Gtk::Box.new :vertical, 0
hbox = Gtk::Box.new :horizontal, 5

A vertical and horizontal boxes are created. The vertical box serves as the base container for our window. Empty space and a horizontal box containing two button widgets are placed into the vertical box.

e_space = Gtk::Alignment.new 0, 0, 0, 0
vbox.pack_start e_space, :expand => true

The Gtk::Alignment widget is used as empty filler. It will push the buttons to the bottom of the window. The :expand parameter will cause the Gtk::Alignment widget consume all extra space allocated to vertical box.

hbox = Gtk::Box.new :horizontal, 5
...
ok_btn = Gtk::Button.new :label => "OK"
ok_btn.set_size_request 70, 30
close_btn = Gtk::Button.new :label => "Close"
close_btn.set_size_request 70, 30

hbox.add ok_btn
hbox.add close_btn         

We create a horizontal box and put two buttons inside it. The second parameter of the Gtk::Box is the amount of spacing between children.

halign = Gtk::Alignment.new 1, 0, 0, 0
halign.add hbox

vbox.pack_start halign, :expand => false, 
    :fill => false, :padding => 5

This will create an alignment container that will place its child widget to the right. The first parameter of the Gtk::Alignment container is a horizontal alignment. A value of 1 will push its child (a horizontal box containing two buttons) to the right. The alignment container takes only one child widget—we had to use a horizontal box.

Buttons
Figure: Buttons

Calculator skeleton

This example creates a skeleton of a calculator with the help of the Gtk::Box and Gtk::Grid widgets.

#!/usr/bin/ruby

'''
ZetCode Ruby GTK tutorial

In this program we create a skeleton of
a calculator. We use a Gtk::Grid widget
and a vertical Gtk::Box.

Author: Jan Bodnar
Website: www.zetcode.com
Last modified: May 2014
'''

require 'gtk3'


class RubyApp < Gtk::Window

    def initialize
        super
    
        init_ui
    end
    
    def init_ui
    
        vbox = Gtk::Box.new :vertical, 2
        
        mb = Gtk::MenuBar.new
        filemenu = Gtk::Menu.new
        file = Gtk::MenuItem.new "File"
        file.set_submenu filemenu
        mb.append file

        vbox.pack_start mb, :expand => false, :fill => false, 
            :padding => 0
            
        vbox.pack_start Gtk::Entry.new, :expand => false, 
            :fill => false, :padding => 0            

        grid = Gtk::Grid.new
        grid.set_property "row-homogeneous", true
        grid.set_property "column-homogeneous", true
         
        grid.attach Gtk::Button.new(:label => "Cls"), 0, 0, 1, 1
        grid.attach Gtk::Button.new(:label => "Bck"), 1, 0, 1, 1
        grid.attach Gtk::Label.new, 2, 0, 1, 1
        grid.attach Gtk::Button.new(:label => "Close"), 3, 0, 1, 1

        grid.attach Gtk::Button.new(:label => "7"), 0, 1, 1, 1
        grid.attach Gtk::Button.new(:label => "8"), 1, 1, 1, 1
        grid.attach Gtk::Button.new(:label => "9"), 2, 1, 1, 1
        grid.attach Gtk::Button.new(:label => "/"), 3, 1, 1, 1

        grid.attach Gtk::Button.new(:label => "4"), 0, 2, 1, 1
        grid.attach Gtk::Button.new(:label => "5"), 1, 2, 1, 1
        grid.attach Gtk::Button.new(:label => "6"), 2, 2, 1, 1
        grid.attach Gtk::Button.new(:label => "*"), 3, 2, 1, 1

        grid.attach Gtk::Button.new(:label => "1"), 0, 3, 1, 1
        grid.attach Gtk::Button.new(:label => "2"), 1, 3, 1, 1
        grid.attach Gtk::Button.new(:label => "3"), 2, 3, 1, 1
        grid.attach Gtk::Button.new(:label => "-"), 3, 3, 1, 1

        grid.attach Gtk::Button.new(:label => "0"), 0, 4, 1, 1
        grid.attach Gtk::Button.new(:label => "."), 1, 4, 1, 1
        grid.attach Gtk::Button.new(:label => "="), 2, 4, 1, 1
        grid.attach Gtk::Button.new(:label => "+"), 3, 4, 1, 1
        
        vbox.pack_start grid, :expand => true, :fill => true, 
            :padding => 0
        
        add vbox
        
        set_title "Calculator"
        signal_connect "destroy" do 
            Gtk.main_quit 
        end        

        set_default_size 300, 250
        set_window_position :center
        
        show_all        
    end
end

Gtk.init
    window = RubyApp.new
Gtk.main

The Gtk::Grid widget arranges widgets in rows and columns.

vbox = Gtk::Box.new :vertical, 2

The Gtk::Box serves as the base container for our application. The box's orientation is vertical and there is 2px space between its children (menubar, entry and grid widgets). Since it is a vertical box, the space is placed vertically between the widgets.

mb = Gtk::MenuBar.new
filemenu = Gtk::Menu.new
file = Gtk::MenuItem.new "File"
file.set_submenu filemenu
mb.append file

vbox.pack_start mb, :expand => false, :fill => false, 
    :padding => 0

A Gtk::MenuBar with one menu is created. It is placed inside the vertical box.

vbox.pack_start Gtk::Entry.new, :expand => false, 
    :fill => false, :padding => 0    

A Gtk::Entry is placed below the menubar. We set the :expand parameter to false because we do not want to expand the entry widget vertically. A widget put in a vertical box is stretched from left to right. If we wanted to change this, we would need an additional horizontal box.

grid = Gtk::Grid.new

The Gtk::Grid container is created.

grid.set_property "row-homogeneous", true
grid.set_property "column-homogeneous", true

We set the row and column homogeneous properties to true. This will cause all the children to have the same size.

grid.attach Gtk::Button.new(:label => "Cls"), 0, 0, 1, 1

We attach a button to the grid container, to its top-left cell. The first two parameters are the column and row indexes. The last two arguments are the column and row spans. All our widgets inside the grid occupy one cell.

vbox.pack_start grid, :expand => true, :fill => true, 
    :padding => 0

We pack the grid widget into the vertical box. The combination of the :expand and the :fill options will make the grid widget and its children occupy the bulk of the window's area.

add vbox

The vertical box is placed inside the Gtk::Window container.

Calculator skeleton
Figure: Calculator skeleton

Windows

In the last example, we will use the Gtk::Grid container. This container places its children into cells, which are delimited by the intersections of rows and columns. The attach method of the grid container takes five arguments. The first argument is the child widget attached. The next two arguments are the row and column indexes at which the child is placed. The last two parameters are the row spand and column span.

#!/usr/bin/ruby

'''
ZetCode Ruby GTK tutorial

This is a more complicated layout example.
We use Gtk::Alignment, Gtk::Box, and Gtk::Grid widgets.

Author: Jan Bodnar
Website: www.zetcode.com
Last modified: May 2014
'''

require 'gtk3'


class RubyApp < Gtk::Window

    def initialize
        super
        
        init_ui
    end
    
    def init_ui
    
        set_border_width 15
                
        grid = Gtk::Grid.new 
        grid.set_column_spacing 5
        grid.set_row_spacing 5

        title = Gtk::Label.new "Windows"

        align1 = Gtk::Alignment.new 0, 0, 0, 0
        align1.add title
        
        grid.attach align1, 0, 0, 1, 1 
                    
        frame = Gtk::Frame.new
        frame.set_hexpand true
        frame.set_vexpand true
        grid.attach frame, 0, 1, 3, 3

        vbox = Gtk::Box.new :vertical, 4
        act_btn = Gtk::Button.new :label => "Activate"
        act_btn.set_size_request 70, 30

        close_btn = Gtk::Button.new :label => "Close"
        close_btn.set_size_request 70, 30
            
        vbox.add act_btn
        vbox.add close_btn
        grid.attach vbox, 3, 1, 1, 1
            
        help_btn = Gtk::Button.new :label => "Help"
        help_btn.set_size_request 70, 30
        align2 = Gtk::Alignment.new 0, 0, 0, 0
        align2.add help_btn        
        grid.attach align2, 0, 4, 1, 1
        
        ok_btn = Gtk::Button.new :label => "OK" 
        ok_btn.set_size_request 70, 30
        grid.attach ok_btn, 3, 4, 1, 1

        add grid
    
        set_title "Windows"
        signal_connect "destroy" do 
            Gtk.main_quit 
        end

        set_default_size 350, 300
        set_window_position :center
        
        show_all        
    end
end

Gtk.init
    window = RubyApp.new
Gtk.main

The code creates a real world window in Ruby GTK.

grid = Gtk::Grid.new 
grid.set_column_spacing 5
grid.set_row_spacing 5

The instance of the Gtk::Grid container is created. This container packs widgets into rows and columns. We set some space between rows and columns.

title = Gtk::Label.new "Windows"

align1 = Gtk::Alignment.new 0, 0, 0, 0
align1.add title

grid.attach align1, 0, 0, 1, 1 

We create a label widget. This widget is place into the Gtk::Alignment widget in order to align it to the left of the space alotted to the label. The attach method of the grid container places the label into its top-left cell. The label will occupy one cell.

frame = Gtk::Frame.new
frame.set_hexpand true
frame.set_vexpand true
grid.attach frame, 0, 1, 3, 3

The frame widget is placed at column=0 and row=1. It spans three rows and tree columns. The set_hexpand and set_vexpand methods set the widget to take any available extra horizontal and vertical space. When the window grows, the frame widget grows; other widgets retain their size.

vbox = Gtk::Box.new :vertical, 4
act_btn = Gtk::Button.new :label => "Activate"
act_btn.set_size_request 70, 30

close_btn = Gtk::Button.new :label => "Close"
close_btn.set_size_request 70, 30
    
vbox.add act_btn
vbox.add close_btn
grid.attach vbox, 3, 1, 1, 1

Two buttons are created and placed into a vertical box. To box is placed next to the frame widget.

help_btn = Gtk::Button.new :label => "Help"
help_btn.set_size_request 70, 30
align2 = Gtk::Alignment.new 0, 0, 0, 0
align2.add help_btn        
grid.attach align2, 0, 4, 1, 1

The help button is placed inside the alignment container which aligns it to the left of the cell set aside by the grid container. The earlier call of the set_hexpand method made the frame widget growable; it also affects widgets in the column that are occupied by the frame widget. Therefore, we need to use the Gtk::Alignment widget to keep the button's size unchanged and align it to the left.

Windows
Figure: Windows

In this part of the Ruby GTK tutorial, we mentioned layout management of widgets.