diff --git a/data/app.css b/data/app.css
index a8712ff..d50aee6 100644
--- a/data/app.css
+++ b/data/app.css
@@ -40,3 +40,17 @@
padding: 0px;
margin: 0px;
}
+
+.ttl-post {
+ padding: 0px;
+}
+.ttl-thread-line {
+ background: @theme_fg_color;
+ opacity: .1;
+ margin-top: -8px;
+ margin-bottom: -8px;
+}
+
+.ttl-large-body {
+ font-size: 110%;
+}
diff --git a/data/ui/views/base.ui b/data/ui/views/base.ui
index 02898bd..d2d055c 100644
--- a/data/ui/views/base.ui
+++ b/data/ui/views/base.ui
@@ -54,8 +54,8 @@
+
diff --git a/src/Views/Thread.vala b/src/Views/Thread.vala
index 78fe573..28ccfb6 100644
--- a/src/Views/Thread.vala
+++ b/src/Views/Thread.vala
@@ -2,88 +2,105 @@ using Gtk;
public class Tootle.Views.Thread : Views.Base, IAccountListener {
- public API.Status root_status { get; construct set; }
- protected InstanceAccount? account = null;
- protected Widgets.Status root_widget;
+ public API.Status root_status { get; construct set; }
+ protected InstanceAccount? account = null;
+ protected Widgets.Status root_widget;
- public Thread (API.Status status) {
- Object (
- root_status: status,
- status_message: STATUS_LOADING,
- label: _("Conversation")
- );
- account_listener_init ();
- }
+ public Thread (API.Status status) {
+ Object (
+ root_status: status,
+ status_message: STATUS_LOADING,
+ label: _("Conversation")
+ );
+ account_listener_init ();
+ }
- public override void on_account_changed (InstanceAccount? acc) {
- account = acc;
- request ();
- }
+ public override void on_account_changed (InstanceAccount? acc) {
+ account = acc;
+ request ();
+ }
- Widgets.Status prepend (Entity entity, bool to_end = false){
- var w = entity.to_widget () as Widgets.Status;
- w.reveal_spoiler = true;
+ Widgets.Status append (Entity entity){
+ var w = entity.to_widget () as Widgets.Status;
+ w.reveal_spoiler = true;
+ content_list.insert (w, -1);
+ return w;
+ }
- if (to_end)
- content_list.insert (w, -1);
- else
- content_list.prepend (w);
+ void connect_threads () {
+ Widgets.Status? last_w = null;
+ string? last_id = null;
- check_resize ();
- return w;
- }
- Widget append (Entity entity) {
- return prepend (entity, true);
- }
+ content.get_children ().foreach (i => {
+ var w = i as Widgets.Status;
+ var id = w.status.formal.in_reply_to_id;
- public void request () {
- new Request.GET (@"/api/v1/statuses/$(root_status.id)/context")
- .with_account (account)
- .with_ctx (this)
- .then ((sess, msg) => {
- var root = network.parse (msg);
+ if (id == last_id) {
+ Widgets.Status.ThreadRole.connect_posts (last_w, w);
+ }
- var ancestors = root.get_array_member ("ancestors");
- ancestors.foreach_element ((array, i, node) => {
- var status = Entity.from_json (typeof (API.Status), node);
- append (status);
- });
+ last_w = w;
+ last_id = w.status.formal.id;
+ });
- root_widget = append (root_status) as Widgets.Status;
- root_widget.expand_root ();
+ content.get_children ().foreach (i => {
+ var w = i as Widgets.Status;
+ w.install_thread_line ();
+ });
- var descendants = root.get_array_member ("descendants");
- descendants.foreach_element ((array, i, node) => {
- var status = Entity.from_json (typeof (API.Status), node);
- append (status);
- });
+ root_widget.thread_line.hide ();
+ }
- on_content_changed ();
+ public void request () {
+ new Request.GET (@"/api/v1/statuses/$(root_status.id)/context")
+ .with_account (account)
+ .with_ctx (this)
+ .then ((sess, msg) => {
- int x,y;
- translate_coordinates (root_widget, 0, header.get_allocated_height (), out x, out y);
- scrolled.vadjustment.value = (double)(y*-1);
- })
- .exec ();
- }
+ var root = network.parse (msg);
- public static void open_from_link (string q) {
- new Request.GET ("/api/v1/search")
- .with_account ()
- .with_param ("q", q)
- .with_param ("resolve", "true")
- .then ((sess, msg) => {
- var root = network.parse (msg);
- var statuses = root.get_array_member ("statuses");
- var node = statuses.get_element (0);
- if (node != null){
- var status = API.Status.from (node);
- window.open_view (new Views.Thread (status));
- }
- else
- Desktop.open_uri (q);
- })
- .exec ();
- }
+ var ancestors = root.get_array_member ("ancestors");
+ ancestors.foreach_element ((array, i, node) => {
+ var status = Entity.from_json (typeof (API.Status), node);
+ append (status);
+ });
+
+ root_widget = append (root_status) as Widgets.Status;
+ root_widget.expand_root ();
+
+ var descendants = root.get_array_member ("descendants");
+ descendants.foreach_element ((array, i, node) => {
+ var status = Entity.from_json (typeof (API.Status), node);
+ append (status);
+ });
+
+ connect_threads ();
+ on_content_changed ();
+
+ int x,y;
+ translate_coordinates (root_widget, 0, header.get_allocated_height (), out x, out y);
+ scrolled.vadjustment.value = (double)(y*-1);
+ })
+ .exec ();
+ }
+
+ public static void open_from_link (string q) {
+ new Request.GET ("/api/v1/search")
+ .with_account ()
+ .with_param ("q", q)
+ .with_param ("resolve", "true")
+ .then ((sess, msg) => {
+ var root = network.parse (msg);
+ var statuses = root.get_array_member ("statuses");
+ var node = statuses.get_element (0);
+ if (node != null){
+ var status = API.Status.from (node);
+ window.open_view (new Views.Thread (status));
+ }
+ else
+ Desktop.open_uri (q);
+ })
+ .exec ();
+ }
}
diff --git a/src/Widgets/Status.vala b/src/Widgets/Status.vala
index a3037a2..ae90cc1 100644
--- a/src/Widgets/Status.vala
+++ b/src/Widgets/Status.vala
@@ -7,10 +7,38 @@ public class Tootle.Widgets.Status : ListBoxRow {
public API.Status status { get; construct set; }
public API.NotificationType? kind { get; construct set; }
+ public enum ThreadRole {
+ NONE,
+ START,
+ MIDDLE,
+ END;
+
+ public static void connect_posts (Widgets.Status? prev, Widgets.Status curr) {
+ if (prev == null) {
+ curr.thread_role = NONE;
+ return;
+ }
+
+ switch (prev.thread_role) {
+ case NONE:
+ prev.thread_role = START;
+ curr.thread_role = END;
+ break;
+ case END:
+ prev.thread_role = MIDDLE;
+ curr.thread_role = END;
+ break;
+ }
+ }
+ }
+
+ public ThreadRole thread_role { get; set; default = ThreadRole.NONE; }
+
[GtkChild] protected Grid grid;
[GtkChild] protected Image header_icon;
[GtkChild] protected Widgets.RichLabel header_label;
+ [GtkChild] public Image thread_line;
[GtkChild] public Widgets.Avatar avatar;
[GtkChild] protected Widgets.RichLabel name_label;
@@ -234,6 +262,7 @@ public class Tootle.Widgets.Status : ListBoxRow {
public void expand_root () {
activatable = false;
content.selectable = true;
+ content.get_style_context ().add_class ("ttl-large-body");
var parent = content_column.get_parent () as Container;
var left_attach = parent.find_child_property ("left-attach");
@@ -242,4 +271,28 @@ public class Tootle.Widgets.Status : ListBoxRow {
parent.set_child_property (content_column, 3, 2, width);
}
+ public void install_thread_line () {
+ var l = thread_line;
+ switch (thread_role) {
+ case NONE:
+ l.visible = false;
+ break;
+ case START:
+ l.valign = Align.FILL;
+ l.margin_top = 24;
+ l.visible = true;
+ break;
+ case MIDDLE:
+ l.valign = Align.FILL;
+ l.margin_top = 0;
+ l.visible = true;
+ break;
+ case END:
+ l.valign = Align.START;
+ l.margin_top = 0;
+ l.visible = true;
+ break;
+ }
+ }
+
}