From 8a97af079d69773733e35b70b3f01919bec6c228 Mon Sep 17 00:00:00 2001 From: Daher Alfawares Date: Thu, 15 Jan 2026 11:01:20 -0800 Subject: [PATCH] Adding linux support --- .gitignore | 1 + CMakeLists.txt | 24 +++ engine/CMakeLists.txt | 69 ++++++ engine/controls/debug/thread.hpp | 51 +++-- engine/controls/engine.hpp | 6 +- engine/controls/network/udp.hpp | 19 +- engine/controls/network/udp_linux.cpp | 152 ++++++++++++++ engine/controls/signal/object.hpp | 4 +- engine/main.cpp | 21 +- engine/platform/dpi.hpp | 36 ++++ engine/platform/font.hpp | 53 +++++ engine/platform/linux/dpi.cpp | 62 ++++++ engine/platform/linux/font.cpp | 145 +++++++++++++ engine/platform/linux/process.cpp | 196 ++++++++++++++++++ engine/platform/platform.hpp | 41 ++++ engine/platform/process.hpp | 74 +++++++ engine/platform/windows/dpi.cpp | 58 ++++++ engine/platform/windows/font.cpp | 59 ++++++ engine/platform/windows/process.cpp | 213 +++++++++++++++++++ engine/ui/app.hpp | 40 ++-- engine/ui/dpi.hpp | 28 +-- engine/ui/font.hpp | 33 ++- engine/ui/icons.hpp | 36 ++-- engine/ui/terminal.hpp | 219 +++----------------- engine/ui/ui.hpp | 207 +++++++++--------- lib/CMakeLists.txt | 48 +++++ lib/imgui-file-dialog/imgui_file_dialog.hpp | 26 ++- 27 files changed, 1502 insertions(+), 419 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 engine/CMakeLists.txt create mode 100644 engine/controls/network/udp_linux.cpp create mode 100644 engine/platform/dpi.hpp create mode 100644 engine/platform/font.hpp create mode 100644 engine/platform/linux/dpi.cpp create mode 100644 engine/platform/linux/font.cpp create mode 100644 engine/platform/linux/process.cpp create mode 100644 engine/platform/platform.hpp create mode 100644 engine/platform/process.hpp create mode 100644 engine/platform/windows/dpi.cpp create mode 100644 engine/platform/windows/font.cpp create mode 100644 engine/platform/windows/process.cpp create mode 100644 lib/CMakeLists.txt diff --git a/.gitignore b/.gitignore index bf1e68e..4944c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ x64/ *.json imgui.ini machine/include/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ab8dff5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.16) +project(gctrl VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Platform detection +if(WIN32) + set(GCTRL_PLATFORM_WINDOWS TRUE) + add_compile_definitions(GCTRL_PLATFORM_WINDOWS) +elseif(UNIX AND NOT APPLE) + set(GCTRL_PLATFORM_LINUX TRUE) + add_compile_definitions(GCTRL_PLATFORM_LINUX) +endif() + +# Find dependencies +find_package(SDL2 REQUIRED) +find_package(OpenGL REQUIRED) +find_package(GLEW REQUIRED) + +# Add subdirectories +add_subdirectory(lib) +add_subdirectory(engine) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt new file mode 100644 index 0000000..9d8588e --- /dev/null +++ b/engine/CMakeLists.txt @@ -0,0 +1,69 @@ +# Main application source +set(ENGINE_SOURCES + main.cpp +) + +# Platform-specific sources +if(GCTRL_PLATFORM_WINDOWS) + list(APPEND ENGINE_SOURCES + controls/network/udp_windows.cpp + platform/windows/process.cpp + platform/windows/dpi.cpp + platform/windows/font.cpp + ) +elseif(GCTRL_PLATFORM_LINUX) + list(APPEND ENGINE_SOURCES + controls/network/udp_linux.cpp + platform/linux/process.cpp + platform/linux/dpi.cpp + platform/linux/font.cpp + ) +endif() + +add_executable(gctrl ${ENGINE_SOURCES}) + +target_include_directories(gctrl PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/lib + ${CMAKE_SOURCE_DIR}/lib/nlohmann + ${CMAKE_SOURCE_DIR}/lib/da0x + ${CMAKE_SOURCE_DIR}/lib/IconFontCppHeaders + ${CMAKE_SOURCE_DIR}/lib/L2DFileDialog/L2DFileDialog/src +) + +target_link_libraries(gctrl PRIVATE + imgui + implot + imgui_node_editor + imgui_color_text_edit + SDL2::SDL2 + OpenGL::GL + GLEW::GLEW +) + +# Platform-specific linking and settings +if(GCTRL_PLATFORM_WINDOWS) + target_link_libraries(gctrl PRIVATE + ws2_32 + dwmapi + shcore + ) + # Windows GUI application (no console) + set_target_properties(gctrl PROPERTIES + WIN32_EXECUTABLE TRUE + ) +elseif(GCTRL_PLATFORM_LINUX) + target_link_libraries(gctrl PRIVATE + pthread + ) +endif() + +# Copy font resources to build directory +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/fonts) +file(COPY + ${CMAKE_CURRENT_SOURCE_DIR}/resources/fonts/cascadia-code.ttf + ${CMAKE_CURRENT_SOURCE_DIR}/resources/fonts/consola.ttf + ${CMAKE_CURRENT_SOURCE_DIR}/resources/fonts/fa-solid-900.otf + DESTINATION ${CMAKE_BINARY_DIR}/fonts +) diff --git a/engine/controls/debug/thread.hpp b/engine/controls/debug/thread.hpp index daee432..e695ca1 100644 --- a/engine/controls/debug/thread.hpp +++ b/engine/controls/debug/thread.hpp @@ -21,10 +21,10 @@ #pragma once - #include #include #include +#include #include #include "controls/network/udp.hpp" #include @@ -40,9 +40,13 @@ namespace controls { std::thread listener; }; - static State state; + inline State& get_state() { + static State state; + return state; + } - void add_signal_value(uint64_t id, float value) { + inline void add_signal_value(uint64_t id, float value) { + auto& state = get_state(); std::lock_guard lock(state.data_mutex); if (state.signal_data[id].size() > 1000) { state.signal_data[id].erase(state.signal_data[id].begin()); @@ -50,31 +54,37 @@ namespace controls { state.signal_data[id].push_back(value); } - void listener_thread() { - network::udp::udp_listener listener("127.0.0.1", 8080); + inline void listener_thread() { + auto& state = get_state(); + try { + network::udp::udp_listener listener("127.0.0.1", 8080); - listener.start([](uint64_t id, const std::vector& data) { - if (data.size() < sizeof(float)) { - std::cerr << "Received data is too small: " << data.size() << " bytes\n"; - return; - } + listener.start([](uint64_t id, const std::vector& data) { + if (data.size() < sizeof(float)) { + std::cerr << "Received data is too small: " << data.size() << " bytes\n"; + return; + } - float value; - std::memcpy(&value, data.data(), sizeof(float)); + float value; + std::memcpy(&value, data.data(), sizeof(float)); - std::cerr << "Received Signal ID: " << id << ", Value: " << value << "\n"; + std::cerr << "Received Signal ID: " << id << ", Value: " << value << "\n"; - add_signal_value(id, value); + add_signal_value(id, value); }); - while (state.keep_listening.load()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } + while (state.keep_listening.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } - listener.stop(); + listener.stop(); + } catch (const std::exception& e) { + std::cerr << "Debug listener failed: " << e.what() << "\n"; + } } - void begin() { + inline void begin() { + auto& state = get_state(); if (state.listener.joinable()) { throw std::runtime_error("Listener thread already running!"); } @@ -82,7 +92,8 @@ namespace controls { state.listener = std::thread(listener_thread); } - void end() { + inline void end() { + auto& state = get_state(); state.keep_listening.store(false); if (state.listener.joinable()) { state.listener.join(); diff --git a/engine/controls/engine.hpp b/engine/controls/engine.hpp index 8f110d2..36221ec 100644 --- a/engine/controls/engine.hpp +++ b/engine/controls/engine.hpp @@ -292,9 +292,9 @@ namespace controls { } void render_run_mode() { - - std::lock_guard lock(debug::state.data_mutex); - debug::viewer::render(machines, debug::state.signal_data); + auto& state = debug::get_state(); + std::lock_guard lock(state.data_mutex); + debug::viewer::render(machines, state.signal_data); } void view_menu(const std::string& label, const std::string& shortcut, ui::navigation::type navigation_type) { diff --git a/engine/controls/network/udp.hpp b/engine/controls/network/udp.hpp index 0960cbc..0a4948a 100644 --- a/engine/controls/network/udp.hpp +++ b/engine/controls/network/udp.hpp @@ -21,18 +21,27 @@ #pragma once +#include "platform/platform.hpp" #include +#include #include #include #include #include #include #include -#include -#include #include -#pragma comment(lib, "Ws2_32.lib") +#if GCTRL_PLATFORM_WINDOWS + #include + #include + #pragma comment(lib, "Ws2_32.lib") + using socket_t = SOCKET; + constexpr socket_t INVALID_SOCKET_VALUE = INVALID_SOCKET; +#else + using socket_t = int; + constexpr socket_t INVALID_SOCKET_VALUE = -1; +#endif namespace network { namespace udp { @@ -59,7 +68,7 @@ namespace network { void stop(); private: - SOCKET socket_fd_; + socket_t socket_fd_; bool stop_ = false; std::thread listener_thread_; void initialize_socket(const std::string& address, uint16_t port); @@ -77,7 +86,7 @@ namespace network { private: std::string address_; uint16_t port_; - SOCKET socket_fd_; + socket_t socket_fd_; void initialize_socket(); void close_socket(); }; diff --git a/engine/controls/network/udp_linux.cpp b/engine/controls/network/udp_linux.cpp new file mode 100644 index 0000000..d364a5d --- /dev/null +++ b/engine/controls/network/udp_linux.cpp @@ -0,0 +1,152 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "controls/network/udp.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace network { + namespace udp { + + udp_listener::udp_listener(const std::string& address, uint16_t port) + : stop_(false), socket_fd_(-1) { + initialize_socket(address, port); + } + + udp_listener::~udp_listener() { + stop(); + } + + void udp_listener::start(signal_callback on_receive) { + listener_thread_ = std::thread([this, on_receive]() { listen_for_packets(on_receive); }); + } + + void udp_listener::stop() { + if (!stop_) { + stop_ = true; + if (socket_fd_ >= 0) { + close(socket_fd_); + socket_fd_ = -1; + } + if (listener_thread_.joinable()) { + listener_thread_.join(); + } + } + } + + void udp_listener::initialize_socket(const std::string& address, uint16_t port) { + socket_fd_ = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd_ < 0) { + throw std::runtime_error("Socket creation failed"); + } + + // Set socket timeout so recvfrom doesn't block forever + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000; // 100ms timeout + setsockopt(socket_fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + sockaddr_in server_addr{}; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(socket_fd_, reinterpret_cast(&server_addr), sizeof(server_addr)) < 0) { + throw std::runtime_error("Socket bind failed"); + } + } + + void udp_listener::listen_for_packets(signal_callback on_receive) { + stop_ = false; + while (!stop_) { + std::vector buffer(1024); + sockaddr_in sender_addr{}; + socklen_t sender_addr_len = sizeof(sender_addr); + + ssize_t received_bytes = recvfrom( + socket_fd_, + buffer.data(), + buffer.size(), + 0, + reinterpret_cast(&sender_addr), + &sender_addr_len + ); + + if (received_bytes > 0) { + buffer.resize(received_bytes); + if (buffer.size() >= sizeof(uint64_t)) { + uint64_t id; + std::memcpy(&id, buffer.data(), sizeof(uint64_t)); + on_receive(id, { buffer.begin() + sizeof(uint64_t), buffer.end() }); + } + } + } + } + + udp_sender::udp_sender(const std::string& address, uint16_t port) + : address_(address), port_(port), socket_fd_(-1) { + initialize_socket(); + } + + void udp_sender::initialize_socket() { + socket_fd_ = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd_ < 0) { + throw std::runtime_error("Socket creation failed"); + } + } + + void udp_sender::send(uint64_t id, const std::vector& data) { + std::vector packet(sizeof(id) + data.size()); + std::memcpy(packet.data(), &id, sizeof(id)); + std::memcpy(packet.data() + sizeof(id), data.data(), data.size()); + + sockaddr_in server_addr{}; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port_); + inet_pton(AF_INET, address_.c_str(), &server_addr.sin_addr); + + sendto( + socket_fd_, + packet.data(), + packet.size(), + 0, + reinterpret_cast(&server_addr), + sizeof(server_addr) + ); + } + + void udp_sender::close_socket() { + if (socket_fd_ >= 0) { + close(socket_fd_); + socket_fd_ = -1; + } + } + + udp_sender::~udp_sender() { + close_socket(); + } + } +} diff --git a/engine/controls/signal/object.hpp b/engine/controls/signal/object.hpp index c0107fd..b21c43c 100644 --- a/engine/controls/signal/object.hpp +++ b/engine/controls/signal/object.hpp @@ -46,7 +46,7 @@ namespace signal { std::string default_value; object(const char* uuid, const char* name, const char* default_value, const char* description, const char* include_path) - : record(uuid, name, "", description), type(type), default_value(default_value), include(include_path) {} + : record(uuid, name, "", description), type(name), default_value(default_value), include(include_path) {} object(const json& j) : record("", j), @@ -77,7 +77,7 @@ namespace signal { } }; - const object::list default_signals = { + inline const object::list default_signals = { signal::object("12270a17-e1f4-4473-91c3-f698d303e093", "float", "0e+0f", "Real Number", ""), signal::object("3a87cbc2-b213-4e5e-8c5a-720dadb19f91", "complex", "(0.0f, 0.0f)", "Complex Number", "complex"), signal::object("9786c237-804e-4109-b3d8-e8f9bc43f682", "bool", "false", "Boolean", ""), diff --git a/engine/main.cpp b/engine/main.cpp index 4d3ae21..b790288 100644 --- a/engine/main.cpp +++ b/engine/main.cpp @@ -19,6 +19,7 @@ // Contact Information: www.gctrl.org // +#include "platform/platform.hpp" #include "controls/debug/thread.hpp" #include "ui/ui.hpp" #include "project.hpp" @@ -27,6 +28,7 @@ namespace fs = std::filesystem; +#if GCTRL_PLATFORM_WINDOWS void parse_command_line(fs::path& project_path, LPSTR lpCmdLine) { std::string cmd_line(lpCmdLine); if (!cmd_line.empty()) { @@ -38,18 +40,31 @@ void parse_command_line(fs::path& project_path, LPSTR lpCmdLine) { } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + fs::path project_path; + parse_command_line(project_path, lpCmdLine); +#else +void parse_command_line(fs::path& project_path, int argc, char* argv[]) { + if (argc > 1) { + fs::path path(argv[1]); + if (fs::exists(path) && path.extension() == ".ctrl") { + project_path = path; + } + } +} + +int main(int argc, char* argv[]) { + fs::path project_path; + parse_command_line(project_path, argc, argv); +#endif ui::good << "gctrl v0.0.1" << ui::endl; ui::good << "Copyright(C) 2024 www.gctrl.org" << ui::endl; ui::warn << "This is free software; see the source for copying conditions." << ui::endl; ui::warn << "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << ui::endl; ui::app app("General Controls Engine"); - fs::path project_path; ui::theme::vs2022::apply(); - parse_command_line(project_path, lpCmdLine); - while (app.frame()) { project::show_project_modal(project_path); app.render(); diff --git a/engine/platform/dpi.hpp b/engine/platform/dpi.hpp new file mode 100644 index 0000000..2fb8c35 --- /dev/null +++ b/engine/platform/dpi.hpp @@ -0,0 +1,36 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#pragma once + +struct SDL_Window; + +namespace platform { +namespace dpi { + + // Get the scaling factor for the display containing the window + float get_scaling_factor(SDL_Window* window); + + // Set DPI awareness for the application (call early in main) + void set_dpi_awareness(); + +} // namespace dpi +} // namespace platform diff --git a/engine/platform/font.hpp b/engine/platform/font.hpp new file mode 100644 index 0000000..6c3dd4e --- /dev/null +++ b/engine/platform/font.hpp @@ -0,0 +1,53 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#pragma once + +#include +#include +#include +#include + +namespace platform { +namespace font { + + // Font resource IDs (matches Windows resource.h) + enum resource_id { + CASCADIA_CODE = 160, + FONT_AWESOME = 161, + CONSOLA = 162 + }; + + // Get the path to the system UI font + std::string get_system_ui_font(); + + // Get the path to a system monospace font + std::string get_system_monospace_font(); + + // Load font data from embedded resource (Windows) or bundled file (Linux) + // Returns the font data buffer, or empty optional on failure + std::optional> load_font_resource(resource_id id); + + // Get font search paths for the platform + std::vector get_font_search_paths(); + +} // namespace font +} // namespace platform diff --git a/engine/platform/linux/dpi.cpp b/engine/platform/linux/dpi.cpp new file mode 100644 index 0000000..82f4e73 --- /dev/null +++ b/engine/platform/linux/dpi.cpp @@ -0,0 +1,62 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/dpi.hpp" +#include +#include + +namespace platform { +namespace dpi { + + float get_scaling_factor(SDL_Window* window) { + // Try SDL's built-in DPI detection first + float ddpi, hdpi, vdpi; + int display_index = SDL_GetWindowDisplayIndex(window); + + if (SDL_GetDisplayDPI(display_index, &ddpi, &hdpi, &vdpi) == 0) { + return hdpi / 96.0f; // 96 DPI is the baseline + } + + // Fallback: Check GDK_SCALE environment variable (GNOME) + const char* gdk_scale = std::getenv("GDK_SCALE"); + if (gdk_scale) { + float scale = std::atof(gdk_scale); + if (scale > 0.0f) return scale; + } + + // Fallback: Check QT_SCALE_FACTOR (KDE/Qt apps) + const char* qt_scale = std::getenv("QT_SCALE_FACTOR"); + if (qt_scale) { + float scale = std::atof(qt_scale); + if (scale > 0.0f) return scale; + } + + return 1.0f; + } + + void set_dpi_awareness() { + // On Linux, DPI awareness is handled by the desktop environment + // We can set SDL hints for better scaling behavior + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); + } + +} // namespace dpi +} // namespace platform diff --git a/engine/platform/linux/font.cpp b/engine/platform/linux/font.cpp new file mode 100644 index 0000000..c7a66ea --- /dev/null +++ b/engine/platform/linux/font.cpp @@ -0,0 +1,145 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/font.hpp" +#include +#include +#include + +namespace fs = std::filesystem; + +namespace platform { +namespace font { + + std::vector get_font_search_paths() { + std::vector paths; + + // User fonts + const char* home = std::getenv("HOME"); + if (home) { + paths.push_back(std::string(home) + "/.local/share/fonts"); + paths.push_back(std::string(home) + "/.fonts"); + } + + // System fonts + paths.push_back("/usr/share/fonts"); + paths.push_back("/usr/local/share/fonts"); + + return paths; + } + + static std::string find_font(const std::vector& candidates) { + for (const auto& base_path : get_font_search_paths()) { + if (!fs::exists(base_path)) continue; + + try { + for (auto& entry : fs::recursive_directory_iterator( + base_path, fs::directory_options::skip_permission_denied)) { + for (const auto& font : candidates) { + if (entry.path().filename() == font) { + return entry.path().string(); + } + } + } + } catch (...) { + // Skip directories we can't access + } + } + return ""; + } + + std::string get_system_ui_font() { + // Check common paths directly first (fast) + static const char* known_paths[] = { + "/usr/share/fonts/TTF/DejaVuSans.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/TTF/LiberationSans-Regular.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf", + "/usr/share/fonts/noto/NotoSans-Regular.ttf" + }; + + for (const auto& path : known_paths) { + if (fs::exists(path)) return path; + } + + // Fall back to search if known paths don't exist + std::vector candidates = { + "DejaVuSans.ttf", + "LiberationSans-Regular.ttf", + "Ubuntu-R.ttf" + }; + return find_font(candidates); + } + + std::string get_system_monospace_font() { + // Check common paths directly first (fast) + static const char* known_paths[] = { + "/usr/share/fonts/TTF/DejaVuSansMono.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", + "/usr/share/fonts/TTF/LiberationMono-Regular.ttf", + "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf", + "/usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf" + }; + + for (const auto& path : known_paths) { + if (fs::exists(path)) return path; + } + + std::vector candidates = { + "DejaVuSansMono.ttf", + "LiberationMono-Regular.ttf" + }; + return find_font(candidates); + } + + std::optional> load_font_resource(resource_id id) { + // On Linux, load from bundled font files instead of Windows resources + std::string font_path; + switch (id) { + case CASCADIA_CODE: + font_path = "fonts/cascadia-code.ttf"; + break; + case FONT_AWESOME: + font_path = "fonts/fa-solid-900.otf"; + break; + case CONSOLA: + font_path = "fonts/consola.ttf"; + break; + default: + return std::nullopt; + } + + std::ifstream file(font_path, std::ios::binary | std::ios::ate); + if (!file) return std::nullopt; + + auto size = file.tellg(); + std::vector buffer(size); + file.seekg(0); + file.read(reinterpret_cast(buffer.data()), size); + + if (!file) return std::nullopt; + + return buffer; + } + +} // namespace font +} // namespace platform diff --git a/engine/platform/linux/process.cpp b/engine/platform/linux/process.cpp new file mode 100644 index 0000000..ea1aa95 --- /dev/null +++ b/engine/platform/linux/process.cpp @@ -0,0 +1,196 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/process.hpp" +#include "ui/terminal.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace platform { +namespace process { + + static state global_state; + static pid_t current_pid = -1; + + state& get_state() { + return global_state; + } + + void log_last_error(const std::string& context) { + ui::cerr << context << ". Error: " << strerror(errno) << ui::endl; + } + + static bool run_single_command(const std::string& command) { + int stdout_pipe[2], stderr_pipe[2]; + + if (pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) { + log_last_error("Failed to create pipes"); + return false; + } + + pid_t pid = fork(); + if (pid < 0) { + log_last_error("Failed to fork process"); + close(stdout_pipe[0]); close(stdout_pipe[1]); + close(stderr_pipe[0]); close(stderr_pipe[1]); + return false; + } + + if (pid == 0) { + // Child process + close(stdout_pipe[0]); + close(stderr_pipe[0]); + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stderr_pipe[1], STDERR_FILENO); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + execl("/bin/sh", "sh", "-c", command.c_str(), nullptr); + _exit(127); + } + + // Parent process + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + { + std::lock_guard lock(global_state.cmd_mutex); + current_pid = pid; + } + + auto read_pipe = [](int fd, ui::terminal_stream& stream) { + char buffer[128]; + ssize_t bytes_read; + while ((bytes_read = read(fd, buffer, sizeof(buffer) - 1)) > 0) { + buffer[bytes_read] = '\0'; + stream << buffer; + stream.flush(); + } + }; + + std::thread stdout_thread(read_pipe, stdout_pipe[0], std::ref(ui::cout)); + std::thread stderr_thread(read_pipe, stderr_pipe[0], std::ref(ui::cerr)); + + stdout_thread.join(); + stderr_thread.join(); + + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + int status; + waitpid(pid, &status, 0); + + { + std::lock_guard lock(global_state.cmd_mutex); + current_pid = -1; + } + + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + if (exit_code != 0) { + ui::cerr << "Command failed with exit code: " << exit_code << ui::endl; + return false; + } + } else { + ui::cerr << "Command terminated abnormally" << ui::endl; + return false; + } + + return true; + } + + void process_next_command() { + global_state.finished_flag = false; + global_state.running = true; + global_state.success_flag = true; + + global_state.future = std::async(std::launch::async, []() -> bool { + while (!global_state.cancel_flag && !global_state.command_queue.empty()) { + std::string command; + { + std::lock_guard lock(global_state.cmd_mutex); + if (global_state.command_queue.empty()) break; + command = global_state.command_queue.front(); + global_state.command_queue.pop(); + } + + ui::good << "$ " << command << ui::endl; + + if (!run_single_command(command)) { + global_state.success_flag = false; + } + } + + global_state.running = false; + global_state.finished_flag = true; + return global_state.success_flag; + }); + } + + void execute(const std::vector& commands) { + std::lock_guard lock(global_state.cmd_mutex); + if (global_state.running) { + throw std::runtime_error("Cannot schedule commands while another group is running."); + } + + global_state.command_queue = std::queue(std::deque(commands.begin(), commands.end())); + global_state.cancel_flag = false; + global_state.success_flag = true; + global_state.finished_flag = false; + process_next_command(); + } + + void cancel() { + std::lock_guard lock(global_state.cmd_mutex); + if (!global_state.running) { + throw std::runtime_error("Cannot cancel a command that is not running."); + } + global_state.cancel_flag = true; + if (current_pid > 0) { + kill(current_pid, SIGTERM); + } + } + + bool is_running() { + return global_state.running; + } + + bool success() { + return global_state.success_flag; + } + + bool finished() { + if (global_state.finished_flag) { + global_state.finished_flag = false; + return true; + } + return false; + } + +} // namespace process +} // namespace platform diff --git a/engine/platform/platform.hpp b/engine/platform/platform.hpp new file mode 100644 index 0000000..18f1b71 --- /dev/null +++ b/engine/platform/platform.hpp @@ -0,0 +1,41 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#pragma once + +// Platform detection +#if defined(_WIN32) || defined(_WIN64) + #define GCTRL_PLATFORM_WINDOWS 1 + #define GCTRL_PLATFORM_LINUX 0 +#elif defined(__linux__) + #define GCTRL_PLATFORM_WINDOWS 0 + #define GCTRL_PLATFORM_LINUX 1 +#else + #error "Unsupported platform" +#endif + +// Platform-specific includes +#if GCTRL_PLATFORM_WINDOWS + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include +#endif diff --git a/engine/platform/process.hpp b/engine/platform/process.hpp new file mode 100644 index 0000000..4f1319a --- /dev/null +++ b/engine/platform/process.hpp @@ -0,0 +1,74 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace platform { +namespace process { + + // Output stream callback type + using stream_callback = std::function; + + // Process execution state + struct state { + std::atomic running{false}; + std::atomic cancel_flag{false}; + std::atomic success_flag{false}; + std::atomic finished_flag{false}; + std::future future; + std::mutex cmd_mutex; + std::queue command_queue; + }; + + // Get the global process state + state& get_state(); + + // Log the last platform-specific error + void log_last_error(const std::string& context); + + // Execute the next command in the queue + void process_next_command(); + + // Execute a list of commands sequentially + void execute(const std::vector& commands); + + // Cancel the currently running process + void cancel(); + + // Check if a process is currently running + bool is_running(); + + // Check if the last command succeeded + bool success(); + + // Check if the command queue finished processing + bool finished(); + +} // namespace process +} // namespace platform diff --git a/engine/platform/windows/dpi.cpp b/engine/platform/windows/dpi.cpp new file mode 100644 index 0000000..bf6556d --- /dev/null +++ b/engine/platform/windows/dpi.cpp @@ -0,0 +1,58 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/dpi.hpp" +#include +#include +#include +#include + +#pragma comment(lib, "Shcore.lib") + +namespace platform { +namespace dpi { + + float get_scaling_factor(SDL_Window* window) { + SDL_SysWMinfo wm_info; + SDL_VERSION(&wm_info.version); + if (SDL_GetWindowWMInfo(window, &wm_info)) { + HWND hwnd = wm_info.info.win.window; + + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + DEVICE_SCALE_FACTOR scale_factor; + if (SUCCEEDED(GetScaleFactorForMonitor(monitor, &scale_factor))) { + return static_cast(scale_factor) / 100.0f; + } + else { + // Fall back to DPI-based scaling + UINT dpi = GetDpiForWindow(hwnd); + return dpi / 96.0f; + } + } + return 1.0f; + } + + void set_dpi_awareness() { + SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + } + +} // namespace dpi +} // namespace platform diff --git a/engine/platform/windows/font.cpp b/engine/platform/windows/font.cpp new file mode 100644 index 0000000..631fd70 --- /dev/null +++ b/engine/platform/windows/font.cpp @@ -0,0 +1,59 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/font.hpp" +#include + +namespace platform { +namespace font { + + std::string get_system_ui_font() { + return "C:\\Windows\\Fonts\\segoeui.ttf"; + } + + std::string get_system_monospace_font() { + return "C:\\Windows\\Fonts\\consola.ttf"; + } + + std::optional> load_font_resource(resource_id id) { + HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(id), RT_FONT); + if (!hRes) return std::nullopt; + + HGLOBAL hMem = LoadResource(NULL, hRes); + if (!hMem) return std::nullopt; + + void* pFontData = LockResource(hMem); + DWORD font_size = SizeofResource(NULL, hRes); + if (!pFontData || font_size == 0) return std::nullopt; + + std::vector data(font_size); + memcpy(data.data(), pFontData, font_size); + return data; + } + + std::vector get_font_search_paths() { + return { + "C:\\Windows\\Fonts" + }; + } + +} // namespace font +} // namespace platform diff --git a/engine/platform/windows/process.cpp b/engine/platform/windows/process.cpp new file mode 100644 index 0000000..a9756d5 --- /dev/null +++ b/engine/platform/windows/process.cpp @@ -0,0 +1,213 @@ +// +// General Controls Engine +// +// Copyright (C) 2024 Daher Alfawares +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Contact Information: www.gctrl.org +// + +#include "platform/process.hpp" +#include "ui/ui.hpp" +#include +#include + +namespace platform { +namespace process { + + static state global_state; + static HANDLE process_handle = nullptr; + + state& get_state() { + return global_state; + } + + void log_last_error(const std::string& context) { + DWORD error_code = GetLastError(); + LPSTR error_msg = nullptr; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error_msg, + 0, + nullptr); + + ui::cerr << context << ". Error " << error_code << ": " << (error_msg ? error_msg : "Unknown error") << ui::endl; + + if (error_msg) { + LocalFree(error_msg); + } + } + + void process_next_command() { + global_state.finished_flag = false; + if (global_state.cancel_flag || global_state.command_queue.empty()) { + global_state.running = false; + global_state.finished_flag = true; + return; + } + + std::string command = global_state.command_queue.front(); + global_state.command_queue.pop(); + global_state.running = true; + global_state.success_flag = false; + + ui::good << "$ " << command << ui::endl; + + global_state.future = std::async(std::launch::async, [command]() -> bool { + HANDLE hStdOutRead, hStdOutWrite; + HANDLE hStdErrRead, hStdErrWrite; + STARTUPINFOA si = { sizeof(STARTUPINFOA) }; + PROCESS_INFORMATION pi = {}; + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE }; + + auto cleanup = [&]() { + CloseHandle(hStdOutRead); + CloseHandle(hStdErrRead); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + std::lock_guard lock(global_state.cmd_mutex); + process_handle = nullptr; + global_state.running = false; + }; + + try { + if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0) || + !CreatePipe(&hStdErrRead, &hStdErrWrite, &sa, 0)) { + log_last_error("Failed to create pipes for command output"); + cleanup(); + return false; + } + + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.hStdOutput = hStdOutWrite; + si.hStdError = hStdErrWrite; + si.wShowWindow = SW_HIDE; + + if (!CreateProcessA(nullptr, + const_cast(command.c_str()), + nullptr, + nullptr, + TRUE, + CREATE_NO_WINDOW, + nullptr, + nullptr, + &si, + &pi)) { + log_last_error("Failed to start command"); + cleanup(); + return false; + } + + CloseHandle(hStdOutWrite); + CloseHandle(hStdErrWrite); + + { + std::lock_guard lock(global_state.cmd_mutex); + process_handle = pi.hProcess; + } + + auto read_pipe = [](HANDLE pipe, ui::terminal_stream& stream) { + char buffer[128]; + DWORD bytesRead; + + while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytesRead, nullptr)) { + if (bytesRead > 0) { + buffer[bytesRead] = '\0'; + stream << buffer; + stream.flush(); + } + } + }; + + std::thread stdout_thread(read_pipe, hStdOutRead, std::ref(ui::cout)); + std::thread stderr_thread(read_pipe, hStdErrRead, std::ref(ui::cerr)); + + stdout_thread.join(); + stderr_thread.join(); + + DWORD exit_code = 0; + if (!GetExitCodeProcess(pi.hProcess, &exit_code)) { + log_last_error("Failed to get exit code"); + cleanup(); + return false; + } + + if (exit_code != 0) { + ui::cerr << "Command failed with exit code: " << exit_code << ui::endl; + cleanup(); + return false; + } + + global_state.success_flag = true; + } + catch (...) { + ui::cerr << "Unexpected error occurred during command execution" << ui::endl; + cleanup(); + throw; + } + + cleanup(); + process_next_command(); + return true; + }); + } + + void execute(const std::vector& commands) { + std::lock_guard lock(global_state.cmd_mutex); + if (global_state.running) { + throw std::runtime_error("Cannot schedule commands while another group is running."); + } + + global_state.command_queue = std::queue(std::deque(commands.begin(), commands.end())); + global_state.cancel_flag = false; + global_state.success_flag = true; + global_state.finished_flag = false; + process_next_command(); + } + + void cancel() { + std::lock_guard lock(global_state.cmd_mutex); + if (!global_state.running) { + throw std::runtime_error("Cannot cancel a command that is not running."); + } + global_state.cancel_flag = true; + if (process_handle) { + TerminateProcess(process_handle, 1); + } + } + + bool is_running() { + return global_state.running; + } + + bool success() { + return global_state.success_flag; + } + + bool finished() { + if (global_state.finished_flag) { + global_state.finished_flag = false; + return true; + } + return false; + } + +} // namespace process +} // namespace platform diff --git a/engine/ui/app.hpp b/engine/ui/app.hpp index f141a68..f74e8b9 100644 --- a/engine/ui/app.hpp +++ b/engine/ui/app.hpp @@ -21,27 +21,28 @@ #pragma once -#include +#include "platform/platform.hpp" +#include "platform/dpi.hpp" +#include "platform/font.hpp" #include "imgui.h" #include "imgui_impl_opengl3.h" #include "imgui_impl_sdl2.h" #include #include "ui/theme.hpp" -#include #include #include #include -#include +#include +#include "ui/font.hpp" +#include "ui/icons.hpp" + +#if GCTRL_PLATFORM_WINDOWS #include #pragma comment(lib, "Dwmapi.lib") #include #pragma comment(lib, "Shcore.lib") -#include #include "resources/resource.h" -#include -#include "ui/font.hpp" -#include "ui/icons.hpp" -#include "ui/dpi.hpp" +#endif namespace ui { @@ -52,7 +53,7 @@ namespace ui { bool fullscreen = false; app(const char* title) { - SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + platform::dpi::set_dpi_awareness(); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) return; @@ -74,21 +75,24 @@ namespace ui { ImPlot::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; +#if GCTRL_PLATFORM_WINDOWS io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; +#endif ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init("#version 130"); - float scaling_factor = scaling::get_scaling_factor_from_sdl(window); + float scaling_factor = platform::dpi::get_scaling_factor(window); io.FontGlobalScale = scaling_factor; const ImWchar glyph_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; - ui::font::load(ui::font::type::ui, "C:\\Windows\\Fonts\\segoeui.ttf", 16.0f); - ui::font::load_from_resource(ui::font::type::ui, IDR_FONT_AWESOME, 12.0f, true, glyph_ranges); - ui::font::load(ui::font::type::ui_large, "C:\\Windows\\Fonts\\segoeui.ttf", 28.0f); - ui::font::load_from_resource(ui::font::type::code, IDR_FONT_CASCADIA_CODE, 16.0f); - ui::font::load_from_resource(ui::font::type::code, IDR_FONT_AWESOME, 12.0f, true, glyph_ranges); - ui::font::load_from_resource(ui::font::type::console, IDR_FONT_CONSOLA, 16.0f); + std::string system_ui_font = platform::font::get_system_ui_font(); + ui::font::load(ui::font::type::ui, system_ui_font.c_str(), 16.0f); + ui::font::load_from_resource(ui::font::type::ui, platform::font::FONT_AWESOME, 12.0f, true, glyph_ranges); + ui::font::load(ui::font::type::ui_large, system_ui_font.c_str(), 28.0f); + ui::font::load_from_resource(ui::font::type::code, platform::font::CASCADIA_CODE, 16.0f); + ui::font::load_from_resource(ui::font::type::code, platform::font::FONT_AWESOME, 12.0f, true, glyph_ranges); + ui::font::load_from_resource(ui::font::type::console, platform::font::CONSOLA, 16.0f); io.Fonts->Build(); apply_theme(); @@ -132,7 +136,9 @@ namespace ui { void render() { ImGui::Render(); - glViewport(0, 0, 1280, 720); + int w, h; + SDL_GL_GetDrawableSize(window, &w, &h); + glViewport(0, 0, w, h); glClearColor(0.45f, 0.55f, 0.60f, 1.00f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/engine/ui/dpi.hpp b/engine/ui/dpi.hpp index a1098d4..1587225 100644 --- a/engine/ui/dpi.hpp +++ b/engine/ui/dpi.hpp @@ -21,35 +21,15 @@ #pragma once -#include +#include "platform/dpi.hpp" #include -#include namespace ui { namespace scaling { - // Get the Windows scaling factor for a specific HWND - float get_windows_scaling_factor(HWND hwnd) { - HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - DEVICE_SCALE_FACTOR scale_factor; - if (SUCCEEDED(GetScaleFactorForMonitor(monitor, &scale_factor))) { - return static_cast(scale_factor) / 100.0f; - } - else { - // Fall back to DPI-based scaling - UINT dpi = GetDpiForWindow(hwnd); - return dpi / 96.0f; - } - } - // Get the scaling factor using SDL's window information - float get_scaling_factor_from_sdl(SDL_Window* window) { - SDL_SysWMinfo wm_info; - SDL_VERSION(&wm_info.version); - if (SDL_GetWindowWMInfo(window, &wm_info)) { - HWND hwnd = wm_info.info.win.window; - return get_windows_scaling_factor(hwnd); - } - return 1.0f; // Default to 1.0f if unable to retrieve + // This is now a thin wrapper around the platform abstraction + inline float get_scaling_factor_from_sdl(SDL_Window* window) { + return platform::dpi::get_scaling_factor(window); } } // namespace scaling } // namespace ui diff --git a/engine/ui/font.hpp b/engine/ui/font.hpp index 96e0407..291a618 100644 --- a/engine/ui/font.hpp +++ b/engine/ui/font.hpp @@ -21,12 +21,11 @@ #pragma once +#include "platform/platform.hpp" +#include "platform/font.hpp" #include #include #include "imgui.h" -#include -#include -#include namespace ui { namespace font { @@ -41,7 +40,9 @@ namespace ui { inline std::unordered_map fonts; - void load(type font_type, const char* file_name, float font_size_pixels = 16.0f, bool merge = false, const ImWchar* glyph_ranges = nullptr) { + inline void load(type font_type, const char* file_name, float font_size_pixels = 16.0f, bool merge = false, const ImWchar* glyph_ranges = nullptr) { + if (!file_name || file_name[0] == '\0') return; + ImGuiIO& io = ImGui::GetIO(); ImFontConfig font_config; font_config.OversampleH = 2; @@ -68,21 +69,13 @@ namespace ui { } } - ImFont* load_from_resource(type font_type, int resource_id, float font_size_pixels = 16.0f, bool merge = false, const ImWchar* glyph_ranges = nullptr) { - - HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(resource_id), RT_FONT); - if (!hRes) return nullptr; - - HGLOBAL hMem = LoadResource(NULL, hRes); - if (!hMem) return nullptr; - - void* pFontData = LockResource(hMem); - DWORD font_size = SizeofResource(NULL, hRes); - if (!pFontData || font_size == 0) return nullptr; + inline ImFont* load_from_resource(type font_type, int resource_id, float font_size_pixels = 16.0f, bool merge = false, const ImWchar* glyph_ranges = nullptr) { + auto font_data = platform::font::load_font_resource(static_cast(resource_id)); + if (!font_data.has_value() || font_data->empty()) return nullptr; - void* font_data_copy = malloc(font_size); + void* font_data_copy = malloc(font_data->size()); if (!font_data_copy) return nullptr; - memcpy(font_data_copy, pFontData, font_size); + memcpy(font_data_copy, font_data->data(), font_data->size()); ImGuiIO& io = ImGui::GetIO(); ImFontConfig font_config; @@ -103,7 +96,7 @@ namespace ui { const ImWchar* ranges_to_use = glyph_ranges ? glyph_ranges : default_glyph_ranges; - ImFont* font = io.Fonts->AddFontFromMemoryTTF(font_data_copy, font_size, font_size_pixels, &font_config, ranges_to_use); + ImFont* font = io.Fonts->AddFontFromMemoryTTF(font_data_copy, static_cast(font_data->size()), font_size_pixels, &font_config, ranges_to_use); if (font && !merge) { fonts[font_type] = font; } @@ -111,13 +104,13 @@ namespace ui { return font; } - void push(type font_type) { + inline void push(type font_type) { if (fonts.find(font_type) != fonts.end()) { ImGui::PushFont(fonts[font_type]); } } - void pop() { + inline void pop() { ImGui::PopFont(); } diff --git a/engine/ui/icons.hpp b/engine/ui/icons.hpp index 4162a7e..590358d 100644 --- a/engine/ui/icons.hpp +++ b/engine/ui/icons.hpp @@ -26,26 +26,26 @@ namespace ui { namespace icon { - typedef const char* type; + using type = const char*; - type save = ICON_FA_FLOPPY_DISK; - type edit = ICON_FA_PENCIL " Edit"; - type insert = ICON_FA_ARROW_RIGHT " Insert"; - type trash = ICON_FA_TRASH; - type play = ICON_FA_PLAY; - type stop = ICON_FA_STOP; - type build = ICON_FA_ARROW_DOWN_SHORT_WIDE; - type rebuild = ICON_FA_ROTATE_RIGHT; + inline constexpr type save = ICON_FA_FLOPPY_DISK; + inline constexpr type edit = ICON_FA_PENCIL " Edit"; + inline constexpr type insert = ICON_FA_ARROW_RIGHT " Insert"; + inline constexpr type trash = ICON_FA_TRASH; + inline constexpr type play = ICON_FA_PLAY; + inline constexpr type stop = ICON_FA_STOP; + inline constexpr type build = ICON_FA_ARROW_DOWN_SHORT_WIDE; + inline constexpr type rebuild = ICON_FA_ROTATE_RIGHT; - type machine = ICON_FA_SERVER; - type controller = ICON_FA_GEAR; - type element = ICON_FA_MICROCHIP; - type function = ICON_FA_CODE; - type driver = ICON_FA_GEARS; - type plug = ICON_FA_SQUARE_PLUS; - type socket = ICON_FA_SQUARE_MINUS; - type signal = ICON_FA_BOLT; + inline constexpr type machine = ICON_FA_SERVER; + inline constexpr type controller = ICON_FA_GEAR; + inline constexpr type element = ICON_FA_MICROCHIP; + inline constexpr type function = ICON_FA_CODE; + inline constexpr type driver = ICON_FA_GEARS; + inline constexpr type plug = ICON_FA_SQUARE_PLUS; + inline constexpr type socket = ICON_FA_SQUARE_MINUS; + inline constexpr type signal = ICON_FA_BOLT; - type square = ICON_FA_SQUARE; + inline constexpr type square = ICON_FA_SQUARE; } } \ No newline at end of file diff --git a/engine/ui/terminal.hpp b/engine/ui/terminal.hpp index fa94a4f..68038dc 100644 --- a/engine/ui/terminal.hpp +++ b/engine/ui/terminal.hpp @@ -21,26 +21,21 @@ #pragma once +#include "platform/platform.hpp" +#include "platform/process.hpp" #include #include #include #include #include #include -#include "ui/ui.hpp" +#include "ui/font.hpp" #include -#include -#include #include #include #include -#include #include -#include -#include #include -#include -#include namespace ui { @@ -99,27 +94,27 @@ namespace ui { } static void render() { - ui::begin("Terminal"); + ImGui::Begin("Terminal", nullptr, ImGuiWindowFlags_None); - if (ui::button("Clear")) { + if (ImGui::Button("Clear")) { terminal_log.clear(); } - ui::same_line(); - if (ui::button("Copy")) { + ImGui::SameLine(); + if (ImGui::Button("Copy")) { ImGui::LogToClipboard(); for (const auto& [line, stream_color] : terminal_log) { - ImGui::LogText(line.c_str()); + ImGui::LogText("%s", line.c_str()); } ImGui::LogFinish(); } - ui::separator(); + ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(30, 30, 30, 255)); ImGui::BeginChild("terminal_output", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar); - ui::font::push(ui::font::type::console); + font::push(font::type::console); for (const auto& [line, stream_color] : terminal_log) { ImVec4 color_vec = colors::to_imvec4(stream_color); ImGui::PushStyleColor(ImGuiCol_Text, color_vec); @@ -130,13 +125,13 @@ namespace ui { if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { ImGui::SetScrollHereY(1.0f); } - ui::font::pop(); + font::pop(); ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::PopStyleVar(); - ui::end(); + ImGui::End(); } private: @@ -170,192 +165,26 @@ namespace ui { namespace terminal { - namespace details { - std::atomic running = false; - std::atomic cancel_flag = false; - std::atomic success_flag = false; - std::atomic finished_flag = false; - std::future future; - std::mutex cmd_mutex; - std::queue command_queue; - HANDLE process_handle = nullptr; - - void log_last_error(const std::string& context) { - DWORD error_code = GetLastError(); - LPSTR error_msg = nullptr; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&error_msg, - 0, - nullptr); - - ui::cerr << context << ". Error " << error_code << ": " << (error_msg ? error_msg : "Unknown error") << ui::endl; - - if (error_msg) { - LocalFree(error_msg); - } - } - - void process_next_command() { - finished_flag = false; - if (cancel_flag || command_queue.empty()) { - running = false; - finished_flag = true; - return; - } - - std::string command = command_queue.front(); - command_queue.pop(); - running = true; - success_flag = false; - - ui::good << "$ " << command << ui::endl; - - future = std::async(std::launch::async, [command]() -> bool { - HANDLE hStdOutRead, hStdOutWrite; - HANDLE hStdErrRead, hStdErrWrite; - STARTUPINFOA si = { sizeof(STARTUPINFOA) }; - PROCESS_INFORMATION pi = {}; - SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE }; - - auto cleanup = [&]() { - CloseHandle(hStdOutRead); - CloseHandle(hStdErrRead); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - std::lock_guard lock(cmd_mutex); - process_handle = nullptr; - running = false; - }; - - try { - if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0) || - !CreatePipe(&hStdErrRead, &hStdErrWrite, &sa, 0)) { - log_last_error("Failed to create pipes for command output"); - cleanup(); - return false; - } - - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.hStdOutput = hStdOutWrite; - si.hStdError = hStdErrWrite; - si.wShowWindow = SW_HIDE; - - if (!CreateProcessA(nullptr, - const_cast(command.c_str()), - nullptr, - nullptr, - TRUE, - CREATE_NO_WINDOW, - nullptr, - nullptr, - &si, - &pi)) { - log_last_error("Failed to start command"); - cleanup(); - return false; - } - - CloseHandle(hStdOutWrite); - CloseHandle(hStdErrWrite); - - { - std::lock_guard lock(cmd_mutex); - process_handle = pi.hProcess; - } - - auto read_pipe = [](HANDLE pipe, ui::terminal_stream& stream) { - char buffer[128]; - DWORD bytesRead; - - while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytesRead, nullptr)) { - if (bytesRead > 0) { - buffer[bytesRead] = '\0'; - stream << buffer; - stream.flush(); - } - } - }; - - std::thread stdout_thread(read_pipe, hStdOutRead, std::ref(ui::cout)); - std::thread stderr_thread(read_pipe, hStdErrRead, std::ref(ui::cerr)); - - stdout_thread.join(); - stderr_thread.join(); - - DWORD exit_code = 0; - if (!GetExitCodeProcess(pi.hProcess, &exit_code)) { - log_last_error("Failed to get exit code"); - cleanup(); - return false; - } - - if (exit_code != 0) { - ui::cerr << "Command failed with exit code: " << exit_code << ui::endl; - cleanup(); - return false; - } - - success_flag = true; - } - catch (...) { - ui::cerr << "Unexpected error occurred during command execution" << ui::endl; - cleanup(); - throw; - } - - cleanup(); - process_next_command(); - return true; - }); - } - - } // namespace details - - void execute(const std::vector& commands) { - std::lock_guard lock(details::cmd_mutex); - if (details::running) { - throw std::runtime_error("Cannot schedule commands while another group is running."); - } - - details::command_queue = std::queue(std::deque(commands.begin(), commands.end())); - details::cancel_flag = false; - details::success_flag = true; - details::finished_flag = false; - details::process_next_command(); + // Thin wrappers around platform::process functions + inline void execute(const std::vector& commands) { + platform::process::execute(commands); } - void cancel() { - std::lock_guard lock(details::cmd_mutex); - if (!details::running) { - throw std::runtime_error("Cannot cancel a command that is not running."); - } - details::cancel_flag = true; - if (details::process_handle) { - TerminateProcess(details::process_handle, 1); - } + inline void cancel() { + platform::process::cancel(); } - bool is_running() { - return details::running; + inline bool is_running() { + return platform::process::is_running(); } - bool success() { - return details::success_flag; + inline bool success() { + return platform::process::success(); } - bool finished() { - if (details::finished_flag) { - details::finished_flag = false; - return true; - } - return false; + inline bool finished() { + return platform::process::finished(); } -} // namespace exec +} // namespace terminal diff --git a/engine/ui/ui.hpp b/engine/ui/ui.hpp index e7659b5..9ea5691 100644 --- a/engine/ui/ui.hpp +++ b/engine/ui/ui.hpp @@ -38,103 +38,103 @@ namespace ui { typedef ImGuiCol_ color_t; namespace colors { - ui::color_t text = ImGuiCol_Text; - ui::color_t text_disabled = ImGuiCol_TextDisabled; - ui::color_t window_bg = ImGuiCol_WindowBg; - ui::color_t child_bg = ImGuiCol_ChildBg; - ui::color_t popup_bg = ImGuiCol_PopupBg; - ui::color_t border = ImGuiCol_Border; - ui::color_t border_shadow = ImGuiCol_BorderShadow; - ui::color_t frame_bg = ImGuiCol_FrameBg; - ui::color_t frame_bg_hovered = ImGuiCol_FrameBgHovered; - ui::color_t frame_bg_active = ImGuiCol_FrameBgActive; - ui::color_t title_bg = ImGuiCol_TitleBg; - ui::color_t title_bg_active = ImGuiCol_TitleBgActive; - ui::color_t title_bg_collapsed = ImGuiCol_TitleBgCollapsed; - ui::color_t menu_bar_bg = ImGuiCol_MenuBarBg; - ui::color_t scrollbar_bg = ImGuiCol_ScrollbarBg; - ui::color_t scrollbar_grab = ImGuiCol_ScrollbarGrab; - ui::color_t scrollbar_grab_hovered = ImGuiCol_ScrollbarGrabHovered; - ui::color_t scrollbar_grab_active = ImGuiCol_ScrollbarGrabActive; - ui::color_t check_mark = ImGuiCol_CheckMark; - ui::color_t slider_grab = ImGuiCol_SliderGrab; - ui::color_t slider_grab_active = ImGuiCol_SliderGrabActive; - ui::color_t button = ImGuiCol_Button; - ui::color_t button_hovered = ImGuiCol_ButtonHovered; - ui::color_t button_active = ImGuiCol_ButtonActive; - ui::color_t header = ImGuiCol_Header; - ui::color_t header_hovered = ImGuiCol_HeaderHovered; - ui::color_t header_active = ImGuiCol_HeaderActive; - ui::color_t separator = ImGuiCol_Separator; - ui::color_t separator_hovered = ImGuiCol_SeparatorHovered; - ui::color_t separator_active = ImGuiCol_SeparatorActive; - ui::color_t resize_grip = ImGuiCol_ResizeGrip; - ui::color_t resize_grip_hovered = ImGuiCol_ResizeGripHovered; - ui::color_t resize_grip_active = ImGuiCol_ResizeGripActive; - ui::color_t tab = ImGuiCol_Tab; - ui::color_t tab_hovered = ImGuiCol_TabHovered; - ui::color_t tab_selected = ImGuiCol_TabSelected; - ui::color_t tab_selected_overline = ImGuiCol_TabSelectedOverline; - ui::color_t tab_dimmed = ImGuiCol_TabDimmed; - ui::color_t tab_dimmed_selected = ImGuiCol_TabDimmedSelected; - ui::color_t tab_dimmed_selected_overline = ImGuiCol_TabDimmedSelectedOverline; - ui::color_t docking_preview = ImGuiCol_DockingPreview; - ui::color_t docking_empty_bg = ImGuiCol_DockingEmptyBg; - ui::color_t plot_lines = ImGuiCol_PlotLines; - ui::color_t plot_lines_hovered = ImGuiCol_PlotLinesHovered; - ui::color_t plot_histogram = ImGuiCol_PlotHistogram; - ui::color_t plot_histogram_hovered = ImGuiCol_PlotHistogramHovered; - ui::color_t table_header_bg = ImGuiCol_TableHeaderBg; - ui::color_t table_border_strong = ImGuiCol_TableBorderStrong; - ui::color_t table_border_light = ImGuiCol_TableBorderLight; - ui::color_t table_row_bg = ImGuiCol_TableRowBg; - ui::color_t table_row_bg_alt = ImGuiCol_TableRowBgAlt; - ui::color_t text_link = ImGuiCol_TextLink; - ui::color_t text_selected_bg = ImGuiCol_TextSelectedBg; - ui::color_t drag_drop_target = ImGuiCol_DragDropTarget; - ui::color_t nav_highlight = ImGuiCol_NavHighlight; - ui::color_t nav_windowing_highlight = ImGuiCol_NavWindowingHighlight; - ui::color_t nav_windowing_dim_bg = ImGuiCol_NavWindowingDimBg; - ui::color_t modal_window_dim_bg = ImGuiCol_ModalWindowDimBg; + inline constexpr ui::color_t text = ImGuiCol_Text; + inline constexpr ui::color_t text_disabled = ImGuiCol_TextDisabled; + inline constexpr ui::color_t window_bg = ImGuiCol_WindowBg; + inline constexpr ui::color_t child_bg = ImGuiCol_ChildBg; + inline constexpr ui::color_t popup_bg = ImGuiCol_PopupBg; + inline constexpr ui::color_t border = ImGuiCol_Border; + inline constexpr ui::color_t border_shadow = ImGuiCol_BorderShadow; + inline constexpr ui::color_t frame_bg = ImGuiCol_FrameBg; + inline constexpr ui::color_t frame_bg_hovered = ImGuiCol_FrameBgHovered; + inline constexpr ui::color_t frame_bg_active = ImGuiCol_FrameBgActive; + inline constexpr ui::color_t title_bg = ImGuiCol_TitleBg; + inline constexpr ui::color_t title_bg_active = ImGuiCol_TitleBgActive; + inline constexpr ui::color_t title_bg_collapsed = ImGuiCol_TitleBgCollapsed; + inline constexpr ui::color_t menu_bar_bg = ImGuiCol_MenuBarBg; + inline constexpr ui::color_t scrollbar_bg = ImGuiCol_ScrollbarBg; + inline constexpr ui::color_t scrollbar_grab = ImGuiCol_ScrollbarGrab; + inline constexpr ui::color_t scrollbar_grab_hovered = ImGuiCol_ScrollbarGrabHovered; + inline constexpr ui::color_t scrollbar_grab_active = ImGuiCol_ScrollbarGrabActive; + inline constexpr ui::color_t check_mark = ImGuiCol_CheckMark; + inline constexpr ui::color_t slider_grab = ImGuiCol_SliderGrab; + inline constexpr ui::color_t slider_grab_active = ImGuiCol_SliderGrabActive; + inline constexpr ui::color_t button = ImGuiCol_Button; + inline constexpr ui::color_t button_hovered = ImGuiCol_ButtonHovered; + inline constexpr ui::color_t button_active = ImGuiCol_ButtonActive; + inline constexpr ui::color_t header = ImGuiCol_Header; + inline constexpr ui::color_t header_hovered = ImGuiCol_HeaderHovered; + inline constexpr ui::color_t header_active = ImGuiCol_HeaderActive; + inline constexpr ui::color_t separator = ImGuiCol_Separator; + inline constexpr ui::color_t separator_hovered = ImGuiCol_SeparatorHovered; + inline constexpr ui::color_t separator_active = ImGuiCol_SeparatorActive; + inline constexpr ui::color_t resize_grip = ImGuiCol_ResizeGrip; + inline constexpr ui::color_t resize_grip_hovered = ImGuiCol_ResizeGripHovered; + inline constexpr ui::color_t resize_grip_active = ImGuiCol_ResizeGripActive; + inline constexpr ui::color_t tab = ImGuiCol_Tab; + inline constexpr ui::color_t tab_hovered = ImGuiCol_TabHovered; + inline constexpr ui::color_t tab_selected = ImGuiCol_TabSelected; + inline constexpr ui::color_t tab_selected_overline = ImGuiCol_TabSelectedOverline; + inline constexpr ui::color_t tab_dimmed = ImGuiCol_TabDimmed; + inline constexpr ui::color_t tab_dimmed_selected = ImGuiCol_TabDimmedSelected; + inline constexpr ui::color_t tab_dimmed_selected_overline = ImGuiCol_TabDimmedSelectedOverline; + inline constexpr ui::color_t docking_preview = ImGuiCol_DockingPreview; + inline constexpr ui::color_t docking_empty_bg = ImGuiCol_DockingEmptyBg; + inline constexpr ui::color_t plot_lines = ImGuiCol_PlotLines; + inline constexpr ui::color_t plot_lines_hovered = ImGuiCol_PlotLinesHovered; + inline constexpr ui::color_t plot_histogram = ImGuiCol_PlotHistogram; + inline constexpr ui::color_t plot_histogram_hovered = ImGuiCol_PlotHistogramHovered; + inline constexpr ui::color_t table_header_bg = ImGuiCol_TableHeaderBg; + inline constexpr ui::color_t table_border_strong = ImGuiCol_TableBorderStrong; + inline constexpr ui::color_t table_border_light = ImGuiCol_TableBorderLight; + inline constexpr ui::color_t table_row_bg = ImGuiCol_TableRowBg; + inline constexpr ui::color_t table_row_bg_alt = ImGuiCol_TableRowBgAlt; + inline constexpr ui::color_t text_link = ImGuiCol_TextLink; + inline constexpr ui::color_t text_selected_bg = ImGuiCol_TextSelectedBg; + inline constexpr ui::color_t drag_drop_target = ImGuiCol_DragDropTarget; + inline constexpr ui::color_t nav_highlight = ImGuiCol_NavHighlight; + inline constexpr ui::color_t nav_windowing_highlight = ImGuiCol_NavWindowingHighlight; + inline constexpr ui::color_t nav_windowing_dim_bg = ImGuiCol_NavWindowingDimBg; + inline constexpr ui::color_t modal_window_dim_bg = ImGuiCol_ModalWindowDimBg; } typedef ImGuiStyleVar var_t; namespace vars { - const var_t alpha = ImGuiStyleVar_Alpha; - const var_t disabled_alpha = ImGuiStyleVar_DisabledAlpha; - const var_t window_padding = ImGuiStyleVar_WindowPadding; - const var_t window_rounding = ImGuiStyleVar_WindowRounding; - const var_t window_border_size = ImGuiStyleVar_WindowBorderSize; - const var_t window_min_size = ImGuiStyleVar_WindowMinSize; - const var_t window_title_align = ImGuiStyleVar_WindowTitleAlign; - const var_t child_rounding = ImGuiStyleVar_ChildRounding; - const var_t child_border_size = ImGuiStyleVar_ChildBorderSize; - const var_t popup_rounding = ImGuiStyleVar_PopupRounding; - const var_t popup_border_size = ImGuiStyleVar_PopupBorderSize; - const var_t frame_padding = ImGuiStyleVar_FramePadding; - const var_t frame_rounding = ImGuiStyleVar_FrameRounding; - const var_t frame_border_size = ImGuiStyleVar_FrameBorderSize; - const var_t item_spacing = ImGuiStyleVar_ItemSpacing; - const var_t item_inner_spacing = ImGuiStyleVar_ItemInnerSpacing; - const var_t indent_spacing = ImGuiStyleVar_IndentSpacing; - const var_t cell_padding = ImGuiStyleVar_CellPadding; - const var_t scrollbar_size = ImGuiStyleVar_ScrollbarSize; - const var_t scrollbar_rounding = ImGuiStyleVar_ScrollbarRounding; - const var_t grab_min_size = ImGuiStyleVar_GrabMinSize; - const var_t grab_rounding = ImGuiStyleVar_GrabRounding; - const var_t tab_rounding = ImGuiStyleVar_TabRounding; - const var_t tab_border_size = ImGuiStyleVar_TabBorderSize; - const var_t tab_bar_border_size = ImGuiStyleVar_TabBarBorderSize; - const var_t tab_bar_overline_size = ImGuiStyleVar_TabBarOverlineSize; - const var_t table_angled_headers_angle = ImGuiStyleVar_TableAngledHeadersAngle; - const var_t table_angled_headers_text_align = ImGuiStyleVar_TableAngledHeadersTextAlign; - const var_t button_text_align = ImGuiStyleVar_ButtonTextAlign; - const var_t selectable_text_align = ImGuiStyleVar_SelectableTextAlign; - const var_t separator_text_border_size = ImGuiStyleVar_SeparatorTextBorderSize; - const var_t separator_text_align = ImGuiStyleVar_SeparatorTextAlign; - const var_t separator_text_padding = ImGuiStyleVar_SeparatorTextPadding; - const var_t docking_separator_size = ImGuiStyleVar_DockingSeparatorSize; + inline constexpr var_t alpha = ImGuiStyleVar_Alpha; + inline constexpr var_t disabled_alpha = ImGuiStyleVar_DisabledAlpha; + inline constexpr var_t window_padding = ImGuiStyleVar_WindowPadding; + inline constexpr var_t window_rounding = ImGuiStyleVar_WindowRounding; + inline constexpr var_t window_border_size = ImGuiStyleVar_WindowBorderSize; + inline constexpr var_t window_min_size = ImGuiStyleVar_WindowMinSize; + inline constexpr var_t window_title_align = ImGuiStyleVar_WindowTitleAlign; + inline constexpr var_t child_rounding = ImGuiStyleVar_ChildRounding; + inline constexpr var_t child_border_size = ImGuiStyleVar_ChildBorderSize; + inline constexpr var_t popup_rounding = ImGuiStyleVar_PopupRounding; + inline constexpr var_t popup_border_size = ImGuiStyleVar_PopupBorderSize; + inline constexpr var_t frame_padding = ImGuiStyleVar_FramePadding; + inline constexpr var_t frame_rounding = ImGuiStyleVar_FrameRounding; + inline constexpr var_t frame_border_size = ImGuiStyleVar_FrameBorderSize; + inline constexpr var_t item_spacing = ImGuiStyleVar_ItemSpacing; + inline constexpr var_t item_inner_spacing = ImGuiStyleVar_ItemInnerSpacing; + inline constexpr var_t indent_spacing = ImGuiStyleVar_IndentSpacing; + inline constexpr var_t cell_padding = ImGuiStyleVar_CellPadding; + inline constexpr var_t scrollbar_size = ImGuiStyleVar_ScrollbarSize; + inline constexpr var_t scrollbar_rounding = ImGuiStyleVar_ScrollbarRounding; + inline constexpr var_t grab_min_size = ImGuiStyleVar_GrabMinSize; + inline constexpr var_t grab_rounding = ImGuiStyleVar_GrabRounding; + inline constexpr var_t tab_rounding = ImGuiStyleVar_TabRounding; + inline constexpr var_t tab_border_size = ImGuiStyleVar_TabBorderSize; + inline constexpr var_t tab_bar_border_size = ImGuiStyleVar_TabBarBorderSize; + inline constexpr var_t tab_bar_overline_size = ImGuiStyleVar_TabBarOverlineSize; + inline constexpr var_t table_angled_headers_angle = ImGuiStyleVar_TableAngledHeadersAngle; + inline constexpr var_t table_angled_headers_text_align = ImGuiStyleVar_TableAngledHeadersTextAlign; + inline constexpr var_t button_text_align = ImGuiStyleVar_ButtonTextAlign; + inline constexpr var_t selectable_text_align = ImGuiStyleVar_SelectableTextAlign; + inline constexpr var_t separator_text_border_size = ImGuiStyleVar_SeparatorTextBorderSize; + inline constexpr var_t separator_text_align = ImGuiStyleVar_SeparatorTextAlign; + inline constexpr var_t separator_text_padding = ImGuiStyleVar_SeparatorTextPadding; + inline constexpr var_t docking_separator_size = ImGuiStyleVar_DockingSeparatorSize; } enum class direction { ltr, rtl }; @@ -345,13 +345,12 @@ namespace ui { namespace ed = ax::NodeEditor; - enum type { - input = ed::PinKind::Input, - output = ed::PinKind::Output - }; + using type = ed::PinKind; + constexpr auto input = ed::PinKind::Input; + constexpr auto output = ed::PinKind::Output; inline void begin(uint64_t id, type pin_type) { - ed::BeginPin(id, static_cast(pin_type)); + ed::BeginPin(id, pin_type); } inline void end() { @@ -634,12 +633,12 @@ namespace ui { namespace ui { namespace plot { - GLuint frame_buffer = 0; - GLuint texture = 0; - float width = 512; - float height = 512; + inline GLuint frame_buffer = 0; + inline GLuint texture = 0; + inline float width = 512; + inline float height = 512; - void initialize() { + inline void initialize() { if (frame_buffer == 0) { glGenFramebuffers(1, &frame_buffer); glGenTextures(1, &texture); @@ -659,7 +658,7 @@ namespace ui { } } - void begin() { + inline void begin() { initialize(); glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); glViewport(0, 0, static_cast(width), static_cast(height)); @@ -667,7 +666,7 @@ namespace ui { glClear(GL_COLOR_BUFFER_BIT); } - void end() { + inline void end() { glBindFramebuffer(GL_FRAMEBUFFER, 0); ImGui::Image((void*)(intptr_t)texture, ImVec2(width, height)); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..6360e2c --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,48 @@ +# ImGui library +add_library(imgui STATIC + imgui/imgui.cpp + imgui/imgui_draw.cpp + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + imgui/backends/imgui_impl_sdl2.cpp + imgui/backends/imgui_impl_opengl3.cpp +) +target_include_directories(imgui PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/imgui + ${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends +) +target_link_libraries(imgui PUBLIC + SDL2::SDL2 + OpenGL::GL +) + +# ImPlot library +add_library(implot STATIC + implot/implot.cpp + implot/implot_items.cpp +) +target_include_directories(implot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/implot +) +target_link_libraries(implot PUBLIC imgui) + +# imgui-node-editor library +add_library(imgui_node_editor STATIC + imgui-node-editor/imgui_node_editor.cpp + imgui-node-editor/imgui_node_editor_api.cpp + imgui-node-editor/imgui_canvas.cpp + imgui-node-editor/crude_json.cpp +) +target_include_directories(imgui_node_editor PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/imgui-node-editor +) +target_link_libraries(imgui_node_editor PUBLIC imgui) + +# imgui-color-text-edit library +add_library(imgui_color_text_edit STATIC + imgui-color-text-edit/text_editor.cpp +) +target_include_directories(imgui_color_text_edit PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/imgui-color-text-edit +) +target_link_libraries(imgui_color_text_edit PUBLIC imgui) diff --git a/lib/imgui-file-dialog/imgui_file_dialog.hpp b/lib/imgui-file-dialog/imgui_file_dialog.hpp index a72c94e..54d3731 100644 --- a/lib/imgui-file-dialog/imgui_file_dialog.hpp +++ b/lib/imgui-file-dialog/imgui_file_dialog.hpp @@ -3,9 +3,19 @@ #include #include #include +#include #include #include +namespace { + inline void safe_strcpy(char* dest, size_t dest_size, const char* src) { + if (dest_size > 0) { + std::strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = '\0'; + } + } +} + namespace ui { enum class file_dialog_sort_order { @@ -70,7 +80,7 @@ namespace ui { } } catch (...) { - strcpy_s(file_dialog_error, "error: unable to read directory."); + safe_strcpy(file_dialog_error, sizeof(file_dialog_error), "error: unable to read directory."); } // Use available space intelligently for the files display @@ -112,7 +122,7 @@ namespace ui { // Copy selected file path to the buffer if a file is selected if (!current_file.empty()) { - std::string full_path = current_path + (current_path.back() == '\\' ? "" : "\\") + current_file; + std::string full_path = current_path + (current_path.back() == std::filesystem::path::preferred_separator ? "" : std::string(1, std::filesystem::path::preferred_separator)) + current_file; strncpy(buffer, full_path.c_str(), buffer_size - 1); buffer[buffer_size - 1] = '\0'; // Ensure null-termination } @@ -125,7 +135,7 @@ namespace ui { // Action buttons if (ImGui::Button("Open")) { if (current_file.empty()) { - strcpy_s(file_dialog_error, "error: you must select a file!"); + safe_strcpy(file_dialog_error, sizeof(file_dialog_error), "error: you must select a file!"); } else { file_dialog_open = false; @@ -155,7 +165,7 @@ namespace ui { // Copy selected file path to the buffer if a file is selected if (!current_folder.empty()) { - std::string full_path = current_path + (current_path.back() == '\\' ? "" : "\\") + current_folder; + std::string full_path = current_path + (current_path.back() == std::filesystem::path::preferred_separator ? "" : std::string(1, std::filesystem::path::preferred_separator)) + current_folder; strncpy(buffer, full_path.c_str(), buffer_size - 1); buffer[buffer_size - 1] = '\0'; // Ensure null-termination } @@ -169,13 +179,13 @@ namespace ui { if (ImGui::Button("Open")) { if (current_folder.empty()) { // Default to current path if no folder is selected - strcpy_s(buffer, buffer_size, current_path.c_str()); + safe_strcpy(buffer, buffer_size, current_path.c_str()); } else { - auto path = current_path + (current_path.back() == '\\' ? "" : "\\") + current_folder; - strcpy_s(buffer, buffer_size, path.c_str()); + auto path = current_path + (current_path.back() == std::filesystem::path::preferred_separator ? "" : std::string(1, std::filesystem::path::preferred_separator)) + current_folder; + safe_strcpy(buffer, buffer_size, path.c_str()); } - strcpy_s(file_dialog_error, ""); + safe_strcpy(file_dialog_error, sizeof(file_dialog_error), ""); file_dialog_open = false; return true; }