Commit 9302dbe4 authored by Christopher Davis's avatar Christopher Davis

Port to GTK4 + libadwaita + gtk4-rs

This commit upgrades our rust dependencies and ports Tour to libadwaita+GTK4. As part of porting we now use GtkPicture instead of GtkImage, and AdwClamps to hold pages in place. This port is part of https://gitlab.gnome.org/GNOME/Initiatives/-/issues/26 and handles https://gitlab.gnome.org/GNOME/Initiatives/-/issues/32 Fixes https://gitlab.gnome.org/GNOME/gnome-tour/-/issues/36
parent 4ac7296b
......@@ -8,22 +8,19 @@ edition = "2018"
video = ["gst_player", "gst"]
[dependencies]
glib = { version = "0.10", features = ["v2_64"] }
gdk = "0.13"
gtk = { version = "0.9", features= ["v3_16"] }
gio = "0.9"
gtk = { package = "gtk4", version = "0.3", features= ["v4_2"]}
log = "0.4"
gettext-rs = { version = "0.6", features = ["gettext-system"] }
libhandy = "0.7"
libadwaita = "0.1.0-alpha-6"
pretty_env_logger = "0.4"
[dependencies.gst_player]
version = "0.16"
version = "0.17"
package = "gstreamer-player"
optional = true
[dependencies.gst]
version = "0.16"
version = "0.17"
package = "gstreamer"
optional = true
......@@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/org/gnome/Tour/">
<file compressed="true" alias="style.css">resources/style.css</file>
<file compressed="true" alias="style-dark.css">resources/style-dark.css</file>
<file compressed="true" alias="welcome.svg">resources/assets/welcome.svg</file>
<file compressed="true" alias="overview.svg">resources/assets/overview.svg</file>
<file compressed="true" alias="search.svg">resources/assets/search.svg</file>
......
.page { color: #fff; }
......@@ -11,8 +11,8 @@ base_id = 'org.gnome.Tour'
dependency('glib-2.0', version: '>= 2.64')
dependency('gio-2.0', version: '>= 2.56')
dependency('gdk-pixbuf-2.0')
dependency('gtk+-3.0', version: '>= 3.16')
dependency('libhandy-1', version: '>= 1')
dependency('gtk4', version: '>= 4.4')
dependency('libadwaita-1', version: '>= 1')
if get_option('video_path') != ''
dependency('gstreamer-1.0', version: '>= 1.12')
......
use crate::config;
use crate::utils;
use crate::widgets::Window;
use gio::prelude::*;
use glib::clone;
use gtk::gdk;
use gtk::gio::{self, prelude::*};
use gtk::glib::{self, clone};
use gtk::prelude::*;
use log::info;
use std::env;
use std::{cell::RefCell, rc::Rc};
pub struct Application {
app: gtk::Application,
app: libadwaita::Application,
window: RefCell<Rc<Option<Window>>>,
}
impl Application {
pub fn new() -> Rc<Self> {
let app =
gtk::Application::new(Some(config::APP_ID), gio::ApplicationFlags::FLAGS_NONE).unwrap();
libadwaita::Application::new(Some(config::APP_ID), gio::ApplicationFlags::FLAGS_NONE);
app.set_resource_base_path(Some("/org/gnome/Tour"));
let application = Rc::new(Self {
app,
......@@ -86,8 +87,6 @@ impl Application {
fn setup_signals(&self, app: Rc<Self>) {
self.app.connect_startup(clone!(@weak app => move |_| {
libhandy::init();
app.setup_css();
app.setup_gactions(app.clone());
}));
self.app
......@@ -100,20 +99,11 @@ impl Application {
}));
}
fn setup_css(&self) {
let p = gtk::CssProvider::new();
gtk::CssProvider::load_from_resource(&p, "/org/gnome/Tour/style.css");
if let Some(screen) = gdk::Screen::get_default() {
gtk::StyleContext::add_provider_for_screen(&screen, &p, 500);
}
}
pub fn run(&self) {
info!("GNOME Tour ({})", config::APP_ID);
info!("Version: {} ({})", config::VERSION, config::PROFILE);
info!("Datadir: {}", config::PKGDATADIR);
let args: Vec<String> = env::args().collect();
self.app.run(&args);
self.app.run();
}
}
use gettextrs::*;
use gtk::glib;
mod application;
mod config;
......
// Source: https://gitlab.gnome.org/World/podcasts/blob/master/podcasts-gtk/src/static_resource.rs
use gio::{resources_register, Resource};
use glib::{Bytes, Error};
use gtk::gio::{resources_register, Resource};
use gtk::glib::{Bytes, Error};
pub(crate) fn init() -> Result<(), Error> {
// load the gresource binary at build time and include/link it into the final
......
// based on https://gitlab.gnome.org/World/podcasts/-/blob/master/podcasts-gtk/src/i18n|utils.rs
use gettextrs::gettext;
use gtk::{gio, glib};
pub fn action<T, F>(thing: &T, name: &str, action: F)
where
T: gio::ActionMapExt,
T: gio::traits::ActionMapExt,
for<'r, 's> F: Fn(&'r gio::SimpleAction, Option<&glib::Variant>) + 'static,
{
// Create a stateless, parameterless action
......
......@@ -15,8 +15,9 @@ impl ImagePageWidget {
}
fn init(&self, resource_uri: &str, head: String, body: String) {
self.widget.set_property_expand(true);
self.widget.get_style_context().add_class("page");
self.widget.set_hexpand(true);
self.widget.set_vexpand(true);
self.widget.add_css_class("page");
self.widget.set_halign(gtk::Align::Fill);
self.widget.set_valign(gtk::Align::Fill);
......@@ -31,11 +32,15 @@ impl ImagePageWidget {
.margin_start(12)
.margin_end(12)
.build();
let clamp = libadwaita::Clamp::new();
clamp.set_child(Some(&container));
let image = gtk::Image::from_resource(&resource_uri);
image.set_valign(gtk::Align::Start);
image.show();
container.add(&image);
let picture = gtk::PictureBuilder::new()
.can_shrink(false)
.keep_aspect_ratio(true)
.build();
picture.set_resource(Some(resource_uri));
container.append(&picture);
let head_label = gtk::LabelBuilder::new()
.label(&head)
......@@ -43,9 +48,8 @@ impl ImagePageWidget {
.valign(gtk::Align::Center)
.margin_top(36)
.build();
head_label.get_style_context().add_class("page-title");
head_label.show();
container.add(&head_label);
head_label.add_css_class("page-title");
container.append(&head_label);
let body_label = gtk::LabelBuilder::new()
.label(&body)
......@@ -55,12 +59,9 @@ impl ImagePageWidget {
.valign(gtk::Align::Center)
.margin_top(12)
.build();
body_label.get_style_context().add_class("page-body");
body_label.show();
container.add(&body_label);
body_label.add_css_class("page-body");
container.append(&body_label);
container.show();
self.widget.add(&container);
self.widget.show();
self.widget.append(&clamp);
}
}
......@@ -4,10 +4,11 @@ use crate::utils::i18n_f;
use gettextrs::gettext;
#[cfg(feature = "video")]
use gio::FileExt;
use gtk::glib;
#[cfg(feature = "video")]
use glib::clone;
use gtk::glib::clone;
#[cfg(feature = "video")]
use glib::{Receiver, Sender};
use gtk::glib::{Receiver, Sender};
use gtk::prelude::*;
#[cfg(feature = "video")]
use std::cell::RefCell;
......@@ -67,19 +68,26 @@ impl WelcomePageWidget {
let container = gtk::BoxBuilder::new()
.orientation(gtk::Orientation::Vertical)
.spacing(0)
.expand(true)
.hexpand(true)
.vexpand(true)
.valign(gtk::Align::Center)
.halign(gtk::Align::Center)
.margin_top(24)
.margin_bottom(24)
.build();
self.widget.get_style_context().add_class("page");
self.widget.get_style_context().add_class("welcome-page");
self.widget.add_css_class("page");
self.widget.add_css_class("welcome-page");
let clamp = libadwaita::Clamp::new();
clamp.set_child(Some(&container));
#[cfg(not(feature = "video"))]
let header = {
let logo = gtk::Image::from_resource("/org/gnome/Tour/welcome.svg");
logo.show();
let logo = gtk::PictureBuilder::new()
.can_shrink(false)
.keep_aspect_ratio(true)
.build();
logo.set_resource(Some("/org/gnome/Tour/welcome.svg"));
logo.upcast::<gtk::Widget>()
};
......@@ -103,11 +111,11 @@ impl WelcomePageWidget {
video_widget.set_size_request(-1, 360);
video_widget.set_property("ignore-alpha", &false).unwrap();
video_widget.show();
video_widget.get_style_context().add_class("video");
video_widget.add_css_class("video");
video_widget
};
container.add(&header);
container.append(&header);
#[cfg(feature = "video")]
{
......@@ -117,7 +125,7 @@ impl WelcomePageWidget {
clone!(@strong self.player as player => move |action| {
match action {
Action::VideoReady => player.play(),
Action::VideoUp => header.get_style_context().add_class("playing"),
Action::VideoUp => header.add_css_class("playing"),
};
glib::Continue(true)
}),
......@@ -150,24 +158,20 @@ impl WelcomePageWidget {
let title = gtk::Label::new(Some(&gettext("Start the Tour")));
title.set_margin_top(36);
title.get_style_context().add_class("page-title");
title.show();
container.add(&title);
title.add_css_class("page-title");
container.append(&title);
let name = glib::get_os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::get_os_info("VERSION").unwrap_or_else(|| "".into());
let name = glib::os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::os_info("VERSION").unwrap_or_else(|| "".into());
// Translators: The following string is formated as "Learn about new and essential features in GNOME 3.36" for example
let text = gtk::Label::new(Some(&i18n_f(
"Learn about the key features in {} {}.",
&[&name, &version],
)));
text.get_style_context().add_class("body");
text.add_css_class("body");
text.set_margin_top(12);
text.show();
container.add(&text);
container.append(&text);
container.show();
self.widget.add(&container);
self.widget.show();
self.widget.append(&clamp);
}
}
use gettextrs::gettext;
use glib::clone;
use gtk::glib::{self, clone};
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
use libhandy::prelude::{CarouselExt, CarouselIndicatorDotsExt, HeaderBarExt};
pub struct PaginatorWidget {
pub widget: gtk::Box,
carousel: libhandy::Carousel,
carousel_dots: libhandy::CarouselIndicatorDots,
headerbar: libhandy::HeaderBar,
carousel: libadwaita::Carousel,
carousel_dots: libadwaita::CarouselIndicatorDots,
headerbar: gtk::HeaderBar,
pages: RefCell<Vec<gtk::Widget>>,
current_page: RefCell<u32>,
next_overlay: gtk::Overlay,
next_btn: gtk::Button,
start_btn: gtk::Button,
finish_btn: gtk::Button,
......@@ -26,10 +25,13 @@ impl PaginatorWidget {
let paginator = Rc::new(Self {
widget,
carousel: libhandy::Carousel::new(),
carousel_dots: libhandy::CarouselIndicatorDots::new(),
headerbar: libhandy::HeaderBar::new(),
carousel: libadwaita::Carousel::new(),
carousel_dots: libadwaita::CarouselIndicatorDots::new(),
headerbar: gtk::HeaderBarBuilder::new()
.show_title_buttons(false)
.build(),
start_btn: gtk::Button::with_label(&gettext("_Start")),
next_overlay: gtk::Overlay::new(),
next_btn: gtk::Button::with_label(&gettext("_Next")),
finish_btn: gtk::Button::with_label(&gettext("_Close")),
close_btn: gtk::Button::with_label(&gettext("_Close")),
......@@ -43,7 +45,7 @@ impl PaginatorWidget {
pub fn try_next(&self) -> Option<()> {
let p = *self.current_page.borrow() + 1;
if p == self.carousel.get_n_pages() {
if p == self.carousel.n_pages() {
return None;
}
self.set_page(p);
......@@ -68,20 +70,24 @@ impl PaginatorWidget {
}
fn update_position(&self) {
let position = self.carousel.get_position();
let position = self.carousel.position();
let page_nr = position.round() as u32;
let n_pages = self.carousel.get_n_pages() as f64;
let n_pages = self.carousel.n_pages() as f64;
let forelast_page = n_pages - 2.0;
let last_page = n_pages - 1.0;
let (opacity_finish, opacity_previous, opacity_start, opacity_next) =
let (opacity_finish, opacity_previous, opacity_start, opacity_next, opacity_close) =
if (0.0..1.0).contains(&position) {
(0.0, position, 1.0, position)
if position == 0.0 {
(0.0, position, 1.0, position, 1.0)
} else {
(0.0, position, 1.0, position, 1f64 - position)
}
} else if (0.0 <= position) && (position <= forelast_page) {
(0.0, 1.0, 1f64 - position, 1.0)
(0.0, 1.0, 1f64 - position, 1.0, 0.0)
} else if (forelast_page < position) && (position <= last_page) {
(position - forelast_page, 1.0, 0.0, 1.0)
(position - forelast_page, 1.0, 0.0, 1.0, 0.0)
} else {
panic!("Position of the carousel is outside the allowed range");
};
......@@ -91,6 +97,7 @@ impl PaginatorWidget {
self.next_btn.set_opacity(opacity_next);
self.next_btn.set_visible(opacity_next > 0_f64);
self.next_overlay.set_can_target(opacity_next > 0_f64);
self.finish_btn.set_opacity(opacity_finish);
self.finish_btn.set_visible(opacity_finish > 0_f64);
......@@ -98,82 +105,69 @@ impl PaginatorWidget {
self.previous_btn.set_opacity(opacity_previous);
self.previous_btn.set_visible(opacity_previous > 0_f64);
self.close_btn.set_opacity(opacity_close);
self.start_btn.set_visible(opacity_close > 0_f64);
self.current_page.replace(page_nr);
}
fn init(&self, p: Rc<Self>) {
self.carousel_dots.show();
self.carousel_dots.set_carousel(Some(&self.carousel));
self.carousel.set_property_expand(true);
self.carousel.set_hexpand(true);
self.carousel.set_vexpand(true);
self.carousel.set_animation_duration(300);
self.carousel.show();
self.carousel
.connect_property_position_notify(clone!(@weak p => move |_| {
.connect_position_notify(clone!(@weak p => move |_| {
p.update_position();
}));
self.start_btn
.get_style_context()
.add_class("suggested-action");
self.start_btn.add_css_class("suggested-action");
self.start_btn.set_use_underline(true);
self.start_btn.set_action_name(Some("app.start-tour"));
self.start_btn.show();
self.next_btn
.get_style_context()
.add_class("suggested-action");
self.next_btn.add_css_class("suggested-action");
self.next_btn.set_use_underline(true);
self.next_btn.set_action_name(Some("app.next-page"));
self.close_btn.set_use_underline(true);
self.close_btn.set_action_name(Some("app.quit"));
self.close_btn.show();
self.finish_btn
.get_style_context()
.add_class("suggested-action");
self.finish_btn.add_css_class("suggested-action");
self.finish_btn.set_use_underline(true);
self.finish_btn.set_action_name(Some("app.quit"));
self.previous_btn.set_use_underline(true);
self.previous_btn.set_action_name(Some("app.previous-page"));
self.next_overlay.set_child(Some(&self.next_btn));
self.next_overlay.add_overlay(&self.finish_btn);
self.next_overlay.set_can_target(false);
let previous_overlay = gtk::Overlay::new();
previous_overlay.add(&self.close_btn);
previous_overlay.set_child(Some(&self.close_btn));
previous_overlay.add_overlay(&self.previous_btn);
previous_overlay.show();
let next_overlay = gtk::Overlay::new();
next_overlay.add(&self.next_btn);
next_overlay.add_overlay(&self.finish_btn);
next_overlay.show();
let start_overlay = gtk::Overlay::new();
start_overlay.add(&self.start_btn);
start_overlay.add_overlay(&next_overlay);
start_overlay.set_overlay_pass_through(&next_overlay, true);
start_overlay.show();
start_overlay.set_child(Some(&self.start_btn));
start_overlay.add_overlay(&self.next_overlay);
let btn_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
btn_size_group.add_widget(&self.previous_btn);
btn_size_group.add_widget(&self.close_btn);
btn_size_group.add_widget(&next_overlay);
btn_size_group.add_widget(&self.next_overlay);
btn_size_group.add_widget(&start_overlay);
btn_size_group.add_widget(&self.finish_btn);
self.headerbar.set_custom_title(Some(&self.carousel_dots));
self.headerbar.set_title_widget(Some(&self.carousel_dots));
self.headerbar.pack_start(&previous_overlay);
self.headerbar.pack_end(&start_overlay);
self.headerbar.set_show_close_button(false);
self.headerbar.show();
self.widget.add(&self.headerbar);
self.widget.add(&self.carousel);
self.widget.show();
self.widget.append(&self.headerbar);
self.widget.append(&self.carousel);
}
pub fn set_page(&self, page_nr: u32) {
if page_nr < self.carousel.get_n_pages() {
if page_nr < self.carousel.n_pages() {
let pages = &self.pages.borrow();
let page = pages.get(page_nr as usize).unwrap();
self.carousel.scroll_to(page);
......
use crate::utils::i18n_f;
use gettextrs::gettext;
use gtk::glib;
use gtk::prelude::*;
use libadwaita::traits::ApplicationWindowExt;
use std::cell::RefCell;
use std::rc::Rc;
......@@ -9,14 +11,13 @@ use super::paginator::PaginatorWidget;
use crate::config::{APP_ID, PROFILE};
pub struct Window {
pub widget: libhandy::ApplicationWindow,
pub widget: libadwaita::ApplicationWindow,
pub paginator: RefCell<Rc<PaginatorWidget>>,
}
impl Window {
pub fn new(app: &gtk::Application) -> Self {
let widget = libhandy::ApplicationWindow::new();
widget.set_application(Some(app));
pub fn new(app: &libadwaita::Application) -> Self {
let widget = libadwaita::ApplicationWindow::new(app);
let paginator = RefCell::new(PaginatorWidget::new());
......@@ -40,7 +41,7 @@ impl Window {
// Devel Profile
if PROFILE == "Devel" {
self.widget.get_style_context().add_class("devel");
self.widget.add_css_class("devel");
}
self.paginator
.borrow_mut()
......@@ -95,18 +96,19 @@ impl Window {
.upcast::<gtk::Widget>(),
);
let name = glib::get_os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::get_os_info("VERSION").unwrap_or_else(|| "".into());
let name = glib::os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::os_info("VERSION").unwrap_or_else(|| "".into());
let last_page = ImagePageWidget::new(
"/org/gnome/Tour/ready-to-go.svg",
gettext("That's it. Have a nice day!"),
gettext("To get more advice and tips, see the Help app."),
);
last_page.widget.get_style_context().add_class("last-page");
last_page.widget.add_css_class("last-page");
self.paginator
.borrow_mut()
.add_page(last_page.widget.upcast::<gtk::Widget>());
self.widget.add(&self.paginator.borrow().widget);
self.widget
.set_content(Some(&self.paginator.borrow().widget));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment