summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-08-03 19:22:36 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-08-03 19:22:36 +0200
commit853bbb3752a5fa2f58ef456ffb6e3a552e13cb11 (patch)
treece49a533f82a42a65fa6a4771a7b8fbfe33798cf /src
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/administration.cpp45
-rw-r--r--src/administration.hpp45
-rw-r--r--src/config.hpp13
-rw-r--r--src/locales/en.cpp29
-rw-r--r--src/locales/locales.cpp29
-rw-r--r--src/locales/locales.hpp21
-rw-r--r--src/main.cpp246
-rw-r--r--src/views/contacts.cpp162
-rw-r--r--src/views/dashboard.cpp65
-rw-r--r--src/views/views.hpp6
10 files changed, 661 insertions, 0 deletions
diff --git a/src/administration.cpp b/src/administration.cpp
new file mode 100644
index 0000000..94b1910
--- /dev/null
+++ b/src/administration.cpp
@@ -0,0 +1,45 @@
+#include "administration.hpp"
+#include <stdio.h>
+administration g_administration;
+
+void init_administration_obj()
+{
+ list_init(&g_administration.contacts);
+}
+
+void destroy_administration_obj()
+{
+ list_destroy(&g_administration.contacts);
+}
+
+bool create_contact(contact data)
+{
+ contact* new_contact = (contact*)malloc(sizeof(contact));
+ memcpy((void*)new_contact, (void*)&data, sizeof(contact));
+ list_append(&g_administration.contacts, new_contact);
+
+ g_administration.next_id++;
+
+ return true;
+}
+
+bool update_contact(contact data)
+{
+ list_iterator_start(&g_administration.contacts);
+ while (list_iterator_hasnext(&g_administration.contacts)) {
+ contact* c = (contact *)list_iterator_next(&g_administration.contacts);
+
+ if (strcmp(c->id, data.id) == 0) {
+ memcpy(c, &data, sizeof(data));
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.contacts);
+
+ return false;
+}
+
+void remove_contact(int index)
+{
+ list_delete_at(&g_administration.contacts, index);
+} \ No newline at end of file
diff --git a/src/administration.hpp b/src/administration.hpp
new file mode 100644
index 0000000..9bf790c
--- /dev/null
+++ b/src/administration.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "config.hpp"
+#include "simclist.h"
+
+typedef struct
+{
+ char id[16];
+ char name[64];
+ char address1[128];
+ char address2[128];
+ char taxid[32];
+ char businessid[32];
+ char email[64];
+ char phone_number[16];
+ char bank_account[32];
+} contact;
+
+typedef struct
+{
+ contact company_info;
+ s32 next_id;
+ char path[4096];
+ char program_version[10];
+ char country_code[2];
+ list_t contacts;
+ // projects
+ // invoices
+ char ai_service[16];
+ char ai_key[32];
+ char email_service[16];
+ char email_key[32];
+} administration;
+
+extern administration g_administration;
+
+void init_administration_obj();
+void destroy_administration_obj();
+
+void remove_contact(int index);
+bool create_contact(contact data);
+bool update_contact(contact data); \ No newline at end of file
diff --git a/src/config.hpp b/src/config.hpp
new file mode 100644
index 0000000..3279e2e
--- /dev/null
+++ b/src/config.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "stdint.h"
+
+#define s8 int8_t
+#define s16 int16_t
+#define s32 int32_t
+#define s64 int64_t
+
+#define u8 uint8_t
+#define u16 uint16_t
+#define u32 uint32_t
+#define u64 uint64_t \ No newline at end of file
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
new file mode 100644
index 0000000..5e31c5c
--- /dev/null
+++ b/src/locales/en.cpp
@@ -0,0 +1,29 @@
+#include "locales.hpp"
+
+locale_entry en_locales[] = {
+ // General form buttons.
+ {"form.create", "+ Create"},
+ {"form.back", "Back"},
+ {"form.save", "Save"},
+ {"form.yes", "Yes"},
+ {"form.no", "No"},
+ {"form.change", "Change"},
+ {"form.delete", "Delete"},
+ {"form.confirmDelete", "Are you sure you want to delete this item?"},
+
+ // Contact strings.
+ {"contact.form.identifier", "Identifier"},
+ {"contact.form.fullname", "Full name / name of business"},
+ {"contact.form.address1", "Street name + house number, appt. number, etc."},
+ {"contact.form.address2", "Zip, city, country"},
+ {"contact.form.taxnumber", "Tax number"},
+ {"contact.form.businessnumber", "Business number"},
+ {"contact.form.email", "Email address"},
+ {"contact.form.phonenumber", "Phone number"},
+ {"contact.form.bankaccount", "Bank account"},
+ {"contact.table.identifier", "Identifier"},
+ {"contact.table.name", "Name"},
+ {"contact.table.address", "Address"},
+};
+
+const int en_locale_count = sizeof(en_locales) / sizeof(en_locales[0]); \ No newline at end of file
diff --git a/src/locales/locales.cpp b/src/locales/locales.cpp
new file mode 100644
index 0000000..6ec1233
--- /dev/null
+++ b/src/locales/locales.cpp
@@ -0,0 +1,29 @@
+#include "locales.hpp"
+
+locale_map locales[] = {
+ {"en", en_locales, en_locale_count},
+ // Add new locales here.
+};
+
+const int locale_map_count = sizeof(locales) / sizeof(locales[0]);
+
+locale_map g_locale = locales[0]; // Default to english.
+
+void set_locale(const char* key)
+{
+ for (int i = 0; i < locale_map_count; ++i) {
+ if (strcmp(locales[i].lang_code, key) == 0) {
+ g_locale = locales[i];
+ }
+ }
+}
+
+const char* localize(const char* key)
+{
+ for (int i = 0; i < g_locale.entry_count; ++i) {
+ if (strcmp(g_locale.entries[i].key, key) == 0) {
+ return g_locale.entries[i].value;
+ }
+ }
+ return "[!MISSING!]";
+} \ No newline at end of file
diff --git a/src/locales/locales.hpp b/src/locales/locales.hpp
new file mode 100644
index 0000000..78bb682
--- /dev/null
+++ b/src/locales/locales.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+
+typedef struct {
+ const char* key;
+ const char* value;
+} locale_entry;
+
+typedef struct {
+ const char* lang_code;
+ locale_entry* entries;
+ int entry_count;
+} locale_map;
+
+extern locale_entry en_locales[];
+extern const int en_locale_count;
+
+void set_locale(const char key[2]);
+const char* localize(const char* key); \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..323a5a0
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,246 @@
+#include "imgui.h"
+#include "imgui_impl_win32.h"
+#include "imgui_impl_dx11.h"
+#include <d3d11.h>
+#include <tchar.h>
+
+#include "views/views.hpp"
+#include "administration.hpp"
+
+// Data
+static ID3D11Device* g_pd3dDevice = nullptr;
+static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr;
+static IDXGISwapChain* g_pSwapChain = nullptr;
+static bool g_SwapChainOccluded = false;
+static UINT g_ResizeWidth = 0, g_ResizeHeight = 0;
+static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr;
+
+// Forward declarations of helper functions
+bool CreateDeviceD3D(HWND hWnd);
+void CleanupDeviceD3D();
+void CreateRenderTarget();
+void CleanupRenderTarget();
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Main code
+//int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+int main()
+{
+ // Make process DPI aware and obtain main monitor scale
+ ImGui_ImplWin32_EnableDpiAwareness();
+ float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY));
+
+ // Create application window
+ WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
+ ::RegisterClassExW(&wc);
+ HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"OpenBooks", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr);
+
+ // Initialize Direct3D
+ if (!CreateDeviceD3D(hwnd))
+ {
+ CleanupDeviceD3D();
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+ return 1;
+ }
+
+ // Show the window
+ ::ShowWindow(hwnd, SW_SHOWDEFAULT);
+ ::UpdateWindow(hwnd);
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+ io.IniFilename = NULL;
+ io.LogFilename = NULL;
+
+ // Setup Dear ImGui style
+ ImGui::StyleColorsDark();
+ //ImGui::StyleColorsLight();
+
+ // Setup scaling
+ ImGuiStyle& style = ImGui::GetStyle();
+ style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again)
+ style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose)
+
+ // Setup Platform/Renderer backends
+ ImGui_ImplWin32_Init(hwnd);
+ ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
+
+ // Load Fonts
+ // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
+ // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
+ // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
+ // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
+ // - Read 'docs/FONTS.md' for more instructions and details.
+ // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
+ style.FontSizeBase = 18.0f;
+ //io.Fonts->AddFontDefault();
+ io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf");
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf");
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf");
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf");
+ //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf");
+ //IM_ASSERT(font != nullptr);
+
+ ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+
+ init_administration_obj();
+
+ // Main loop
+ bool done = false;
+ while (!done)
+ {
+ // Poll and handle messages (inputs, window resize, etc.)
+ // See the WndProc() function below for our to dispatch events to the Win32 backend.
+ MSG msg;
+ while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ if (msg.message == WM_QUIT)
+ done = true;
+ }
+ if (done)
+ break;
+
+ // Handle window being minimized or screen locked
+ if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED)
+ {
+ ::Sleep(10);
+ continue;
+ }
+ g_SwapChainOccluded = false;
+
+ // Handle window resize (we don't resize directly in the WM_SIZE handler)
+ if (g_ResizeWidth != 0 && g_ResizeHeight != 0)
+ {
+ CleanupRenderTarget();
+ g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0);
+ g_ResizeWidth = g_ResizeHeight = 0;
+ CreateRenderTarget();
+ }
+
+ // Start the Dear ImGui frame
+ ImGui_ImplDX11_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+ ImGui::NewFrame();
+
+ show_dashboard();
+
+ // Rendering
+ ImGui::Render();
+ const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
+ g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
+ g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
+ ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
+
+ // Present
+ HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync
+ //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync
+ g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
+ }
+
+ destroy_administration_obj();
+
+ // Cleanup
+ ImGui_ImplDX11_Shutdown();
+ ImGui_ImplWin32_Shutdown();
+ ImGui::DestroyContext();
+
+ CleanupDeviceD3D();
+ ::DestroyWindow(hwnd);
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+
+ return 0;
+}
+
+// Helper functions
+
+bool CreateDeviceD3D(HWND hWnd)
+{
+ // Setup swap chain
+ DXGI_SWAP_CHAIN_DESC sd;
+ ZeroMemory(&sd, sizeof(sd));
+ sd.BufferCount = 2;
+ sd.BufferDesc.Width = 0;
+ sd.BufferDesc.Height = 0;
+ sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ sd.BufferDesc.RefreshRate.Numerator = 60;
+ sd.BufferDesc.RefreshRate.Denominator = 1;
+ sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
+ sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ sd.OutputWindow = hWnd;
+ sd.SampleDesc.Count = 1;
+ sd.SampleDesc.Quality = 0;
+ sd.Windowed = TRUE;
+ sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
+
+ UINT createDeviceFlags = 0;
+ //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
+ D3D_FEATURE_LEVEL featureLevel;
+ const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
+ HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
+ if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available.
+ res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
+ if (res != S_OK)
+ return false;
+
+ CreateRenderTarget();
+ return true;
+}
+
+void CleanupDeviceD3D()
+{
+ CleanupRenderTarget();
+ if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; }
+ if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; }
+ if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
+}
+
+void CreateRenderTarget()
+{
+ ID3D11Texture2D* pBackBuffer;
+ g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
+ g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
+ pBackBuffer->Release();
+}
+
+void CleanupRenderTarget()
+{
+ if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; }
+}
+
+// Forward declare message handler from imgui_impl_win32.cpp
+extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Win32 message handler
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
+ return true;
+
+ switch (msg)
+ {
+ case WM_SIZE:
+ if (wParam == SIZE_MINIMIZED)
+ return 0;
+ g_ResizeWidth = (UINT)LOWORD(lParam); // Queue resize
+ g_ResizeHeight = (UINT)HIWORD(lParam);
+ return 0;
+ case WM_SYSCOMMAND:
+ if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
+ return 0;
+ break;
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ return 0;
+ }
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+} \ No newline at end of file
diff --git a/src/views/contacts.cpp b/src/views/contacts.cpp
new file mode 100644
index 0000000..de5e766
--- /dev/null
+++ b/src/views/contacts.cpp
@@ -0,0 +1,162 @@
+#include <stdio.h>
+
+#include "views.hpp"
+#include "imgui.h"
+#include "../administration.hpp"
+
+typedef enum {
+ VIEW,
+ EDIT,
+ CREATE,
+ INSPECT,
+} contact_view_state;
+
+contact_view_state view_state = VIEW;
+static int selected_for_removal = -1; // Index in contact list selected for removal.
+
+static contact edit_contact;
+
+static void draw_required_tag()
+{
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ const char* text = "required";
+ ImVec2 text_pos = ImGui::GetCursorScreenPos();
+ ImVec2 text_size = ImGui::CalcTextSize(text);
+ text_pos.y += text_size.y/4.0f;
+
+ ImVec4 bg_color = ImVec4(0.9f, 0.235f, 0.235f, 0.4f); // Blue background
+ ImVec4 text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White text
+ float rounding = 2.0f;
+ float padding = 2.0f;
+
+ // Background rectangle
+ ImVec2 bg_min = ImVec2(text_pos.x - padding, text_pos.y - padding);
+ ImVec2 bg_max = ImVec2(text_pos.x + text_size.x + padding, text_pos.y + text_size.y + padding);
+ draw_list->AddRectFilled(bg_min, bg_max, ImColor(bg_color), rounding);
+
+ // Foreground text
+ ImGui::PushStyleColor(ImGuiCol_Text, text_color);
+ ImGui::TextUnformatted(text);
+ ImGui::PopStyleColor();
+}
+
+static void show_edit_contact()
+{
+ if (ImGui::Button(localize("form.back"))) {
+ view_state = contact_view_state::VIEW;
+ memset(&edit_contact, 0, sizeof(contact));
+ }
+ ImGui::Spacing();
+
+ // Input fields
+ ImGui::BeginDisabled();
+
+ ImGui::InputText(localize("contact.form.identifier"), edit_contact.id, IM_ARRAYSIZE(edit_contact.id));
+ ImGui::EndDisabled();
+
+ ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), edit_contact.name, IM_ARRAYSIZE(edit_contact.name));
+ ImGui::SameLine();draw_required_tag();
+
+ ImGui::InputTextWithHint(localize("contact.form.address1"), localize("contact.form.address1"), edit_contact.address1, IM_ARRAYSIZE(edit_contact.address1));
+ ImGui::SameLine();draw_required_tag();
+
+ ImGui::InputTextWithHint(localize("contact.form.address2"), localize("contact.form.address2"), edit_contact.address2, IM_ARRAYSIZE(edit_contact.address2));
+ ImGui::SameLine();draw_required_tag();
+
+ ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), edit_contact.taxid, IM_ARRAYSIZE(edit_contact.taxid));
+ ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), edit_contact.businessid, IM_ARRAYSIZE(edit_contact.businessid));
+ ImGui::InputTextWithHint(localize("contact.form.email"), localize("contact.form.email"), edit_contact.email, IM_ARRAYSIZE(edit_contact.email));
+ ImGui::InputTextWithHint(localize("contact.form.phonenumber"), localize("contact.form.phonenumber"), edit_contact.phone_number, IM_ARRAYSIZE(edit_contact.phone_number));
+ ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), edit_contact.bank_account, IM_ARRAYSIZE(edit_contact.bank_account));
+
+ bool can_save = strlen(edit_contact.name) > 0 && strlen(edit_contact.address1) > 0 && strlen(edit_contact.address2) > 0;
+
+ if (!can_save) ImGui::BeginDisabled();
+
+ // Save button
+ ImGui::Spacing();
+ if (ImGui::Button(localize("form.save"))) {
+ if (view_state == contact_view_state::CREATE)
+ create_contact(edit_contact);
+
+ else if (view_state == contact_view_state::EDIT)
+ update_contact(edit_contact);
+
+ memset(&edit_contact, 0, sizeof(contact));
+ view_state = contact_view_state::VIEW;
+ }
+
+ if (!can_save) ImGui::EndDisabled();
+}
+
+void show_contacts()
+{
+ if (view_state == contact_view_state::CREATE || view_state == contact_view_state::EDIT) {
+ show_edit_contact();
+ return;
+ }
+
+ if (ImGui::Button(localize("form.create")))
+ {
+ view_state = contact_view_state::CREATE;
+ memset(&edit_contact, 0, sizeof(contact));
+ snprintf(edit_contact.id, IM_ARRAYSIZE(edit_contact.id), "C/%d", g_administration.next_id);
+ }
+ ImGui::Spacing();
+
+ if (ImGui::BeginTable("TableContacts", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
+
+ ImGui::TableSetupColumn(localize("contact.table.identifier"), ImGuiTableColumnFlags_WidthFixed, 80);
+ ImGui::TableSetupColumn(localize("contact.table.name"), ImGuiTableColumnFlags_WidthStretch);
+ ImGui::TableSetupColumn(localize("contact.table.address"), ImGuiTableColumnFlags_WidthStretch);
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 120);
+ ImGui::TableHeadersRow();
+
+ list_iterator_start(&g_administration.contacts);
+ while (list_iterator_hasnext(&g_administration.contacts)) {
+ contact c = *(contact *)list_iterator_next(&g_administration.contacts);
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::Text(c.id);
+ ImGui::TableSetColumnIndex(1); ImGui::Text(c.name);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%s %s", c.address1, c.address2);
+
+ ImGui::TableSetColumnIndex(3);
+
+ char btn_name[20];
+ sprintf(btn_name, "%s##%d", localize("form.change"), g_administration.contacts.iter_pos);
+ if (ImGui::Button(btn_name)) {
+ edit_contact = c;
+ view_state = contact_view_state::EDIT;
+ }
+
+ ImGui::SameLine();
+
+ // @TODO check to make sure no invoices are connected to this contact.
+ sprintf(btn_name, "%s##%d", localize("form.delete"), g_administration.contacts.iter_pos);
+ if (ImGui::Button(btn_name)) {
+ selected_for_removal = g_administration.contacts.iter_pos;
+ ImGui::OpenPopup("ConfirmDeletePopup");
+ }
+ }
+ list_iterator_stop(&g_administration.contacts);
+
+ if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) {
+ ImGui::Text(localize("form.confirmDelete"));
+ ImGui::Separator();
+
+ if (ImGui::Button(localize("form.yes"), ImVec2(120, 0))) {
+ remove_contact(selected_for_removal-1);
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SameLine();
+ if (ImGui::Button(localize("form.no"), ImVec2(120, 0))) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+
+ ImGui::EndTable();
+ }
+} \ No newline at end of file
diff --git a/src/views/dashboard.cpp b/src/views/dashboard.cpp
new file mode 100644
index 0000000..16d31c4
--- /dev/null
+++ b/src/views/dashboard.cpp
@@ -0,0 +1,65 @@
+#include "views.hpp"
+
+#include "imgui.h"
+
+void show_dashboard()
+{
+ if (ImGui::BeginMainMenuBar())
+ {
+ if (ImGui::BeginMenu("File"))
+ {
+ if (ImGui::MenuItem("Open", "Ctrl+O")) { /* Handle Open */ }
+ if (ImGui::MenuItem("Save", "Ctrl+S")) { /* Handle Save */ }
+
+ ImGui::EndMenu();
+ }
+ ImGui::EndMainMenuBar();
+ }
+
+ ImGuiIO& io = ImGui::GetIO();
+ float menuBarHeight = ImGui::GetFrameHeight();
+ float statusBarHeight = 26.0f;
+ ImGui::SetNextWindowPos(ImVec2(0, menuBarHeight));
+ ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y - menuBarHeight - statusBarHeight));
+
+ ImGui::Begin("AccountingMainWindow", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);
+
+ ImGui::AlignTextToFramePadding();
+ const char* names[3] = { "Incoming", "Outgoing", "Contacts" };
+ void (*drawcalls[3])(void) = { show_contacts, show_contacts, show_contacts };
+
+ if (ImGui::BeginTabBar("mainNavigationTabBar", 0))
+ {
+ for (int n = 0; n < IM_ARRAYSIZE(names); n++)
+ if (ImGui::BeginTabItem(names[n], 0, ImGuiTabItemFlags_None))
+ {
+ ImGui::Spacing();
+ drawcalls[n]();
+ ImGui::EndTabItem();
+ }
+ ImGui::EndTabBar();
+ }
+
+ ImGui::End();
+
+ // Status bar.
+ ImGui::SetNextWindowPos(ImVec2(0, io.DisplaySize.y - statusBarHeight));
+ ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, statusBarHeight));
+
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 3));
+ ImGui::Begin("StatusBar", nullptr,
+ ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoScrollbar |
+ ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoBringToFrontOnFocus |
+ ImGuiWindowFlags_NoCollapse);
+
+ ImGui::Text("Working on: []");
+ ImGui::SameLine();
+ ImGui::Text("Status: []");
+
+ ImGui::End();
+ ImGui::PopStyleVar();
+} \ No newline at end of file
diff --git a/src/views/views.hpp b/src/views/views.hpp
new file mode 100644
index 0000000..adefea2
--- /dev/null
+++ b/src/views/views.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "../locales/locales.hpp"
+
+void show_dashboard();
+void show_contacts(); \ No newline at end of file