From 3dc4d7f1558bb574eb99dade9a05727604e2e2ca Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 4 Nov 2021 17:35:11 +0100 Subject: Add (disabled) multi-party call UI --- main/src/ui/call_window/call_window.vala | 262 ++++++++++++++++--------------- 1 file changed, 138 insertions(+), 124 deletions(-) (limited to 'main/src/ui/call_window/call_window.vala') diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala index 3b3d4dc2..fab424bf 100644 --- a/main/src/ui/call_window/call_window.vala +++ b/main/src/ui/call_window/call_window.vala @@ -1,87 +1,155 @@ +using Gee; +using Xmpp; using Dino.Entities; using Gtk; namespace Dino.Ui { public class CallWindow : Gtk.Window { - public string counterpart_display_name { get; set; } - // TODO should find another place for this + public signal void menu_dump_dot(); + public CallWindowController controller; public Overlay overlay = new Overlay() { visible=true }; + public Grid grid = new Grid() { visible=true }; public EventBox event_box = new EventBox() { visible=true }; public CallBottomBar bottom_bar = new CallBottomBar() { visible=true }; public Revealer bottom_bar_revealer = new Revealer() { valign=Align.END, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true }; - public HeaderBar header_bar = new HeaderBar() { show_close_button=true, visible=true }; - public Revealer header_bar_revealer = new Revealer() { valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true }; - public Stack stack = new Stack() { visible=true }; - public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { expand=true, visible=true }; + public HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true }; + public Revealer header_bar_revealer = new Revealer() { halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true }; + public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { halign=Align.END, valign=Align.END, visible=true }; + public Revealer invite_button_revealer = new Revealer() { margin_top=50, margin_right=30, halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200 }; + public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE }; private Widget? own_video = null; - private Box? own_video_border = new Box(Orientation.HORIZONTAL, 0) { expand=true }; // hack to draw a border around our own video, since we apparently can't draw a border around the Gst widget + private HashMap participant_widgets = new HashMap(); + private ArrayList participants = new ArrayList(); private int own_video_width = 150; private int own_video_height = 100; - private bool hide_controll_elements = false; - private uint hide_controll_handler = 0; - private Widget? main_widget = null; + private bool hide_control_elements = false; + private uint hide_control_handler = 0; + public bool controls_active { get; set; default=true; } construct { + Util.force_css(header_bar, "* { background: none; border: 0; border-radius: 0; }"); header_bar.get_style_context().add_class("call-header-bar"); header_bar_revealer.add(header_bar); + bottom_bar_revealer.add(bottom_bar); + invite_button.get_style_context().add_class("black-element"); + invite_button_revealer.add(invite_button); + own_video_box.get_style_context().add_class("own-video"); this.get_style_context().add_class("dino-call-window"); - bottom_bar_revealer.add(bottom_bar); - + overlay.add(grid); overlay.add_overlay(own_video_box); - overlay.add_overlay(own_video_border); overlay.add_overlay(bottom_bar_revealer); overlay.add_overlay(header_bar_revealer); + overlay.add_overlay(invite_button_revealer); + overlay.get_child_position.connect(on_get_child_position); event_box.add(overlay); add(event_box); - - Util.force_css(own_video_border, "* { border: 1px solid #616161; background-color: transparent; }"); } public CallWindow() { - event_box.events |= Gdk.EventMask.POINTER_MOTION_MASK; - event_box.events |= Gdk.EventMask.ENTER_NOTIFY_MASK; - event_box.events |= Gdk.EventMask.LEAVE_NOTIFY_MASK; - - this.bind_property("counterpart-display-name", header_bar, "title", BindingFlags.SYNC_CREATE); - this.bind_property("counterpart-display-name", bottom_bar, "counterpart-display-name", BindingFlags.SYNC_CREATE); + this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE); + this.bind_property("controls-active", header_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE); + this.bind_property("controls-active", invite_button_revealer, "reveal-child", BindingFlags.SYNC_CREATE); event_box.motion_notify_event.connect(reveal_control_elements); event_box.enter_notify_event.connect(reveal_control_elements); event_box.leave_notify_event.connect(reveal_control_elements); this.configure_event.connect(reveal_control_elements); // upon resizing - this.configure_event.connect(update_own_video_position); + + this.configure_event.connect(reposition_participant_widgets); this.set_titlebar(new OutsideHeaderBar(this.header_bar) { visible=true }); reveal_control_elements(); } - public void set_video_fallback(StreamInteractor stream_interactor, Conversation conversation) { - hide_controll_elements = false; + public void add_participant(string participant, ParticipantWidget participant_widget) { + participant_widget.visible = true; + this.bind_property("controls-active", participant_widget, "controls-active", BindingFlags.SYNC_CREATE); + this.bind_property("controls-active", participant_widget.encryption_button, "controls-active", BindingFlags.SYNC_CREATE); + + participants.add(participant); + participant_widgets[participant] = participant_widget; + grid.attach(participant_widget, 0, 0); + + reposition_participant_widgets(); + } + + public void remove_participant(string participant) { + participants.remove(participant); + grid.remove(participant_widgets[participant]); + participant_widgets.unset(participant); - Box box = new Box(Orientation.HORIZONTAL, 0) { visible=true }; - box.get_style_context().add_class("video-placeholder-box"); - AvatarImage avatar = new AvatarImage() { hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100, visible=true }; - avatar.set_conversation(stream_interactor, conversation); - box.add(avatar); + reposition_participant_widgets(); + } - set_new_main_widget(box); + public void set_video(string participant, Widget widget) { + participant_widgets[participant].set_video(widget); + hide_control_elements = true; + timeout_hide_control_elements(); } - public void set_video(Widget widget) { - hide_controll_elements = true; + public void set_placeholder(string participant, Conversation? conversation, StreamInteractor stream_interactor) { + participant_widgets[participant].set_placeholder(conversation, stream_interactor); + hide_control_elements = false; + foreach (ParticipantWidget participant_widget in participant_widgets.values) { + if (participant_widget.shows_video) { + hide_control_elements = true; + } + } - widget.visible = true; - set_new_main_widget(widget); + if (!hide_control_elements) { + reveal_control_elements(); + } + } + + private bool reposition_participant_widgets() { + int width, height; + this.get_size(out width,out height); + reposition_participant_widgets_rec(participants, width, height, 0, 0, 0, 0); + return false; + } + + private void reposition_participant_widgets_rec(ArrayList participants, int width, int height, int margin_top, int margin_right, int margin_bottom, int margin_left) { + if (participants.size == 0) return; + + if (participants.size == 1) { + participant_widgets[participants[0]].margin_top = margin_top; + participant_widgets[participants[0]].margin_end = margin_right; + participant_widgets[participants[0]].margin_bottom = margin_bottom; + participant_widgets[participants[0]].margin_start = margin_left; + + participant_widgets[participants[0]].on_lowest_row_changed(margin_bottom == 0); + participant_widgets[participants[0]].on_highest_row_changed(margin_top == 0); + return; + } + + ArrayList first_part = new ArrayList(); + ArrayList last_part = new ArrayList(); + + for (int i = 0; i < participants.size; i++) { + if (i < Math.ceil((double)participants.size / (double)2)) { + first_part.add(participants[i]); + } else { + last_part.add(participants[i]); + } + } + + if (width > height) { + reposition_participant_widgets_rec(first_part, width / 2, height, margin_top, margin_right + width / 2, margin_bottom, margin_left); + reposition_participant_widgets_rec(last_part, width / 2, height, margin_top, margin_right, margin_bottom, margin_left + width / 2); + } else { + reposition_participant_widgets_rec(first_part, width, height / 2, margin_top, margin_right, margin_bottom + height / 2, margin_left); + reposition_participant_widgets_rec(last_part, width, height / 2, margin_top + height / 2, margin_right, margin_bottom, margin_left); + } } public void set_own_video(Widget? widget_) { @@ -92,13 +160,7 @@ namespace Dino.Ui { own_video = new Box(Orientation.HORIZONTAL, 0) { expand=true }; } own_video.visible = true; - own_video.width_request = 150; - own_video.height_request = 100; own_video_box.add(own_video); - - own_video_border.visible = true; - - update_own_video_position(); } public void set_own_video_ratio(int width, int height) { @@ -109,78 +171,25 @@ namespace Dino.Ui { this.own_video_width = width * 100 / height; this.own_video_height = 100; } - - own_video.width_request = own_video_width; - own_video.height_request = own_video_height; - - update_own_video_position(); } public void unset_own_video() { own_video_box.foreach((widget) => { own_video_box.remove(widget); }); - - own_video_border.visible = false; - } - - public void set_test_video() { - hide_controll_elements = true; - - var pipeline = new Gst.Pipeline(null); - var src = Gst.ElementFactory.make("videotestsrc", null); - pipeline.add(src); - Gst.Video.Sink sink = (Gst.Video.Sink) Gst.ElementFactory.make("gtksink", null); - Gtk.Widget widget; - sink.get("widget", out widget); - widget.unparent(); - pipeline.add(sink); - src.link(sink); - widget.visible = true; - - pipeline.set_state(Gst.State.PLAYING); - - sink.get_static_pad("sink").notify["caps"].connect(() => { - int width, height; - sink.get_static_pad("sink").caps.get_structure(0).get_int("width", out width); - sink.get_static_pad("sink").caps.get_structure(0).get_int("height", out height); - widget.width_request = width; - widget.height_request = height; - }); - - set_new_main_widget(widget); } - private void set_new_main_widget(Widget widget) { - if (main_widget != null) overlay.remove(main_widget); - overlay.add(widget); - main_widget = widget; + public void set_status(string participant_id, string state) { + participant_widgets[participant_id].set_status(state); } - public void set_status(string state) { - switch (state) { - case "requested": - header_bar.subtitle = _("Calling…"); - break; - case "ringing": - header_bar.subtitle = _("Ringing…"); - break; - case "establishing": - header_bar.subtitle = _("Connecting…"); - break; - default: - header_bar.subtitle = null; - break; - } - } - - public void show_counterpart_ended(string? reason_name, string? reason_text) { - hide_controll_elements = false; + public void show_counterpart_ended(string who_terminated, string? reason_name, string? reason_text) { + hide_control_elements = false; reveal_control_elements(); string text = ""; if (reason_name == Xmpp.Xep.Jingle.ReasonElement.SUCCESS) { - text = _("%s ended the call").printf(counterpart_display_name); + text = _("%s ended the call").printf(who_terminated); } else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) { - text = _("%s declined the call").printf(counterpart_display_name); + text = _("%s declined the call").printf(who_terminated); } else { text = "The call has been terminated: " + (reason_name ?? "") + " " + (reason_text ?? ""); } @@ -188,48 +197,53 @@ namespace Dino.Ui { bottom_bar.show_counterpart_ended(text); } - public bool reveal_control_elements() { + private bool reveal_control_elements() { if (!bottom_bar_revealer.child_revealed) { - bottom_bar_revealer.set_reveal_child(true); - header_bar_revealer.set_reveal_child(true); + controls_active = true; } - if (hide_controll_handler != 0) { - Source.remove(hide_controll_handler); - hide_controll_handler = 0; + timeout_hide_control_elements(); + return false; + } + + private void timeout_hide_control_elements() { + if (hide_control_handler != 0) { + Source.remove(hide_control_handler); + hide_control_handler = 0; } - if (!hide_controll_elements) { - return false; + if (!hide_control_elements) { + return; } - hide_controll_handler = Timeout.add_seconds(3, () => { - if (!hide_controll_elements) { + hide_control_handler = Timeout.add_seconds(3, () => { + if (!hide_control_elements) { return false; } if (bottom_bar.is_menu_active()) { - return true; + return false; } - header_bar_revealer.set_reveal_child(false); - bottom_bar_revealer.set_reveal_child(false); - hide_controll_handler = 0; + controls_active = false; + + hide_control_handler = 0; return false; }); - return false; } - private bool update_own_video_position() { - if (own_video == null) return false; - - int width, height; - this.get_size(out width,out height); - - own_video.margin_end = own_video.margin_bottom = own_video_border.margin_end = own_video_border.margin_bottom = 20; - own_video.margin_start = own_video_border.margin_start = width - own_video_width - 20; - own_video.margin_top = own_video_border.margin_top = height - own_video_height - 20; - + private bool on_get_child_position(Widget widget, out Gdk.Rectangle allocation) { + if (widget == own_video_box) { + int width, height; + this.get_size(out width,out height); + + allocation = Gdk.Rectangle(); + allocation.width = own_video_width; + allocation.height = own_video_height; + allocation.x = width - own_video_width - 20; + allocation.y = height - own_video_height - 20; + return true; + } return false; } } -- cgit v1.2.3-54-g00ecf