diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index aae78ef5..499c665b 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -3,14 +3,20 @@
ui/shortcuts.ui
ui/content.ui
+ ui/content-item.ui
+ ui/content-message-row.ui
+ ui/content-divider-row.ui
+ ui/content-state-row.ui
ui/login.ui
ui/session.ui
ui/sidebar.ui
ui/sidebar-item.ui
ui/sidebar-room-row.ui
ui/window.ui
+ ui/context-menu-bin.ui
style.css
icons/scalable/actions/send-symbolic.svg
icons/scalable/status/welcome.svg
+
diff --git a/data/resources/style.css b/data/resources/style.css
index 0360bab4..650883e3 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -51,4 +51,11 @@
background-color: @theme_selected_bg_color;
}
-
+/* Content */
+.codeview {
+ border-radius: 5px;
+ padding: 6px;
+ font-family: monospace;
+ background-color: @text_view_bg;
+ color: @theme_text_color;
+}
diff --git a/data/resources/ui/content-divider-row.ui b/data/resources/ui/content-divider-row.ui
new file mode 100644
index 00000000..540b7e3c
--- /dev/null
+++ b/data/resources/ui/content-divider-row.ui
@@ -0,0 +1,35 @@
+
+
+
+ False
+
+
+
+
+
+
diff --git a/data/resources/ui/content-item-row-menu.ui b/data/resources/ui/content-item-row-menu.ui
new file mode 100644
index 00000000..5e4fe940
--- /dev/null
+++ b/data/resources/ui/content-item-row-menu.ui
@@ -0,0 +1,102 @@
+
+
+
+
diff --git a/data/resources/ui/content-item.ui b/data/resources/ui/content-item.ui
new file mode 100644
index 00000000..3dbf6b97
--- /dev/null
+++ b/data/resources/ui/content-item.ui
@@ -0,0 +1,18 @@
+
+
+
+ False
+
+
+ GtkListItem
+
+
+
+
+
+ GtkListItem
+
+
+
+
+
diff --git a/data/resources/ui/content-message-row.ui b/data/resources/ui/content-message-row.ui
new file mode 100644
index 00000000..37c74018
--- /dev/null
+++ b/data/resources/ui/content-message-row.ui
@@ -0,0 +1,50 @@
+
+
+
+
+
+ 6
+
+
+ True
+ 24
+
+
+
+
+
+ 6
+ vertical
+
+
+
+
+
+ True
+ True
+
+
+
+
+
+
+
+
diff --git a/data/resources/ui/content-state-row.ui b/data/resources/ui/content-state-row.ui
new file mode 100644
index 00000000..8fbf5983
--- /dev/null
+++ b/data/resources/ui/content-state-row.ui
@@ -0,0 +1,21 @@
+
+
+
+
+
+ 6
+ vertical
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index d59ed88b..298c1fd1 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -23,7 +23,7 @@
-
+
True
@@ -35,20 +35,37 @@
-
+
True
- True
+ never
-
-
+
+
+ True
+ True
+
+
+
+
+
+ /org/gnome/FractalNext/content-item.ui
+
+
+
+ Room History
+
+
+
-
+
-
+
@@ -86,3 +103,4 @@
+
diff --git a/data/resources/ui/context-menu-bin.ui b/data/resources/ui/context-menu-bin.ui
new file mode 100644
index 00000000..51b296c5
--- /dev/null
+++ b/data/resources/ui/context-menu-bin.ui
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/src/components/context_menu_bin.rs b/src/components/context_menu_bin.rs
new file mode 100644
index 00000000..420e8d08
--- /dev/null
+++ b/src/components/context_menu_bin.rs
@@ -0,0 +1,188 @@
+use adw::subclass::prelude::*;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::{gdk, gio, glib, glib::clone, CompositeTemplate};
+use log::debug;
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+
+ #[derive(Debug, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/context-menu-bin.ui")]
+ pub struct ContextMenuBin {
+ #[template_child]
+ pub click_gesture: TemplateChild,
+ #[template_child]
+ pub long_press_gesture: TemplateChild,
+ pub popover: gtk::PopoverMenu,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for ContextMenuBin {
+ const NAME: &'static str = "ContextMenuBin";
+ type Type = super::ContextMenuBin;
+ type ParentType = adw::Bin;
+
+ fn new() -> Self {
+ Self {
+ click_gesture: TemplateChild::default(),
+ long_press_gesture: TemplateChild::default(),
+ // WORKAROUND: there is some issue with creating the popover from the template
+ popover: gtk::PopoverMenuBuilder::new()
+ .position(gtk::PositionType::Bottom)
+ .has_arrow(false)
+ .halign(gtk::Align::Start)
+ .build(),
+ }
+ }
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+
+ klass.install_action("context-menu.activate", None, move |widget, _, _| {
+ widget.open_menu_at(0, 0)
+ });
+ klass.add_binding_action(
+ gdk::keys::constants::F10,
+ gdk::ModifierType::SHIFT_MASK,
+ "context-menu.activate",
+ None,
+ );
+ klass.add_binding_action(
+ gdk::keys::constants::Menu,
+ gdk::ModifierType::empty(),
+ "context-menu.activate",
+ None,
+ );
+ }
+
+ fn instance_init(obj: &InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for ContextMenuBin {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_object(
+ "context-menu",
+ "Context Menu",
+ "The context menu",
+ gio::MenuModel::static_type(),
+ glib::ParamFlags::READWRITE,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "context-menu" => {
+ let context_menu = value
+ .get::