Line data Source code
1 : #include "gui/gui_manager.h"
2 : #include "gui/pedalboard/pedal_board.h"
3 : #include "gui/theme/theme.h"
4 : #include "gui/dialogs/file_dialog.h"
5 : #include "gui/commands/command.h"
6 : #include "gui/state/gui_graph_state.h"
7 : #include "preset_manager.h"
8 :
9 : #include "gui/gl_setup.h"
10 : #include <imgui.h>
11 : #include <imgui_impl_sdl2.h>
12 : #include <imgui_impl_opengl3.h>
13 : #include <iostream>
14 : #include <cstring>
15 : #include <cmath>
16 : #include <algorithm>
17 : #include <cstdio>
18 : #include <SDL2/SDL.h>
19 : #if defined(__APPLE__)
20 : # include <TargetConditionals.h>
21 : #endif
22 : #if defined(EMSCRIPTEN) || (defined(__APPLE__) && TARGET_OS_IOS)
23 : # define AMPLITRON_NO_DESKTOP_SHELL 1
24 : #endif
25 : #ifdef __EMSCRIPTEN__
26 : # include <emscripten.h>
27 : #endif
28 :
29 : #pragma GCC diagnostic push
30 : #if defined(__GNUC__) && !defined(__clang__)
31 : #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
32 : #endif
33 : #define NANOSVG_IMPLEMENTATION
34 : #include "nanosvg.h"
35 : #define NANOSVGRAST_IMPLEMENTATION
36 : #include "nanosvgrast.h"
37 : #pragma GCC diagnostic pop
38 :
39 : namespace Amplitron {
40 :
41 20 : GuiManager::GuiManager(AudioEngine& engine)
42 15 : : engine_(engine),
43 15 : command_history_(),
44 5 : tuner_pedal_(std::make_shared<TunerPedal>()),
45 10 : gui_presets_(engine, command_history_),
46 20 : gui_midi_(midi_manager_)
47 5 : {
48 15 : pedal_board_ = std::make_unique<PedalBoard>(engine_, command_history_, &gui_midi_);
49 15 : gui_presets_.set_pedal_board(pedal_board_.get());
50 15 : gui_presets_.set_midi_manager(&midi_manager_);
51 15 : }
52 :
53 20 : GuiManager::~GuiManager() {
54 15 : shutdown();
55 25 : }
56 :
57 0 : bool GuiManager::initialize(int width, int height) {
58 0 : window_width_ = width;
59 0 : window_height_ = height;
60 :
61 0 : if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
62 0 : std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
63 0 : return false;
64 : }
65 :
66 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
67 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, GLSetup::GL_CONTEXT_PROFILE);
68 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, GLSetup::GL_MAJOR);
69 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, GLSetup::GL_MINOR);
70 0 : SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
71 0 : SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
72 0 : SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
73 :
74 0 : window_ = SDL_CreateWindow(
75 : Theme::WINDOW_TITLE,
76 : SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
77 0 : window_width_, window_height_,
78 : SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI
79 : );
80 :
81 0 : if (!window_) {
82 0 : std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
83 0 : return false;
84 : }
85 :
86 0 : gl_context_ = SDL_GL_CreateContext(window_);
87 0 : if (!gl_context_) {
88 0 : std::cerr << "SDL_GL_CreateContext failed: " << SDL_GetError() << std::endl;
89 0 : return false;
90 : }
91 0 : SDL_GL_MakeCurrent(window_, gl_context_);
92 0 : SDL_GL_SetSwapInterval(1); // vsync
93 :
94 : // Initialize ImGui
95 0 : IMGUI_CHECKVERSION();
96 0 : ImGui::CreateContext();
97 0 : ImGuiIO& io = ImGui::GetIO();
98 0 : io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
99 :
100 : // Amplitron design system
101 0 : ImGui::StyleColorsDark();
102 0 : Theme::ApplyStyle();
103 :
104 : // --- DPI scaling and font loading ---
105 0 : float dpi_scale = 1.0f;
106 0 : {
107 0 : int draw_w = window_width_, draw_h = window_height_;
108 0 : SDL_GL_GetDrawableSize(window_, &draw_w, &draw_h);
109 0 : if (window_width_ > 0)
110 0 : dpi_scale = static_cast<float>(draw_w) / static_cast<float>(window_width_);
111 : }
112 :
113 : #ifdef __EMSCRIPTEN__
114 : // If SDL didn't pick up a high DPI scaling factor inside the browser, fallback safely
115 : if (dpi_scale <= 1.0f) {
116 : dpi_scale = emscripten_get_device_pixel_ratio();
117 : if (dpi_scale <= 0.0f) dpi_scale = 1.0f;
118 : }
119 : #endif
120 :
121 0 : GuiGraphState::get_instance().dpi_scale = dpi_scale;
122 :
123 0 : {
124 0 : const float base_font_size = 14.0f;
125 0 : const float scaled_size = base_font_size * dpi_scale;
126 :
127 0 : ImFont* loaded_font = nullptr;
128 0 : auto try_font = [&](const std::string& path) {
129 0 : if (!loaded_font)
130 0 : loaded_font = io.Fonts->AddFontFromFileTTF(path.c_str(), scaled_size);
131 0 : };
132 :
133 0 : char* base_path = SDL_GetBasePath();
134 0 : if (base_path) {
135 0 : try_font(std::string(base_path) + "assets/fonts/Roboto-Medium.ttf");
136 0 : SDL_free(base_path);
137 0 : }
138 0 : try_font("assets/fonts/Roboto-Medium.ttf");
139 0 : try_font("../assets/fonts/Roboto-Medium.ttf");
140 0 : try_font("external/imgui/misc/fonts/Roboto-Medium.ttf");
141 0 : try_font("../external/imgui/misc/fonts/Roboto-Medium.ttf");
142 :
143 0 : if (!loaded_font) {
144 0 : io.Fonts->AddFontDefault();
145 0 : io.FontGlobalScale = 1.0f;
146 0 : } else {
147 : // On all platforms (Desktop & Web viewports), ImGui operates in logical coordinates.
148 : // We load fonts at high physical resolution (scaled_size) to keep text sharp,
149 : // and set FontGlobalScale to 1.0f / dpi_scale to draw them at their intended logical size.
150 0 : io.FontGlobalScale = 1.0f / dpi_scale;
151 : }
152 : }
153 :
154 : // Load window icon from assets/icon.svg
155 0 : {
156 0 : std::string icon_path;
157 0 : char* base = SDL_GetBasePath();
158 0 : if (base) {
159 0 : icon_path = std::string(base) + "assets/icon.svg";
160 0 : SDL_free(base);
161 0 : }
162 0 : NSVGimage* svg = nullptr;
163 0 : if (!icon_path.empty())
164 0 : svg = nsvgParseFromFile(icon_path.c_str(), "px", 96.0f);
165 0 : if (!svg)
166 0 : svg = nsvgParseFromFile("../assets/icon.svg", "px", 96.0f);
167 0 : if (!svg)
168 0 : svg = nsvgParseFromFile("assets/icon.svg", "px", 96.0f);
169 0 : if (svg) {
170 0 : const int icon_size = 64;
171 0 : NSVGrasterizer* rast = nsvgCreateRasterizer();
172 0 : if (rast) {
173 0 : unsigned char* img = new unsigned char[icon_size * icon_size * 4];
174 0 : nsvgRasterize(rast, svg, 0, 0,
175 0 : icon_size / svg->width,
176 0 : img, icon_size, icon_size,
177 : icon_size * 4);
178 :
179 0 : SDL_Surface* icon = SDL_CreateRGBSurfaceFrom(
180 0 : img, icon_size, icon_size, 32, icon_size * 4,
181 : 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
182 0 : if (icon) {
183 0 : SDL_SetWindowIcon(window_, icon);
184 0 : SDL_FreeSurface(icon);
185 0 : }
186 0 : delete[] img;
187 0 : nsvgDeleteRasterizer(rast);
188 0 : }
189 0 : nsvgDelete(svg);
190 0 : } else {
191 0 : std::cerr << "Warning: Could not load assets/icon.svg" << std::endl;
192 : }
193 0 : }
194 :
195 0 : ImGui_ImplSDL2_InitForOpenGL(window_, gl_context_);
196 0 : ImGui_ImplOpenGL3_Init(GLSetup::GLSL_VERSION);
197 :
198 0 : pedal_board_ = std::make_unique<PedalBoard>(engine_, command_history_, &gui_midi_);
199 0 : gui_presets_.set_pedal_board(pedal_board_.get());
200 0 : gui_presets_.set_midi_manager(&midi_manager_);
201 :
202 0 : PresetManager::load_config();
203 :
204 : // MIDI: load config first; if no saved mappings, install defaults
205 0 : midi_manager_.load_config();
206 0 : if (midi_manager_.mappings().empty()) {
207 0 : midi_manager_.install_default_mappings();
208 0 : }
209 0 : midi_manager_.initialize();
210 :
211 : #ifndef AMPLITRON_NO_DESKTOP_SHELL
212 0 : update_check_thread_ = std::thread([this]() { this->check_for_updates(); });
213 : #endif
214 :
215 0 : initialized_ = true;
216 0 : return true;
217 0 : }
218 :
219 :
220 33 : void GuiManager::shutdown() {
221 33 : if (!initialized_) return;
222 0 : initialized_ = false;
223 :
224 0 : if (update_check_thread_.joinable()) {
225 0 : update_check_thread_.join();
226 0 : }
227 :
228 0 : midi_manager_.save_config();
229 0 : midi_manager_.shutdown();
230 :
231 0 : engine_.clear_tuner_tap();
232 0 : pedal_board_.reset();
233 :
234 0 : ImGui_ImplOpenGL3_Shutdown();
235 0 : ImGui_ImplSDL2_Shutdown();
236 0 : ImGui::DestroyContext();
237 :
238 0 : if (gl_context_) {
239 0 : SDL_GL_DeleteContext(gl_context_);
240 0 : gl_context_ = nullptr;
241 0 : }
242 0 : if (window_) {
243 0 : SDL_DestroyWindow(window_);
244 0 : window_ = nullptr;
245 0 : }
246 0 : SDL_Quit();
247 11 : }
248 :
249 : } // namespace Amplitron
|