prep for chat_gui refactor
This commit is contained in:
251
src/chat_gui/file_selector.cpp
Normal file
251
src/chat_gui/file_selector.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include "./file_selector.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
void FileSelector::reset(void) {
|
||||
_is_valid = [](auto){ return true; };
|
||||
_on_choose = [](auto){};
|
||||
_on_cancel = [](){};
|
||||
}
|
||||
|
||||
FileSelector::FileSelector(void) {
|
||||
reset();
|
||||
}
|
||||
|
||||
void FileSelector::requestFile(
|
||||
std::function<bool(std::filesystem::path& path)>&& is_valid,
|
||||
std::function<void(const std::filesystem::path& path)>&& on_choose,
|
||||
std::function<void(void)>&& on_cancel
|
||||
) {
|
||||
_open_popup = true;
|
||||
|
||||
_is_valid = std::move(is_valid);
|
||||
_on_choose = std::move(on_choose);
|
||||
_on_cancel = std::move(on_cancel);
|
||||
}
|
||||
|
||||
void FileSelector::render(void) {
|
||||
if (_open_popup) {
|
||||
_open_popup = false;
|
||||
ImGui::OpenPopup("file picker##FileSelector");
|
||||
|
||||
const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
ImGui::SetNextWindowSize({TEXT_BASE_WIDTH*100, TEXT_BASE_HEIGHT*30});
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("file picker##FileSelector", nullptr/*, ImGuiWindowFlags_NoDecoration*/)) {
|
||||
const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
std::filesystem::path current_path = _current_file_path;
|
||||
current_path.remove_filename();
|
||||
|
||||
ImGui::Text("path: %s", _current_file_path.generic_u8string().c_str());
|
||||
|
||||
// begin table with selectables
|
||||
constexpr ImGuiTableFlags table_flags =
|
||||
ImGuiTableFlags_BordersInnerV |
|
||||
ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_SizingFixedFit |
|
||||
ImGuiTableFlags_ScrollY |
|
||||
ImGuiTableFlags_Sortable
|
||||
;
|
||||
if (ImGui::BeginTable("dir listing", 4, table_flags, {0, -TEXT_BASE_HEIGHT * 2.5f})) {
|
||||
enum class SortID : ImGuiID {
|
||||
name = 1,
|
||||
size,
|
||||
date
|
||||
};
|
||||
ImGui::TableSetupColumn("type", 0, TEXT_BASE_WIDTH);
|
||||
ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultSort, 0.f, static_cast<ImGuiID>(SortID::name));
|
||||
ImGui::TableSetupColumn("size", 0, 0.f, static_cast<ImGuiID>(SortID::size));
|
||||
ImGui::TableSetupColumn("dd.mm.yyyy - hh:mm", 0, 0.f, static_cast<ImGuiID>(SortID::date));
|
||||
|
||||
ImGui::TableSetupScrollFreeze(0, 2);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
//ImGui::TableNextRow(0, TEXT_BASE_HEIGHT);
|
||||
|
||||
if (current_path.has_parent_path()) {
|
||||
if (ImGui::TableNextColumn()) {
|
||||
if (ImGui::Selectable("D##..", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
|
||||
// the first "parent_path()" only removes the filename and the ending "/"
|
||||
_current_file_path = _current_file_path.parent_path().parent_path() / "";
|
||||
//_current_file_path = _current_file_path.remove_filename().parent_path() / "";
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("..");
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextDisabled("---");
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextDisabled("---");
|
||||
}
|
||||
}
|
||||
size_t tmp_id = 0;
|
||||
// dirs
|
||||
static const ImU32 dir_bg0_color = ImGui::GetColorU32(ImVec4(0.6, 0.6, 0.1, 0.15));
|
||||
static const ImU32 dir_bg1_color = ImGui::GetColorU32(ImVec4(0.7, 0.7, 0.2, 0.15));
|
||||
|
||||
std::vector<std::filesystem::directory_entry> dirs;
|
||||
std::vector<std::filesystem::directory_entry> files;
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator(current_path)) {
|
||||
if (dir_entry.is_directory()) {
|
||||
dirs.push_back(dir_entry);
|
||||
} else if (dir_entry.is_regular_file()) {
|
||||
files.push_back(dir_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// do sorting here
|
||||
// TODO: cache the result (lol)
|
||||
if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); sorts_specs != nullptr && sorts_specs->SpecsCount >= 1) {
|
||||
switch (static_cast<SortID>(sorts_specs->Specs->ColumnUserID)) {
|
||||
break; case SortID::name:
|
||||
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
|
||||
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.path() < b.path();
|
||||
});
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.path().filename() < b.path().filename();
|
||||
});
|
||||
} else {
|
||||
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.path() > b.path();
|
||||
});
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.path().filename() > b.path().filename();
|
||||
});
|
||||
}
|
||||
break; case SortID::size:
|
||||
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
|
||||
// TODO: sort dirs?
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.file_size() < b.file_size();
|
||||
});
|
||||
} else {
|
||||
// TODO: sort dirs?
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.file_size() > b.file_size();
|
||||
});
|
||||
}
|
||||
break; case SortID::date:
|
||||
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
|
||||
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.last_write_time() < b.last_write_time();
|
||||
});
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.last_write_time() < b.last_write_time();
|
||||
});
|
||||
} else {
|
||||
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.last_write_time() > b.last_write_time();
|
||||
});
|
||||
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
|
||||
return a.last_write_time() > b.last_write_time();
|
||||
});
|
||||
}
|
||||
break; default: ;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& dir_entry : dirs) {
|
||||
if (ImGui::TableNextColumn()) {
|
||||
if (tmp_id & 0x01) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, dir_bg0_color);
|
||||
} else {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, dir_bg1_color);
|
||||
}
|
||||
ImGui::PushID(tmp_id++);
|
||||
if (ImGui::Selectable("D", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
|
||||
_current_file_path = dir_entry.path() / "";
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted((dir_entry.path().filename().generic_u8string() + "/").c_str());
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextDisabled("---");
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
dir_entry.last_write_time()
|
||||
- decltype(dir_entry.last_write_time())::clock::now()
|
||||
+ std::chrono::system_clock::now()
|
||||
);
|
||||
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
|
||||
|
||||
const auto ltime = std::localtime(&ctime);
|
||||
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
|
||||
}
|
||||
}
|
||||
|
||||
// files
|
||||
for (auto const& dir_entry : files) {
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::PushID(tmp_id++);
|
||||
if (ImGui::Selectable("F", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
|
||||
_current_file_path = dir_entry.path();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted(dir_entry.path().filename().generic_u8string().c_str());
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextDisabled("%s", std::to_string(dir_entry.file_size()).c_str());
|
||||
}
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
dir_entry.last_write_time()
|
||||
- decltype(dir_entry.last_write_time())::clock::now()
|
||||
+ std::chrono::system_clock::now()
|
||||
);
|
||||
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
|
||||
|
||||
const auto ltime = std::localtime(&ctime);
|
||||
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
//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})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
_on_cancel();
|
||||
reset();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("choose ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) {
|
||||
if (_is_valid(_current_file_path)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
_on_choose(_current_file_path);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
31
src/chat_gui/file_selector.hpp
Normal file
31
src/chat_gui/file_selector.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
struct FileSelector {
|
||||
std::filesystem::path _current_file_path = std::filesystem::canonical(".") / ""; // add /
|
||||
|
||||
bool _open_popup {false};
|
||||
|
||||
std::function<bool(std::filesystem::path& path)> _is_valid = [](auto){ return true; };
|
||||
std::function<void(const std::filesystem::path& path)> _on_choose = [](auto){};
|
||||
std::function<void(void)> _on_cancel = [](){};
|
||||
|
||||
void reset(void);
|
||||
|
||||
public:
|
||||
FileSelector(void);
|
||||
|
||||
// TODO: supply hints
|
||||
// HACK: until we supply hints, is_valid can modify
|
||||
void requestFile(
|
||||
std::function<bool(std::filesystem::path& path)>&& is_valid,
|
||||
std::function<void(const std::filesystem::path& path)>&& on_choose,
|
||||
std::function<void(void)>&& on_cancel
|
||||
);
|
||||
|
||||
// call this each frame
|
||||
void render(void);
|
||||
};
|
||||
|
512
src/chat_gui/send_image_popup.cpp
Normal file
512
src/chat_gui/send_image_popup.cpp
Normal file
@ -0,0 +1,512 @@
|
||||
#include "./send_image_popup.hpp"
|
||||
|
||||
#include "../image_loader_sdl_bmp.hpp"
|
||||
#include "../image_loader_stb.hpp"
|
||||
#include "../image_loader_webp.hpp"
|
||||
#include "../image_loader_qoi.hpp"
|
||||
#include "../image_loader_sdl_image.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
// fwd
|
||||
namespace Message {
|
||||
uint64_t getTimeMS(void);
|
||||
}
|
||||
|
||||
SendImagePopup::SendImagePopup(TextureUploaderI& tu) : _tu(tu) {
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderQOI>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||
_image_loaders.push_back(std::make_unique<ImageLoaderSDLImage>());
|
||||
}
|
||||
|
||||
void SendImagePopup::reset(void) {
|
||||
_on_send = [](const auto&, auto){};
|
||||
_on_cancel = [](){};
|
||||
|
||||
// hm
|
||||
original_data.clear();
|
||||
{
|
||||
original_image.width = 0;
|
||||
original_image.height = 0;
|
||||
original_image.frames.clear();
|
||||
}
|
||||
|
||||
// clear preview img
|
||||
for (const auto& tex_id : preview_image.textures) {
|
||||
_tu.destroy(tex_id);
|
||||
}
|
||||
preview_image = {};
|
||||
|
||||
cropping = false;
|
||||
dragging_last_frame_ul = false;
|
||||
dragging_last_frame_lr = false;
|
||||
}
|
||||
|
||||
bool SendImagePopup::load(void) {
|
||||
// try all loaders after another
|
||||
for (auto& il : _image_loaders) {
|
||||
original_image = il->loadFromMemoryRGBA(original_data.data(), original_data.size());
|
||||
if (original_image.frames.empty() || original_image.height == 0 || original_image.width == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if 1
|
||||
crop_rect.x = 0;
|
||||
crop_rect.y = 0;
|
||||
crop_rect.w = original_image.width;
|
||||
crop_rect.h = original_image.height;
|
||||
#else
|
||||
crop_rect.x = original_image.width * 0.05f;
|
||||
crop_rect.y = original_image.height * 0.05f;
|
||||
crop_rect.w = original_image.width * 0.9f;
|
||||
crop_rect.h = original_image.height * 0.9f;
|
||||
#endif
|
||||
|
||||
crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height);
|
||||
crop_before_drag = crop_rect;
|
||||
|
||||
original_file_ext = ".";
|
||||
if (original_image.file_ext != nullptr) {
|
||||
original_file_ext += original_image.file_ext;
|
||||
} else {
|
||||
// HACK: manually probe for png
|
||||
if (!original_raw
|
||||
&& original_data.size() >= 4
|
||||
&& original_data.at(0) == 0x89
|
||||
&& original_data.at(1) == 'P'
|
||||
&& original_data.at(2) == 'N'
|
||||
&& original_data.at(3) == 'G'
|
||||
) {
|
||||
original_file_ext += "png";
|
||||
} else {
|
||||
original_file_ext += "unk"; // very meh, default to png?
|
||||
}
|
||||
}
|
||||
|
||||
assert(preview_image.textures.empty());
|
||||
preview_image.timestamp_last_rendered = Message::getTimeMS();
|
||||
preview_image.current_texture = 0;
|
||||
for (const auto& [ms, data] : original_image.frames) {
|
||||
const auto n_t = _tu.uploadRGBA(data.data(), original_image.width, original_image.height);
|
||||
preview_image.textures.push_back(n_t);
|
||||
preview_image.frame_duration.push_back(ms);
|
||||
}
|
||||
|
||||
// redundant
|
||||
preview_image.width = original_image.width;
|
||||
preview_image.height = original_image.height;
|
||||
|
||||
if (original_image.frames.size() > 1) {
|
||||
std::cout << "SIP: loaded animation\n";
|
||||
} else {
|
||||
std::cout << "SIP: loaded image\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SendImagePopup::Rect SendImagePopup::sanitizeCrop(Rect crop_rect, int32_t image_width, int32_t image_height) {
|
||||
// w and h min is 1 -> x/y need to be smaller so the img is atleast 1px in any dim
|
||||
if (crop_rect.x >= image_width-1) {
|
||||
crop_rect.x = image_width-2;
|
||||
} else if (crop_rect.x < 0) {
|
||||
crop_rect.x = 0;
|
||||
}
|
||||
|
||||
if (crop_rect.y >= image_height-1) {
|
||||
crop_rect.y = image_height-2;
|
||||
} else if (crop_rect.y < 0) {
|
||||
crop_rect.y = 0;
|
||||
}
|
||||
|
||||
if (crop_rect.w > image_width - crop_rect.x) {
|
||||
crop_rect.w = image_width - crop_rect.x;
|
||||
} else if (crop_rect.w < 1) {
|
||||
crop_rect.w = 1;
|
||||
// TODO: adjust X
|
||||
}
|
||||
|
||||
if (crop_rect.h > image_height - crop_rect.y) {
|
||||
crop_rect.h = image_height - crop_rect.y;
|
||||
} else if (crop_rect.h < 1) {
|
||||
crop_rect.h = 1;
|
||||
// TODO: adjust Y
|
||||
}
|
||||
|
||||
return crop_rect;
|
||||
}
|
||||
|
||||
void SendImagePopup::sendMemory(
|
||||
const uint8_t* data, size_t data_size,
|
||||
std::function<void(const std::vector<uint8_t>&, std::string_view)>&& on_send,
|
||||
std::function<void(void)>&& on_cancel
|
||||
) {
|
||||
original_raw = false;
|
||||
if (data == nullptr || data_size == 0) {
|
||||
return; // error
|
||||
}
|
||||
|
||||
// copy paste data to memory
|
||||
original_data.clear();
|
||||
original_data.insert(original_data.begin(), data, data+data_size);
|
||||
|
||||
if (!load()) {
|
||||
std::cerr << "SIP: failed to load image from memory\n";
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
_open_popup = true;
|
||||
|
||||
_on_send = std::move(on_send);
|
||||
_on_cancel = std::move(on_cancel);
|
||||
|
||||
}
|
||||
|
||||
void SendImagePopup::render(float time_delta) {
|
||||
if (_open_popup) {
|
||||
_open_popup = false;
|
||||
ImGui::OpenPopup("send image##SendImagePopup");
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
preview_image.doAnimation(Message::getTimeMS());
|
||||
|
||||
time += time_delta;
|
||||
time = fmod(time, 1.f); // fract()
|
||||
|
||||
//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<void*>(),
|
||||
ImVec2{static_cast<float>(width), static_cast<float>(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<float>(crop_before_drag.x + drag_total.x, 0.01f);
|
||||
crop_rect.y = std::max<float>(crop_before_drag.y + drag_total.y, 0.01f);
|
||||
|
||||
crop_rect.x = std::min<int32_t>(crop_rect.x, original_image.width-2);
|
||||
crop_rect.y = std::min<int32_t>(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;
|
||||
}
|
||||
}
|
||||
|
||||
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<float>(crop_before_drag.w + drag_total.x, original_image.width);
|
||||
crop_rect.h = std::min<float>(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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
|
||||
// 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<void*>(),
|
||||
ImVec2{static_cast<float>(width), static_cast<float>(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
|
||||
}
|
||||
}
|
||||
|
||||
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<uint8_t> 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();
|
||||
}
|
||||
}
|
||||
|
76
src/chat_gui/send_image_popup.hpp
Normal file
76
src/chat_gui/send_image_popup.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "../image_loader.hpp"
|
||||
#include "../texture_cache.hpp"
|
||||
|
||||
struct SendImagePopup {
|
||||
TextureUploaderI& _tu;
|
||||
|
||||
// private
|
||||
std::vector<std::unique_ptr<ImageLoaderI>> _image_loaders;
|
||||
std::vector<std::unique_ptr<ImageEncoderI>> _image_encoders;
|
||||
|
||||
// copy of the original data, dont touch!
|
||||
std::vector<uint8_t> original_data;
|
||||
bool original_raw {false};
|
||||
std::string original_file_ext; // if !original_raw
|
||||
|
||||
ImageLoaderI::ImageResult original_image;
|
||||
|
||||
struct Rect {
|
||||
int32_t x {0};
|
||||
int32_t y {0};
|
||||
|
||||
int32_t w {0};
|
||||
int32_t h {0};
|
||||
};
|
||||
Rect crop_rect;
|
||||
Rect crop_before_drag;
|
||||
|
||||
bool cropping {false};
|
||||
bool dragging_last_frame_ul {false};
|
||||
bool dragging_last_frame_lr {false};
|
||||
|
||||
// texture to render (orig img)
|
||||
TextureEntry preview_image;
|
||||
|
||||
bool compress {false};
|
||||
uint32_t quality {80u};
|
||||
uint32_t compression_level {8u};
|
||||
|
||||
float time {0.f}; // cycling form 0 to 1 over time
|
||||
|
||||
bool _open_popup {false};
|
||||
|
||||
std::function<void(const std::vector<uint8_t>&, std::string_view)> _on_send = [](const auto&, auto){};
|
||||
std::function<void(void)> _on_cancel = [](){};
|
||||
|
||||
void reset(void);
|
||||
|
||||
// loads the image in original_data
|
||||
// fills in original_image, preview_image and crop_rect
|
||||
// returns if loaded successfully
|
||||
bool load(void);
|
||||
|
||||
static Rect sanitizeCrop(Rect crop_rect, int32_t image_width, int32_t image_height);
|
||||
|
||||
public:
|
||||
SendImagePopup(TextureUploaderI& tu);
|
||||
|
||||
void sendMemory(
|
||||
const uint8_t* data, size_t data_size,
|
||||
std::function<void(const std::vector<uint8_t>&, std::string_view)>&& on_send,
|
||||
std::function<void(void)>&& on_cancel
|
||||
);
|
||||
// from memory_raw
|
||||
// from file_path
|
||||
|
||||
// call this each frame
|
||||
void render(float time_delta);
|
||||
};
|
||||
|
220
src/chat_gui/settings_window.cpp
Normal file
220
src/chat_gui/settings_window.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
#include "./settings_window.hpp"
|
||||
|
||||
#include <solanaceae/util/simple_config_model.hpp>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
template<typename FNCat, typename ...MapTypes>
|
||||
void iterateMultMaps(FNCat&& fn_cat, MapTypes& ...map_types) {
|
||||
// the iterators
|
||||
std::tuple its{map_types.begin()...};
|
||||
|
||||
// assuming ever maps returned .begin() has a unique type
|
||||
// TODO: use index instead
|
||||
while ( ((std::get<decltype(map_types.begin())>(its) != map_types.end()) || ...) ) {
|
||||
// figure out current module and participants
|
||||
|
||||
std::string_view current_module;
|
||||
// find current_module
|
||||
((
|
||||
current_module = (
|
||||
std::get<decltype(map_types.begin())>(its) != map_types.end() && (current_module.empty() || current_module >= std::get<decltype(map_types.begin())>(its)->first)
|
||||
) ? std::get<decltype(map_types.begin())>(its)->first : current_module
|
||||
), ...);
|
||||
|
||||
assert(!current_module.empty());
|
||||
|
||||
fn_cat(
|
||||
current_module,
|
||||
((
|
||||
std::get<decltype(map_types.begin())>(its) != map_types.end() &&
|
||||
current_module == std::get<decltype(map_types.begin())>(its)->first
|
||||
) ? std::get<decltype(map_types.begin())>(its)->second : decltype(map_types.begin()->second){})...
|
||||
);
|
||||
|
||||
// increment participanting iterators
|
||||
((
|
||||
(
|
||||
std::get<decltype(map_types.begin())>(its) != map_types.end() &&
|
||||
current_module == std::get<decltype(map_types.begin())>(its)->first
|
||||
) ? void(++std::get<decltype(map_types.begin())>(its)) : void(0)
|
||||
), ...);
|
||||
}
|
||||
}
|
||||
|
||||
//template<typename T>
|
||||
//static void renderConfigCat(SimpleConfigModel& conf, std::string_view cmodule, std::string_view ccat, const std::pair<std::optional<T>, std::map<std::string, T>>& cat_dat) {
|
||||
//if (cat_dat.first.has_value()) {
|
||||
//T tmp = cat_dat.first.value();
|
||||
////if (ImGui::Checkbox(ccat.data(), &tmp)) {
|
||||
////conf.set(cmodule, ccat, tmp);
|
||||
////}
|
||||
//} else if (!cat_dat.second.empty()) {
|
||||
//ImGui::Text("uhhh");
|
||||
//}
|
||||
//}
|
||||
static void renderConfigCat(SimpleConfigModel& conf, std::string_view cmodule, std::string_view ccat, const std::pair<std::optional<bool>, std::map<std::string, bool>>& cat_dat) {
|
||||
if (cat_dat.first.has_value()) {
|
||||
bool tmp = cat_dat.first.value();
|
||||
if (ImGui::Checkbox(ccat.data(), &tmp)) {
|
||||
conf.set(cmodule, ccat, tmp);
|
||||
}
|
||||
} else if (!cat_dat.second.empty()) {
|
||||
ImGui::Text("uhhh");
|
||||
}
|
||||
}
|
||||
static void renderConfigCat(SimpleConfigModel& conf, std::string_view cmodule, std::string_view ccat, const std::pair<std::optional<int64_t>, std::map<std::string, int64_t>>& cat_dat) {
|
||||
if (cat_dat.first.has_value()) {
|
||||
int64_t tmp = cat_dat.first.value();
|
||||
if (ImGui::InputScalar(ccat.data(), ImGuiDataType_S64, &tmp)) {
|
||||
conf.set(cmodule, ccat, tmp);
|
||||
}
|
||||
} else if (!cat_dat.second.empty()) {
|
||||
ImGui::Text("uhhh");
|
||||
}
|
||||
}
|
||||
static void renderConfigCat(SimpleConfigModel& conf, std::string_view cmodule, std::string_view ccat, const std::pair<std::optional<double>, std::map<std::string, double>>& cat_dat) {
|
||||
if (cat_dat.first.has_value()) {
|
||||
double tmp = cat_dat.first.value();
|
||||
if (ImGui::InputDouble(ccat.data(), &tmp)) {
|
||||
conf.set(cmodule, ccat, tmp);
|
||||
}
|
||||
} else if (!cat_dat.second.empty()) {
|
||||
ImGui::Text("uhhh");
|
||||
}
|
||||
}
|
||||
static void renderConfigCat(SimpleConfigModel& conf, std::string_view cmodule, std::string_view ccat, const std::pair<std::optional<std::string>, std::map<std::string, std::string>>& cat_dat) {
|
||||
if (cat_dat.first.has_value()) {
|
||||
std::string tmp = cat_dat.first.value();
|
||||
if (ImGui::InputText(ccat.data(), &tmp)) {
|
||||
conf.set(cmodule, ccat, CM_ISV{tmp});
|
||||
}
|
||||
} else if (!cat_dat.second.empty()) {
|
||||
ImGui::Text("uhhh");
|
||||
}
|
||||
}
|
||||
|
||||
SettingsWindow::SettingsWindow(SimpleConfigModel& conf) : _conf(conf) {
|
||||
}
|
||||
|
||||
void SettingsWindow::render(void) {
|
||||
{ // main window menubar injection
|
||||
// assumes the window "tomato" was rendered already by cg
|
||||
if (ImGui::Begin("tomato")) {
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("Settings")) {
|
||||
if (ImGui::MenuItem("settings window")) {
|
||||
_show_window = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (_show_window) {
|
||||
if (ImGui::Begin("Settings", &_show_window)) {
|
||||
ImGui::TextDisabled("most settings are only fetched once at program startup");
|
||||
|
||||
// ufff
|
||||
// we need to iterate 4 maps, ideally in lexicographic order
|
||||
// (pain)
|
||||
// std::maps should be ordered more or less correctly
|
||||
// (less pain)
|
||||
// its recursive
|
||||
// (pain)
|
||||
// but the code is reusable
|
||||
// (less pain)
|
||||
|
||||
iterateMultMaps(
|
||||
[this](std::string_view cmodule, const auto& ...mod_map_types) {
|
||||
if (ImGui::CollapsingHeader(cmodule.data())) {
|
||||
iterateMultMaps(
|
||||
[this, cmodule](std::string_view ccat, const auto& ...cat_map_types) {
|
||||
(renderConfigCat(_conf, cmodule, ccat, cat_map_types), ...);
|
||||
},
|
||||
mod_map_types...
|
||||
);
|
||||
}
|
||||
},
|
||||
_conf._map_bool,
|
||||
_conf._map_int,
|
||||
_conf._map_double,
|
||||
_conf._map_string
|
||||
);
|
||||
|
||||
#if 0
|
||||
auto it_b = _conf._map_bool.begin();
|
||||
auto it_i = _conf._map_int.begin();
|
||||
auto it_d = _conf._map_double.begin();
|
||||
auto it_s = _conf._map_string.begin();
|
||||
|
||||
while (
|
||||
it_b != _conf._map_bool.end() ||
|
||||
it_i != _conf._map_int.end() ||
|
||||
it_d != _conf._map_double.end() ||
|
||||
it_s != _conf._map_string.end()
|
||||
) {
|
||||
// figure out current module
|
||||
std::string_view current_module;
|
||||
bool bool_is_curr = false;
|
||||
bool int_is_curr = false;
|
||||
bool double_is_curr = false;
|
||||
bool string_is_curr = false;
|
||||
|
||||
if (it_b != _conf._map_bool.end() && (current_module.empty() || current_module >= it_b->first)) {
|
||||
//std::cerr << "SET bool\n";
|
||||
bool_is_curr = true;
|
||||
current_module = it_b->first;
|
||||
}
|
||||
if (it_i != _conf._map_int.end() && (current_module.empty() || current_module >= it_i->first)) {
|
||||
//std::cerr << "SET int\n";
|
||||
int_is_curr = true;
|
||||
current_module = it_i->first;
|
||||
}
|
||||
if (it_d != _conf._map_double.end() && (current_module.empty() || current_module >= it_d->first)) {
|
||||
//std::cerr << "SET double\n";
|
||||
double_is_curr = true;
|
||||
current_module = it_d->first;
|
||||
}
|
||||
if (it_s != _conf._map_string.end() && (current_module.empty() || current_module >= it_s->first)) {
|
||||
//std::cerr << "SET string\n";
|
||||
string_is_curr = true;
|
||||
current_module = it_s->first;
|
||||
}
|
||||
|
||||
assert(!current_module.empty());
|
||||
|
||||
//ImGui::Text("module '%s'", current_module.data());
|
||||
if (ImGui::CollapsingHeader(current_module.data())) {
|
||||
// ... do the whole 4way iterate again but on categoy
|
||||
}
|
||||
|
||||
// after the module, increment iterators pointing to current_module
|
||||
if (bool_is_curr) {
|
||||
it_b++;
|
||||
}
|
||||
if (int_is_curr) {
|
||||
it_i++;
|
||||
}
|
||||
if (double_is_curr) {
|
||||
it_d++;
|
||||
}
|
||||
if (string_is_curr) {
|
||||
it_s++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
15
src/chat_gui/settings_window.hpp
Normal file
15
src/chat_gui/settings_window.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
struct SimpleConfigModel;
|
||||
|
||||
class SettingsWindow {
|
||||
bool _show_window {false};
|
||||
// TODO: add iteration api to interface
|
||||
SimpleConfigModel& _conf;
|
||||
|
||||
public:
|
||||
SettingsWindow(SimpleConfigModel& conf);
|
||||
|
||||
void render(void);
|
||||
};
|
||||
|
Reference in New Issue
Block a user