From cc7db3b85f7b29bfac333937d8bf09a81d8dc4a5 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 8 Jan 2023 21:41:10 +0100 Subject: [PATCH] Fix scaling image for GTK4 --- main/data/style.css | 8 +- .../file_image_widget.vala | 25 +- main/src/ui/util/scaling_image.vala | 261 +++++++----------- 3 files changed, 111 insertions(+), 183 deletions(-) diff --git a/main/data/style.css b/main/data/style.css index 63d0a606..fffee8a3 100644 --- a/main/data/style.css +++ b/main/data/style.css @@ -132,7 +132,7 @@ window.dino-main .dino-quote:hover { margin-bottom: 0; } -/* Fie Widget */ +/* File Widget */ window.dino-main .file-box-outer, window.dino-main .call-box-outer { @@ -148,7 +148,7 @@ window.dino-main .call-box { window.dino-main .file-image-widget { border: 1px solid alpha(@theme_fg_color, 0.1); - border-radius: 3px; + border-radius: 6px; } window.dino-main .file-image-widget .file-box-outer { @@ -167,6 +167,10 @@ window.dino-main .file-image-widget .file-box-outer button:hover { background: rgba(100, 100, 100, 0.5); } +.dino-main .file-image-widget picture { + border-radius: 6px; +} + /* Call widget */ window.dino-main .call-box-outer.incoming { diff --git a/main/src/ui/conversation_content_view/file_image_widget.vala b/main/src/ui/conversation_content_view/file_image_widget.vala index 097ac695..ec8481b7 100644 --- a/main/src/ui/conversation_content_view/file_image_widget.vala +++ b/main/src/ui/conversation_content_view/file_image_widget.vala @@ -8,7 +8,6 @@ namespace Dino.Ui { public class FileImageWidget : Box { - private ScalingImage image; FileDefaultWidget file_default_widget; FileDefaultWidgetController file_default_widget_controller; @@ -19,29 +18,7 @@ public class FileImageWidget : Box { } public async void load_from_file(File file, string file_name, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error { - // Load and prepare image in tread - Thread thread = new Thread (null, () => { - ScalingImage image = new ScalingImage() { halign=Align.START, visible = true, max_width = MAX_WIDTH, max_height = MAX_HEIGHT }; - - Gdk.Pixbuf pixbuf; - try { - pixbuf = new Gdk.Pixbuf.from_file(file.get_path()); - } catch (Error error) { - warning("Can't load picture %s - %s", file.get_path(), error.message); - Idle.add(load_from_file.callback); - return null; - } - - pixbuf = pixbuf.apply_embedded_orientation(); - - image.load(pixbuf); - - Idle.add(load_from_file.callback); - return image; - }); - yield; - image = thread.join(); - if (image == null) throw new Error(-1, 0, "Error loading image"); + FixedRatioPicture image = new FixedRatioPicture() { min_width = 100, min_height = 100, max_width = MAX_WIDTH, max_height = MAX_HEIGHT, file = file }; FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); string? mime_type = file_info.get_content_type(); diff --git a/main/src/ui/util/scaling_image.vala b/main/src/ui/util/scaling_image.vala index 20ff6718..d6ca31fd 100644 --- a/main/src/ui/util/scaling_image.vala +++ b/main/src/ui/util/scaling_image.vala @@ -2,183 +2,130 @@ using Gdk; using Gtk; namespace Dino.Ui { -class ScalingImage : Widget { - public int min_width { get; set; default = -1; } + +class FixedRatioLayout : Gtk.LayoutManager { + public int min_width { get; set; default = 0; } public int target_width { get; set; default = -1; } - public int max_width { get; set; default = -1; } - public int min_height { get; set; default = -1; } - public int max_height { get; set; default = -1; } + public int max_width { get; set; default = int.MAX; } + public int min_height { get; set; default = 0; } + public int target_height { get; set; default = -1; } + public int max_height { get; set; default = int.MAX; } - private Pixbuf image; - private double image_ratio; - private int image_height = 0; - private int image_width = 0; - private int last_allocation_height = -1; - private int last_allocation_width = -1; - private int last_scale_factor = -1; - private Cairo.ImageSurface? cached_surface; - private static int8 use_image_surface = -1; - - public void load(Pixbuf image) { - this.image = image; - this.image_ratio = ((double)image.height) / ((double)image.width); - this.image_height = image.height; - this.image_width = image.width; - queue_resize(); + public FixedRatioLayout() { + this.notify.connect(layout_changed); } - public override void dispose() { - base.dispose(); - image = null; - cached_surface = null; - } + private void measure_target_size(Gtk.Widget widget, out int width, out int height) { + if (target_width != -1 && target_height != -1) { + width = target_width; + height = target_height; + return; + } + Widget child; + width = min_width; + height = min_height; - private void calculate_size(ref double exact_width, ref double exact_height) { - if (exact_width == -1 && exact_height == -1) { - if (target_width == -1) { - exact_width = ((double)image_width) / ((double)scale_factor); - exact_height = ((double)image_height) / ((double)scale_factor); - } else { - exact_width = target_width; - exact_height = exact_width * image_ratio; + child = widget.get_first_child(); + while (child != null) { + if (child.should_layout()) { + int child_min = 0; + int child_nat = 0; + int child_min_baseline = -1; + int child_nat_baseline = -1; + child.measure(Orientation.HORIZONTAL, -1, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline); + width = int.max(child_nat, width); } - } else if (exact_width != -1) { - exact_height = exact_width * image_ratio; - } else if (exact_height != -1) { - exact_width = exact_height / image_ratio; - } else { - if (exact_width * image_ratio > exact_height + scale_factor) { - exact_width = exact_height / image_ratio; - } else if (exact_height / image_ratio > exact_width + scale_factor) { - exact_height = exact_width * image_ratio; + child = child.get_next_sibling(); + } + width = int.min(width, max_width); + + child = widget.get_first_child(); + while (child != null) { + if (child.should_layout()) { + int child_min = 0; + int child_nat = 0; + int child_min_baseline = -1; + int child_nat_baseline = -1; + child.measure(Orientation.VERTICAL, width, out child_min, out child_nat, out child_min_baseline, out child_nat_baseline); + height = int.max(child_nat, height); } + child = child.get_next_sibling(); } - if (max_width != -1 && exact_width > max_width) { - exact_width = max_width; - exact_height = exact_width * image_ratio; - } - if (max_height != -1 && exact_height > max_height) { - exact_height = max_height; - exact_width = exact_height / image_ratio; - } - if (exact_width < min_width) exact_width = min_width; - if (exact_height < min_height) exact_height = min_height; - } - public override void size_allocate(int width, int height, int baseline) { - if (max_width != -1) width = int.min(width, max_width); - if (max_height != -1) height = int.min(height, max_height); - height = int.max(height, min_height); - width = int.max(width, min_width); - double exact_width = width, exact_height = height; - calculate_size(ref exact_width, ref exact_height); - base.size_allocate(width, height, baseline); - if (last_allocation_height != height || last_allocation_width != width || last_scale_factor != scale_factor) { - last_allocation_height = height; - last_allocation_width = width; - last_scale_factor = scale_factor; - cached_surface = null; - } - } + if (height > max_height) { + height = max_height; + width = min_width; - public override void snapshot(Gtk.Snapshot snapshot) { - Cairo.Context context = snapshot.append_cairo(Graphene.Rect.alloc().init(0, 0, get_allocated_width(), get_allocated_height())); - draw(context); - } - - public bool draw(Cairo.Context ctx_in) { - if (image == null) return false; - Cairo.Context ctx = ctx_in; - int width = this.get_allocated_width(), height = this.get_allocated_height(), base_factor = 1; - if (use_image_surface == -1) { - // TODO: detect if we have to buffer in image surface - use_image_surface = 1; - } - if (use_image_surface == 1) { - ctx_in.scale(1f / scale_factor, 1f / scale_factor); - if (cached_surface != null) { - ctx_in.set_source_surface(cached_surface, 0, 0); - ctx_in.paint(); - ctx_in.set_source_rgb(0, 0, 0); - return true; + child = widget.get_first_child(); + while (child != null) { + if (child.should_layout()) { + int child_min = 0; + int child_nat = 0; + int child_min_baseline = -1; + int child_nat_baseline = -1; + child.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); + } + child = child.get_next_sibling(); } - width *= scale_factor; - height *= scale_factor; - base_factor *= scale_factor; - cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); - ctx = new Cairo.Context(cached_surface); + width = int.min(width, max_width); } - - double radius = 3 * base_factor; - double degrees = Math.PI / 180.0; - ctx.new_sub_path(); - ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); - ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); - ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); - ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); - ctx.close_path(); - ctx.clip(); - - Cairo.Surface buffer = sub_surface(ctx, width, height); - ctx.set_source_surface(buffer, 0, 0); - ctx.paint(); - - if (use_image_surface == 1) { - ctx_in.set_source_surface(ctx.get_target(), 0, 0); - ctx_in.paint(); - ctx_in.set_source_rgb(0, 0, 0); - } - - return true; } - private Cairo.Surface sub_surface(Cairo.Context ctx, int width, int height) { - Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); - Cairo.Context bufctx = new Cairo.Context(buffer); - double w_scale = (double) width / image_width; - double h_scale = (double) height / image_height; - double scale = double.max(w_scale, h_scale); - bufctx.scale(scale, scale); - - double x_off = 0, y_off = 0; - if (scale == h_scale) { - x_off = (width / scale - image_width) / 2.0; - } else { - y_off = (height / scale - image_height) / 2.0; - } - Gdk.cairo_set_source_pixbuf(bufctx, image, 0, 0); - bufctx.get_source().set_filter(Cairo.Filter.BILINEAR); - bufctx.paint(); - bufctx.set_source_rgb(0, 0, 0); - return buffer; - } - - public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) { - double natural_width = -1, natural_height = -1; - calculate_size(ref natural_width, ref natural_height); + public override void measure(Gtk.Widget widget, 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(widget, out width, out height); if (orientation == Orientation.HORIZONTAL) { - natural = (int) Math.ceil(natural_width); + minimum = min_width; + natural = width; + } else if (for_size == -1) { + minimum = min_height; + natural = height; } else { - natural = (int) Math.ceil(natural_height); + minimum = natural = height * for_size / width; } - if (for_size == -1) { - minimum = 0; - } else { - if (orientation == Orientation.HORIZONTAL) { - double exact_width = -1, exact_height = for_size; - calculate_size(ref exact_width, ref exact_height); - minimum = int.max((int)Math.floor(exact_width), min_width); - } else { - double exact_width = for_size, exact_height = -1; - calculate_size(ref exact_width, ref exact_height); - minimum = int.max((int)Math.floor(exact_height), min_height); - } - } - minimum_baseline = natural_baseline = -1; } - public override SizeRequestMode get_request_mode() { + public override void allocate(Gtk.Widget widget, int width, int height, int baseline) { + Widget child = widget.get_first_child(); + while (child != null) { + if (child.should_layout()) { + child.allocate(width, height, baseline, null); + } + child = child.get_next_sibling(); + } + } + + public override SizeRequestMode get_request_mode(Gtk.Widget widget) { return SizeRequestMode.HEIGHT_FOR_WIDTH; } } + +class FixedRatioPicture : Gtk.Widget { + public int min_width { get { return layout.min_width; } set { layout.min_width = value; } } + public int target_width { get { return layout.target_width; } set { layout.target_width = value; } } + public int max_width { get { return layout.max_width; } set { layout.max_width = value; } } + public int min_height { get { return layout.min_height; } set { layout.min_height = value; } } + public int target_height { get { return layout.target_height; } set { layout.target_height = value; } } + public int max_height { get { return layout.max_height; } set { layout.max_height = value; } } + public File file { get { return inner.file; } set { inner.file = value; } } + public Gdk.Paintable paintable { get { return inner.paintable; } set { inner.paintable = value; } } +#if GTK_4_8 && VALA_0_58 + public Gtk.ContentFit content_fit { get { return inner.content_fit; } set { inner.content_fit = value; } } +#endif + private Gtk.Picture inner = new Gtk.Picture(); + private FixedRatioLayout layout = new FixedRatioLayout(); + + public FixedRatioPicture() { + layout_manager = layout; + inner.insert_after(this, null); + } + + public override void dispose() { + inner.unparent(); + base.dispose(); + } } +} \ No newline at end of file