diff options
| -rw-r--r-- | main/data/style.css | 7 | ||||
| -rw-r--r-- | main/meson.build | 1 | ||||
| -rw-r--r-- | main/src/ui/conversation_content_view/file_video_widget.vala | 174 | ||||
| -rw-r--r-- | main/src/ui/conversation_content_view/file_widget.vala | 16 |
4 files changed, 196 insertions, 2 deletions
diff --git a/main/data/style.css b/main/data/style.css index 5dc5122e..f3bd082c 100644 --- a/main/data/style.css +++ b/main/data/style.css @@ -273,11 +273,16 @@ window.dino-main .call-box { margin: 12px 16px 12px 12px; } -window.dino-main .file-image-widget { +window.dino-main .file-image-widget, +window.dino-main .file-video-widget { border: 1px solid alpha(@theme_fg_color, 0.1); border-radius: 0; } +window.dino-main .file-video-widget { + background: black; +} + window.dino-main .file-image-widget .file-box-outer { color: #eee; background: rgba(0, 0, 0, 0.5); diff --git a/main/meson.build b/main/meson.build index 56f31237..446bb0d2 100644 --- a/main/meson.build +++ b/main/meson.build @@ -51,6 +51,7 @@ sources = files( 'src/ui/conversation_content_view/file_default_widget.vala', 'src/ui/conversation_content_view/file_image_widget.vala', 'src/ui/conversation_content_view/file_transmission_progress.vala', + 'src/ui/conversation_content_view/file_video_widget.vala', 'src/ui/conversation_content_view/file_widget.vala', 'src/ui/conversation_content_view/item_actions.vala', 'src/ui/conversation_content_view/message_widget.vala', diff --git a/main/src/ui/conversation_content_view/file_video_widget.vala b/main/src/ui/conversation_content_view/file_video_widget.vala new file mode 100644 index 00000000..703428d9 --- /dev/null +++ b/main/src/ui/conversation_content_view/file_video_widget.vala @@ -0,0 +1,174 @@ +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui { + +public class FileVideoWidget : Widget { + private const int MIN_WIDTH = 100; + private const int MIN_HEIGHT = 100; + private const int MAX_WIDTH = 600; + private const int MAX_HEIGHT = 300; + + private Overlay overlay = new Overlay(); + private FixedRatioVideo video = new FixedRatioVideo() { + min_width = MIN_WIDTH, + min_height = MIN_HEIGHT, + max_width = MAX_WIDTH, + max_height = MAX_HEIGHT + }; + private Gtk.Box video_overlay_toolbar = new Gtk.Box(Orientation.VERTICAL, 0) { + halign = Align.END, + valign = Align.START, + margin_top = 10, + margin_start = 10, + margin_end = 10, + margin_bottom = 10, + vexpand = false, + visible = false + }; + + construct { + layout_manager = new BinLayout(); + check_widget_leak(this); + } + + public FileVideoWidget(FileTransfer file_transfer) { + this.halign = Align.START; + this.add_css_class("file-video-widget"); + + MenuButton button = new MenuButton(); + button.icon_name = "dino-view-more"; + Menu menu_model = new Menu(); + menu_model.append(_("Open"), "file.open"); + menu_model.append(_("Save as…"), "file.save_as"); + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); + button.popover = popover_menu; + video_overlay_toolbar.append(button); + video_overlay_toolbar.add_css_class("card"); + video_overlay_toolbar.add_css_class("toolbar"); + video_overlay_toolbar.add_css_class("compact-card-toolbar"); + video_overlay_toolbar.set_cursor_from_name("default"); + + video.file = file_transfer.get_file(); + + overlay.set_child(video); + overlay.add_overlay(video_overlay_toolbar); + overlay.set_clip_overlay(video_overlay_toolbar, true); + overlay.insert_after(this, null); + + EventControllerMotion motion_events = new EventControllerMotion(); + this.add_controller(motion_events); + motion_events.enter.connect(() => { + video_overlay_toolbar.visible = true; + }); + motion_events.leave.connect(() => { + if (button.popover != null && button.popover.visible) return; + video_overlay_toolbar.visible = false; + }); + } + + public static bool can_display(FileTransfer file_transfer) { + if (file_transfer.state != FileTransfer.State.COMPLETE || file_transfer.path == null || file_transfer.content_type == null) { + return false; + } + + string? mime_type = file_transfer.content_type.get_mime_type(); + return mime_type != null && mime_type.has_prefix("video/"); + } + + public override void dispose() { + if (video.media_stream != null) video.media_stream.pause(); + if (overlay != null && overlay.parent != null) overlay.unparent(); + base.dispose(); + } +} + +private class FixedRatioVideo : Widget { + public int min_width { get; set; default = -1; } + public int max_width { get; set; default = int.MAX; } + public int min_height { get; set; default = -1; } + public int max_height { get; set; default = int.MAX; } + public File? file { get { return inner.file; } set { inner.file = value; } } + public MediaStream? media_stream { get { return inner.media_stream; } } + + private Gtk.Video inner = new Gtk.Video() { + autoplay = false, + loop = false + }; + + construct { + set_css_name("video"); + inner.insert_after(this, null); + this.notify.connect(queue_resize); + check_widget_leak(this); + } + + private void measure_target_size(out int width, out int height) { + if (width_request != -1 && height_request != -1) { + width = width_request; + height = height_request; + return; + } + + width = min_width; + height = min_height; + + if (inner.should_layout()) { + int child_min, child_nat, child_min_baseline, child_nat_baseline; + inner.measure(Orientation.HORIZONTAL, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline); + width = int.max(child_nat, width); + } + width = int.min(width, max_width); + + if (inner.should_layout()) { + int child_min, child_nat, child_min_baseline, child_nat_baseline; + inner.measure(Orientation.VERTICAL, width, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline); + height = int.max(child_nat, height); + } + + if (height > max_height) { + height = max_height; + width = min_width; + + if (inner.should_layout()) { + int child_min, child_nat, child_min_baseline, child_nat_baseline; + inner.measure(Orientation.HORIZONTAL, max_height, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline); + width = int.max(child_nat, width); + } + width = int.min(width, max_width); + } + } + + public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) { + minimum_baseline = -1; + natural_baseline = -1; + + int width, height; + measure_target_size(out width, out height); + if (orientation == Orientation.HORIZONTAL) { + minimum = min_width; + natural = int.max(min_width, int.min(width, max_width)); + } else if (for_size == -1) { + minimum = min_height; + natural = int.max(min_height, int.min(height, max_height)); + } else { + minimum = natural = int.max(min_height, int.min(height * for_size / width, height)); + } + } + + public override void size_allocate(int width, int height, int baseline) { + if (inner.should_layout()) inner.allocate(width, height, baseline, null); + } + + public override SizeRequestMode get_request_mode() { + return SizeRequestMode.HEIGHT_FOR_WIDTH; + } + + public override void dispose() { + inner.unparent(); + base.dispose(); + } +} + +} diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index ecc2d3e9..3c21412a 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -46,6 +46,7 @@ public class FileWidget : SizeRequestBin { enum State { IMAGE, + VIDEO, DEFAULT } @@ -94,6 +95,7 @@ public class FileWidget : SizeRequestBin { private async void update_widget() { bool show_image = FileImageWidget.can_display(file_transfer); + bool show_video = FileVideoWidget.can_display(file_transfer); if (show_image && state != State.IMAGE) { var content_bak = content; @@ -114,7 +116,19 @@ public class FileWidget : SizeRequestBin { } catch (Error e) { } } - if (!show_image && state != State.DEFAULT) { + if (show_image && state == State.IMAGE) return; + + if (show_video && state != State.VIDEO) { + if (content != null) content.unparent(); + content = new FileVideoWidget(file_transfer); + state = State.VIDEO; + content.insert_after(this, null); + return; + } + + if (show_video && state == State.VIDEO) return; + + if (state != State.DEFAULT) { if (content != null) content.unparent(); FileDefaultWidget default_file_widget = new FileDefaultWidget(); default_widget_controller = new FileDefaultWidgetController(default_file_widget); |
