Adding Widgets To GTK3
Now that we have our window, which currently does nothing but display a window itself, it's time to start generating some widgets to attach to the window. A widget is simply a GUI object that attaches to a window, like a text box or a label.
Create a Scrolled Window
The information that we are collecting will be displayed in a scrolled window, and because a gtk::Window
can only contain one widget, that widget is going to be a gtk::ScrolledWindow
. The new()
method of a gtk::ScrolledWindow
takes two input variables which are used to control alignment. We aren't going to set any alignment options so we will simply tell them None
.
let scrolled_window = gtk::ScrolledWindow::new(None, None).unwrap();
It would also be a good idea to set a minimum width for our scrolled_window widget.
scrolled_window.set_min_content_width(600);
The new scrolled_window variable is useless by itself with no widgets to attach to it, however, so we need to create all the widgets we need for each of our articles. However, just like a gtk::Window
, a gtk::ScrolledWindow
will only allow one widget to be attached to it, so we will use a gtk::Box
as a container for all of the articles.
Create a Container
A gtk::Box
can have any number widgets attached to them, and are laid out either horizontally or vertically with gtk::Orienation::Horizontal
and gtk::Orientation::Vertical
respectively. You may also define the margins between widgets, but it's generally best to leave this at 0.
let container = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
Collecting Articles
Before we can start the creation of widgets for our GUI, we need to collect the list of Articles.
let articles = Article::get_articles(&homepage::offline());
Creating Widgets From the Vec<Article>
Each article will consist of a gtk::Box
that contains the entire Article. First is a gtk::LinkButton
containing both the Title and URL. The details and summaries will be contained within their own respective gtk::TextView
widgets. To keep our launch()
function cleaned up, we will create a new function specifically for the creation of the list of articles.
fn generate_article_widgets(container: >k::Box, articles: &Vec<Article>) {
for article in articles {
// Code Here
}
}
This function will take the container variable we created earlier, along with the Vec
of Articles
as input, and simply iterate over each element -- attaching a new GUI widget with each loop.
Obtaining the Title and URL Widget
Let's start by creating a widget for the title and link, where we will create a gtk::LinkButton
. The gtk::LinkButton::new_with_label()
function takes two inputs: the URL and a label as &str
types. By default, this will be centered so we can use the set_halign()
method to set a left alignment with gtk::Align::Start
. We can get the URL via article.link
and the label with article.title
.
for article in articles {
let url = format!("https://phoronix.com/{}", article.link);
let title_and_url = gtk::LinkButton::new_with_label(&url, &article.title).unwrap();
title_and_url.set_halign(gtk::Align::Start);
}
Obtaining Details
The next step is creating a gtk::TextView
widget containing the details obtained from article.details
. Like our title widget, we also want to left-align the text, and this time also set a left and right margin. The set_left_margin()
and set_right_margin()
may be used to specify how many pixels to use for the margins of the text inside the gtk::TextView
. We also do not want users to be able to edit the text, so we will disable editing with set_editable(false)
. You can't set the text during creation of the widget though, so that will have to be defined after creation using get_buffer()
and set_text()
.
let details = gtk::TextView::new().unwrap(); // Create the TextView Widget
details.set_halign(gtk::Align::Start); // Set a left alignment for the text
details.set_left_margin(10); // 10 pixel left margin
details.set_right_margin(10); // 10 pixel right margin
details.set_editable(false); // Disable text editing
details.get_buffer().unwrap().set_text(&article.details); // Set the text in the widget
Obtaining Summaries
Now all we need to get is a widget for the summaries. The process is very much the same, but we will use a few extra features for setting how the widget operates, namely defining a wrap mode with set_wrap_mode()
and setting the number of pixels above and below lines with set_pixels_above_lines()
and set_pixels_below_lines()
.
let summary = gtk::TextView::new().unwrap(); // Create the TextView Widget
summary.set_wrap_mode(gtk::WrapMode::Word); // Wrap Lines By Words
summary.set_left_margin(10); // 10 pixel left margin
summary.set_right_margin(10); // 10 pixel right margin
summary.set_pixels_above_lines(10); // 10 pixels above summmary
summary.set_pixels_below_lines(10); // 10 pixels below summary
summary.set_editable(false); // Disable text editing
summary.get_buffer().unwrap().set_text(&article.summary); // Set the text in the widget
Add Widgets to Container
Now all we just have to do is add these widgets to the container and we are pretty much finished with this function, albeit we have yet to perform any kind of coloring to these widgets.
container.add(&title_and_url);
container.add(&details);
container.add(&summary);
Adding Widgets to the Window and Displaying Them
Adding the container to our scrolled_window, and the scrolled_window to the window should be pretty straightforward.
scrolled_window.add(&container);
window.add(&scrolled_window);
window.show_all();
Quitting Program When Esc Is Pressed
If you would like to be able to program the window to quit the program when the Esc key is pressed, we can make use of the connect_key_press_event()
method for the gtk::Window
type. This will take itself and the key that was pressed when an event is triggered, and is used to match the key to any code of your choice that you want to assign to that key press.
// Define actions on key press
window.connect_key_press_event(move |_, key| {
match key.keyval as i32 {
key::Escape => gtk::main_quit(),
_ => ()
}
gtk::signal::Inhibit(false)
});
Review
phoronix_gui.rs
use article::Article;
use homepage;
use gtk;
use gtk::traits::*;
use gdk::ffi::GdkRGBA;
use pango;
pub fn launch() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
// Create widgets for the articles
let container = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
let articles = Article::get_articles(&homepage::online());
generate_article_widgets(&container, &articles);
// Insert the articles into a scrolled window
let scrolled_window = gtk::ScrolledWindow::new(None, None).unwrap();
scrolled_window.set_min_content_width(600);
scrolled_window.add(&article_box);
// Add the scrolled window to a main window
let window = gtk::Window::new(gtk::WindowType::Toplevel).unwrap();
configure_window(&window);
window.add(&scrolled_window);
window.show_all();
// Define actions on key press
window.connect_key_press_event(move |_, key| {
match key.keyval as i32 {
key::Escape => gtk::main_quit(),
_ => ()
}
gtk::signal::Inhibit(false)
});
gtk::main();
}
// configre_window configures the given window.
fn configure_window(window: >k::Window) {
window.set_title("Phoronix Reader");
let (width, height) = (600, 500);
window.set_default_size(width, height);
window.connect_delete_event(|_,_| {
gtk::main_quit();
gtk::signal::Inhibit(true)
});
}
// generate_article_widgets takes a vector of articles as well as a gtk::Box and fills up the gtk::Box
// with widgets generated from each article
fn generate_article_widgets(article_box: >k::Box, articles: &Vec<Article>) {
for article in articles {
// Creates the title as a gtk::LinkButton for each article
let url = format!("https://phoronix.com/{}", article.link);
let title_and_url = gtk::LinkButton::new_with_label(&url, &article.title).unwrap();
title_and_url.set_halign(gtk::Align::Start);
title_and_url.set_margin_start(0);
// Details of the article inside of a gtk::TextView
let details = gtk::TextView::new().unwrap();
details.set_halign(gtk::Align::Start);
details.set_left_margin(10);
details.set_right_margin(10);
details.set_editable(false);
details.get_buffer().unwrap().set_text(&article.details);
// Summary of the article inside of a gtk::TextView
let summary = gtk::TextView::new().unwrap();
summary.set_wrap_mode(gtk::WrapMode::Word);
summary.set_left_margin(10);
summary.set_right_margin(10);
summary.set_pixels_above_lines(10);
summary.set_pixels_below_lines(10);
summary.set_editable(false);
summary.get_buffer().unwrap().set_text(&article.summary);
// Attach the title+url, details and summary to the article_box
container.add(&title_and_url);
container.add(&details);
container.add(&summary);
container.add(>k::Separator::new(gtk::Orientation::Horizontal).unwrap());
}
}