0xc3 4 giorni fa
commit
4842588ad9

+ 0 - 0
back/.gitignore


+ 3 - 0
back/go.mod

@@ -0,0 +1,3 @@
+module 0xc3.xyz/crm-prototype
+
+go 1.25.5

+ 1 - 0
back/src/cmd/app/main.go

@@ -0,0 +1 @@
+package main

+ 4 - 0
front/.gitignore

@@ -0,0 +1,4 @@
+.cache/
+build/
+vcpkg_installed/
+imgui.ini

+ 68 - 0
front/CMakeLists.txt

@@ -0,0 +1,68 @@
+cmake_minimum_required(VERSION 3.15...4.0)
+project(app)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# === CCACHE АВТОМАТИЧЕСКИЙ ЗАПУСК ===
+find_program(CCACHE_PROGRAM ccache)
+if(CCACHE_PROGRAM)
+    message(STATUS "ccache найден: ${CCACHE_PROGRAM}")
+
+    # Это работает и с Ninja, и с Makefiles
+    set(CMAKE_C_COMPILER_LAUNCHER   "${CCACHE_PROGRAM}")
+    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+
+    # Для старых версий CMake (< 3.4) можно использовать альтернативу:
+    # set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
+endif()
+
+set(OUTPUT_DIR ${CMAKE_BINARY_DIR}/$<CONFIG>)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR})
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR})
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR})
+
+file(GLOB_RECURSE LIB_SOURCES
+    "${PROJECT_SOURCE_DIR}/src/saura/main/main.cpp"
+    "${PROJECT_SOURCE_DIR}/src/saura/**/*.cpp"
+)
+
+# Создаем библиотеку
+add_library(${PROJECT_NAME}_lib STATIC ${LIB_SOURCES})
+
+# Пути
+set(SRC_RES_DIR "${CMAKE_SOURCE_DIR}/res")
+set(RES_SYMLINK "${OUTPUT_DIR}/res")
+
+find_package(SDL3 CONFIG REQUIRED)
+find_package(spdlog CONFIG REQUIRED)
+find_package(imgui CONFIG REQUIRED)
+find_package(glm CONFIG REQUIRED)
+find_package(cpr CONFIG REQUIRED)
+
+# Добавляем include-пути
+target_include_directories(${PROJECT_NAME}_lib PUBLIC
+    ${PROJECT_SOURCE_DIR}
+    ${PROJECT_SOURCE_DIR}/src
+)
+
+target_include_directories(${PROJECT_NAME}_lib PRIVATE
+    ${PROJECT_SOURCE_DIR}
+    ${PROJECT_SOURCE_DIR}/src
+)
+
+target_link_libraries(${PROJECT_NAME}_lib PUBLIC
+    SDL3::SDL3
+    spdlog::spdlog
+    imgui::imgui
+    glm::glm
+    cpr::cpr
+)
+
+file(GLOB CMD_PROJECTS "src/saura/cmd/*")
+foreach(cmd_path ${CMD_PROJECTS})
+    if(IS_DIRECTORY ${cmd_path} AND EXISTS ${cmd_path}/CMakeLists.txt)
+        add_subdirectory(${cmd_path})
+    endif()
+endforeach()

+ 4 - 0
front/README.md

@@ -0,0 +1,4 @@
+```
+cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
+cmake --build build --config Debug -j $(nproc)
+```

+ 16 - 0
front/scripts/setup.sh

@@ -0,0 +1,16 @@
+#!/bin/sh
+
+VCPKG_ROOT="~/.local/share/vcpkg"
+PATH="$PATH:$VCPKG_ROOT"
+
+# Создаём директорию и сразу клонируем официальный репозиторий Microsoft
+git clone https://github.com/microsoft/vcpkg $VCPKG_ROOT
+
+# Делаем первый bootstrap (собирает сам vcpkg)
+$VCPKG_ROOT/bootstrap-vcpkg.sh
+
+vcpkg version
+
+pushd ..
+vcpkg install
+popd

+ 55 - 0
front/src/saura/app/app.cpp

@@ -0,0 +1,55 @@
+#include "app.hpp"
+
+#include "saura/core/app_base/app_base.hpp"
+
+namespace saura {
+App::App() {
+  is_running = true;
+
+  os_ctx = std::make_unique<OS>();
+  server_api_ctx = std::make_shared<ServerAPI>();
+  ui_ctx = std::make_unique<UI>(server_api_ctx);
+}
+
+App::~App() {}
+
+void App::run() {
+  const double fps_max = 60.0;
+  const double period_max = 1.0 / fps_max;
+  const double perf_frequency = os_ctx->get_performance_frequency();
+
+  double time = 0.0;
+  double begin_counter = 0.0;
+  double end_counter = 0.0;
+
+  get_app_base()->start();
+
+  while (is_running) {
+    double counter_elapsed = begin_counter - end_counter;
+    double dt = counter_elapsed / perf_frequency;
+    double fps = perf_frequency / counter_elapsed;
+
+    begin_counter = os_ctx->get_performance_counter();
+
+    if (dt >= period_max) {
+      if (dt >= 1.0) {
+        dt = period_max;
+      }
+
+      if (!os_ctx->update_events()) {
+        is_running = false;
+      }
+      os_ctx->update();
+      os_ctx->draw_begin();
+      get_app_base()->update(dt);
+      os_ctx->draw_end();
+
+      end_counter = begin_counter;
+    }
+
+    os_ctx->sleep(period_max);
+  }
+
+  get_app_base()->stop();
+}
+} // namespace saura

+ 29 - 0
front/src/saura/app/app.hpp

@@ -0,0 +1,29 @@
+#ifndef SAURA_APP_HPP_
+#define SAURA_APP_HPP_
+
+#include "saura/core/os/os.hpp"
+#include "saura/core/server/server.hpp"
+#include "saura/core/ui/ui.hpp"
+
+#include <SDL3/SDL_render.h>
+#include <SDL3/SDL_video.h>
+#include <memory>
+
+namespace saura {
+class App {
+private:
+  std::unique_ptr<OS> os_ctx;
+  std::unique_ptr<UI> ui_ctx;
+  std::shared_ptr<ServerAPI> server_api_ctx;
+
+  bool is_running;
+
+public:
+  App();
+  ~App();
+
+  void run();
+};
+} // namespace saura
+
+#endif

+ 7 - 0
front/src/saura/cmd/sandbox/CMakeLists.txt

@@ -0,0 +1,7 @@
+get_filename_component(TARGET_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+
+file(GLOB SOURCES "*.cpp")
+
+add_executable(${TARGET_NAME} ${SOURCES})
+
+target_link_libraries(${TARGET_NAME} PRIVATE ${PROJECT_NAME}_lib)

+ 22 - 0
front/src/saura/cmd/sandbox/sandbox.cpp

@@ -0,0 +1,22 @@
+#include <saura/core/app_base/app_base.hpp>
+
+#include <spdlog/spdlog.h>
+
+namespace saura {
+struct SandboxApp : AppBase {
+  void start() override { spdlog::info("Sandbox start!"); }
+
+  void update(double dt) override {
+    // static float t = 0;
+    // t += dt;
+    // if (int(t) % 3 == 0)
+    //   spdlog::info("tick {}", t);
+  }
+
+  void stop() override { spdlog::info("Sandbox stop!"); }
+
+  const char *name() const override { return "Cool Sandbox"; }
+};
+
+AppBase *g_app_base_instance = new SandboxApp();
+} // namespace saura

+ 26 - 0
front/src/saura/core/app_base/app_base.hpp

@@ -0,0 +1,26 @@
+#pragma once
+
+#include <spdlog/spdlog.h>
+
+namespace saura {
+struct AppBase {
+  virtual ~AppBase() = default;
+  virtual void start() {}
+  virtual void update(double dt) {}
+  virtual void stop() {}
+  virtual const char *name() const { return "Unnamed App"; }
+};
+
+// Эта глобальная переменная будет переопределена линковщиком в каждом
+// cmd-проекте
+extern AppBase *g_app_base_instance;
+
+inline AppBase *get_app_base() {
+  if (!g_app_base_instance) {
+    spdlog::error(
+        "g_app_instance не определён!");
+    std::terminate();
+  }
+  return g_app_base_instance;
+}
+} // namespace saura

+ 173 - 0
front/src/saura/core/os/os.cpp

@@ -0,0 +1,173 @@
+#include "os.hpp"
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_video.h>
+
+#include <imgui.h>
+#include <imgui_impl_sdl3.h>
+#include <imgui_impl_sdlrenderer3.h>
+
+#include <filesystem>
+#include <locale>
+#include <string>
+
+namespace saura {
+OS::OS() {
+  root_window_ctx = std::make_unique<Window_Context>();
+  root_window_ctx->title = "(Saura Studios) [Dev]";
+  root_window_ctx->w = 1280;
+  root_window_ctx->h = 720;
+
+  std::locale::global(std::locale("en_US.UTF-8"));
+
+  if (!SDL_Init(SDL_INIT_VIDEO)) {
+    throw std::runtime_error("Failed SDL_Init!");
+  }
+
+  SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN;
+  root_window_ctx->handle =
+      SDL_CreateWindow(root_window_ctx->title.c_str(), root_window_ctx->w,
+                       root_window_ctx->h, window_flags);
+  if (root_window_ctx->handle == nullptr) {
+    throw std::runtime_error("Failed SDL_CreateWindow!");
+  }
+
+  root_window_ctx->renderer =
+      SDL_CreateRenderer(root_window_ctx->handle, nullptr);
+  if (root_window_ctx->renderer == nullptr) {
+    throw std::runtime_error("Failed SDL_CreateRenderer!");
+  }
+  SDL_SetRenderVSync(root_window_ctx->renderer, SDL_RENDERER_VSYNC_DISABLED);
+  SDL_SetWindowPosition(root_window_ctx->handle, SDL_WINDOWPOS_CENTERED,
+                        SDL_WINDOWPOS_CENTERED);
+  SDL_ShowWindow(root_window_ctx->handle);
+
+  // Setup imgui
+  IMGUI_CHECKVERSION();
+  ImGui::CreateContext();
+  ImGuiIO &io = ImGui::GetIO();
+  (void)io;
+  io.ConfigFlags |=
+      ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+  io.ConfigFlags |=
+      ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+
+  // Setup Dear ImGui style
+  ImGui::StyleColorsDark();
+
+  // Setup Platform/Renderer backends
+  ImGui_ImplSDL3_InitForSDLRenderer(root_window_ctx->handle,
+                                    root_window_ctx->renderer);
+  ImGui_ImplSDLRenderer3_Init(root_window_ctx->renderer);
+}
+
+OS::~OS() {
+  ImGui_ImplSDLRenderer3_Shutdown();
+  ImGui_ImplSDL3_Shutdown();
+  ImGui::DestroyContext();
+
+  SDL_DestroyRenderer(root_window_ctx->renderer);
+  SDL_DestroyWindow(root_window_ctx->handle);
+  SDL_Quit();
+}
+
+void OS::draw_begin() {
+  ImGui_ImplSDLRenderer3_NewFrame();
+  ImGui_ImplSDL3_NewFrame();
+  ImGui::NewFrame();
+}
+
+void OS::draw_end() {
+  ImVec4 clear_color = {0.15f, 0.15f, 0.15f, 0.0f};
+  ImGuiIO &io = ImGui::GetIO();
+  (void)io;
+  ImGui::Render();
+  SDL_SetRenderScale(root_window_ctx->renderer, io.DisplayFramebufferScale.x,
+                     io.DisplayFramebufferScale.y);
+  SDL_SetRenderDrawColorFloat(root_window_ctx->renderer, clear_color.x,
+                              clear_color.y, clear_color.z, clear_color.w);
+  SDL_RenderClear(root_window_ctx->renderer);
+  ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(),
+                                        root_window_ctx->renderer);
+
+  SDL_RenderPresent(root_window_ctx->renderer);
+}
+
+void OS::update() {
+  int w, h = 0;
+  SDL_GetWindowSize(root_window_ctx->handle, &w, &h);
+  root_window_ctx->w = w;
+  root_window_ctx->h = h;
+}
+
+bool OS::update_events() {
+  SDL_Event event = {};
+  while (SDL_PollEvent(&event)) {
+
+    ImGui_ImplSDL3_ProcessEvent(&event);
+
+    switch (event.type) {
+    case SDL_EVENT_KEY_UP: {
+      if (event.key.key == SDLK_ESCAPE) {
+        return false;
+      }
+      break;
+    }
+    case SDL_EVENT_QUIT: {
+      return false;
+    }
+    case SDL_EVENT_WINDOW_CLOSE_REQUESTED: {
+      if (event.window.windowID == SDL_GetWindowID(root_window_ctx->handle)) {
+        return false;
+      }
+      break;
+    }
+    }
+  }
+  return true;
+}
+
+void OS::sleep(double ms) { SDL_Delay((Uint32)ms * 1000); }
+
+int OS::get_root_window_width() { return root_window_ctx->w; }
+
+int OS::get_root_window_height() { return root_window_ctx->h; }
+
+double OS::get_performance_frequency() {
+  return (double)SDL_GetPerformanceFrequency();
+}
+
+double OS::get_performance_counter() {
+  return (double)SDL_GetPerformanceCounter();
+}
+
+std::string OS::get_safe_getenv(const std::string key) {
+  return SDL_getenv(key.c_str());
+}
+
+fs::path OS::get_home_config_path() {
+  fs::path res = {};
+
+#if defined(_WIN32)
+  auto config = get_safe_getenv("APPDATA");
+
+  if (config.empty()) {
+    throw std::runtime_error("APPDATA environment variable not found");
+  }
+
+  res = fs::path(config);
+#elif defined(__linux__)
+  auto config = get_safe_getenv("HOME");
+
+  if (config.empty()) {
+    throw std::runtime_error("HOME environment variable not found");
+  }
+
+  res = fs::path(config) / ".config";
+#endif
+
+  return res;
+}
+
+std::string OS::get_user_name() { return get_safe_getenv("USERNAME"); }
+} // namespace saura

+ 47 - 0
front/src/saura/core/os/os.hpp

@@ -0,0 +1,47 @@
+#ifndef SAURA_OS_HPP_
+#define SAURA_OS_HPP_
+
+#include <cstdint>
+#include <filesystem>
+
+#include <SDL3/SDL_render.h>
+#include <SDL3/SDL_video.h>
+
+namespace fs = std::filesystem;
+
+namespace saura {
+class OS {
+  struct Window_Context {
+    uint32_t w, h;
+    std::string title;
+    SDL_Window *handle;
+    SDL_Renderer *renderer;
+  };
+
+private:
+  std::unique_ptr<Window_Context> root_window_ctx;
+
+public:
+  OS();
+  ~OS();
+
+  void draw_begin();
+  void draw_end();
+
+  void update();
+  bool update_events();
+  void sleep(double ms);
+
+  int get_root_window_width();
+  int get_root_window_height();
+
+  double get_performance_frequency();
+  double get_performance_counter();
+
+  static std::string get_safe_getenv(const std::string key);
+  static fs::path get_home_config_path();
+  static std::string get_user_name();
+};
+} // namespace saura
+
+#endif

+ 32 - 0
front/src/saura/core/server/server.cpp

@@ -0,0 +1,32 @@
+#include "server.hpp"
+#include <chrono>
+#include <cpr/api.h>
+#include <cpr/cprtypes.h>
+#include <cpr/timeout.h>
+#include <thread>
+
+namespace saura {
+ServerAPI::ServerAPI() {
+  worker = std::thread([this]() { check_is_live(); });
+}
+
+ServerAPI::~ServerAPI() {
+  if (worker.joinable())
+    worker.join();
+}
+
+void ServerAPI::check_is_live() {
+  while (true) {
+    auto res =
+        cpr::Get(cpr::Url{"http://localhost:8090/ping"}, cpr::Timeout{1000});
+    if (res.status_code == 200) {
+      is_live = true;
+    } else {
+      is_live = false;
+    }
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+  }
+}
+
+bool ServerAPI::get_is_live() { return is_live; }
+} // namespace saura

+ 20 - 0
front/src/saura/core/server/server.hpp

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+namespace saura {
+class ServerAPI {
+private:
+  std::atomic<bool> is_live;
+  std::thread worker;
+
+public:
+  ServerAPI();
+  ~ServerAPI();
+
+  void check_is_live();
+
+  bool get_is_live();
+};
+}; // namespace saura

+ 105 - 0
front/src/saura/core/ui/ui.cpp

@@ -0,0 +1,105 @@
+#include "ui.hpp"
+
+#include <imgui.h>
+#include <imgui_internal.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "saura/core/server/server.hpp"
+
+namespace saura {
+UI::UI(std::shared_ptr<ServerAPI> server_api_ctx) {
+  current_ent = nullptr;
+  server_info = {};
+  deps.server_api_ctx = server_api_ctx;
+
+  auto items = std::vector<Entity>{
+      {
+          .uuid = "0",
+          .title = "Talltale Online",
+          .url = "https://talltale.online/",
+      },
+      {
+          .uuid = "1",
+          .title = "Google",
+          .url = "https://google.com/",
+      },
+      {
+          .uuid = "2",
+          .title = "Arch Linux",
+          .url = "https://archlinux.org/",
+      },
+      {
+          .uuid = "3",
+          .title = "GitHub",
+          .url = "https://github.com/",
+      },
+  };
+
+  for (uint32_t i = 0; i < items.size(); i += 1) {
+    entities.push_back(items[i]);
+  }
+}
+
+UI::~UI() {}
+
+void UI::draw() {
+  server_info.is_connect = deps.server_api_ctx.lock()->get_is_live();
+
+  ImGui::SetNextWindowPos({0.0f, 0.0f});
+  ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
+  ImGui::Begin("Drag & Drop List", 0, ImGuiWindowFlags_NoDecoration);
+
+  {
+    ImGui::Text("server.is_connect:");
+    ImVec2 text_pos = ImGui::GetItemRectMax();
+    float spacing = 5.0f;
+    ImVec2 center = ImVec2(text_pos.x + spacing + 4.0f,
+                           text_pos.y - ImGui::GetTextLineHeight() * 0.5f);
+    float radius = 4.0f;
+    ImU32 color = IM_COL32(255, 0, 0, 255);
+    if (server_info.is_connect == true) {
+      color = IM_COL32(0, 255, 0, 255);
+    }
+    ImDrawList *draw_list = ImGui::GetWindowDrawList();
+    draw_list->AddCircleFilled(center, radius, color, 16);
+  }
+
+  ImGui::Text("ent.count: %d", (int)entities.size());
+  if (current_ent != nullptr) {
+    ImGui::Text("current_ent_uuid: %s", current_ent->uuid.c_str());
+  } else {
+    ImGui::Text("current_ent_uuid: None");
+  }
+
+  ImVec2 mouse = ImGui::GetMousePos();
+  uint32_t rect_count = 0;
+
+  // --- Render ---
+  for (auto &ent : entities) {
+    ImGui::PushID(ent.uuid.c_str());
+    ImGui::Button(ent.title.c_str(), {150, 22});
+    ent.local_tmp_id = ImGui::GetItemID();
+    ent.rect.min = ImGui::GetItemRectMin();
+    ent.rect.max = ImGui::GetItemRectMax();
+    ImGui::PopID();
+  }
+
+  // --- Logic ---
+  for (auto &ent : entities) {
+    ImGuiID ent_hovered_id = ImGui::GetCurrentContext()->HoveredId;
+
+    // --- Handle drag start ---
+    if (ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
+        ent.local_tmp_id == ent_hovered_id) {
+      current_ent = &ent;
+    } else if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
+      current_ent = nullptr;
+    }
+  }
+
+  ImGui::End();
+}
+
+} // namespace saura

+ 41 - 0
front/src/saura/core/ui/ui.hpp

@@ -0,0 +1,41 @@
+#pragma once
+
+#include "saura/core/server/server.hpp"
+#include <cstdint>
+#include <imgui.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace saura {
+struct Entity {
+  std::string uuid;
+  uint32_t local_tmp_id;
+  std::string title;
+  std::string url;
+  struct {
+    ImVec2 min;
+    ImVec2 max;
+  } rect;
+};
+
+struct ServerInfo {
+  bool is_connect;
+};
+
+class UI {
+private:
+  Entity *current_ent;
+  std::vector<Entity> entities;
+  ServerInfo server_info;
+
+  struct {
+    std::weak_ptr<ServerAPI> server_api_ctx;
+  } deps;
+
+public:
+  UI(std::shared_ptr<ServerAPI> server_api_ctx);
+  ~UI();
+  void draw();
+};
+} // namespace saura

+ 9 - 0
front/src/saura/main/main.cpp

@@ -0,0 +1,9 @@
+#include "saura/app/app.hpp"
+
+int main() {
+  auto *app_ctx = new (saura::App);
+  app_ctx->run();
+  // TODO: вызывает вылет!
+  // delete app_ctx;
+  return 0;
+}

+ 15 - 0
front/vcpkg.json

@@ -0,0 +1,15 @@
+{
+  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+  "name": "bookmarks",
+  "version": "0.1.0",
+  "dependencies": [
+    {
+      "name": "imgui",
+      "features": ["sdl3-binding", "sdl3-renderer-binding"]
+    },
+    "spdlog",
+    "sdl3",
+    "glm",
+    "cpr"
+  ]
+}