diff --git a/Cargo.lock b/Cargo.lock index 1b7f638e..0873b493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,6 +563,7 @@ dependencies = [ "gstreamer-pbutils", "gstreamer-player", "gtk", + "gtk-sys", "html2pango", "itertools 0.8.2", "lazy_static", diff --git a/fractal-gtk/Cargo.toml b/fractal-gtk/Cargo.toml index 16d1302e..04b8b794 100644 --- a/fractal-gtk/Cargo.toml +++ b/fractal-gtk/Cargo.toml @@ -31,6 +31,7 @@ serde_json = "1.0.48" letter-avatar = "1.3.0" sourceview4 = "0.2.0" gspell = "0.5.0" +gtk-sys = "0.10.0" [dependencies.gst] version = "0.16.1" diff --git a/fractal-gtk/res/ui/account_settings.ui b/fractal-gtk/res/ui/account_settings.ui index 8e7730b1..bc550faa 100644 --- a/fractal-gtk/res/ui/account_settings.ui +++ b/fractal-gtk/res/ui/account_settings.ui @@ -5,12 +5,42 @@ True False + vertical + + + True + False + True + Account Settings + + + True + True + True + app.deck-back + + + True + False + go-previous-symbolic + + + + + Back + + + + + + 200 True True never + True 200 @@ -567,44 +597,6 @@ - - False - True - 0 - - - - - True - False - - - True - False - True - True - Account Settings - - - True - True - True - app.back - - - True - False - go-previous-symbolic - - - - - Back - - - - - diff --git a/fractal-gtk/res/ui/login_flow.ui b/fractal-gtk/res/ui/login_flow.ui index ebaac45a..c504651f 100644 --- a/fractal-gtk/res/ui/login_flow.ui +++ b/fractal-gtk/res/ui/login_flow.ui @@ -2,63 +2,79 @@ - + False + True True - GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT - + True False - center - center vertical - 18 - + True False - center - 18 - 18 - 18 - 128 - chat-icon + True + Fractal - + True False - Welcome to Fractal - 48 - True - PANGO_WRAP_WORD_CHAR - - - - - - - - - True - True - _Log In - login.server_chooser - 48 - - - - - - True - True - _Create Account - 48 - login.create-account + center + center + True + vertical + 18 + + + True + False + center + 18 + 18 + 18 + 128 + chat-icon + + + + + True + False + Welcome to Fractal + 48 + True + PANGO_WRAP_WORD_CHAR + + + + + + + + + True + True + _Log In + login.server_chooser + 48 + + + + + + True + True + _Create Account + 48 + login.create-account + + @@ -67,315 +83,308 @@ - + True False - center - center vertical - 18 + True - + True False - center - 18 - 18 - 128 - network-server-symbolic - - - - - - True - False - What is your Provider? - 30 - True - PANGO_WRAP_WORD_CHAR - - - - + True + 360 + Choose Provider + + + True + True + login.back + + + True + go-previous-symbolic + + + + + start + + + + + True + True + True + login.credentials + _Next + + + + end + + - + True False + center + center + True vertical - 6 + 18 - + True + False center - -1 - 300 - GTK_INPUT_PURPOSE_URL + 18 + 18 + 128 + network-server-symbolic + True False - Matrix provider domain, e.g. myserver.co + What is your Provider? + 30 + True + PANGO_WRAP_WORD_CHAR + + + + + + + + + True + False + vertical + 6 + + + True + center + -1 + 300 + GTK_INPUT_PURPOSE_URL + + + + + True + False + Matrix provider domain, e.g. myserver.co + True + PANGO_WRAP_WORD_CHAR + + + + + + + + False + False + True + The domain may not be empty. True PANGO_WRAP_WORD_CHAR - - - False - False - True - The domain may not be empty. - True - PANGO_WRAP_WORD_CHAR - - - server-chooser - + True False - center - center - 12 - 24 - - - True - True - False - _User ID - end - end - True - PANGO_WRAP_WORD_CHAR - username_entry - - - + vertical - + True - True False - _Password - end - True - PANGO_WRAP_WORD_CHAR - password_entry - + True + Log In + + + True + True + login.back + + + True + go-previous-symbolic + + + + + start + + + + + True + True + True + login.login + _Log In + + + + end + + - - 3 - 0 - - + True False - vertical - 6 + center + center + True + 12 + 24 - + True - -1 - 232 - True + True + False + _User ID + end + end + True + PANGO_WRAP_WORD_CHAR + username_entry + True + True False - User name, email, or phone number - start + _Password + end True PANGO_WRAP_WORD_CHAR + password_entry + + 3 + 0 + - - - 1 - 2 - - - - - True - -1 - 232 - True - False - GTK_INPUT_PURPOSE_PASSWORD - - - 3 - 1 - - - - - True - _Forgot Password? - https://riot.im/app/#/login - start - - - - 4 - 1 - - - - - False - False - True - 0 - Invalid username or password - True - PANGO_WRAP_WORD_CHAR - - - - 5 - 1 - - - - - credentials - - - - - False - True - - - - - - True - False - True - Fractal - - - greeter - - - - - True - False - True - 360 - Choose Provider - - - True - True - login.back - + True - go-previous-symbolic + False + vertical + 6 + + + True + -1 + 232 + True + + + + + True + False + User name, email, or phone number + start + True + PANGO_WRAP_WORD_CHAR + + + + + 1 + 2 + - - - start - - - - - True - True - True - login.credentials - _Next - - - - end - - - - - server-chooser - - - - - True - False - True - Log In - - - True - True - login.back - + True - go-previous-symbolic + -1 + 232 + True + False + GTK_INPUT_PURPOSE_PASSWORD + + 3 + 1 + + + + + True + _Forgot Password? + https://riot.im/app/#/login + start + + + + 4 + 1 + + + + + False + False + True + 0 + Invalid username or password + True + PANGO_WRAP_WORD_CHAR + + + + 5 + 1 + - - start - - - - - True - True - True - login.login - _Log In - - - - end - diff --git a/fractal-gtk/res/ui/main_window.ui b/fractal-gtk/res/ui/main_window.ui index 4762cd2f..d0a6624c 100644 --- a/fractal-gtk/res/ui/main_window.ui +++ b/fractal-gtk/res/ui/main_window.ui @@ -2,610 +2,698 @@ - + False 860 640 False - - False - False + + True - - - - - - True + False - True + False - - 200 + True - False - fill - False - vertical - - - 200 - True - False - - - True - True - edit-find-symbolic - False - False - - - - - False - True - 0 - - + True + True + False - - 200 + True - True - never + room_list + True - - 200 + True - False - none + vertical - + True False - vertical + fill + True - + + True + True + True + user_popover + + + True + False + open-menu-symbolic + + + + + + + + User + + + + + end + 0 + + + + + True + True + True + add_room_popover + + + True + False + list-add-symbolic + + + + + + + + Add + + + + + end + 1 + + + + + True + True + True + + + True + False + system-search-symbolic + + + + + + + + + + Room search + + + - - - - - - True - True - 1 - - - - - sidebar - - - - - True - False - - - - - - True - False - - - True - False - - - room - False - + + 200 True False + fill + False + True vertical - + + 200 True False - vertical + + + True + True + edit-find-symbolic + False + False + + - True + False True - 1 + 0 - + + 200 True - + True + never + + + 200 + True + False + none + + + True + False + vertical + + + + + + + + + + True + True + 1 + - - True - True - 0 - - room_view - room_view + sidebar - + True False - True + vertical + - loading - loading - 1 + False - + True - False vertical - 30 - center - - - True - False - 16 - /org/gnome/Fractal/icons/chat-icon.svg - - - - True - False - No room selected - 3 - center - - - - + + True + False + true + 360 + + + + + crossfade + True + + + app.back + True + True + True + + + True + False + go-previous-symbolic + + + + + + + + Back + + + + + + + + + True + False + True + center + never + never + True + False + + + True + False + vertical + + + False + + Room name + end + + + + False + True + 0 + + + + + False + + Room topic + end + + + + False + True + 1 + + + + + + + + + True + True + True + room_popover + + + True + False + view-more-symbolic + + + + + + + Room Menu + + + + + end + 1 + + - + True False - Join a room to start chatting - center - - - - - - noroom - No room - 2 - - - - - -1 - - - - - True - False - start - center - - - True - False - center - - - True - False - 0 - none - + True False - 10 - + + False + + + True + False + vertical + + + True + False + vertical + + + True + True + 1 + + + + + True + + + + + + True + True + 0 + + + + + room_view + room_view + + + + True - True False + True - False - True - 0 + loading + loading + 1 - + True False + vertical + 30 + center + True + + + True + False + 16 + /org/gnome/Fractal/icons/chat-icon.svg + + + + + True + False + No room selected + 3 + center + + + + + + + + + True + False + Join a room to start chatting + center + + + - False - True - end - 0 + noroom + No room + 2 + + -1 + - - - - - - - - - True - -1 - - - - - - - content - - - - - chat - Chat - - - - - True - False - vertical - - - True - True - never - - - True - False - - - False - - - True - False - - + + True False - True + start + center + + + True + False + center + + + True + False + 0 + none + + + True + False + 10 + + + True + True + False + + + False + True + 0 + + + + + True + False + + + False + True + end + 0 + + + + + + + + + + + + + True + -1 + + - - True - True - 0 - - - - - - - True - True - 1 - - - - - directory - Directory - 1 - - - - - True - False - vertical - - - True - False - True - - - True - True - 0 - - - - - loading - Loading - 2 - - - - - - - True - - - True - False - True - False - - - True - True - over - True - - - True - False - fill - True - - - True - True - True - user_popover - - - True - False - open-menu-symbolic - - - - - - - - User - - - - - end - 0 - - - - - True - True - True - add_room_popover - - - True - False - list-add-symbolic - - - - - - - - Add - - - - end - 1 + content - - - True - True - True - - - True - False - system-search-symbolic - - - - - - - - - - Room search - - - - - sidebar + chat - - True + False - vertical - - - - - - True - False - true - 360 + False - - - - crossfade + True + False + vertical - - app.back - True - True - True + + False + True + 360 + HDY_CENTERING_POLICY_STRICT - + True - False - go-previous-symbolic - - - - - - - - Back + True + True + app.deck-back + + + True + False + go-previous-symbolic + + + + + Back + + - - - - - - - True - False - True - never - never - True - False - - - True - False - vertical - - + + False - - Room name - end - + True + 288 + 288 + + + True + True + True + edit-find-symbolic + False + False + + - - False - True - 0 - - - False - - Room topic - end - + + True + True + True + server_chooser_popover + + + + + True + False + 6 + + + True + False + Default Matrix Server + + + False + True + 0 + + + + + True + False + pan-down-symbolic + + + False + True + 1 + + + + + + + True + False + 6 + + + True + False + network-server-symbolic + + + + + True + False + pan-down-symbolic + + + + + + - False - True - 1 + end - - - - - True - True - True - room_popover - + True False - view-more-symbolic - - - - - - - Room Menu + vertical + + + True + True + never + True + fill + fill + + + True + False + + + False + + + True + False + + + True + False + True + + + True + True + 0 + + + + + + + + + + - end - 1 + directory - content + subview - normal - normal - 1 + main_view @@ -614,147 +702,34 @@ False vertical - + False True Fractal - - - loading - loading - 1 - - - - - True - False - vertical - + + True False - True - 360 - HDY_CENTERING_POLICY_STRICT + vertical - + True - True - True - app.back - - - True - False - go-previous-symbolic - - - - - Back - - - - - - False - True - 288 - 288 - - - True - True - True - edit-find-symbolic - False - False - - - - - - - True - True - True - server_chooser_popover - - - - - True - False - 6 - - - True - False - Default Matrix Server - - - False - True - 0 - - - - - True - False - pan-down-symbolic - - - False - True - 1 - - - - - - - True - False - 6 - - - True - False - network-server-symbolic - - - - - True - False - pan-down-symbolic - - - - - - + True + True + fill + fill - end - - False - True - 0 - - back - Back - 2 + loading @@ -765,38 +740,10 @@ - - - horizontal - - - - - - - horizontal - - - - - - - horizontal - - - - - - - - - - - diff --git a/fractal-gtk/res/ui/media_viewer.ui b/fractal-gtk/res/ui/media_viewer.ui index 3ecf81a5..df16f4ef 100644 --- a/fractal-gtk/res/ui/media_viewer.ui +++ b/fractal-gtk/res/ui/media_viewer.ui @@ -5,7 +5,84 @@ True False - True + vertical + + + True + False + + + True + False + + Media viewer + True + + + True + True + True + app.deck-back + + + True + False + go-previous-symbolic + + + + + Back + + + + + + + True + True + True + + + True + False + view-more-symbolic + + + + + end + 1 + + + + + True + True + True + Toggle fullscreen + + + True + False + view-fullscreen-symbolic + + + + + end + 2 + + + + + True + True + 0 + + + + True @@ -208,79 +285,4 @@ - - True - False - - - True - False - - Media viewer - True - - - True - True - True - app.back - - - True - False - go-previous-symbolic - - - - - Back - - - - - - - True - True - True - - - True - False - view-more-symbolic - - - - - end - 1 - - - - - True - True - True - Toggle fullscreen - - - True - False - view-fullscreen-symbolic - - - - - end - 2 - - - - - True - True - 0 - - - diff --git a/fractal-gtk/res/ui/room_settings.ui b/fractal-gtk/res/ui/room_settings.ui index 5d91404b..1832290b 100644 --- a/fractal-gtk/res/ui/room_settings.ui +++ b/fractal-gtk/res/ui/room_settings.ui @@ -5,9 +5,39 @@ True False + vertical + + + True + False + True + Details + + + True + True + True + app.deck-back + + + True + False + go-previous-symbolic + + + + + Back + + + + + + True + True True never @@ -999,44 +1029,6 @@ - - False - True - 0 - - - - - True - False - - - True - False - True - True - Details - - - True - True - True - app.back - - - True - False - go-previous-symbolic - - - - - Back - - - - - diff --git a/fractal-gtk/src/actions/global.rs b/fractal-gtk/src/actions/global.rs index 936e55cf..2db1ad22 100644 --- a/fractal-gtk/src/actions/global.rs +++ b/fractal-gtk/src/actions/global.rs @@ -13,8 +13,9 @@ use fractal_api::identifiers::{EventId, RoomId}; use gio::prelude::*; use gio::SimpleAction; use gtk::prelude::*; +use libhandy::prelude::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum AppState { Login, Loading, @@ -81,6 +82,7 @@ pub fn new(app: >k::Application, op: &Arc>) { let main_menu = SimpleAction::new("main_menu", None); let open_room = SimpleAction::new("open-room", glib::VariantTy::new("s").ok()); + let deck_back = SimpleAction::new("deck-back", None); let back = SimpleAction::new("back", None); let media_viewer = SimpleAction::new("open-media-viewer", glib::VariantTy::new("s").ok()); let account = SimpleAction::new("open-account-settings", None); @@ -115,6 +117,7 @@ pub fn new(app: >k::Application, op: &Arc>) { app.add_action(&shortcuts); app.add_action(&about); app.add_action(&open_room); + app.add_action(&deck_back); app.add_action(&back); app.add_action(&directory); app.add_action(&room_settings); @@ -272,6 +275,13 @@ pub fn new(app: >k::Application, op: &Arc>) { back.borrow_mut().push(AppState::MediaViewer); })); + deck_back.connect_activate(clone!(@strong op => move |_, _| { + let deck = op.lock().unwrap().deck.clone(); + if deck.get_can_swipe_back() { + deck.navigate(libhandy::NavigationDirection::Back); + } + })); + let mv = op.lock().unwrap().media_viewer.clone(); let back_weak = Rc::downgrade(&back_history); back.connect_activate(clone!(@weak mv => move |_, _| { @@ -344,6 +354,7 @@ pub fn new(app: >k::Application, op: &Arc>) { app.set_accels_for_action("app.older-messages", &["Page_Up"]); app.set_accels_for_action("app.newer-messages", &["Page_Down"]); app.set_accels_for_action("app.back", &["Escape"]); + app.set_accels_for_action("app.deck-back", &["Escape"]); app.set_accels_for_action("app.main_menu", &["F10"]); // connect mouse back button to app.back action diff --git a/fractal-gtk/src/actions/login.rs b/fractal-gtk/src/actions/login.rs index 8bac5a90..72991c1d 100644 --- a/fractal-gtk/src/actions/login.rs +++ b/fractal-gtk/src/actions/login.rs @@ -1,6 +1,5 @@ -use log::{debug, warn}; -use std::cell::RefCell; -use std::rc::Rc; +use libhandy::prelude::*; +use log::warn; use gio::prelude::*; use gio::SimpleAction; @@ -41,8 +40,7 @@ impl ToString for LoginState { } pub fn new( - stack: >k::Stack, - headers: >k::Stack, + deck: &libhandy::Deck, server_entry: >k::Entry, err_label: >k::Label, ) -> SimpleActionGroup { @@ -60,8 +58,8 @@ pub fn new( actions.add_action(&back); actions.add_action(&login); - create_account.connect_activate(clone!(@weak stack => move |_, _| { - let toplevel = stack + create_account.connect_activate(clone!(@weak deck => move |_, _| { + let toplevel = deck .get_toplevel() .expect("Could not grab toplevel widget") .downcast::() @@ -73,40 +71,25 @@ pub fn new( } })); - let back_history: Rc>> = Rc::new(RefCell::new(vec![])); - - server_chooser.connect_activate( - clone!(@weak stack, @weak back_history as back => move |_, _| { - let state = LoginState::ServerChooser; - stack.set_visible_child_name(&state.to_string()); - back.borrow_mut().push(state); - }), - ); + server_chooser.connect_activate(clone!(@weak deck => move |_, _| { + deck.navigate(libhandy::NavigationDirection::Forward); + })); credentials.connect_activate(clone!( - @weak stack, - @weak back_history as back, + @weak err_label, @weak server_entry, - @weak err_label - => move |_, _| { + @weak deck => move |_, _| { if server_entry.get_text().is_empty() { err_label.show(); } else { err_label.hide(); - let state = LoginState::Credentials; - stack.set_visible_child_name(&state.to_string()); - back.borrow_mut().push(state); + deck.navigate(libhandy::NavigationDirection::Forward); } })); - back.connect_activate(clone!(@weak stack => move |_, _| { - back_history.borrow_mut().pop(); - if let Some(state) = back_history.borrow().last() { - debug!("Go back to state {}", state.to_string()); - stack.set_visible_child_name(&state.to_string()); - } else { - debug!("There is no state to go back to. Go back to state greeter"); - stack.set_visible_child_name(&LoginState::Greeter.to_string()); + back.connect_activate(clone!(@weak deck => move |_, _| { + if let Some(_) = deck.get_adjacent_child(libhandy::NavigationDirection::Back) { + deck.navigate(libhandy::NavigationDirection::Back); } })); @@ -123,8 +106,7 @@ pub fn new( }) }); - stack.insert_action_group("login", Some(&actions)); - headers.insert_action_group("login", Some(&actions)); + deck.insert_action_group("login", Some(&actions)); actions } diff --git a/fractal-gtk/src/actions/message.rs b/fractal-gtk/src/actions/message.rs index a3234821..09aa4adc 100644 --- a/fractal-gtk/src/actions/message.rs +++ b/fractal-gtk/src/actions/message.rs @@ -162,7 +162,7 @@ pub fn new( Continue(true) }, Ok(Ok(fname)) => { - if let Some(path) = save(&window, &name, &[]) { + if let Some(path) = save(&window.upcast_ref(), &name, &[]) { // TODO use glib to copy file if fs::copy(fname, path).is_err() { ErrorDialog::new(false, &i18n("Couldn’t save file")); diff --git a/fractal-gtk/src/app/connect/headerbar.rs b/fractal-gtk/src/app/connect/headerbar.rs index 961d5615..99a3e06e 100644 --- a/fractal-gtk/src/app/connect/headerbar.rs +++ b/fractal-gtk/src/app/connect/headerbar.rs @@ -1,18 +1,19 @@ use glib::clone; use gtk::prelude::*; +use libhandy::HeaderBarExt; use crate::app::App; impl App { pub fn connect_headerbars(&self) { if let Some(set) = gtk::Settings::get_default() { - let left_header: gtk::HeaderBar = self + let left_header: libhandy::HeaderBar = self .ui .builder .get_object("left-header") .expect("Can't find left-header in ui file."); - let right_header: gtk::HeaderBar = self + let right_header: libhandy::HeaderBar = self .ui .builder .get_object("room_header_bar") diff --git a/fractal-gtk/src/app/connect/mod.rs b/fractal-gtk/src/app/connect/mod.rs index a180abed..2a2514bb 100644 --- a/fractal-gtk/src/app/connect/mod.rs +++ b/fractal-gtk/src/app/connect/mod.rs @@ -11,6 +11,7 @@ mod markdown; mod new_room; mod roomlist_search; mod send; +mod swipeable_widgets; use crate::app::App; @@ -34,5 +35,6 @@ impl App { self.connect_direct_chat(); self.connect_roomlist_search(); + self.connect_swipeable_widgets(); } } diff --git a/fractal-gtk/src/app/connect/swipeable_widgets.rs b/fractal-gtk/src/app/connect/swipeable_widgets.rs new file mode 100644 index 00000000..6514a205 --- /dev/null +++ b/fractal-gtk/src/app/connect/swipeable_widgets.rs @@ -0,0 +1,67 @@ +use gio::prelude::*; +use gtk::prelude::*; +use libhandy::prelude::*; + +use crate::app::App; + +impl App { + // Set up HdyDeck and HdyLeaflet so that swipes trigger the + // same behaviour as the back button. + pub fn connect_swipeable_widgets(&self) { + let deck: libhandy::Deck = self + .ui + .builder + .get_object("main_deck") + .expect("Can't find main_deck in UI file"); + let leaflet: libhandy::Leaflet = self + .ui + .builder + .get_object("chat_page") + .expect("Can't find chat_page in UI file"); + + let app = gio::Application::get_default() + .expect("Could not get default application") + .downcast::() + .unwrap(); + let global_back = app + .lookup_action("back") + .expect("Could not get back action"); + + deck.connect_property_transition_running_notify( + clone!(@weak app, @weak global_back => move |deck| { + let child: Option = deck.get_visible_child_name().map(|g| g.to_string()); + if !deck.get_transition_running() && child == Some("chat".to_string()) { + // Re-enable global back when returning to main view + let _ = global_back.set_property("enabled", &true); + app.activate_action("back", None); + } + }), + ); + + deck.connect_property_visible_child_notify( + clone!(@weak app, @weak global_back => move |deck| { + let child: Option = deck.get_visible_child_name().map(|g| g.to_string()); + if !deck.get_transition_running() && child == Some("chat".to_string()) { + let _ = global_back.set_property("enabled", &true); + app.activate_action("back", None); + } + }), + ); + + leaflet.connect_property_child_transition_running_notify( + clone!(@weak app => move |leaflet| { + let child: Option = leaflet.get_visible_child_name().map(|g| g.to_string()); + if !leaflet.get_child_transition_running() && child == Some("sidebar".to_string()) { + app.activate_action("back", None); + } + }), + ); + + leaflet.connect_property_visible_child_notify(clone!(@weak app => move |leaflet| { + let child: Option = deck.get_visible_child_name().map(|g| g.to_string()); + if !leaflet.get_child_transition_running() && child == Some("sidebar".to_string()) { + app.activate_action("back", None); + } + })); + } +} diff --git a/fractal-gtk/src/app/mod.rs b/fractal-gtk/src/app/mod.rs index 68b7a983..2e9824ff 100644 --- a/fractal-gtk/src/app/mod.rs +++ b/fractal-gtk/src/app/mod.rs @@ -41,7 +41,7 @@ macro_rules! APPOP { // Our application struct for containing all the state we have to carry around. // TODO: subclass gtk::Application once possible pub struct App { - main_window: gtk::ApplicationWindow, + main_window: libhandy::ApplicationWindow, /* Add widget directly here in place of uibuilder::UI*/ ui: uibuilder::UI, @@ -71,7 +71,7 @@ impl App { ); let ui = uibuilder::UI::new(); - let window: gtk::ApplicationWindow = ui + let window: libhandy::ApplicationWindow = ui .builder .get_object("main_window") .expect("Couldn't find main_window in ui file."); @@ -98,8 +98,8 @@ impl App { let leaflet = ui .builder - .get_object::("chat_state_leaflet") - .expect("Can't find chat_state_leaflet in ui file."); + .get_object::("chat_page") + .expect("Can't find chat_page in ui file."); let container = ui .builder .get_object::("history_container") @@ -124,33 +124,28 @@ impl App { } })); - let stack = ui + let view_stack = ui .builder - .get_object::("main_content_stack") - .expect("Can't find main_content_stack in ui file."); - let stack_header = ui - .builder - .get_object::("headerbar_stack") - .expect("Can't find headerbar_stack in ui file."); + .get_object::("subview_stack") + .expect("Can't find subview_stack in ui file."); - /* Add account settings view to the main stack */ + /* Add account settings view to the view stack */ let child = ui .builder .get_object::("account_settings_box") .expect("Can't find account_settings_box in ui file."); - let child_header = ui - .builder - .get_object::("account_settings_headerbar") - .expect("Can't find account_settings_headerbar in ui file."); - stack.add_named(&child, "account-settings"); - stack_header.add_named(&child_header, "account-settings"); + view_stack.add_named(&child, "account-settings"); let op = Arc::new(Mutex::new(AppOp::new(ui.clone()))); + let main_stack = ui + .builder + .get_object::("main_content_stack") + .expect("Can't find main_content_stack in ui file."); + // Add login view to the main stack let login = widgets::LoginWidget::new(&op); - stack.add_named(&login.container, "login"); - stack_header.add_named(&login.headers, "login"); + main_stack.add_named(&login.container, "login"); gtk_app.set_accels_for_action("login.back", &["Escape"]); @@ -175,6 +170,9 @@ impl App { // Create application let app = App::new(gtk_app); + // Initialize libhandy + libhandy::init(); + gtk_app.connect_activate(clone!(@weak app => move |_| { app.on_activate(); })); @@ -186,7 +184,8 @@ impl App { app.main_window.connect_delete_event(move |window, _| { let settings: gio::Settings = gio::Settings::new("org.gnome.Fractal"); - let window_state = WindowState::from_window(window); + let w = window.upcast_ref(); + let window_state = WindowState::from_window(w); if let Err(err) = window_state.save_in_gsettings(&settings) { error!("Can't save the window settings: {:?}", err); } diff --git a/fractal-gtk/src/appop/media_viewer.rs b/fractal-gtk/src/appop/media_viewer.rs index 8a76fee1..786fe400 100644 --- a/fractal-gtk/src/appop/media_viewer.rs +++ b/fractal-gtk/src/appop/media_viewer.rs @@ -18,13 +18,8 @@ impl AppOp { let stack = self .ui .builder - .get_object::("main_content_stack") - .expect("Can't find main_content_stack in ui file."); - let stack_header = self - .ui - .builder - .get_object::("headerbar_stack") - .expect("Can't find headerbar_stack in ui file."); + .get_object::("subview_stack") + .expect("Can't find subview_stack in ui file."); let main_window = self .ui @@ -66,12 +61,8 @@ impl AppOp { if let Some(widget) = stack.get_child_by_name("media-viewer") { stack.remove(&widget); } - if let Some(widget) = stack_header.get_child_by_name("media-viewer") { - stack_header.remove(&widget); - } stack.add_named(&body, "media-viewer"); - stack_header.add_named(&header, "media-viewer"); } self.set_state(AppState::MediaViewer); diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs index 328d1094..97d895b8 100644 --- a/fractal-gtk/src/appop/mod.rs +++ b/fractal-gtk/src/appop/mod.rs @@ -116,6 +116,7 @@ pub struct AppOp { pub directory: Vec, pub leaflet: libhandy::Leaflet, + pub deck: libhandy::Deck, pub thread_pool: ThreadPool, pub user_info_cache: UserInfoCache, @@ -127,8 +128,12 @@ impl AppOp { pub fn new(ui: uibuilder::UI) -> AppOp { let leaflet = ui .builder - .get_object::("header_leaflet") - .expect("Couldn't find header_leaflet in ui file"); + .get_object::("chat_page") + .expect("Couldn't find chat_page in ui file"); + let deck = ui + .builder + .get_object::("main_deck") + .expect("Couldn't find main_deck in ui file"); AppOp { ui, @@ -159,6 +164,7 @@ impl AppOp { directory: vec![], leaflet, + deck, thread_pool: ThreadPool::new(20), user_info_cache: Arc::new(Mutex::new( diff --git a/fractal-gtk/src/appop/room_settings.rs b/fractal-gtk/src/appop/room_settings.rs index 841e109d..f961df67 100644 --- a/fractal-gtk/src/appop/room_settings.rs +++ b/fractal-gtk/src/appop/room_settings.rs @@ -16,13 +16,8 @@ impl AppOp { let stack = self .ui .builder - .get_object::("main_content_stack") - .expect("Can't find main_content_stack in ui file."); - let stack_header = self - .ui - .builder - .get_object::("headerbar_stack") - .expect("Can't find headerbar_stack in ui file."); + .get_object::("subview_stack") + .expect("Can't find subview_stack in ui file."); { let room = self.rooms.get(&self.active_room.clone()?)?; @@ -33,18 +28,14 @@ impl AppOp { login_data.server_url, login_data.access_token, ); - let (body, header) = panel.create()?; + let page = panel.create()?; /* remove old panel */ if let Some(widget) = stack.get_child_by_name("room-settings") { stack.remove(&widget); } - if let Some(widget) = stack_header.get_child_by_name("room-settings") { - stack_header.remove(&widget); - } - stack.add_named(&body, "room-settings"); - stack_header.add_named(&header, "room-settings"); + stack.add_named(&page, "room-settings"); self.room_settings = Some(panel); } diff --git a/fractal-gtk/src/appop/state.rs b/fractal-gtk/src/appop/state.rs index 6a062a9b..aa7811bd 100644 --- a/fractal-gtk/src/appop/state.rs +++ b/fractal-gtk/src/appop/state.rs @@ -1,5 +1,6 @@ +use gio::prelude::*; use gtk::prelude::*; -use libhandy::LeafletExt; +use libhandy::prelude::*; use super::RoomSearchPagination; use crate::actions::AppState; @@ -8,6 +9,79 @@ use crate::appop::AppOp; impl AppOp { pub fn set_state(&mut self, state: AppState) { self.state = state; + + match self.state { + AppState::Login => self.set_stack_state("login"), + AppState::NoRoom | AppState::Room => { + self.set_stack_state("main_view"); + self.set_chat_state(state); + } + AppState::Directory => self.set_deck_state(Some("directory"), state), + AppState::Loading => self.set_stack_state("loading"), + AppState::AccountSettings => self.set_deck_state(Some("account-settings"), state), + AppState::RoomSettings => self.set_deck_state(Some("room-settings"), state), + AppState::MediaViewer => self.set_deck_state(Some("media-viewer"), state), + }; + + //set focus for room directory + if let AppState::Directory = self.state { + self.ui + .builder + .get_object::("directory_search_entry") + .expect("Can't find widget to set focus in ui file.") + .grab_focus(); + self.directory_pagination = RoomSearchPagination::Initial; + self.search_rooms(); + } + } + + fn set_deck_state(&self, view: Option<&str>, state: AppState) { + let deck = self + .ui + .builder + .get_object::("main_deck") + .expect("Could not find main_deck in ui file"); + let stack = self + .ui + .builder + .get_object::("subview_stack") + .expect("Could not find subview_stack in ui file"); + let app = gio::Application::get_default().unwrap(); + + let global_back = app.lookup_action("back"); + + let direction = match state { + AppState::Room | AppState::NoRoom => libhandy::NavigationDirection::Back, + _ => libhandy::NavigationDirection::Forward, + }; + + if let Some(v) = view { + stack.set_visible_child_name(v); + } + + if deck.get_adjacent_child(direction).is_some() { + deck.navigate(direction); + if direction == libhandy::NavigationDirection::Forward { + // Disable global back while in a subview + global_back.map(|a| a.set_property("enabled", &false)); + } + } + } + + fn set_stack_state(&self, state: &str) { + self.ui + .builder + .get_object::("main_content_stack") + .expect("Can't find main_content_stack in ui file.") + .set_visible_child_name(state); + } + + fn set_chat_state(&mut self, state: AppState) { + let deck = self + .ui + .builder + .get_object::("main_deck") + .expect("Could not find main_deck in ui file"); let stack = self .ui .builder @@ -16,74 +90,33 @@ impl AppOp { let headerbar = self .ui .builder - .get_object::("room_header_bar") + .get_object::("room_header_bar") .expect("Can't find room_header_bar in ui file."); - let widget_name = match self.state { - AppState::Login => "login", + match state { AppState::NoRoom => { self.set_state_no_room(&headerbar); - self.leaflet.set_visible_child_name("sidebar"); + self.leaflet.navigate(libhandy::NavigationDirection::Back); stack.set_visible_child_name("noroom"); - "chat" } AppState::Room => { self.set_state_room(&headerbar); - self.leaflet.set_visible_child_name("content"); + self.leaflet + .navigate(libhandy::NavigationDirection::Forward); stack.set_visible_child_name("room_view"); - "chat" } - AppState::Directory => "directory", - AppState::Loading => "loading", - AppState::AccountSettings => "account-settings", - AppState::RoomSettings => "room-settings", - AppState::MediaViewer => "media-viewer", - }; - - self.ui - .builder - .get_object::("main_content_stack") - .expect("Can't find main_content_stack in ui file.") - .set_visible_child_name(widget_name); - - //setting headerbar - let bar_name = match self.state { - AppState::Login => "login", - AppState::Directory => "back", - AppState::Loading => "loading", - AppState::AccountSettings => "account-settings", - AppState::RoomSettings => "room-settings", - AppState::MediaViewer => "media-viewer", - _ => "normal", - }; - - self.ui - .builder - .get_object::("headerbar_stack") - .expect("Can't find headerbar_stack in ui file.") - .set_visible_child_name(bar_name); - - //set focus for views - let widget_focus = match self.state { - AppState::Directory => "directory_search_entry", - _ => "", - }; - - if !widget_focus.is_empty() { - self.ui - .builder - .get_object::(widget_focus) - .expect("Can't find widget to set focus in ui file.") - .grab_focus(); + _ => (), } - if let AppState::Directory = self.state { - self.directory_pagination = RoomSearchPagination::Initial; - self.search_rooms(); + if deck + .get_adjacent_child(libhandy::NavigationDirection::Back) + .is_some() + { + deck.navigate(libhandy::NavigationDirection::Back); } } - fn set_state_room(&self, headerbar: >k::HeaderBar) { + fn set_state_room(&self, headerbar: &libhandy::HeaderBar) { for ch in headerbar.get_children().iter() { ch.show(); } @@ -105,7 +138,7 @@ impl AppOp { } // WORKAROUND this is needed because NoRoom isn't a real app state - fn set_state_no_room(&mut self, headerbar: >k::HeaderBar) { + fn set_state_no_room(&mut self, headerbar: &libhandy::HeaderBar) { for ch in headerbar.get_children().iter() { ch.hide(); diff --git a/fractal-gtk/src/main.rs b/fractal-gtk/src/main.rs index 73326ab1..4d8244c2 100644 --- a/fractal-gtk/src/main.rs +++ b/fractal-gtk/src/main.rs @@ -1,4 +1,6 @@ #![deny(dead_code, unused_imports, unused_variables)] +#[macro_use] +extern crate glib; mod backend; mod client; diff --git a/fractal-gtk/src/meson.build b/fractal-gtk/src/meson.build index feb1696c..9c670fb9 100644 --- a/fractal-gtk/src/meson.build +++ b/fractal-gtk/src/meson.build @@ -66,6 +66,7 @@ app_sources = files( 'app/connect/new_room.rs', 'app/connect/roomlist_search.rs', 'app/connect/send.rs', + 'app/connect/swipeable_widgets.rs', 'app/mod.rs', 'app/windowstate.rs', 'appop/about.rs', diff --git a/fractal-gtk/src/widgets/login.rs b/fractal-gtk/src/widgets/login.rs index f9b22f89..8757982f 100644 --- a/fractal-gtk/src/widgets/login.rs +++ b/fractal-gtk/src/widgets/login.rs @@ -2,6 +2,7 @@ use fractal_api::url::Url; use gio::prelude::*; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use log::info; use crate::actions; @@ -18,8 +19,7 @@ use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] pub struct LoginWidget { - pub container: gtk::Stack, - pub headers: gtk::Stack, + pub container: libhandy::Deck, pub server_entry: gtk::Entry, pub username_entry: gtk::Entry, pub password_entry: gtk::Entry, @@ -157,8 +157,7 @@ impl Default for LoginWidget { fn default() -> Self { let builder = gtk::Builder::from_resource("/org/gnome/Fractal/ui/login_flow.ui"); - let container: gtk::Stack = builder.get_object("login_flow_stack").unwrap(); - let headers: gtk::Stack = builder.get_object("login_flow_headers").unwrap(); + let container: libhandy::Deck = builder.get_object("login_flow_deck").unwrap(); let server_entry = builder.get_object("server_chooser_entry").unwrap(); let username_entry = builder.get_object("username_entry").unwrap(); let password_entry = builder.get_object("password_entry").unwrap(); @@ -166,14 +165,12 @@ impl Default for LoginWidget { let server_err_label = builder.get_object("server_err_label").unwrap(); let credentials_err_label = builder.get_object("credentials_err_label").unwrap(); - let actions = actions::Login::new(&container, &headers, &server_entry, &server_err_label); + let actions = actions::Login::new(&container, &server_entry, &server_err_label); container.show_all(); - headers.show_all(); LoginWidget { container, - headers, server_entry, username_entry, password_entry, diff --git a/fractal-gtk/src/widgets/media_viewer.rs b/fractal-gtk/src/widgets/media_viewer.rs index b1475b01..359f253e 100644 --- a/fractal-gtk/src/widgets/media_viewer.rs +++ b/fractal-gtk/src/widgets/media_viewer.rs @@ -17,6 +17,7 @@ use glib::signal; use glib::source::Continue; use gtk::prelude::*; use gtk::Overlay; +use libhandy::HeaderBarExt; use crate::types::Message; use crate::types::Room; @@ -157,7 +158,7 @@ impl Data { .expect("Can't find media_viewer_headerbar_box in ui file."); let media_viewer_headerbar = self .builder - .get_object::("media_viewer_headerbar") + .get_object::("media_viewer_headerbar") .expect("Can't find media_viewer_headerbar in ui file."); let headerbar_revealer = self .builder @@ -209,7 +210,7 @@ impl Data { .expect("Can't find media_viewer_headerbar_box in ui file."); let media_viewer_headerbar = self .builder - .get_object::("media_viewer_headerbar") + .get_object::("media_viewer_headerbar") .expect("Can't find media_viewer_headerbar in ui file."); let headerbar_revealer = self .builder @@ -225,7 +226,6 @@ impl Data { } media_viewer_headerbar.pack_start(&media_viewer_back_button); - media_viewer_headerbar.set_child_position(&media_viewer_back_button, 0); media_viewer_headerbar.set_show_close_button(true); let full_screen_button_icon = self @@ -991,7 +991,7 @@ impl MediaViewer { fn set_header_title(ui: >k::Builder, title: &str) { let media_viewer_headerbar = ui - .get_object::("media_viewer_headerbar") + .get_object::("media_viewer_headerbar") .expect("Cant find media_viewer_headerbar in ui file."); media_viewer_headerbar.set_title(Some(title)); } diff --git a/fractal-gtk/src/widgets/room_settings.rs b/fractal-gtk/src/widgets/room_settings.rs index 681454cb..132ec785 100644 --- a/fractal-gtk/src/widgets/room_settings.rs +++ b/fractal-gtk/src/widgets/room_settings.rs @@ -68,15 +68,11 @@ impl RoomSettings { /* creates a empty list with members.len() rows, the content will be loaded when the row is * drawn */ - pub fn create(&mut self) -> Option<(gtk::Box, gtk::Box)> { - let body = self + pub fn create(&mut self) -> Option { + let page = self .builder .get_object::("room_settings_box") .expect("Can't find room_settings_box in ui file."); - let header = self - .builder - .get_object::("room_settings_headerbar") - .expect("Can't find room_settings_headerbar in ui file."); let stack = self .builder .get_object::("room_settings_stack") @@ -94,7 +90,7 @@ impl RoomSettings { self.init_room_settings(); self.connect(); - Some((body, header)) + Some(page) } pub fn connect(&mut self) {