Calls: Use GtkHeaderBar for each participant

This commit is contained in:
Marvin W 2022-02-12 12:54:48 +01:00
parent be751a5dda
commit 0f5f57888e
No known key found for this signature in database
GPG key ID: 072E9235DB996F2A
4 changed files with 95 additions and 110 deletions

View file

@ -256,6 +256,10 @@ box.dino-input-error label.input-status-highlight-once {
/* Call window */ /* Call window */
.dino-call-window decoration {
border-radius: 0;
}
.dino-call-window .titlebar { .dino-call-window .titlebar {
min-height: 0; min-height: 0;
} }
@ -264,12 +268,6 @@ box.dino-input-error label.input-status-highlight-once {
box-shadow: none; 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 { .dino-call-window button.call-button {
outline: 0; outline: 0;
border-radius: 1000px; border-radius: 1000px;
@ -335,31 +333,39 @@ box.dino-input-error label.input-status-highlight-once {
background: rgba(20,20,20,0.5); 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)); background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0));
border: 0; border: 0;
border-radius: 0; border-radius: 0;
} }
.dino-call-window .call-header-bar { .dino-call-window .participant-header-bar button {
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 {
background: none; 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; 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 { .dino-call-window .call-bottom-bar {
background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3)); background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3));
border: 0; border: 0;

View file

@ -15,11 +15,9 @@ namespace Dino.Ui {
public Grid grid = new Grid() { visible=true }; public Grid grid = new Grid() { visible=true };
public CallBottomBar bottom_bar = new CallBottomBar() { 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 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 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.CROSSFADE, transition_duration=200, visible=true }; 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 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 Widget? own_video = null;
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>(); private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
private ArrayList<string> participants = new ArrayList<string>(); private ArrayList<string> participants = new ArrayList<string>();
@ -32,12 +30,11 @@ namespace Dino.Ui {
public bool controls_active { get; set; default=true; } public bool controls_active { get; set; default=true; }
construct { 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.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); header_bar_revealer.add(header_bar);
bottom_bar_revealer.add(bottom_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"); own_video_box.get_style_context().add_class("own-video");
this.get_style_context().add_class("dino-call-window"); 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(own_video_box);
overlay.add_overlay(bottom_bar_revealer); overlay.add_overlay(bottom_bar_revealer);
overlay.add_overlay(header_bar_revealer); overlay.add_overlay(header_bar_revealer);
overlay.add_overlay(invite_button_revealer);
overlay.get_child_position.connect(on_get_child_position); overlay.get_child_position.connect(on_get_child_position);
add(overlay); add(overlay);
@ -54,8 +50,6 @@ namespace Dino.Ui {
public CallWindow() { public CallWindow() {
this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", 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);
this.motion_notify_event.connect(reveal_control_elements); this.motion_notify_event.connect(reveal_control_elements);
this.enter_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_bottom = margin_bottom;
participant_widgets[participants[0]].margin_start = margin_left; participant_widgets[participants[0]].margin_start = margin_left;
participant_widgets[participants[0]].on_lowest_row_changed(margin_bottom == 0); participant_widgets[participants[0]].on_row_changed(margin_top == 0, margin_bottom == 0, margin_left == 0, margin_right == 0);
participant_widgets[participants[0]].on_highest_row_changed(margin_top == 0);
return; return;
} }

View file

@ -16,12 +16,12 @@ public class Dino.Ui.CallWindowController : Object {
private HashMap<string, Plugins.VideoCallWidget> participant_videos = new HashMap<string, Plugins.VideoCallWidget>(); private HashMap<string, Plugins.VideoCallWidget> participant_videos = new HashMap<string, Plugins.VideoCallWidget>();
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>(); private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
private HashMap<string, PeerState> peer_states = new HashMap<string, PeerState>(); private HashMap<string, PeerState> peer_states = new HashMap<string, PeerState>();
private HashMap<string, ulong> invite_handler_ids = new HashMap<string, ulong>();
private int window_height = -1; private int window_height = -1;
private int window_width = -1; private int window_width = -1;
private bool window_size_changed = false; private bool window_size_changed = false;
private ulong[] call_window_handler_ids = new ulong[0]; private ulong[] call_window_handler_ids = new ulong[0];
private ulong[] bottom_bar_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) { public CallWindowController(CallWindow call_window, CallState call_state, StreamInteractor stream_interactor) {
this.call_window = call_window; this.call_window = call_window;
@ -93,18 +93,6 @@ public class Dino.Ui.CallWindowController : Object {
call_window_handler_ids += call_window.realize.connect(() => { call_window_handler_ids += call_window.realize.connect(() => {
capture_window_size(); capture_window_size();
}); });
invite_handler_ids += call_window.invite_button.clicked.connect(() => {
Gee.List<Account> acc_list = new ArrayList<Account>(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) => { calls.conference_info_received.connect((call, conference_info) => {
if (!this.call.equals(call)) return; if (!this.call.equals(call)) return;
@ -129,6 +117,19 @@ public class Dino.Ui.CallWindowController : Object {
update_own_video(); update_own_video();
} }
private void invite_button_clicked() {
Gee.List<Account> acc_list = new ArrayList<Account>(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) { private void connect_peer_signals(PeerState peer_state) {
string peer_id = peer_state.internal_id; string peer_id = peer_state.internal_id;
Jid peer_jid = peer_state.jid; Jid peer_jid = peer_state.jid;
@ -149,7 +150,7 @@ public class Dino.Ui.CallWindowController : Object {
call_state.can_convert_into_groupcall.begin((_, res) => { call_state.can_convert_into_groupcall.begin((_, res) => {
bool can_convert = call_state.can_convert_into_groupcall.end(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) => { call_plugin.devices_changed.connect((media, incoming) => {
@ -160,7 +161,7 @@ public class Dino.Ui.CallWindowController : Object {
update_audio_device_choices(); update_audio_device_choices();
update_video_device_choices(); update_video_device_choices();
} else if (participant_widgets.size >= 1) { } 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) => { 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(); 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); 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 }; var conn_details_window = new CallConnectionDetailsWindow() { title=participant_name, visible=true };
conn_details_window.update_content(peer_states[participant_id].get_info()); conn_details_window.update_content(peer_states[participant_id].get_info());
uint timeout_handle_id = Timeout.add_seconds(1, () => { uint timeout_handle_id = Timeout.add_seconds(1, () => {
@ -227,6 +229,7 @@ public class Dino.Ui.CallWindowController : Object {
conn_details_window.present(); conn_details_window.present();
this.call_window.destroy.connect(() => conn_details_window.close() ); 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; participant_widgets[participant_id] = participant_widget;
call_window.add_participant(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()); 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_videos.unset(participant_id);
participant_widgets[participant_id].disconnect(invite_handler_ids[participant_id]);
participant_widgets.unset(participant_id); participant_widgets.unset(participant_id);
invite_handler_ids.unset(participant_id);
peer_states.unset(participant_id); peer_states.unset(participant_id);
call_window.remove_participant(participant_id); call_window.remove_participant(participant_id);
} }
@ -337,9 +342,9 @@ public class Dino.Ui.CallWindowController : Object {
public override void dispose() { public override void dispose() {
foreach (ulong handler_id in call_window_handler_ids) call_window.disconnect(handler_id); 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 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(); base.dispose();
} }

View file

@ -9,63 +9,57 @@ namespace Dino.Ui {
public class ParticipantWidget : Gtk.Overlay { public class ParticipantWidget : Gtk.Overlay {
public Widget main_widget; 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 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 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 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 MenuButton menu_button = new MenuButton() { relief=ReliefStyle.NONE, visible=true, image=new Image.from_icon_name("open-menu-symbolic", IconSize.MENU) };
public Label name_label = new Label("") { ellipsize=EllipsizeMode.MIDDLE, visible=true }; public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE, visible=true };
public Button menu_button = new Button.from_icon_name("view-more-horizontal-symbolic") { relief=ReliefStyle.NONE, visible=true };
public bool shows_video = false; public bool shows_video = false;
public string? participant_name; public string? participant_name;
bool is_highest_row = false; bool is_highest_row = false;
bool is_lowest_row = false;
public bool controls_active { get; set; } 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) { public ParticipantWidget(string participant_name) {
this.participant_name = 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(); this.add_overlay(header_bar);
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.notify["controls-active"].connect(reveal_or_hide_controls); this.notify["controls-active"].connect(reveal_or_hide_controls);
} }
public void on_show_names_changed(bool show) { public void on_row_changed(bool is_highest, bool is_lowest, bool is_start, bool is_end) {
name_label.visible = show;
reveal_or_hide_controls();
}
public void on_highest_row_changed(bool is_highest) {
is_highest_row = is_highest; is_highest_row = is_highest;
reveal_or_hide_controls(); header_bar.show_close_button = is_highest_row;
} invite_button.visible = may_show_invite_button && is_highest_row && is_start;
if (is_highest_row) {
public void on_lowest_row_changed(bool is_lowest) { header_bar.get_style_context().add_class("call-header-background");
is_lowest_row = is_lowest; 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(); reveal_or_hide_controls();
} }
@ -98,32 +92,19 @@ namespace Dino.Ui {
} }
public void set_status(string state) { public void set_status(string state) {
status_label.visible = true;
if (state == "requested") { if (state == "requested") {
status_label.label = _("Calling…"); header_bar.subtitle = _("Calling…");
} else if (state == "ringing") { } else if (state == "ringing") {
status_label.label = _("Ringing…"); header_bar.subtitle = _("Ringing…");
} else if (state == "establishing") { } else if (state == "establishing") {
status_label.label = _("Connecting…"); header_bar.subtitle = _("Connecting…");
} else { } else {
status_label.visible = false; header_bar.subtitle = "";
} }
} }
private void reveal_or_hide_controls() { private void reveal_or_hide_controls() {
if (controls_active && name_label.visible) { header_bar.opacity = controls_active ? 1.0 : 0.0;
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");
}
} }
} }
} }