1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
|
# Ruby/GTK4 getting started
This is a ruby adaptation of the [official tutorial](https://docs.gtk.org/gtk4/getting_started.html) for the C language.
The Gtk4 module of the ruby-gnome project is an implementation of the ruby bindings for GTK.
GTK is a [widget toolkit](http://en.wikipedia.org/wiki/Widget_toolkit). Each user interface created by GTK consists of widgets. Widgets are organized in a hierarchy. The window widget is the main container. The user interface is then built by adding buttons, drop-down menus, input fields, and other widgets to the window. If you are creating complex user interfaces it is recommended to use `Gtk::Builder` and its GTK-specific markup description language, instead of assembling the interface manually.
GTK is event-driven. The toolkit listens for events such as a click on a button, and passes the event to your application.
This chapter contains some tutorial information to get you started with GTK programming. It assumes that you have GTK, its dependencies and ruby with the gtk4 gem installed.
## Basics
To begin our introduction to GTK, we’ll start with a very simple application. This program will create an empty 200 × 200 pixel window.

```ruby
# example-0.rb
require "gtk4"
application = Gtk::Application.new("org.gtk.example", :flags_none)
application.signal_connect "activate" do |app|
window = Gtk::ApplicationWindow.new(app)
window.title = "Window"
window.set_default_size(200, 200)
window.show
end
application.run
```
When creating a Gtk::Application you need to pick an application identifier (a name) and input to `Gtk::Application#new` as parameter. For this example *org.gtk.example* is used but for choosing an identifier for your application see this [guide](https://wiki.gnome.org/HowDoI/ChooseApplicationID).
Lastly `Gtk::Application#new` takes a `Gio::ApplicationFlags` constant as input for your application, if your application would have special needs (those constants can be replaced by theirs respective symbol ie. `Gio::ApplicationFlags::FLAGS_NONE` == `:flags_none`). You must know that `Gio:::Application` ignores arguments passed to `Gio::Application#run` on the Windows systems. It always uses command line arguments even when we pass an empty array to `Gio::Application#run`.
If you plan to create a cross-platform application, it is recommended to use the `:handles_command_line` flags and the *command-line* signal. (reference : https://github.com/ruby-gnome/ruby-gnome/issues/721 ).
Next we add instructions for the "activate" event of the `Gtk::Application` instance we created. The activate signal will be sent when your application is launched with the method `Gtk::Application#run` on the line below. This method also takes as arguments a ruby array of string. This allows GTK to parse specific command line arguments that control the behavior of GTK itself. Your application can override the command line handling, e.g. to open files passed on the commandline.
A window title is set using `Gtk::ApplicationWindow#title=`. This setter function takes a string as input.
Finally the window size is set using `Gtk::ApplicationWindow#set_default_size` and the window is then shown by GTK via `Gtk::Widget#show`.
While the program is running, GTK is receiving *events*. These are typically input events caused by the user interacting with your program, but also things like messages from the window manager or other applications. GTK processes these and as a result, signals may be emitted on your widgets. Connecting handlers for these signals is how you normally make your program do something in response to user input.
The following example is slightly more complex, and tries to showcase some of the capabilities of GTK.
## Hello, World
In the long tradition of programming languages and libraries, this example is called *Hello, World*.

```ruby
# example-1.rb
application = Gtk::Application.new("org.gtk.example", :flags_none)
application.signal_connect "activate" do |app|
window = Gtk::ApplicationWindow.new(app)
window.title = "Window"
window.set_default_size(200, 200)
button_box = Gtk::Box.new(:horizontal)
button_box.halign = :center
button_box.valign = :center
window.set_child(button_box)
button = Gtk::Button.new(label: "Hello World")
button.signal_connect "clicked" do
puts "Hello World"
window.destroy
end
button_box.append(button)
window.show
end
application.run
```
As seen above, example-1.rb builds further upon example-0.rb by adding a button to our window, with the label “Hello World”.
The button_box variable stores a `Gtk::Box` object, which is GTK's way of controlling the size and layout of widgets. The `Gtk::Box` is created with the method `Gtk::Box#new` which takes a `Gtk::Orientation `constant as parameter or the related symbols (`:vertical` or `:horizontal`).
The buttons which this box will contain can either be stored horizontally or vertically but this does not matter in this particular case as we are dealing with only one button. After initializing button_box with horizontal orientation, the code adds the button_box widget to the window widget using `Gtk::Box#append`.
Next the button variable is initialized in similar manner. The method `Gtk::Button#new` is called which returns a `Gtk::Button` to be stored in button. A label is set using a ruby hash as argument: `label: "Hello World"`. Afterwards button is added to our button_box.
Using the method `Gtk::Button#signal_connect` we add instructions, so that when the button is clicked, a message will be displayed in the terminal if the GTK application was started from one.
After that, `Gtk::ApplicationWindow#destroy` is called. This method is inherited from `Gtk::Widget`. This has the effect that when the button is clicked, the whole GTK window is destroyed. More information about creating buttons can be found [here](https://wiki.gnome.org/HowDoI/Buttons).
The rest of the code in example-1.rb is identical to example-0.rb. Next section will elaborate further on how to add several GtkWidgets to your GTK application.
## Packing
When creating an application, you’ll want to put more than one widget inside a window. When you do so, it becomes important to control how each widget is positioned and sized. This is where packing comes in.
GTK comes with a large variety of layout containers whose purpose it is to control the layout of the child widgets that are added to them, like:
* [Gtk::Box](https://docs.gtk.org/gtk4/class.Box.html)
* [Gtk::Grid](https://docs.gtk.org/gtk4/class.Grid.html)
* [Gtk::Revealer](https://docs.gtk.org/gtk4/class.Revealer.html)
* [Gtk::Stack](https://docs.gtk.org/gtk4/class.Stack.html)
* [Gtk::Overlay](https://docs.gtk.org/gtk4/class.Overlay.html)
* [Gtk::Paned](https://docs.gtk.org/gtk4/class.Paned.html)
* [Gtk::Expander](https://docs.gtk.org/gtk4/class.Expander.html)
* [Gtk::Fixed](https://docs.gtk.org/gtk4/class.Fixed.html)
The following example shows how the `Gtk::Grid` container lets you arrange several buttons:

```ruby
# example-2.rb
require "gtk4"
application = Gtk::Application.new("org.gtk.example", :flags_none)
application.signal_connect "activate" do |app|
# Create a new window and set its title
win = Gtk::ApplicationWindow.new(app)
win.title = "Window"
# Here we construct the container that is going to pack our buttons.
grid = Gtk::Grid.new
# Pack the container in the window
win.set_child(grid)
button = Gtk::Button.new(label: "Button 1")
button.signal_connect("clicked") { puts "Hello World!" }
# Place the first button in the grid cell (0, 0), and make it fill
# just 1 cell horizontally and vertically (ie no spanning)
grid.attach(button, 0, 0, 1, 1)
button = Gtk::Button.new(label: "Button 2")
button.signal_connect("clicked") { puts "Hello World!" }
# Place the second button in the grid cell (1, 0), and make it fill
# just 1 cell horizontally and vertically (ie no spanning)
grid.attach(button, 1, 0, 1, 1)
button = Gtk::Button.new(label: "Quit")
button.signal_connect("clicked") { win.destroy }
# Place the Quit button in the grid cell (0, 1), and make it
# span 2 columns.
grid.attach(button, 0, 1, 2, 1)
win.show
end
application.run
```
## Custom Drawing
Many widgets, like buttons, do all their drawing themselves. You just tell them the label you want to see, and they figure out what font to use, draw the button outline and focus rectangle, etc. Sometimes, it is necessary to do some custom drawing. In that case, a `Gtk::DrawingArea` might be the right widget to use. It offers a canvas on which you can draw by setting its draw function.
The contents of a widget often need to be partially or fully redrawn, e.g. when another window is moved and uncovers part of the widget, or when the window containing it is resized. It is also possible to explicitly cause a widget to be redrawn, by calling `Gtk::Widget#queue_draw`. GTK takes care of most of the details by providing a ready-to-use cairo context to the draw function.
The following example shows how to use a draw function with `Gtk::DrawingArea`. It is a bit more complicated than the previous examples, since it also demonstrates input event handling with event controllers.

### Drawing in response to input
```ruby
# example-3.rb
require "gtk4"
def clear_surface(surface)
cr = Cairo::Context.new(surface)
cr.set_source_rgb(1, 1, 1)
cr.paint
cr.destroy
end
# Draw a rectangle on the surface at the given position
def draw_brush(widget, surface, x, y)
cr = Cairo::Context.new(surface)
cr.rectangle(x - 3, y - 3, 6, 6)
cr.fill
widget.queue_draw
end
def generate_surface_from_widget(widget)
widget.native.surface.create_similar_surface(Cairo::CONTENT_COLOR,
widget.allocated_width,
widget.allocated_height)
end
myapp = Gtk::Application.new("org.gtk.example", :flags_none)
myapp.signal_connect "activate" do |app|
win = Gtk::ApplicationWindow.new(app)
win.set_title("Drawing Area")
surface = nil
win.signal_connect "destroy" do
surface.destroy if surface
win.destroy
end
drawing_area = Gtk::DrawingArea.new
# Set a minimum size
drawing_area.set_size_request(100, 100)
win.set_child(drawing_area)
drawing_area.set_draw_func do |da, cr|
if surface.nil?
surface = generate_surface_from_widget(da)
## Initialize the surface to white
clear_surface(surface)
end
# Redraw the screen from the surface. Note that the draw
# callback receives a ready-to-be-used cairo_t that is already
# clipped to only draw the exposed areas of the widget
cr.set_source(surface, 0, 0)
cr.paint
end
drawing_area.signal_connect_after "resize" do |da, alloc|
surface.destroy if surface
surface = generate_surface_from_widget(da)
# Initialize the surface to white
clear_surface(surface) if surface
end
drag = Gtk::GestureDrag.new
drag.button = Gdk::BUTTON_PRIMARY
drawing_area.add_controller(drag)
start_x = 0.0
start_y = 0.0
drag.signal_connect "drag-begin" do |gesture, x, y|
start_x = x
start_y = y
draw_brush(drawing_area, surface, x, y)
end
drag.signal_connect "drag-update" do |gesture, x, y|
draw_brush(drawing_area, surface, start_x + x, start_y + y)
end
drag.signal_connect "drag-end" do |gesture, x, y|
draw_brush(drawing_area, surface, start_x + x, start_y + y)
end
press = Gtk::GestureClick.new
press.button = Gdk::BUTTON_SECONDARY
drawing_area.add_controller(press)
press.signal_connect "pressed" do |gesture, n_press, x, y|
clear_surface(surface)
drawing_area.queue_draw
end
win.show
end
myapp.run
```
## Building user interfaces
When constructing a more complicated user interface, with dozens or hundreds of widgets, doing all the setup work in code is cumbersome, and making changes becomes next to impossible.
Thankfully, GTK supports the separation of user interface layout from your business logic, by using UI descriptions in an XML format that can be parsed by the `Gtk::Builder` class.
### Packing buttons with GtkBuilder
Create a new file with the following content:
```ruby
# example-4.rb
require "gtk4"
app = Gtk::Application.new("org.gtk.example", :flags_none)
app.signal_connect "activate" do
# Construct a GtkBuilder instance and load our UI description
builder = Gtk::Builder.new(file: File.join(__dir__, "builder.ui"))
# Connect signal handlers to the constructed widgets.
window = builder["window"]
window.set_application(app)
button = builder["button1"]
button.signal_connect("clicked") { puts "Hello World!" }
button = builder["button2"]
button.signal_connect("clicked") { puts "Hello World!" }
button = builder["quit"]
button.signal_connect("clicked") { window.close }
window.show
end
app.run
```
Then, create a new file with the following content named `builder.ui`.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object id="window" class="GtkWindow">
<property name="title">Grid</property>
<child>
<object id="grid" class="GtkGrid">
<child>
<object id="button1" class="GtkButton">
<property name="label">Button 1</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object id="button2" class="GtkButton">
<property name="label">Button 2</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object id="quit" class="GtkButton">
<property name="label">Quit</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</interface>
```
The usage of the `Gtk::Builder` is easy, we just create an instance from the
file "builder.ui" with `Gtk::Builder.new(file: path/to/builder.ui)`. Then you can access every widget or part of the interface thanks to its name: `window = builder["window"]`. Note that `Gtk::Builder` can also be used to construct objects that are not widgets, such as tree models, adjustments, etc.
It is also possible to embed the UI description in the source code as a string and use `GtkBuilder::add_from_string` to load it. But keeping the UI description in a separate file has several advantages:
* it is easier to isolate the UI code from the business logic of your application
* it is easier to restructure your UI into separate classes using composite widget templates
|