From 0f5f57888e2e237549b1bc7002770ec102ff0e6b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 12 Feb 2022 12:54:48 +0100 Subject: [PATCH] Calls: Use GtkHeaderBar for each participant --- main/data/theme.css | 50 +++++----- main/src/ui/call_window/call_window.vala | 17 +--- .../call_window/call_window_controller.vala | 41 ++++---- .../ui/call_window/participant_widget.vala | 97 ++++++++----------- 4 files changed, 95 insertions(+), 110 deletions(-) diff --git a/main/data/theme.css b/main/data/theme.css index b8def4e1..2451f5d9 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -256,6 +256,10 @@ box.dino-input-error label.input-status-highlight-once { /* Call window */ +.dino-call-window decoration { + border-radius: 0; +} + .dino-call-window .titlebar { min-height: 0; } @@ -264,12 +268,6 @@ box.dino-input-error label.input-status-highlight-once { box-shadow: none; } -.dino-call-window .titlebutton.close:hover { - background: rgba(255,255,255,0.15); - border-color: rgba(255,255,255,0); - box-shadow: none; -} - .dino-call-window button.call-button { outline: 0; border-radius: 1000px; @@ -335,31 +333,39 @@ box.dino-input-error label.input-status-highlight-once { background: rgba(20,20,20,0.5); } -.dino-call-window .call-header-bar { +.dino-call-window .participant-header-bar { + background: none; + border: none; + border-radius: 0; + color: #ededec; + text-shadow: 0 0 2px black; +} + +.dino-call-window .call-header-background { background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0)); border: 0; border-radius: 0; } -.dino-call-window .call-header-bar { - color: #ededec; -} - -.dino-call-window .call-header-bar button image { - color: alpha(white, 0.7); -} - -.dino-call-window .call-header-bar button:hover image { - color: white; -} - -.dino-call-window .participant-title-button { +.dino-call-window .participant-header-bar button { background: none; - border: 0; - border-radius: 0; +} + +.dino-call-window .participant-header-bar button:hover { + background: rgba(255,255,255,0.15); + border-color: rgba(255,255,255,0); box-shadow: none; } +.dino-call-window .participant-header-bar button image { + color: alpha(white, 0.7); + -gtk-icon-shadow: none; +} + +.dino-call-window .participant-header-bar button:hover image { + color: white; +} + .dino-call-window .call-bottom-bar { background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3)); border: 0; diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala index cf9ca0e6..ab969597 100644 --- a/main/src/ui/call_window/call_window.vala +++ b/main/src/ui/call_window/call_window.vala @@ -15,11 +15,9 @@ namespace Dino.Ui { public Grid grid = new Grid() { 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() { 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 HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true, opacity=0.0 }; + public Revealer header_bar_revealer = new Revealer() { halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.SLIDE_LEFT, transition_duration=200, visible=true, reveal_child=false }; 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, visible=true }; private Widget? own_video = null; private HashMap participant_widgets = new HashMap(); private ArrayList participants = new ArrayList(); @@ -32,12 +30,11 @@ namespace Dino.Ui { 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.custom_title = new Box(Orientation.VERTICAL, 0); + header_bar.spacing = 0; 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"); @@ -46,7 +43,6 @@ namespace Dino.Ui { overlay.add_overlay(own_video_box); 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); add(overlay); @@ -54,8 +50,6 @@ namespace Dino.Ui { public CallWindow() { 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); this.motion_notify_event.connect(reveal_control_elements); this.enter_notify_event.connect(reveal_control_elements); @@ -125,8 +119,7 @@ namespace Dino.Ui { 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); + participant_widgets[participants[0]].on_row_changed(margin_top == 0, margin_bottom == 0, margin_left == 0, margin_right == 0); return; } diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index e482e3aa..445f88ea 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -16,12 +16,12 @@ public class Dino.Ui.CallWindowController : Object { private HashMap participant_videos = new HashMap(); private HashMap participant_widgets = new HashMap(); private HashMap peer_states = new HashMap(); + private HashMap invite_handler_ids = new HashMap(); private int window_height = -1; private int window_width = -1; private bool window_size_changed = false; private ulong[] call_window_handler_ids = new ulong[0]; private ulong[] bottom_bar_handler_ids = new ulong[0]; - private ulong[] invite_handler_ids = new ulong[0]; public CallWindowController(CallWindow call_window, CallState call_state, StreamInteractor stream_interactor) { this.call_window = call_window; @@ -93,18 +93,6 @@ public class Dino.Ui.CallWindowController : Object { call_window_handler_ids += call_window.realize.connect(() => { capture_window_size(); }); - invite_handler_ids += call_window.invite_button.clicked.connect(() => { - Gee.List acc_list = new ArrayList(Account.equals_func); - acc_list.add(call.account); - SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list); - add_chat_dialog.set_transient_for((Window) call_window.get_toplevel()); - add_chat_dialog.title = _("Invite to Call"); - add_chat_dialog.ok_button.label = _("Invite"); - add_chat_dialog.selected.connect((account, jid) => { - call_state.invite_to_call.begin(jid); - }); - add_chat_dialog.present(); - }); calls.conference_info_received.connect((call, conference_info) => { if (!this.call.equals(call)) return; @@ -129,6 +117,19 @@ public class Dino.Ui.CallWindowController : Object { update_own_video(); } + private void invite_button_clicked() { + Gee.List acc_list = new ArrayList(Account.equals_func); + acc_list.add(call.account); + SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list); + add_chat_dialog.set_transient_for((Window) call_window.get_toplevel()); + add_chat_dialog.title = _("Invite to Call"); + add_chat_dialog.ok_button.label = _("Invite"); + add_chat_dialog.selected.connect((account, jid) => { + call_state.invite_to_call.begin(jid); + }); + add_chat_dialog.present(); + } + private void connect_peer_signals(PeerState peer_state) { string peer_id = peer_state.internal_id; Jid peer_jid = peer_state.jid; @@ -149,7 +150,7 @@ public class Dino.Ui.CallWindowController : Object { call_state.can_convert_into_groupcall.begin((_, res) => { bool can_convert = call_state.can_convert_into_groupcall.end(res); - call_window.invite_button_revealer.visible = can_convert; + participant_widgets.values.@foreach((widget) => widget.may_show_invite_button = true); }); call_plugin.devices_changed.connect((media, incoming) => { @@ -160,7 +161,7 @@ public class Dino.Ui.CallWindowController : Object { update_audio_device_choices(); update_video_device_choices(); } else if (participant_widgets.size >= 1) { - call_window.invite_button_revealer.visible = true; + participant_widgets.values.@foreach((widget) => widget.may_show_invite_button = true); } }); peer_state.counterpart_sends_video_updated.connect((mute) => { @@ -215,7 +216,8 @@ public class Dino.Ui.CallWindowController : Object { string participant_name = conversation != null ? Util.get_conversation_display_name(stream_interactor, conversation) : jid.bare_jid.to_string(); ParticipantWidget participant_widget = new ParticipantWidget(participant_name); - participant_widget.menu_button.clicked.connect((event) => { + participant_widget.may_show_invite_button = !participant_widgets.is_empty; + participant_widget.debug_information_clicked.connect(() => { var conn_details_window = new CallConnectionDetailsWindow() { title=participant_name, visible=true }; conn_details_window.update_content(peer_states[participant_id].get_info()); uint timeout_handle_id = Timeout.add_seconds(1, () => { @@ -227,6 +229,7 @@ public class Dino.Ui.CallWindowController : Object { conn_details_window.present(); this.call_window.destroy.connect(() => conn_details_window.close() ); }); + invite_handler_ids[participant_id] += participant_widget.invite_button_clicked.connect(() => invite_button_clicked()); participant_widgets[participant_id] = participant_widget; call_window.add_participant(participant_id, participant_widget); @@ -256,7 +259,9 @@ public class Dino.Ui.CallWindowController : Object { if (peer_states.has_key(participant_id)) debug(@"[%s] Call window controller | Remove participant: %s", call.account.bare_jid.to_string(), peer_states[participant_id].jid.to_string()); participant_videos.unset(participant_id); + participant_widgets[participant_id].disconnect(invite_handler_ids[participant_id]); participant_widgets.unset(participant_id); + invite_handler_ids.unset(participant_id); peer_states.unset(participant_id); call_window.remove_participant(participant_id); } @@ -337,9 +342,9 @@ public class Dino.Ui.CallWindowController : Object { public override void dispose() { foreach (ulong handler_id in call_window_handler_ids) call_window.disconnect(handler_id); foreach (ulong handler_id in bottom_bar_handler_ids) call_window.bottom_bar.disconnect(handler_id); - foreach (ulong handler_id in invite_handler_ids) call_window.invite_button.disconnect(handler_id); + participant_widgets.keys.@foreach((peer_id) => { remove_participant(peer_id); return true; }); - call_window_handler_ids = bottom_bar_handler_ids = invite_handler_ids = new ulong[0]; + call_window_handler_ids = bottom_bar_handler_ids = new ulong[0]; base.dispose(); } diff --git a/main/src/ui/call_window/participant_widget.vala b/main/src/ui/call_window/participant_widget.vala index cbf8df2d..0d8d25b4 100644 --- a/main/src/ui/call_window/participant_widget.vala +++ b/main/src/ui/call_window/participant_widget.vala @@ -9,63 +9,57 @@ namespace Dino.Ui { public class ParticipantWidget : Gtk.Overlay { public Widget main_widget; - public Box outer_box = new Box(Orientation.HORIZONTAL, 0) { valign=Align.START, visible=true }; + public HeaderBar header_bar = new HeaderBar() { valign=Align.START, visible=true }; public Box inner_box = new Box(Orientation.HORIZONTAL, 0) { margin_start=5, margin_top=5, hexpand=true, visible=true }; public Box title_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER, hexpand=true, visible=true }; public CallEncryptionButton encryption_button = new CallEncryptionButton() { opacity=0, relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_end=5, visible=true }; - public Label status_label = new Label("") { ellipsize=EllipsizeMode.MIDDLE }; - public Label name_label = new Label("") { ellipsize=EllipsizeMode.MIDDLE, visible=true }; - public Button menu_button = new Button.from_icon_name("view-more-horizontal-symbolic") { relief=ReliefStyle.NONE, visible=true }; + public MenuButton menu_button = new MenuButton() { relief=ReliefStyle.NONE, visible=true, image=new Image.from_icon_name("open-menu-symbolic", IconSize.MENU) }; + public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE, visible=true }; public bool shows_video = false; public string? participant_name; bool is_highest_row = false; - bool is_lowest_row = false; public bool controls_active { get; set; } + public bool may_show_invite_button { get; set; } + + public signal void debug_information_clicked(); + public signal void invite_button_clicked(); public ParticipantWidget(string participant_name) { this.participant_name = participant_name; - name_label.label = participant_name; + header_bar.title = participant_name; + header_bar.get_style_context().add_class("participant-header-bar"); + header_bar.pack_start(invite_button); + header_bar.pack_start(encryption_button); + header_bar.pack_end(menu_button); + PopoverMenu menu = new PopoverMenu(); + Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; + ModelButton debug_information_button = new ModelButton() { text=_("Debug information"), visible=true }; + debug_information_button.clicked.connect(() => debug_information_clicked()); + box.add(debug_information_button); + menu.add(box); + menu_button.popover = menu; + invite_button.clicked.connect(() => invite_button_clicked()); - name_label.attributes = new AttrList(); - name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); - - name_label.attributes = new AttrList(); - name_label.attributes.filter((attr) => attr.equal(attr_scale_new(0.9))); - status_label.get_style_context().add_class("dim-label"); - - Util.force_css(outer_box, "* { color: white; text-shadow: 1px 1px black; }"); - menu_button.get_style_context().add_class("participant-title-button"); - encryption_button.get_style_context().add_class("participant-title-button"); - - title_box.add(name_label); - title_box.add(status_label); - - outer_box.add(inner_box); - - inner_box.add(menu_button); - inner_box.add(encryption_button); - inner_box.add(title_box); - inner_box.add(new Button.from_icon_name("go-up-symbolic") { opacity=0, visible=true }); - inner_box.add(new Button.from_icon_name("go-up-symbolic") { opacity=0, visible=true }); - - this.add_overlay(outer_box); + this.add_overlay(header_bar); this.notify["controls-active"].connect(reveal_or_hide_controls); } - public void on_show_names_changed(bool show) { - name_label.visible = show; - reveal_or_hide_controls(); - } - - public void on_highest_row_changed(bool is_highest) { + public void on_row_changed(bool is_highest, bool is_lowest, bool is_start, bool is_end) { is_highest_row = is_highest; - reveal_or_hide_controls(); - } - - public void on_lowest_row_changed(bool is_lowest) { - is_lowest_row = is_lowest; + header_bar.show_close_button = is_highest_row; + invite_button.visible = may_show_invite_button && is_highest_row && is_start; + if (is_highest_row) { + header_bar.get_style_context().add_class("call-header-background"); + Gtk.Settings? gtk_settings = Gtk.Settings.get_default(); + if (gtk_settings != null) { + string[] buttons = gtk_settings.gtk_decoration_layout.split(":"); + header_bar.decoration_layout = (is_start ? buttons[0] : "") + ":" + (is_end && buttons.length == 2 ? buttons[1] : ""); + } + } else { + header_bar.get_style_context().remove_class("call-header-background"); + } reveal_or_hide_controls(); } @@ -98,32 +92,19 @@ namespace Dino.Ui { } public void set_status(string state) { - status_label.visible = true; - if (state == "requested") { - status_label.label = _("Calling…"); + header_bar.subtitle = _("Calling…"); } else if (state == "ringing") { - status_label.label = _("Ringing…"); + header_bar.subtitle = _("Ringing…"); } else if (state == "establishing") { - status_label.label = _("Connecting…"); + header_bar.subtitle = _("Connecting…"); } else { - status_label.visible = false; + header_bar.subtitle = ""; } } private void reveal_or_hide_controls() { - if (controls_active && name_label.visible) { - title_box.opacity = 1; - menu_button.opacity = 1; - } else { - title_box.opacity = 0; - menu_button.opacity = 0; - } - if (is_highest_row && controls_active) { - outer_box.get_style_context().add_class("call-header-bar"); - } else { - outer_box.get_style_context().remove_class("call-header-bar"); - } + header_bar.opacity = controls_active ? 1.0 : 0.0; } } } \ No newline at end of file