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 @@ True False - 700 - 700 + 650 + 650 True diff --git a/data/ui/widgets/status.ui b/data/ui/widgets/status.ui index 14f06d6..ec11bfd 100644 --- a/data/ui/widgets/status.ui +++ b/data/ui/widgets/status.ui @@ -417,9 +417,26 @@ - + + 4 + 32 + False + center + 0 + + + + 0 + 0 + 3 + + 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; + } + } + }