diff --git a/Cargo.toml b/Cargo.toml
index 3e207658..f3fb9d4a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,6 @@ git = "https://github.com/gtk-rs/gtk4-rs"
# We need to use the same version as libadwaita does
#rev = "abea0c9980bc083494eceb30dfab5eeb99a73118"
-[dependencies.libadwaita]
+[dependencies.adw]
package = "libadwaita"
git = "https://gitlab.gnome.org/bilelmoussaoui/libadwaita-rs.git"
diff --git a/data/resources.gresource.xml b/data/resources.gresource.xml
new file mode 100644
index 00000000..025bbe47
--- /dev/null
+++ b/data/resources.gresource.xml
@@ -0,0 +1,14 @@
+
+
+
+ resources/ui/shortcuts.ui
+ resources/ui/content.ui
+ resources/ui/login.ui
+ resources/ui/session.ui
+ resources/ui/sidebar.ui
+ resources/ui/window.ui
+
+ resources/style.css
+ resources/icons/scalable/actions/send-symbolic.svg
+
+
diff --git a/data/resources/icons/scalable/actions/send-symbolic.svg b/data/resources/icons/scalable/actions/send-symbolic.svg
new file mode 100644
index 00000000..01f60ca8
--- /dev/null
+++ b/data/resources/icons/scalable/actions/send-symbolic.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/data/resources/style.css b/data/resources/style.css
index 3c4bd471..c3d88b6e 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -1,4 +1,16 @@
-.title-header{
+.title-header {
font-size: 36px;
font-weight: bold;
}
+
+.content {
+ background-color: @theme_base_color;
+}
+
+.content:backdrop {
+ background-color: @theme_unfocused_base_color;
+}
+
+.send-message-area {
+ margin: 6px;
+}
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
new file mode 100644
index 00000000..411aa691
--- /dev/null
+++ b/data/resources/ui/content.ui
@@ -0,0 +1,88 @@
+
+
+
+ True
+ True
+
+
+
+
+
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
new file mode 100644
index 00000000..94347e78
--- /dev/null
+++ b/data/resources/ui/login.ui
@@ -0,0 +1,18 @@
+
+
+
+
+
+ vertical
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/resources/ui/session.ui b/data/resources/ui/session.ui
new file mode 100644
index 00000000..0f0c5968
--- /dev/null
+++ b/data/resources/ui/session.ui
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/resources/ui/sidebar.ui b/data/resources/ui/sidebar.ui
new file mode 100644
index 00000000..4e315197
--- /dev/null
+++ b/data/resources/ui/sidebar.ui
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/data/resources/ui/window.ui b/data/resources/ui/window.ui
index c89a8a35..95c2b884 100644
--- a/data/resources/ui/window.ui
+++ b/data/resources/ui/window.ui
@@ -1,39 +1,19 @@
+
-
-
+
600
400
-
-
-
-
- Hello world!
-
+
+ session
+ crossfade
+
+
+
+
+
+
+
diff --git a/src/application.rs b/src/application.rs
index 7f8a50ed..996a14bb 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,5 +1,5 @@
use crate::config;
-use crate::window::ExampleApplicationWindow;
+use crate::widgets::FrctlWindow;
use gio::ApplicationFlags;
use glib::clone;
use glib::WeakRef;
@@ -17,7 +17,7 @@ mod imp {
#[derive(Debug)]
pub struct ExampleApplication {
- pub window: OnceCell>,
+ pub window: OnceCell>,
}
impl ObjectSubclass for ExampleApplication {
@@ -43,8 +43,7 @@ mod imp {
fn activate(&self, app: &Self::Type) {
debug!("GtkApplication::activate");
- let priv_ = ExampleApplication::from_instance(app);
- if let Some(window) = priv_.window.get() {
+ if let Some(window) = self.window.get() {
let window = window.upgrade().unwrap();
window.show();
window.present();
@@ -54,7 +53,7 @@ mod imp {
app.set_resource_base_path(Some("/org/gnome/FractalNext/"));
app.setup_css();
- let window = ExampleApplicationWindow::new(app);
+ let window = FrctlWindow::new(app);
self.window
.set(window.downgrade())
.expect("Window already set.");
@@ -83,14 +82,18 @@ impl ExampleApplication {
pub fn new() -> Self {
glib::Object::new(&[
("application-id", &Some(config::APP_ID)),
- ("flags", &ApplicationFlags::empty()),
+ ("flags", &ApplicationFlags::default()),
])
.expect("Application initialization failed...")
}
- fn get_main_window(&self) -> ExampleApplicationWindow {
- let priv_ = imp::ExampleApplication::from_instance(self);
- priv_.window.get().unwrap().upgrade().unwrap()
+ fn get_main_window(&self) -> FrctlWindow {
+ imp::ExampleApplication::from_instance(self)
+ .window
+ .get()
+ .unwrap()
+ .upgrade()
+ .unwrap()
}
fn setup_gactions(&self) {
diff --git a/src/main.rs b/src/main.rs
index 4ab963e9..7a2ec43b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,9 @@
mod application;
#[rustfmt::skip]
mod config;
-mod window;
+mod widgets;
+use adw;
use application::ExampleApplication;
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
use gettextrs::*;
@@ -23,6 +24,7 @@ fn main() {
gtk::glib::set_prgname(Some("fractal"));
gtk::init().expect("Unable to start GTK4");
+ adw::init();
let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file");
gio::resources_register(&res);
diff --git a/src/meson.build b/src/meson.build
index bc737b2c..27d59b67 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,7 +22,12 @@ sources = files(
'application.rs',
'config.rs',
'main.rs',
- 'window.rs',
+ 'widgets/window.rs',
+ 'widgets/content.rs',
+ 'widgets/sidebar.rs',
+ 'widgets/login.rs',
+ 'widgets/mod.rs',
+ 'widgets/session.rs',
)
custom_target(
diff --git a/src/widgets/content.rs b/src/widgets/content.rs
new file mode 100644
index 00000000..4554e3e3
--- /dev/null
+++ b/src/widgets/content.rs
@@ -0,0 +1,110 @@
+use adw;
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass;
+ use std::cell::Cell;
+
+ #[derive(Debug, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/content.ui")]
+ pub struct FrctlContent {
+ pub compact: Cell,
+ #[template_child]
+ pub headerbar: TemplateChild,
+ #[template_child]
+ pub room_history: TemplateChild,
+ }
+
+ impl ObjectSubclass for FrctlContent {
+ const NAME: &'static str = "FrctlContent";
+ type Type = super::FrctlContent;
+ type ParentType = adw::Bin;
+ type Interfaces = ();
+ type Instance = subclass::simple::InstanceStruct;
+ type Class = subclass::simple::ClassStruct;
+
+ glib::object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ compact: Cell::new(false),
+ headerbar: TemplateChild::default(),
+ room_history: TemplateChild::default(),
+ }
+ }
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ // You must call `Widget`'s `init_template()` within `instance_init()`.
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for FrctlContent {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy> = Lazy::new(|| {
+ vec![glib::ParamSpec::boolean(
+ "compact",
+ "Compact",
+ "Wheter a compact view is used or not",
+ false,
+ glib::ParamFlags::READWRITE,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.get_name() {
+ "compact" => {
+ let compact = value
+ .get()
+ .expect("type conformity checked by `Object::set_property`");
+ self.compact.set(compact.unwrap());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ pspec: &glib::ParamSpec,
+ ) -> glib::Value {
+ match pspec.get_name() {
+ "compact" => self.compact.get().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for FrctlContent {}
+ impl BinImpl for FrctlContent {}
+}
+
+glib::wrapper! {
+ pub struct FrctlContent(ObjectSubclass)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl FrctlContent {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create FrctlContent")
+ }
+}
diff --git a/src/widgets/login.rs b/src/widgets/login.rs
new file mode 100644
index 00000000..06b607ea
--- /dev/null
+++ b/src/widgets/login.rs
@@ -0,0 +1,58 @@
+use adw;
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass;
+
+ #[derive(Debug, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/login.ui")]
+ pub struct FrctlLogin {
+ #[template_child]
+ pub headerbar: TemplateChild,
+ }
+
+ impl ObjectSubclass for FrctlLogin {
+ const NAME: &'static str = "FrctlLogin";
+ type Type = super::FrctlLogin;
+ type ParentType = adw::Bin;
+ type Interfaces = ();
+ type Instance = subclass::simple::InstanceStruct;
+ type Class = subclass::simple::ClassStruct;
+
+ glib::object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ headerbar: TemplateChild::default(),
+ }
+ }
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ // You must call `Widget`'s `init_template()` within `instance_init()`.
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for FrctlLogin {}
+ impl WidgetImpl for FrctlLogin {}
+ impl BinImpl for FrctlLogin {}
+}
+
+glib::wrapper! {
+ pub struct FrctlLogin(ObjectSubclass)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl FrctlLogin {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create FrctlLogin")
+ }
+}
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
new file mode 100644
index 00000000..126ba693
--- /dev/null
+++ b/src/widgets/mod.rs
@@ -0,0 +1,11 @@
+mod content;
+mod login;
+mod session;
+mod sidebar;
+mod window;
+
+pub use crate::widgets::content::FrctlContent;
+pub use crate::widgets::login::FrctlLogin;
+pub use crate::widgets::session::FrctlSession;
+pub use crate::widgets::sidebar::FrctlSidebar;
+pub use crate::widgets::window::FrctlWindow;
diff --git a/src/widgets/session.rs b/src/widgets/session.rs
new file mode 100644
index 00000000..0f3b222b
--- /dev/null
+++ b/src/widgets/session.rs
@@ -0,0 +1,63 @@
+use crate::widgets::FrctlContent;
+use crate::widgets::FrctlSidebar;
+use adw;
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass;
+
+ #[derive(Debug, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/session.ui")]
+ pub struct FrctlSession {
+ #[template_child]
+ pub sidebar: TemplateChild,
+ #[template_child]
+ pub content: TemplateChild,
+ }
+
+ impl ObjectSubclass for FrctlSession {
+ const NAME: &'static str = "FrctlSession";
+ type Type = super::FrctlSession;
+ type ParentType = adw::Bin;
+ type Interfaces = ();
+ type Instance = subclass::simple::InstanceStruct;
+ type Class = subclass::simple::ClassStruct;
+
+ glib::object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ sidebar: TemplateChild::default(),
+ content: TemplateChild::default(),
+ }
+ }
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ // You must call `Widget`'s `init_template()` within `instance_init()`.
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for FrctlSession {}
+ impl WidgetImpl for FrctlSession {}
+ impl BinImpl for FrctlSession {}
+}
+
+glib::wrapper! {
+ pub struct FrctlSession(ObjectSubclass)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl FrctlSession {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create FrctlSession")
+ }
+}
diff --git a/src/widgets/sidebar.rs b/src/widgets/sidebar.rs
new file mode 100644
index 00000000..003fc346
--- /dev/null
+++ b/src/widgets/sidebar.rs
@@ -0,0 +1,110 @@
+use adw;
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass;
+ use std::cell::Cell;
+
+ #[derive(Debug, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
+ pub struct FrctlSidebar {
+ pub compact: Cell,
+ #[template_child]
+ pub headerbar: TemplateChild,
+ #[template_child]
+ pub listview: TemplateChild,
+ }
+
+ impl ObjectSubclass for FrctlSidebar {
+ const NAME: &'static str = "FrctlSidebar";
+ type Type = super::FrctlSidebar;
+ type ParentType = adw::Bin;
+ type Interfaces = ();
+ type Instance = subclass::simple::InstanceStruct;
+ type Class = subclass::simple::ClassStruct;
+
+ glib::object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ compact: Cell::new(false),
+ listview: TemplateChild::default(),
+ headerbar: TemplateChild::default(),
+ }
+ }
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ // You must call `Widget`'s `init_template()` within `instance_init()`.
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for FrctlSidebar {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy> = Lazy::new(|| {
+ vec![glib::ParamSpec::boolean(
+ "compact",
+ "Compact",
+ "Wheter a compact view is used or not",
+ false,
+ glib::ParamFlags::READWRITE,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.get_name() {
+ "compact" => {
+ let compact = value
+ .get()
+ .expect("type conformity checked by `Object::set_property`");
+ self.compact.set(compact.unwrap());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ pspec: &glib::ParamSpec,
+ ) -> glib::Value {
+ match pspec.get_name() {
+ "compact" => self.compact.get().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for FrctlSidebar {}
+ impl BinImpl for FrctlSidebar {}
+}
+
+glib::wrapper! {
+ pub struct FrctlSidebar(ObjectSubclass)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl FrctlSidebar {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create FrctlSidebar")
+ }
+}
diff --git a/src/window.rs b/src/widgets/window.rs
similarity index 61%
rename from src/window.rs
rename to src/widgets/window.rs
index ed668918..379ed6af 100644
--- a/src/window.rs
+++ b/src/widgets/window.rs
@@ -1,5 +1,8 @@
use crate::application::ExampleApplication;
use crate::config::{APP_ID, PROFILE};
+use crate::widgets::FrctlLogin;
+use crate::widgets::FrctlSession;
+use adw::subclass::prelude::AdwApplicationWindowImpl;
use glib::signal::Inhibit;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
@@ -12,16 +15,22 @@ mod imp {
#[derive(Debug, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/window.ui")]
- pub struct ExampleApplicationWindow {
+ pub struct FrctlWindow {
#[template_child]
- pub headerbar: TemplateChild,
+ pub main_stack: TemplateChild,
+ #[template_child]
+ pub login: TemplateChild,
+ // Eventually we want to create the session dynamically, since we want multi account
+ // support
+ #[template_child]
+ pub session: TemplateChild,
pub settings: gio::Settings,
}
- impl ObjectSubclass for ExampleApplicationWindow {
- const NAME: &'static str = "ExampleApplicationWindow";
- type Type = super::ExampleApplicationWindow;
- type ParentType = gtk::ApplicationWindow;
+ impl ObjectSubclass for FrctlWindow {
+ const NAME: &'static str = "FrctlWindow";
+ type Type = super::FrctlWindow;
+ type ParentType = adw::ApplicationWindow;
type Interfaces = ();
type Instance = subclass::simple::InstanceStruct;
type Class = subclass::simple::ClassStruct;
@@ -30,7 +39,9 @@ mod imp {
fn new() -> Self {
Self {
- headerbar: TemplateChild::default(),
+ main_stack: TemplateChild::default(),
+ login: TemplateChild::default(),
+ session: TemplateChild::default(),
settings: gio::Settings::new(APP_ID),
}
}
@@ -45,7 +56,7 @@ mod imp {
}
}
- impl ObjectImpl for ExampleApplicationWindow {
+ impl ObjectImpl for FrctlWindow {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
@@ -55,7 +66,7 @@ mod imp {
// Devel Profile
if PROFILE == "Devel" {
- obj.get_style_context().add_class("devel");
+ obj.add_css_class("devel");
}
// load latest window state
@@ -63,7 +74,7 @@ mod imp {
}
}
- impl WindowImpl for ExampleApplicationWindow {
+ impl WindowImpl for FrctlWindow {
// save window state on delete event
fn close_request(&self, obj: &Self::Type) -> Inhibit {
if let Err(err) = obj.save_window_size() {
@@ -73,29 +84,24 @@ mod imp {
}
}
- impl WidgetImpl for ExampleApplicationWindow {}
- impl ApplicationWindowImpl for ExampleApplicationWindow {}
+ impl WidgetImpl for FrctlWindow {}
+ impl ApplicationWindowImpl for FrctlWindow {}
+ impl AdwApplicationWindowImpl for FrctlWindow {}
}
glib::wrapper! {
- pub struct ExampleApplicationWindow(ObjectSubclass)
- @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, @implements gio::ActionMap, gio::ActionGroup;
+ pub struct FrctlWindow(ObjectSubclass)
+ @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionMap, gio::ActionGroup;
}
-impl ExampleApplicationWindow {
- pub fn new(app: &ExampleApplication) -> Self {
- let window: Self =
- glib::Object::new(&[]).expect("Failed to create ExampleApplicationWindow");
- window.set_application(Some(app));
-
- // Set icons for shell
- gtk::Window::set_default_icon_name(APP_ID);
-
- window
+impl FrctlWindow {
+ pub fn new(app: &FrctlApplication) -> Self {
+ glib::Object::new(&[("application", &Some(app)), ("icon-name", &Some(APP_ID))])
+ .expect("Failed to create FrctlWindow")
}
pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
- let settings = &imp::ExampleApplicationWindow::from_instance(self).settings;
+ let settings = &imp::FrctlWindow::from_instance(self).settings;
let size = self.get_default_size();
@@ -108,16 +114,13 @@ impl ExampleApplicationWindow {
}
fn load_window_size(&self) {
- let settings = &imp::ExampleApplicationWindow::from_instance(self).settings;
+ let settings = &imp::FrctlWindow::from_instance(self).settings;
let width = settings.get_int("window-width");
let height = settings.get_int("window-height");
let is_maximized = settings.get_boolean("is-maximized");
self.set_default_size(width, height);
-
- if is_maximized {
- self.maximize();
- }
+ self.set_property_maximized(is_maximized);
}
}