Commit 117ebe90 authored by Bilal Elmoussaoui's avatar Bilal Elmoussaoui

Merge branch 'bilelmoussaoui/video' into 'master'

Add a configurable welcome video thing See merge request GNOME/gnome-tour!16
parents 298f0bfa b70e4f16
......@@ -53,6 +53,12 @@ dependencies = [
]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
......@@ -381,6 +387,8 @@ dependencies = [
"gettext-rs",
"gio",
"glib",
"gstreamer",
"gstreamer-player",
"gtk",
"libhandy",
"log",
......@@ -399,6 +407,137 @@ dependencies = [
]
[[package]]
name = "gstreamer"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609"
dependencies = [
"bitflags",
"cfg-if",
"futures-channel",
"futures-core",
"futures-util",
"glib",
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"libc",
"muldiv",
"num-rational",
"once_cell",
"paste",
"pretty-hex",
"thiserror",
]
[[package]]
name = "gstreamer-base"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872893487ce8876f18c63730402822804c5762869f631d8e3e6b18aafc8399f0"
dependencies = [
"bitflags",
"glib",
"glib-sys",
"gobject-sys",
"gstreamer",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
]
[[package]]
name = "gstreamer-base-sys"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fd5a77d39b47568fba01274dfcb28dc32382513c697009f80b89ef63fd32fd"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-player"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e8a850be21b18fb21f21fa16394bfc176013f7fffdc6719db0ea23b3c99a2b"
dependencies = [
"bitflags",
"glib",
"glib-sys",
"gobject-sys",
"gstreamer",
"gstreamer-player-sys",
"gstreamer-sys",
"gstreamer-video",
"libc",
]
[[package]]
name = "gstreamer-player-sys"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f0cd125febba8f9b3cae13881c0d845a77a4e4797b9552aa4058330c23e958"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"gstreamer-video-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-sys"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-video"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a52c141d83113e6dd080347ca49ef9be296e7900e081f2b67eaad6a3d5ef26"
dependencies = [
"bitflags",
"futures-channel",
"futures-util",
"glib",
"glib-sys",
"gobject-sys",
"gstreamer",
"gstreamer-base",
"gstreamer-base-sys",
"gstreamer-sys",
"gstreamer-video-sys",
"libc",
"once_cell",
]
[[package]]
name = "gstreamer-video-sys"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f105143a7676d2032c386c10b2d376106b5562b7a11b694b634113456f1935"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gtk"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
......@@ -561,10 +700,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "muldiv"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204"
[[package]]
name = "num-integer"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
[[package]]
name = "pango"
......@@ -594,6 +769,25 @@ dependencies = [
]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]]
name = "pin-project"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
......@@ -626,6 +820,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
[[package]]
name = "pretty-hex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
......
......@@ -4,6 +4,9 @@ version = "0.0.1"
authors = ["Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>"]
edition = "2018"
[features]
video = ["gst_player", "gst"]
[dependencies]
glib = { version = "0.10", features = ["v2_64"] }
gdk = "0.13"
......@@ -14,3 +17,13 @@ gettext-rs = { version = "0.4", features = ["gettext-system"] }
libhandy = "1.0.0-alpha"
pretty_env_logger = "0.4"
anyhow = "1.0"
[dependencies.gst_player]
version = "0.16"
package = "gstreamer-player"
optional = true
[dependencies.gst]
version = "0.16"
package = "gstreamer"
optional = true
# GNOME Greeter & Tour
# Tour
<img src="https://gitlab.gnome.org/GNOME/gnome-tour/raw/master/data/icons/org.gnome.Tour.svg" width="128" height="128" />
<p>GNOME's Tour & Greeter.</p>
## Screenshots
<div align="center">
![screenshot](data/resources/screenshots/screenshot1.png)
</div>
### Video Feature
Tour uses by default the logo of the distribution based on the info from `/etc/os-release`. The application comes with a feature to replace the logo with a welcome video shipped by the distribution.
To enable the feature, you need to build the application with
```bash
meson _builddir -Dvideo_path=/absolute/path/to/the/video.mp4
```
If you're testing the application using Builder, make sure to change the `config-opts` accordinagly & give the application filesystem access so it can play the video file.
Example:
This needs to be added to the `gnome-tour` module
```json
"config-opts" : [
"-Dvideo_path=/home/username/to/the/video.mp4"
]
```
along with `--filesystem=home` in `finish-args`
......@@ -4,17 +4,18 @@ export MESON_BUILD_ROOT="$1"
export MESON_SOURCE_ROOT="$2"
export CARGO_TARGET_DIR="$MESON_BUILD_ROOT"/target
export CARGO_HOME="$CARGO_TARGET_DIR"/cargo-home
FEATURES="$6"
if [[ $4 = "Devel" ]]
then
echo "DEBUG MODE"
cargo build --manifest-path \
"$MESON_SOURCE_ROOT"/Cargo.toml --verbose && \
"$MESON_SOURCE_ROOT"/Cargo.toml $FEATURES && \
cp "$CARGO_TARGET_DIR"/debug/$5 $3
else
echo "RELEASE MODE"
cargo build --manifest-path \
"$MESON_SOURCE_ROOT"/Cargo.toml --release && \
"$MESON_SOURCE_ROOT"/Cargo.toml $FEATURES --release && \
cp "$CARGO_TARGET_DIR"/release/$5 $3
fi
......@@ -14,11 +14,7 @@
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
"--filesystem=xdg-run/dconf",
"--filesystem=~/.config/dconf:ro",
"--talk-name=ca.desrt.dconf",
"--env=DCONF_USER_CONFIG_DIR=.config/dconf"
"--device=dri"
],
"build-options" : {
"append-path" : "/usr/lib/sdk/rust-stable/bin",
......
......@@ -4,7 +4,7 @@
<id>@app-id@</id>
<metadata_license>CC0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>GNOME Tour</name>
<name>Tour</name>
<summary>GNOME Tour and Greeter.</summary>
<description>
<p>A guided tour and greeter for GNOME.</p>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -11,6 +11,15 @@
font-weight: 400;
font-size: 12pt;
}
.video {
opacity: 0;
}
.video.playing {
opacity: 1;
transition-property: opacity;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.last-page {
background-color: #4a86cf; /*GNOME blue*/
......
......@@ -13,6 +13,11 @@ dependency('gio-2.0', version: '>= 2.56')
dependency('gdk-pixbuf-2.0')
dependency('gtk+-3.0', version: '>= 3.16')
if get_option('video_path') != ''
dependency('gstreamer-1.0', version: '>= 1.12')
dependency('gstreamer-video-1.0', version: '>= 1.12')
dependency('gstreamer-player-1.0', version: '>= 1.12')
endif
glib_compile_resources = find_program('glib-compile-resources', required: true)
desktop_file_validate = find_program('desktop-file-validate', required: false)
......
......@@ -9,3 +9,9 @@ option (
description: 'The build profile for GNOME Tour. One of "default" or "development".'
)
option(
'video_path',
type : 'string',
value: '',
description : 'Sets the absolute path of a welcome video'
)
......@@ -5,3 +5,5 @@ pub static NAME_SUFFIX: &str = @NAME_SUFFIX@;
pub static VERSION: &str = @VERSION@;
pub static GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
pub static LOCALEDIR: &str = @LOCALEDIR@;
#[cfg(feature = "video")]
pub static VIDEO_PATH: &str = @VIDEO_PATH@;
......@@ -20,6 +20,8 @@ fn main() {
glib::set_prgname(Some("Tour"));
gtk::init().expect("Unable to start GTK3");
#[cfg(feature = "video")]
gst::init().expect("Unable to start gst");
static_resources::init().expect("Failed to initialize the resource file.");
......
......@@ -6,6 +6,7 @@ global_conf.set_quoted('NAME_SUFFIX', name_suffix)
global_conf.set_quoted('VERSION', version + version_suffix)
global_conf.set_quoted('GETTEXT_PACKAGE', gettext_package)
global_conf.set_quoted('LOCALEDIR', localedir)
global_conf.set_quoted('VIDEO_PATH', get_option('video_path'))
config = configure_file(
input: 'config.rs.in',
output: 'config.rs',
......@@ -49,6 +50,10 @@ sources = files(
'utils.rs',
)
features = ''
if get_option('video_path') != ''
features = '--features video'
endif
custom_target(
'cargo-build',
build_by_default: true,
......@@ -65,6 +70,7 @@ custom_target(
'@OUTPUT@',
profile,
meson.project_name(),
features
]
)
use super::page::Pageable;
use gtk::prelude::*;
pub struct ImagePageWidget {
pub widget: gtk::Box,
pub title: String,
}
impl Pageable for ImagePageWidget {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast::<gtk::Widget>()
}
fn get_title(&self) -> String {
self.title.clone()
}
}
impl ImagePageWidget {
pub fn new(resource_uri: &str, title: String, head: String, body: String) -> Self {
pub fn new(resource_uri: &str, head: String, body: String) -> Self {
let widget = gtk::Box::new(gtk::Orientation::Vertical, 0);
let image_page = Self { widget, title };
let image_page = Self { widget };
image_page.init(resource_uri, head, body);
image_page
......@@ -34,6 +22,7 @@ impl ImagePageWidget {
let container = gtk::Box::new(gtk::Orientation::Vertical, 12);
container.set_halign(gtk::Align::Center);
container.set_valign(gtk::Align::Center);
container.set_vexpand(true);
container.set_margin_bottom(48);
container.set_margin_top(12);
container.set_margin_start(12);
......
mod image;
mod page;
mod welcome;
pub use image::ImagePageWidget;
pub use page::Pageable;
pub use welcome::WelcomePageWidget;
pub trait Pageable {
fn get_widget(&self) -> gtk::Widget;
fn get_title(&self) -> String;
}
use super::page::Pageable;
#[cfg(feature = "video")]
use crate::config;
use gettextrs::gettext;
#[cfg(feature = "video")]
use gio::FileExt;
#[cfg(feature = "video")]
use glib::clone;
#[cfg(feature = "video")]
use glib::{Receiver, Sender};
use gtk::prelude::*;
#[cfg(feature = "video")]
use std::cell::RefCell;
pub struct WelcomePageWidget {
pub widget: gtk::Box,
pub title: String,
#[derive(PartialEq)]
#[cfg(feature = "video")]
pub enum Action {
VideoReady,
VideoUp,
}
impl Pageable for WelcomePageWidget {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast::<gtk::Widget>()
}
fn get_title(&self) -> String {
self.title.clone()
}
pub struct WelcomePageWidget {
pub widget: libhandy::WindowHandle,
#[cfg(feature = "video")]
player: gst_player::Player,
#[cfg(feature = "video")]
receiver: RefCell<Option<Receiver<Action>>>,
#[cfg(feature = "video")]
sender: Sender<Action>,
}
impl WelcomePageWidget {
pub fn new() -> Self {
let widget = gtk::Box::new(gtk::Orientation::Vertical, 0);
let widget = libhandy::WindowHandle::new();
#[cfg(feature = "video")]
let player = {
let dispatcher = gst_player::PlayerGMainContextSignalDispatcher::new(None);
let sink = gst::ElementFactory::make("gtksink", None).expect("Missing dependency: element gtksink is needed (usually, in gstreamer-plugins-good or in gst-plugin-gtk).");
let renderer = gst_player::PlayerVideoOverlayVideoRenderer::with_sink(&sink).upcast();
gst_player::Player::new(Some(&renderer), Some(&dispatcher.upcast::<gst_player::PlayerSignalDispatcher>()))
};
#[cfg(feature = "video")]
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
#[cfg(feature = "video")]
let receiver = RefCell::new(Some(r));
let welcome_page = Self {
widget,
title: gettext("Welcome Tour"),
#[cfg(feature = "video")]
player,
#[cfg(feature = "video")]
sender,
#[cfg(feature = "video")]
receiver,
};
welcome_page.init();
......@@ -30,32 +59,99 @@ impl WelcomePageWidget {
}
fn init(&self) {
self.widget.set_property_expand(true);
self.widget.set_valign(gtk::Align::Center);
self.widget.set_halign(gtk::Align::Center);
self.widget.set_margin_top(24);
self.widget.set_margin_bottom(24);
let container = gtk::Box::new(gtk::Orientation::Vertical, 0);
let name = glib::get_os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::get_os_info("VERSION").unwrap_or_else(|| "3.36".into());
container.set_property_expand(true);
container.set_valign(gtk::Align::Center);
container.set_halign(gtk::Align::Center);
container.set_margin_top(24);
container.set_margin_bottom(24);
#[cfg(not(feature = "video"))]
let header = {
let icon = glib::get_os_info("LOGO").unwrap_or_else(|| "start-here-symbolic".into());
let logo = gtk::Image::from_icon_name(Some(&icon), gtk::IconSize::Dialog);
logo.set_pixel_size(196);
logo.show();
self.widget.add(&logo);
logo.upcast::<gtk::Widget>()
};
#[cfg(feature = "video")]
let header = {
let video_widget = self
.player
.get_pipeline()
.get_property("video-sink")
.unwrap()
.get::<gst::Element>()
.expect("The player of a VideoPlayerWidget should not use the default sink.")
.unwrap()
.get_property("widget")
.unwrap()
.get::<gtk::Widget>()
.unwrap()
.unwrap();
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
};
container.add(&header);
#[cfg(feature = "video")]
{
let receiver = self.receiver.borrow_mut().take().unwrap();
receiver.attach(
None,
clone!(@strong self.player as player => move |action| {
match action {
Action::VideoReady => player.play(),
Action::VideoUp => header.get_style_context().add_class("playing"),
};
glib::Continue(true)
}),
);
self.player.connect_state_changed(clone!(@strong self.sender as sender => move |_p,state| {
if state == gst_player::PlayerState::Playing {
sender.send(Action::VideoUp).unwrap();
}
}));
self.player.connect_uri_loaded(clone!(@strong self.sender as sender => move |_p, _uri| {
sender.send(Action::VideoReady).unwrap();
}));
self.player.connect_end_of_stream(move |p| p.stop());
let video_file = gio::File::new_for_path(config::VIDEO_PATH);
gtk::timeout_add(
500,
clone!(@strong self.player as player => move || {
player.set_uri(&video_file.get_uri());
glib::Continue(false)
}),
);
};
let name = glib::get_os_info("NAME").unwrap_or_else(|| "GNOME".into());
let version = glib::get_os_info("VERSION").unwrap_or_else(|| "3.36".into());
let title = gtk::Label::new(Some(&gettext(format!("Welcome to {} {}", name, version))));
title.set_margin_top(36);
title.get_style_context().add_class("large-title");
title.show();
self.widget.add(&title);
container.add(&title);
let text = gtk::Label::new(Some(&gettext("Hi there! Take the tour to learn your way around and discover essential features.")));
text.get_style_context().add_class("body");
text.set_margin_top(12);
text.show();
self.widget.add(&text);
container.add(&text);
let actions_container = gtk::Box::new(gtk::Orientation::Horizontal, 12);
actions_container.set_halign(gtk::Align::Center);
......@@ -81,7 +177,10 @@ impl WelcomePageWidget {
actions_container.show();
self.widget.add(&actions_container);
container.add(&actions_container);
container.show();
self.widget.add(&container);
self.widget.show();
}
}
......@@ -5,7 +5,6 @@ use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
use super::pages::Pageable;
use libhandy::prelude::{CarouselExt, CarouselIndicatorDotsExt, HeaderBarExt};
pub struct PaginatorWidget {
......@@ -13,7 +12,7 @@ pub struct PaginatorWidget {
carousel: libhandy::Carousel,
carousel_dots: libhandy::CarouselIndicatorDots,
headerbar: libhandy::HeaderBar,
pages: RefCell<Vec<Box<dyn Pageable>>>,
pages: RefCell<Vec<gtk::Widget>>,
current_page: RefCell<u32>,
next_btn: gtk::Button,
close_btn: gtk::Button,
......@@ -57,9 +56,9 @@ impl PaginatorWidget {
Ok(())
}
pub fn add_page(&self, page: Box<dyn Pageable>) {
pub fn add_page(&self, page: gtk::Widget) {
let page_nr = self.pages.borrow().len();
self.carousel.insert(&page.get_widget(), page_nr as i32);
self.carousel.insert(&page, page_nr as i32);
self.pages.borrow_mut().push(page);
self.update_position();
......@@ -135,7 +134,7 @@ impl PaginatorWidget {
if page_nr < self.carousel.get_n_pages() {
let pages = &self.pages.borrow();
let page = pages.get(page_nr as usize).unwrap();
self.carousel.scroll_to(&page.get_widget());
self.carousel.scroll_to(page);
}
}
}
......@@ -35,59 +35,73 @@ impl Window {
}
fn init(&mut self) {
self.widget.set_default_size(720, 500);
self.widget.set_default_size(960, 720);
self.widget.set_icon_name(Some(APP_ID));
// Devel Profile
if PROFILE == "Devel" {
self.widget.get_style_context().add_class("devel");
}
self.paginator.borrow_mut().add_page(Box::new(WelcomePageWidget::new()));
self.paginator.borrow_mut().add_page(WelcomePageWidget::new().widget.upcast::<gtk::Widget>());
self.paginator.borrow_mut().add_page(Box::new(ImagePageWidget::new(
self.paginator.borrow_mut().add_page(
ImagePageWidget::new(
"/org/gnome/Tour/activities.svg",
gettext("Activities Overview"),
gettext("Open Activities to launch apps"),
gettext("The activities view can also be used to switch windows and search."),
)));
)
.widget
.upcast::<gtk::Widget>(),
);
self.paginator.borrow_mut().add_page(Box::new(ImagePageWidget::new(
self.paginator.borrow_mut().add_page(
ImagePageWidget::new(
"/org/gnome/Tour/search.svg",
gettext("Search"),
gettext("Just type to search"),
gettext("In the activities view, just start typing to search for apps, settings and more."),
)));
)
.widget
.upcast::<gtk::Widget>(),
);
self.paginator.borrow_mut().add_page(Box::new(ImagePageWidget::new(
self.paginator.borrow_mut().add_page(
ImagePageWidget::new(
"/org/gnome/Tour/calendar.svg",
gettext("Date & Time"),
gettext("Click the time to see notifications"),
gettext("The notifications popover also includes personal planning tools."),
)));
)
.widget
.upcast::<gtk::Widget>(),
);
self.paginator.borrow_mut().add_page(Box::new(ImagePageWidget::new(
self.paginator.borrow_mut().add_page(
ImagePageWidget::new(
"/org/gnome/Tour/status-menu.svg",
gettext("System Menu"),
gettext("View system information and settings"),
gettext("Get an overview of the system status and quickly change settings."),
)));
)
.widget
.upcast::<gtk::Widget>(),
);
self.paginator.borrow_mut().add_page(Box::new(ImagePageWidget::new(
self.paginator.borrow_mut().add_page(
ImagePageWidget::new(
"/org/gnome/Tour/software.svg",
gettext("Software"),
gettext("Use Software to find and install apps"),
gettext("Discover great apps through search, browsing and our recommendations."),
)));
)
.widget
.upcast::<gtk::Widget>(),
);
let name = glib::get_os_info("NAME").unwrap_or_else(|| "GNOME".into());
let last_page = ImagePageWidget::new(
"/org/gnome/Tour/ready-to-go.svg",
gettext("Tour Completed"),
utils::i18n_f("That's it! We hope that you enjoy {}.", &[&name]),
gettext("To get more advice and tips, see the Help app."),
);
last_page.widget.get_style_context().add_class("last-page");
self.paginator.borrow_mut().add_page(Box::new(last_page));
self.paginator.borrow_mut().add_page(last_page.widget.upcast::<gtk::Widget>());
self.widget.add(&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