summaryrefslogtreecommitdiff
path: root/src/ui/imgui_extensions.cpp
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-20 19:16:15 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-20 19:16:15 +0200
commit214852f61cd3b9fda257044e9d822b94b4be7e5d (patch)
treeaa570a16623f1c7eae94474c80b484815ea85c88 /src/ui/imgui_extensions.cpp
parent3402cba0fe6fa1b89a029c612b622e7a4771d901 (diff)
refactor ui widgets
Diffstat (limited to 'src/ui/imgui_extensions.cpp')
-rw-r--r--src/ui/imgui_extensions.cpp414
1 files changed, 414 insertions, 0 deletions
diff --git a/src/ui/imgui_extensions.cpp b/src/ui/imgui_extensions.cpp
new file mode 100644
index 0000000..5fdc3a7
--- /dev/null
+++ b/src/ui/imgui_extensions.cpp
@@ -0,0 +1,414 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ui.hpp"
+#include "strops.hpp"
+#include "config.hpp"
+#include "locales.hpp"
+#include "administration.hpp"
+
+namespace ImGui
+{
+ void FormInputTextWithErrorHint(const char* hint, void* data, char* buffer, size_t buf_size, bool has_error)
+ {
+ float widthAvailable = ImGui::GetContentRegionAvail().x;
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+
+ char id[MAX_LEN_LONG_DESC];
+ snprintf(id, sizeof(id), "%s##%p", hint, data);
+
+ if (has_error) {
+ ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f);
+ }
+ ImGui::InputTextWithHint(id, hint, buffer, buf_size);
+ if (has_error) {
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor();
+
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x);
+ ImGui::TextColored(ImVec4(1, 0, 0, 1), "*");
+ }
+ }
+
+ void FormCountryCombo(void* data, char* buffer, size_t buf_size)
+ {
+ const char* selected_country = 0;
+ char id[MAX_LEN_LONG_DESC];
+ float widthAvailable = ImGui::GetContentRegionAvail().x;
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+
+ const char* countries[30];
+ for (int x = 0; x < country_count; x++)
+ {
+ char locale_str[20];
+ snprintf(locale_str, 20, "country.%s", country_codes[x]);
+ countries[x] = localize(locale_str);
+ }
+
+ for (int i = 0; i < country_count; i++)
+ {
+ if (strcmp(country_codes[i], buffer) == 0)
+ {
+ selected_country = countries[i];
+ break;
+ }
+ }
+ bool has_a_selection = selected_country != 0;
+
+ int selected_country_index = -1;
+ snprintf(id, sizeof(id), "%s##%p", localize("contact.form.country"), data);
+
+ if (!has_a_selection) {
+ ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f);
+ }
+
+ if (ImGui::BeginCombo(id, selected_country))
+ {
+ for (int n = 0; n < country_count; n++)
+ {
+ bool is_selected = (selected_country == countries[n]);
+ if (ImGui::Selectable(countries[n], is_selected)) {
+ selected_country = countries[n];
+ selected_country_index = n;
+ }
+ }
+ ImGui::EndCombo();
+ }
+
+ if (!has_a_selection) {
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor();
+
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x);
+ ImGui::TextColored(ImVec4(1, 0, 0, 1), "*");
+ }
+
+ if (selected_country_index != -1) {
+ strops_copy(buffer, country_codes[selected_country_index], buf_size);
+ }
+ }
+
+ void FormContactTypeCombo(contact_type* type)
+ {
+ float widthAvailable = ImGui::GetContentRegionAvail().x;
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ const char* customer_types[2] = { localize("contact.form.type.business"), localize("contact.form.type.consumer") };
+ int currentItem = static_cast<int>(*type);
+ if (ImGui::Combo(localize("contact.form.type"), &currentItem, customer_types, IM_ARRAYSIZE(customer_types)))
+ {
+ *type = static_cast<contact_type>(currentItem);
+ }
+ }
+
+
+ int TextInputWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char* suggestions[], int suggestion_count, bool has_error)
+ {
+ int result = -1;
+ static bool is_open = false;
+
+ if (has_error) {
+ ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f);
+ }
+ ImGui::InputTextWithHint(hint, hint, buffer, buf_size);
+ if (has_error) {
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor();
+
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x);
+ ImGui::TextColored(ImVec4(1, 0, 0, 1), "*");
+ }
+
+
+ if (buffer[0] == '\0' && is_open) is_open = false;
+ if (suggestion_count == 0 && is_open) is_open = false;
+
+ bool is_active = ImGui::IsItemActive();
+ if (is_active && buffer[0] != '\0' && suggestion_count > 0)
+ {
+ is_open = true;
+ }
+
+ if (is_open) {
+ ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true);
+ {
+ ImVec2 win_pos = ImGui::GetWindowPos();
+ ImVec2 win_size = ImGui::GetWindowSize();
+ ImVec2 mouse_pos = ImGui::GetMousePos();
+
+ bool mouse_clicked_outside = !is_active && ImGui::IsMouseClicked(0) &&
+ (mouse_pos.x < win_pos.x || mouse_pos.x > win_pos.x + win_size.x ||
+ mouse_pos.y < win_pos.y || mouse_pos.y > win_pos.y + win_size.y);
+
+ if (mouse_clicked_outside) is_open = false;
+
+ for (int i = 0; i < suggestion_count; ++i)
+ {
+ ImGui::PushID(i);
+ if (ImGui::Selectable(suggestions[i]))
+ {
+ // Copy selected suggestion to buffer
+ strops_copy(buffer, suggestions[i], buf_size);
+ buffer[buf_size - 1] = '\0';
+ result = i;
+ is_open = false;
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::EndChild();
+ }
+ }
+ return result;
+ }
+
+ void FormContactAutocomplete(contact* buffer, bool has_error)
+ {
+ float widthAvailable = ImGui::GetContentRegionAvail().x;
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+
+ contact autocomplete_list[5];
+ int autocomplete_count = administration_contact_get_autocompletions(autocomplete_list, 5, buffer->name);
+ char* autocomplete_strings[5];
+
+ for (int i = 0; i < autocomplete_count; i++)
+ {
+ autocomplete_strings[i] = (char*)malloc(200);
+ snprintf(autocomplete_strings[i], 200, "%s (%s %s)", autocomplete_list[i].name, autocomplete_list[i].address.address1, autocomplete_list[i].address.address2);
+ }
+
+ int autocomplete_index = ImGui::TextInputWithAutocomplete(localize("contact.form.fullname"),
+ buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count, has_error);
+
+ if (autocomplete_index != -1)
+ {
+ memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact));
+ }
+
+ for (int i = 0; i < autocomplete_count; i++)
+ {
+ free(autocomplete_strings[i]);
+ }
+ }
+
+ void FormCostCenterCombo(char* costcenter_id)
+ {
+ u32 costcenter_count = administration_cost_center_count();
+ cost_center* buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count);
+
+ cost_center* selected_costcenter = NULL;
+ costcenter_count = administration_cost_center_get_all(buffer);
+
+ // Select cost center by given id.
+ if (strlen(costcenter_id) > 0)
+ {
+ for (u32 i = 0; i < costcenter_count; i++)
+ {
+ if (strcmp(buffer[i].id, costcenter_id) == 0)
+ {
+ selected_costcenter = &buffer[i];
+ break;
+ }
+ }
+ }
+
+ int selected_costcenter_index = -1;
+ if (ImGui::BeginCombo(localize("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : localize(selected_costcenter->description)))
+ {
+ for (u32 n = 0; n < costcenter_count; n++)
+ {
+ bool is_selected = selected_costcenter && strcmp(selected_costcenter->id, buffer[n].id) == 0;
+ if (ImGui::Selectable(localize(buffer[n].description), is_selected)) {
+ selected_costcenter_index = n;
+ }
+ }
+ ImGui::EndCombo();
+ }
+
+ if (selected_costcenter_index != -1) {
+ strops_copy(costcenter_id, buffer[selected_costcenter_index].id, MAX_LEN_ID);
+ }
+
+ free(buffer);
+ }
+
+ void FormProjectCombo(char* project_id)
+ {
+ u32 project_count = administration_project_count();
+ project* buffer = (project*) malloc(sizeof(project) * project_count);
+
+ project* selected_project = NULL;
+ project_count = administration_project_get_all(buffer);
+
+ // Select project by given id.
+ if (strlen(project_id) > 0)
+ {
+ for (u32 i = 0; i < project_count; i++)
+ {
+ if (strcmp(buffer[i].id, project_id) == 0)
+ {
+ selected_project = &buffer[i];
+ break;
+ }
+ }
+ }
+
+ int selected_project_index = -1;
+ if (ImGui::BeginCombo(localize("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description))
+ {
+ for (u32 n = 0; n < project_count; n++)
+ {
+ bool is_selected = selected_project && strcmp(selected_project->id, buffer[n].id) == 0;
+ if (ImGui::Selectable(buffer[n].description, is_selected)) {
+ selected_project_index = n;
+ }
+ }
+ ImGui::EndCombo();
+ }
+
+ if (selected_project_index != -1) {
+ strops_copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID);
+ }
+
+ free(buffer);
+ }
+
+ void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country)
+ {
+ u32 tax_rate_count = administration_tax_rate_count();
+ tax_rate* buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_count);
+
+ tax_rate* selected_tax_rate = NULL;
+ char* tax_country_codes[2] = {orig_country, dest_country};
+ tax_rate_count = administration_tax_rate_get_by_country(buffer, strcmp(orig_country, dest_country) == 0 ? 1 : 2, tax_country_codes);
+
+ // Select tax rate by given id.
+ if (strlen(tax_rate_id) > 0)
+ {
+ for (u32 i = 0; i < tax_rate_count; i++)
+ {
+ if (strcmp(buffer[i].id, tax_rate_id) == 0)
+ {
+ selected_tax_rate = &buffer[i];
+ break;
+ }
+ }
+ }
+
+ int selected_tax_rate_index = -1;
+ char rate_str_buf[40];
+ rate_str_buf[0] = 0;
+ if (selected_tax_rate)
+ {
+ if (strcmp(selected_tax_rate->country_code, "00") == 0) {
+ char category_code_desc[MAX_LEN_LONG_DESC];
+ snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", selected_tax_rate->category_code);
+ snprintf(rate_str_buf, 40, "%s", localize(category_code_desc));
+ }
+ else snprintf(rate_str_buf, 40, "%s/%.1f%%", selected_tax_rate->country_code, selected_tax_rate->rate);
+ }
+
+ if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf))
+ {
+ for (u32 n = 0; n < tax_rate_count; n++)
+ {
+ bool is_selected = selected_tax_rate && strcmp(selected_tax_rate->id, buffer[n].id) == 0;
+
+ if (strcmp(buffer[n].country_code, "00") == 0) {
+ char category_code_desc[MAX_LEN_LONG_DESC];
+ snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", buffer[n].category_code);
+ snprintf(rate_str_buf, 40, "%s", localize(category_code_desc));
+ }
+ else snprintf(rate_str_buf, 40, "%s/%.1f%%", buffer[n].country_code, buffer[n].rate);
+
+ if (ImGui::Selectable(rate_str_buf, is_selected)) {
+ selected_tax_rate_index = n;
+ }
+ }
+ ImGui::EndCombo();
+ }
+
+ if (selected_tax_rate_index != -1) {
+ strops_copy(tax_rate_id, buffer[selected_tax_rate_index].id, MAX_LEN_ID);
+ }
+
+ free(buffer);
+ }
+
+ bool FormCurrencyCombo(char* currency)
+ {
+ int currentCurrency = 0;
+ bool result = false;
+
+ // Top 15 most traded currencies + all EU official currencies
+ const char* currencies[] = {
+ // Top 15
+ "EUR", "USD", "JPY", "GBP", "AUD", "CAD", "CHF",
+ "CNY", "HKD", "NZD", "SEK", "KRW", "SGD", "NOK", "MXN",
+
+ // Additional EU currencies
+ "BGN", // Bulgarian Lev
+ "CZK", // Czech Koruna
+ "DKK", // Danish Krone
+ "HUF", // Hungarian Forint
+ "PLN", // Polish Zloty
+ "RON", // Romanian Leu
+ // "HRK", // Croatian Kuna (legacy, replaced by EUR in 2023)
+ };
+ int currency_count = sizeof(currencies) / sizeof(char*);
+
+ if (strlen(currency) > 0)
+ {
+ for (int i = 0; i < currency_count; i++)
+ {
+ if (strcmp(currencies[i], currency) == 0)
+ {
+ currentCurrency = i;
+ break;
+ }
+ }
+ }
+
+ ImGui::SetNextItemWidth(100.0f);
+ if (ImGui::BeginCombo("##currency", currencies[currentCurrency]))
+ {
+ for (int n = 0; n < IM_ARRAYSIZE(currencies); n++)
+ {
+ bool isSelected = (currentCurrency == n);
+ if (ImGui::Selectable(currencies[n], isSelected))
+ {
+ result = true;
+ strops_copy(currency, currencies[n], MAX_LEN_CURRENCY);
+ }
+
+ if (isSelected)
+ ImGui::SetItemDefaultFocus();
+ }
+ ImGui::EndCombo();
+ }
+
+ return result;
+ }
+
+ void FormToggleCombo(bool *buffer, char* option1, char* option2)
+ {
+ const char* items[] = { option1, option2 };
+ if (ImGui::BeginCombo("Mode", items[*buffer])) {
+ for (int n = 0; n < 2; n++) {
+ bool is_selected = (n == (int)*buffer);
+ if (ImGui::Selectable(items[n], is_selected)) {
+ *buffer = n;
+ }
+ if (is_selected) {
+ ImGui::SetItemDefaultFocus();
+ }
+ }
+ ImGui::EndCombo();
+ }
+ }
+} \ No newline at end of file