From 90a28d727b7d11447c21031f437857e982afaef0 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Mon, 17 Mar 2025 22:52:31 +0100 Subject: [PATCH] image viewer popup when clicking an image in a message --- flake.lock | 12 +- src/CMakeLists.txt | 2 + src/chat_gui/image_viewer_popup.cpp | 66 +++ src/chat_gui/image_viewer_popup.hpp | 32 ++ src/chat_gui/send_image_popup.cpp | 615 ++++++++++++++-------------- src/chat_gui4.cpp | 8 +- src/chat_gui4.hpp | 2 + 7 files changed, 425 insertions(+), 312 deletions(-) create mode 100644 src/chat_gui/image_viewer_popup.cpp create mode 100644 src/chat_gui/image_viewer_popup.hpp diff --git a/flake.lock b/flake.lock index 51ec220..85fcfc5 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -37,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1720691131, - "narHash": "sha256-CWT+KN8aTPyMIx8P303gsVxUnkinIz0a/Cmasz1jyIM=", + "lastModified": 1735651292, + "narHash": "sha256-YLbzcBtYo1/FEzFsB3AnM16qFc6fWPMIoOuSoDwvg9g=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a046c1202e11b62cbede5385ba64908feb7bfac4", + "rev": "0da3c44a9460a26d2025ec3ed2ec60a895eb1114", "type": "github" }, "original": { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fce7be3..db10498 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,8 @@ target_sources(tomato PUBLIC ./chat_gui/contact_list.cpp ./chat_gui/file_selector.hpp ./chat_gui/file_selector.cpp + ./chat_gui/image_viewer_popup.hpp + ./chat_gui/image_viewer_popup.cpp ./chat_gui/send_image_popup.hpp ./chat_gui/send_image_popup.cpp ./chat_gui/settings_window.hpp diff --git a/src/chat_gui/image_viewer_popup.cpp b/src/chat_gui/image_viewer_popup.cpp new file mode 100644 index 0000000..423ba09 --- /dev/null +++ b/src/chat_gui/image_viewer_popup.cpp @@ -0,0 +1,66 @@ +#include "./image_viewer_popup.hpp" + +#include + +#include + +ImageViewerPopup::ImageViewerPopup(MessageTextureCache& mtc) : _mtc(mtc) { +} + +// open popup with (image) file +void ImageViewerPopup::view(Message3Handle m) { + if (static_cast(_m)) { + std::cout << "IVP warning: overriding open image\n"; + } + + _m = m; + + _open_popup = true; +} + +// call this each frame +void ImageViewerPopup::render(float) { + if (_open_popup) { + _open_popup = false; + ImGui::OpenPopup("Image##ImageViewerPopup"); + } + + if (!ImGui::BeginPopup("Image##ImageViewerPopup", ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize)) { + _m = {}; // meh, event on close would be nice, but the reset is cheap + _scale = 1.f; + return; + } + + ImGui::SliderFloat("scale", &_scale, 0.05f, 2.f); + + auto [id, img_width, img_height] = _mtc.get(_m); + + img_width = std::max(5, _scale * img_width); + img_height = std::max(5, _scale * img_height); + + ImGui::Image( + id, + ImVec2{ + static_cast(img_width), + static_cast(img_height) + } + ); + + // TODO: figure out nice scroll zooming + //ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY); + //const auto prev_scale = _scale; + //_scale += ImGui::GetIO().MouseWheel * 0.05f; + //_scale = std::clamp(_scale, 0.05f, 3.f); + + //if (std::abs(prev_scale - _scale) > 0.001f) { + //} + + if (ImGui::Shortcut(ImGuiKey_Escape)) { + ImGui::CloseCurrentPopup(); + _m = {}; + _scale = 1.f; + } + + ImGui::EndPopup(); +} + diff --git a/src/chat_gui/image_viewer_popup.hpp b/src/chat_gui/image_viewer_popup.hpp new file mode 100644 index 0000000..a8da8f1 --- /dev/null +++ b/src/chat_gui/image_viewer_popup.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "./texture_cache_defs.hpp" + +#include +#include + +struct ImageViewerPopup { + MessageTextureCache& _mtc; + + Message3Handle _m{}; + float _scale {1.f}; + + bool _open_popup {false}; + + //void reset(void); + + public: + ImageViewerPopup(MessageTextureCache& mtc); + + // open popup with (image) message + //void view(ObjectHandle o); + void view(Message3Handle m); + + // call this each frame + void render(float time_delta); + + // TODO: events (destroy/update) +}; + diff --git a/src/chat_gui/send_image_popup.cpp b/src/chat_gui/send_image_popup.cpp index 14903b1..b4d9389 100644 --- a/src/chat_gui/send_image_popup.cpp +++ b/src/chat_gui/send_image_popup.cpp @@ -219,339 +219,346 @@ void SendImagePopup::render(float time_delta) { } // TODO: add cancel shortcut (esc) - if (ImGui::BeginPopupModal("send image##SendImagePopup", nullptr/*, ImGuiWindowFlags_NoDecoration*/)) { - //const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; - const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + if (!ImGui::BeginPopupModal("send image##SendImagePopup", nullptr/*, ImGuiWindowFlags_NoDecoration*/)) { + return; + } - preview_image.doAnimation(getTimeMS()); + //const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); - time += time_delta; - time = fmod(time, 1.f); // fract() + preview_image.doAnimation(getTimeMS()); - //ImGui::Text("send file....\n......"); + time += time_delta; + time = fmod(time, 1.f); // fract() - { - float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; - float height = crop_rect.h * (width / crop_rect.w); - if (cropping) { - height = original_image.height * (width / original_image.width); + //ImGui::Text("send file....\n......"); + + { + float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; + float height = crop_rect.h * (width / crop_rect.w); + if (cropping) { + height = original_image.height * (width / original_image.width); + } + + const float max_height = + ImGui::GetWindowContentRegionMax().y + - ( + ImGui::GetWindowContentRegionMin().y + + TEXT_BASE_HEIGHT*(2-1) // row of buttons (-1 bc fh inclues fontsize) + + ImGui::GetFrameHeightWithSpacing()*4 + ) + ; + if (height > max_height) { + width *= max_height / height; + height = max_height; + } + + // TODO: propergate texture id type + + // save curser pos + const auto pre_img_curser_screen = ImGui::GetCursorScreenPos(); + const auto pre_img_curser = ImGui::GetCursorPos(); + + if (cropping) { // if cropping + // display full image + ImGui::Image( + preview_image.getID(), + ImVec2{static_cast(width), static_cast(height)} + ); + const auto post_img_curser = ImGui::GetCursorPos(); + + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.5f, 0.5f, 0.5f, 0.4f}); + + auto ul_clipper_pos = ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}; + { // crop upper left clipper + + ImGui::SetCursorPos({ + pre_img_curser.x + ul_clipper_pos.x * width, + pre_img_curser.y + ul_clipper_pos.y * height + }); + + ImGui::Button("##ul_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + if (dragging_last_frame_ul) { + auto drag_total = ImGui::GetMouseDragDelta(); + drag_total.x = (drag_total.x / width) * original_image.width; + drag_total.y = (drag_total.y / height) * original_image.height; + + crop_rect.x = std::max(crop_before_drag.x + drag_total.x, 0.01f); + crop_rect.y = std::max(crop_before_drag.y + drag_total.y, 0.01f); + + crop_rect.x = std::min(crop_rect.x, original_image.width-2); + crop_rect.y = std::min(crop_rect.y, original_image.height-2); + + crop_rect.w = crop_before_drag.w - (crop_rect.x - crop_before_drag.x); + crop_rect.h = crop_before_drag.h - (crop_rect.y - crop_before_drag.y); + } else { + if (ImGui::IsItemActive()) { + dragging_last_frame_ul = true; + // drag started on button, start drag + } + } + } else if (dragging_last_frame_ul) { // was dragging + dragging_last_frame_ul = false; + crop_before_drag = crop_rect; + } } - const float max_height = - ImGui::GetWindowContentRegionMax().y - - ( - ImGui::GetWindowContentRegionMin().y - + TEXT_BASE_HEIGHT*(2-1) // row of buttons (-1 bc fh inclues fontsize) - + ImGui::GetFrameHeightWithSpacing()*4 - ) - ; - if (height > max_height) { - width *= max_height / height; - height = max_height; + auto lr_clipper_pos = ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height}; + { // crop lower right clipper + ImGui::SetCursorPos({ + pre_img_curser.x + lr_clipper_pos.x * width - TEXT_BASE_HEIGHT, + pre_img_curser.y + lr_clipper_pos.y * height - TEXT_BASE_HEIGHT + }); + + ImGui::Button("##lr_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + if (dragging_last_frame_lr) { + auto drag_total = ImGui::GetMouseDragDelta(); + drag_total.x = (drag_total.x / width) * original_image.width; + drag_total.y = (drag_total.y / height) * original_image.height; + + crop_rect.w = std::min(crop_before_drag.w + drag_total.x, original_image.width); + crop_rect.h = std::min(crop_before_drag.h + drag_total.y, original_image.height); + } else { + if (ImGui::IsItemActive()) { + dragging_last_frame_lr = true; + // drag started on button, start drag + } + } + } else if (dragging_last_frame_lr) { // was dragging + dragging_last_frame_lr = false; + crop_before_drag = crop_rect; + } } - // TODO: propergate texture id type + // sanitzie after tool + crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); - // save curser pos - const auto pre_img_curser_screen = ImGui::GetCursorScreenPos(); - const auto pre_img_curser = ImGui::GetCursorPos(); - - if (cropping) { // if cropping - // display full image - ImGui::Image( - preview_image.getID(), - ImVec2{static_cast(width), static_cast(height)} - ); - const auto post_img_curser = ImGui::GetCursorPos(); - - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.5f, 0.5f, 0.5f, 0.4f}); - - auto ul_clipper_pos = ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}; - { // crop upper left clipper - - ImGui::SetCursorPos({ - pre_img_curser.x + ul_clipper_pos.x * width, - pre_img_curser.y + ul_clipper_pos.y * height - }); - - ImGui::Button("##ul_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - if (dragging_last_frame_ul) { - auto drag_total = ImGui::GetMouseDragDelta(); - drag_total.x = (drag_total.x / width) * original_image.width; - drag_total.y = (drag_total.y / height) * original_image.height; - - crop_rect.x = std::max(crop_before_drag.x + drag_total.x, 0.01f); - crop_rect.y = std::max(crop_before_drag.y + drag_total.y, 0.01f); - - crop_rect.x = std::min(crop_rect.x, original_image.width-2); - crop_rect.y = std::min(crop_rect.y, original_image.height-2); - - crop_rect.w = crop_before_drag.w - (crop_rect.x - crop_before_drag.x); - crop_rect.h = crop_before_drag.h - (crop_rect.y - crop_before_drag.y); - } else { - if (ImGui::IsItemActive()) { - dragging_last_frame_ul = true; - // drag started on button, start drag + { // 4 lines delimiting the crop result + ImU32 line_color = 0xffffffff; + { // calc color + auto rgb = [](float x) -> ImVec4 { + auto f = [](float x) { + while (x < 0.f) { + x += 1.f; } - } - } else if (dragging_last_frame_ul) { // was dragging - dragging_last_frame_ul = false; - crop_before_drag = crop_rect; - } - } - auto lr_clipper_pos = ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height}; - { // crop lower right clipper - ImGui::SetCursorPos({ - pre_img_curser.x + lr_clipper_pos.x * width - TEXT_BASE_HEIGHT, - pre_img_curser.y + lr_clipper_pos.y * height - TEXT_BASE_HEIGHT - }); + x = std::fmod(x, 1.f); // fract() - ImGui::Button("##lr_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - if (dragging_last_frame_lr) { - auto drag_total = ImGui::GetMouseDragDelta(); - drag_total.x = (drag_total.x / width) * original_image.width; - drag_total.y = (drag_total.y / height) * original_image.height; - - crop_rect.w = std::min(crop_before_drag.w + drag_total.x, original_image.width); - crop_rect.h = std::min(crop_before_drag.h + drag_total.y, original_image.height); - } else { - if (ImGui::IsItemActive()) { - dragging_last_frame_lr = true; - // drag started on button, start drag + if (x < 1.f/3) { + return x * 3; + } else if (x < 2.f/3) { + return (1 - (x - (1.f/3))) * 3 - 2; + } else { + return 0.f; } - } - } else if (dragging_last_frame_lr) { // was dragging - dragging_last_frame_lr = false; - crop_before_drag = crop_rect; - } - } - - // sanitzie after tool - crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); - - { // 4 lines delimiting the crop result - ImU32 line_color = 0xffffffff; - { // calc color - auto rgb = [](float x) -> ImVec4 { - auto f = [](float x) { - while (x < 0.f) { - x += 1.f; - } - - x = std::fmod(x, 1.f); // fract() - - if (x < 1.f/3) { - return x * 3; - } else if (x < 2.f/3) { - return (1 - (x - (1.f/3))) * 3 - 2; - } else { - return 0.f; - } - }; - - float red = f(x); - float green = f(x - (1.f/3)); - float blue = f(x - (2.f/3)); - - return {red, green, blue, 1.f}; }; - line_color = ImGui::GetColorU32(rgb(time)); - } + float red = f(x); + float green = f(x - (1.f/3)); + float blue = f(x - (2.f/3)); - // x vertical - ImGui::GetWindowDrawList()->AddLine( - {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y}, - {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y + height}, - line_color, - 1.f - ); + return {red, green, blue, 1.f}; + }; - // y horizontal - ImGui::GetWindowDrawList()->AddLine( - {pre_img_curser_screen.x, pre_img_curser_screen.y + ul_clipper_pos.y * height}, - {pre_img_curser_screen.x + width, pre_img_curser_screen.y + ul_clipper_pos.y * height}, - line_color, - 1.f - ); - - // w vertical - ImGui::GetWindowDrawList()->AddLine( - {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y}, - {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y + height}, - line_color, - 1.f - ); - - // h horizontal - ImGui::GetWindowDrawList()->AddLine( - {pre_img_curser_screen.x, pre_img_curser_screen.y + lr_clipper_pos.y * height}, - {pre_img_curser_screen.x + width, pre_img_curser_screen.y + lr_clipper_pos.y * height}, - line_color, - 1.f - ); + line_color = ImGui::GetColorU32(rgb(time)); } - // cancel/ok buttons in the img center? - - ImGui::PopStyleColor(2); - - ImGui::SetCursorPos(post_img_curser); - } else { - crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); - - // display cropped area - ImGui::Image( - preview_image.getID(), - ImVec2{static_cast(width), static_cast(height)}, - ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}, - ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height} + // x vertical + ImGui::GetWindowDrawList()->AddLine( + {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y}, + {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y + height}, + line_color, + 1.f ); - // transparent crop button on image -#if 0 - const auto post_img_curser = ImGui::GetCursorPos(); + // y horizontal + ImGui::GetWindowDrawList()->AddLine( + {pre_img_curser_screen.x, pre_img_curser_screen.y + ul_clipper_pos.y * height}, + {pre_img_curser_screen.x + width, pre_img_curser_screen.y + ul_clipper_pos.y * height}, + line_color, + 1.f + ); - ImGui::SetCursorPos(pre_img_curser); + // w vertical + ImGui::GetWindowDrawList()->AddLine( + {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y}, + {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y + height}, + line_color, + 1.f + ); - // TODO: fancy cropping toggle - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); - if (ImGui::Button("crop", {TEXT_BASE_WIDTH*8, TEXT_BASE_HEIGHT*2})) { - cropping = true; - } - ImGui::PopStyleColor(); - - ImGui::SetCursorPos(post_img_curser); -#endif + // h horizontal + ImGui::GetWindowDrawList()->AddLine( + {pre_img_curser_screen.x, pre_img_curser_screen.y + lr_clipper_pos.y * height}, + {pre_img_curser_screen.x + width, pre_img_curser_screen.y + lr_clipper_pos.y * height}, + line_color, + 1.f + ); } - } - const bool cropped = crop_rect.x != 0 || crop_rect.y != 0 || crop_rect.w != original_image.width || crop_rect.h != original_image.height; - if (cropping) { - if (ImGui::Button("done")) { - cropping = false; - } + // cancel/ok buttons in the img center? + + ImGui::PopStyleColor(2); + + ImGui::SetCursorPos(post_img_curser); } else { - if (ImGui::Button("crop")) { + crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); + + // display cropped area + ImGui::Image( + preview_image.getID(), + ImVec2{static_cast(width), static_cast(height)}, + ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}, + ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height} + ); + + // transparent crop button on image +#if 0 + const auto post_img_curser = ImGui::GetCursorPos(); + + ImGui::SetCursorPos(pre_img_curser); + + // TODO: fancy cropping toggle + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); + if (ImGui::Button("crop", {TEXT_BASE_WIDTH*8, TEXT_BASE_HEIGHT*2})) { cropping = true; } + ImGui::PopStyleColor(); + + ImGui::SetCursorPos(post_img_curser); +#endif } - ImGui::SameLine(); - if (ImGui::Button("reset")) { - crop_rect.x = 0; - crop_rect.y = 0; - crop_rect.w = original_image.width; - crop_rect.h = original_image.height; - crop_before_drag = crop_rect; - } - ImGui::SameLine(); - ImGui::Text("x:%d y:%d w:%d h:%d", crop_rect.x, crop_rect.y, crop_rect.w, crop_rect.h); - - bool recalc_size = false; - if (cropped) { - if (!compress) { - // looks like a change - recalc_size = true; - } - compress = true; - } - - recalc_size |= ImGui::Checkbox("compress", &compress); - if (cropped && ImGui::IsItemHovered()) { - ImGui::SetTooltip("required since cropped!"); - } - - static int current_compressor = 0; - - if (compress) { - ImGui::SameLine(); - ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\0qoi\0"); - - ImGui::Indent(); - // combo "webp""webp-lossless""png""jpg?" - // if lossy quality slider (1-100) default 80 - if (current_compressor == 0 || current_compressor == 1) { - const uint32_t qmin = 1; - const uint32_t qmax = 100; - recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); - } - if (current_compressor == 2) { - const uint32_t qmin = 0; - const uint32_t qmax = 9; - recalc_size |= ImGui::SliderScalar("compression_level", ImGuiDataType_U32, &compression_level, &qmin, &qmax); - } - - if (recalc_size) { - // compress and save temp? cooldown? async? save size only? - // print size where? - } - - ImGui::Unindent(); - } - - //if (ImGui::Button("X cancel", {ImGui::GetWindowContentRegionWidth()/2.f, TEXT_BASE_HEIGHT*2})) { - if (ImGui::Button("X cancel", {ImGui::GetContentRegionAvail().x/2.f, TEXT_BASE_HEIGHT*2})) { - _on_cancel(); - ImGui::CloseCurrentPopup(); - reset(); - } - ImGui::SameLine(); - if (ImGui::Button("send ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) { - if (compress || cropped) { - // TODO: copy bad - ImageLoaderI::ImageResult tmp_img; - if (cropped) { - std::cout << "SIP: CROP!!!!!\n"; - tmp_img = original_image.crop( - crop_rect.x, - crop_rect.y, - crop_rect.w, - crop_rect.h - ); - } else { - tmp_img = original_image; - } - - std::vector new_data; - - // HACK: generic list - if (current_compressor == 0) { - new_data = ImageEncoderWebP{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); - if (!new_data.empty()) { - _on_send(new_data, ".webp"); - } - } else if (current_compressor == 1) { - new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}});; - if (!new_data.empty()) { - _on_send(new_data, ".jpg"); - } - } else if (current_compressor == 2) { - new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}});; - if (!new_data.empty()) { - _on_send(new_data, ".png"); - } - } else if (current_compressor == 3) { - new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {});; - if (!new_data.empty()) { - _on_send(new_data, ".qoi"); - } - } - // error - - } else { - _on_send(original_data, original_file_ext); - } - - ImGui::CloseCurrentPopup(); - reset(); - } - - ImGui::EndPopup(); } + + const bool cropped = crop_rect.x != 0 || crop_rect.y != 0 || crop_rect.w != original_image.width || crop_rect.h != original_image.height; + if (cropping) { + if (ImGui::Button("done")) { + cropping = false; + } + } else { + if (ImGui::Button("crop")) { + cropping = true; + } + } + ImGui::SameLine(); + if (ImGui::Button("reset")) { + crop_rect.x = 0; + crop_rect.y = 0; + crop_rect.w = original_image.width; + crop_rect.h = original_image.height; + crop_before_drag = crop_rect; + } + ImGui::SameLine(); + ImGui::Text("x:%d y:%d w:%d h:%d", crop_rect.x, crop_rect.y, crop_rect.w, crop_rect.h); + + bool recalc_size = false; + if (cropped) { + if (!compress) { + // looks like a change + recalc_size = true; + } + compress = true; + } + + recalc_size |= ImGui::Checkbox("compress", &compress); + if (cropped && ImGui::IsItemHovered()) { + ImGui::SetTooltip("required since cropped!"); + } + + static int current_compressor = 0; + + if (compress) { + ImGui::SameLine(); + ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\0qoi\0"); + + ImGui::Indent(); + // combo "webp""webp-lossless""png""jpg?" + // if lossy quality slider (1-100) default 80 + if (current_compressor == 0 || current_compressor == 1) { + const uint32_t qmin = 1; + const uint32_t qmax = 100; + recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); + } + if (current_compressor == 2) { + const uint32_t qmin = 0; + const uint32_t qmax = 9; + recalc_size |= ImGui::SliderScalar("compression_level", ImGuiDataType_U32, &compression_level, &qmin, &qmax); + } + + if (recalc_size) { + // compress and save temp? cooldown? async? save size only? + // print size where? + } + + ImGui::Unindent(); + } + + //if (ImGui::Button("X cancel", {ImGui::GetWindowContentRegionWidth()/2.f, TEXT_BASE_HEIGHT*2})) { + if (ImGui::Button("X cancel", {ImGui::GetContentRegionAvail().x/2.f, TEXT_BASE_HEIGHT*2})) { + _on_cancel(); + ImGui::CloseCurrentPopup(); + reset(); + } + ImGui::SameLine(); + if (ImGui::Button("send ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) { + if (compress || cropped) { + // TODO: copy bad + ImageLoaderI::ImageResult tmp_img; + if (cropped) { + std::cout << "SIP: CROP!!!!!\n"; + tmp_img = original_image.crop( + crop_rect.x, + crop_rect.y, + crop_rect.w, + crop_rect.h + ); + } else { + tmp_img = original_image; + } + + std::vector new_data; + + // HACK: generic list + if (current_compressor == 0) { + new_data = ImageEncoderWebP{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); + if (!new_data.empty()) { + _on_send(new_data, ".webp"); + } + } else if (current_compressor == 1) { + new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}});; + if (!new_data.empty()) { + _on_send(new_data, ".jpg"); + } + } else if (current_compressor == 2) { + new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}});; + if (!new_data.empty()) { + _on_send(new_data, ".png"); + } + } else if (current_compressor == 3) { + new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {});; + if (!new_data.empty()) { + _on_send(new_data, ".qoi"); + } + } + // error + + } else { + _on_send(original_data, original_file_ext); + } + + ImGui::CloseCurrentPopup(); + reset(); + } + + if (ImGui::Shortcut(ImGuiKey_Escape)) { + ImGui::CloseCurrentPopup(); + reset(); + } + + ImGui::EndPopup(); } diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index cf71f0c..7e27733 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -201,7 +201,8 @@ ChatGui4::ChatGui4( _msg_tc(msg_tc), _b_tc(_bil, tu), _theme(theme), - _sip(tu) + _sip(tu), + _ivp(_msg_tc) { _os_sr.subscribe(ObjectStore_Event::object_update); } @@ -220,6 +221,7 @@ ChatGui4::~ChatGui4(void) { float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused) { _fss.render(); _sip.render(time_delta); + _ivp.render(time_delta); _b_tc.update(); _b_tc.workLoadQueue(); @@ -1330,7 +1332,9 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { {0.5f, 0.5f, 0.5f, 0.8f} // border ); - // TODO: clickable to open in internal image viewer + if (ImGui::IsItemClicked()) { + _ivp.view(Message3Handle{reg, e}); + } } } else if (o.all_of()) { // only show info if not inlined image // just filename diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp index 26b3f96..43c2ecf 100644 --- a/src/chat_gui4.hpp +++ b/src/chat_gui4.hpp @@ -14,6 +14,7 @@ #include "./bitset_image_loader.hpp" #include "./chat_gui/file_selector.hpp" #include "./chat_gui/send_image_popup.hpp" +#include "./chat_gui/image_viewer_popup.hpp" #include @@ -39,6 +40,7 @@ class ChatGui4 : public ObjectStoreEventI { FileSelector _fss; SendImagePopup _sip; + ImageViewerPopup _ivp; // TODO: refactor this to allow multiple open contacts std::optional _selected_contact;