mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 03:02:05 +01:00
Pulling a panel's grip into the corner of the screen pushed it a couple of pixels past the main window, and ImGui gave it an OS window of its own. Now a panel only comes out when dragged with the cursor outside the main window. A panel that stays in the main window needs its grips in reach, so a resize stops at the edge, and a panel left past the edge or too big for the window is moved back and shrunk to fit. This is a new io.ConfigViewportsNoAutoDetach option in our copy of ImGui, so it has to be carried over when ImGui gets updated. ImGui itself has no option for this, as of 1.93.1.
768 lines
24 KiB
C++
768 lines
24 KiB
C++
/*
|
||
This Source Code Form is subject to the
|
||
terms of the Mozilla Public License, v.
|
||
2.0. If a copy of the MPL was not
|
||
distributed with this file, You can
|
||
obtain one at
|
||
http://mozilla.org/MPL/2.0/.
|
||
*/
|
||
|
||
#include "stdafx.h"
|
||
#include "application/uilayer.h"
|
||
|
||
#include <utility>
|
||
|
||
#include "utilities/Globals.h"
|
||
#include "rendering/renderer.h"
|
||
#include "utilities/Logs.h"
|
||
#include "simulation/simulation.h"
|
||
#include "utilities/translation.h"
|
||
#include "application/application.h"
|
||
#include "application/editormode.h"
|
||
#include "application/editoruilayer.h"
|
||
|
||
#include "imgui/imgui_impl_glfw.h"
|
||
#include "imgui/imgui_internal.h"
|
||
|
||
GLFWwindow *ui_layer::m_window{nullptr};
|
||
GLFWwindow *ui_layer::m_keywindow{nullptr};
|
||
ImGuiIO *ui_layer::m_imguiio{nullptr};
|
||
GLint ui_layer::m_textureunit{GL_TEXTURE0};
|
||
bool ui_layer::m_cursorvisible;
|
||
int ui_layer::m_palette{0};
|
||
ImFont *ui_layer::font_default{nullptr};
|
||
ImFont *ui_layer::font_mono{nullptr};
|
||
ImFont *ui_layer::font_loading{nullptr};
|
||
ImFont *ui_layer::font_bold{nullptr};
|
||
|
||
ui_panel::ui_panel(std::string Identifier, bool const Isopen) : is_open(Isopen), m_name(std::move(Identifier)) {}
|
||
|
||
void ui_panel::render()
|
||
{
|
||
if (false == is_open)
|
||
return;
|
||
|
||
int flags = window_flags;
|
||
if (flags == -1)
|
||
flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse;
|
||
if (size.x > 0)
|
||
flags |= ImGuiWindowFlags_NoResize;
|
||
if (no_title_bar)
|
||
flags |= ImGuiWindowFlags_NoTitleBar;
|
||
|
||
if (pos.x != -1 && pos.y != -1)
|
||
ImGui::SetNextWindowPos(ImVec2(pos.x, pos.y), ImGuiCond_Always);
|
||
if (size.x > 0)
|
||
ImGui::SetNextWindowSize(ImVec2S(size.x, size.y), ImGuiCond_Always);
|
||
else if (size_min.x == -1)
|
||
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver);
|
||
|
||
if (size_min.x > 0)
|
||
ImGui::SetNextWindowSizeConstraints(ImVec2S(size_min.x, size_min.y), ImVec2S(size_max.x, size_max.y));
|
||
|
||
auto const panelname{(title.empty() ? m_name : title) + "###" + m_name};
|
||
if (ImGui::Begin(panelname.c_str(), &is_open, flags))
|
||
{
|
||
render_contents();
|
||
|
||
popups.remove_if([](const std::unique_ptr<ui::popup> &popup) { return popup->render(); });
|
||
}
|
||
|
||
ImGui::End();
|
||
}
|
||
|
||
void ui_panel::render_contents()
|
||
{
|
||
for (auto const &line : text_lines)
|
||
{
|
||
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
|
||
}
|
||
}
|
||
|
||
void ui_panel::register_popup(std::unique_ptr<ui::popup> &&popup)
|
||
{
|
||
popups.push_back(std::move(popup));
|
||
}
|
||
|
||
void ui_expandable_panel::render_contents()
|
||
{
|
||
ImGui::Checkbox(STR_C("expand"), &is_expanded);
|
||
ui_panel::render_contents();
|
||
}
|
||
|
||
void ui_log_panel::render_contents()
|
||
{
|
||
ImGui::PushFont(ui_layer::font_mono);
|
||
|
||
for (const std::string &s : log_scrollback)
|
||
ImGui::TextUnformatted(s.c_str());
|
||
if (ImGui::GetScrollY() == ImGui::GetScrollMaxY())
|
||
ImGui::SetScrollHereY(1.0f);
|
||
|
||
ImGui::PopFont();
|
||
}
|
||
|
||
ui_layer::ui_layer()
|
||
{
|
||
if (Global.loading_log)
|
||
add_external_panel(&m_logpanel);
|
||
m_logpanel.size = {700, 400};
|
||
}
|
||
|
||
ui_layer::~ui_layer() {}
|
||
|
||
bool ui_layer::key_callback(int key, int scancode, int action, int mods)
|
||
{
|
||
ImGui_ImplGlfw_KeyCallback(m_keywindow != nullptr ? m_keywindow : m_window, key, scancode, action, mods);
|
||
return m_imguiio->WantCaptureKeyboard;
|
||
}
|
||
|
||
bool ui_layer::char_callback(unsigned int c)
|
||
{
|
||
ImGui_ImplGlfw_CharCallback(m_window, c);
|
||
return m_imguiio->WantCaptureKeyboard;
|
||
}
|
||
|
||
bool ui_layer::scroll_callback(double xoffset, double yoffset)
|
||
{
|
||
ImGui_ImplGlfw_ScrollCallback(m_window, xoffset, yoffset);
|
||
return m_imguiio->WantCaptureMouse;
|
||
}
|
||
|
||
bool ui_layer::mouse_button_callback(int button, int action, int mods)
|
||
{
|
||
ImGui_ImplGlfw_MouseButtonCallback(m_window, button, action, mods);
|
||
return m_imguiio->WantCaptureMouse;
|
||
}
|
||
|
||
// the glfw backend of imgui works with events since 1.87: position, hover and focus of the window are passed to it as they come
|
||
void ui_layer::cursor_pos_callback(double x, double y)
|
||
{
|
||
if (ImGui::GetCurrentContext() == nullptr)
|
||
return;
|
||
ImGui_ImplGlfw_CursorPosCallback(m_window, x, y);
|
||
}
|
||
|
||
void ui_layer::cursor_enter_callback(int entered)
|
||
{
|
||
if (ImGui::GetCurrentContext() == nullptr)
|
||
return;
|
||
ImGui_ImplGlfw_CursorEnterCallback(m_window, entered);
|
||
}
|
||
|
||
void ui_layer::focus_callback(int focused)
|
||
{
|
||
if (ImGui::GetCurrentContext() == nullptr)
|
||
return;
|
||
ImGui_ImplGlfw_WindowFocusCallback(m_window, focused);
|
||
}
|
||
|
||
void ui_layer::viewport_key_callback(GLFWwindow *Window, int key, int scancode, int action, int mods)
|
||
{
|
||
// imgui gets the key with the window it came from (the state of modifiers is read from it), the simulator gets it unless imgui takes it
|
||
m_keywindow = Window;
|
||
Application.on_key(key, scancode, action, mods);
|
||
m_keywindow = nullptr;
|
||
}
|
||
|
||
namespace
|
||
{
|
||
// window creation of the glfw backend, wrapped to give the new windows the key callback of the simulator
|
||
void (*imgui_create_window)(ImGuiViewport *Viewport){nullptr};
|
||
|
||
void create_viewport_window(ImGuiViewport *Viewport)
|
||
{
|
||
imgui_create_window(Viewport);
|
||
if (Viewport->PlatformHandle != nullptr)
|
||
glfwSetKeyCallback(static_cast<GLFWwindow *>(Viewport->PlatformHandle), ui_layer::viewport_key_callback);
|
||
#ifdef _WIN32
|
||
if (Viewport->PlatformHandleRaw != nullptr)
|
||
::SetWindowLongPtr(static_cast<HWND>(Viewport->PlatformHandleRaw), GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(ImGui::GetMainViewport()->PlatformHandleRaw));
|
||
#endif
|
||
}
|
||
} // namespace
|
||
|
||
void ui_layer::imgui_colors(int const Palette)
|
||
{
|
||
m_palette = Palette;
|
||
auto const rgb = [](int const Color, float const Alpha = 1.0f) {
|
||
return ImVec4(((Color >> 16) & 0xff) / 255.0f, ((Color >> 8) & 0xff) / 255.0f, (Color & 0xff) / 255.0f, Alpha);
|
||
};
|
||
auto const editor{Palette == 1};
|
||
// palette shared with the MaSzyna starter: flat dark panels, thin borders, green accents; the one of the editor
|
||
// has amber accents on cooler grey panels
|
||
int const panel{editor ? 0x1f2226 : 0x1e2327}; // window background
|
||
int const base{editor ? 0x1b1d21 : 0x15171b}; // darker background, between panels
|
||
int const header{editor ? 0x2c3036 : 0x2a3036}; // section headers, hovered items
|
||
int const border{editor ? 0x3a3f47 : 0x3a424a};
|
||
int const field{editor ? 0x15171a : 0x121517}; // input fields, combos
|
||
int const accent{editor ? 0xb07a1c : 0x177f00}; // selection, active elements
|
||
int const accentbright{editor ? 0xe5a73a : 0x41c400}; // marks: checkmarks, active tab underline
|
||
int const accentdim{editor ? 0x5a4319 : 0x164b0e};
|
||
|
||
ImVec4 *colors = ImGui::GetStyle().Colors;
|
||
|
||
colors[ImGuiCol_Text] = rgb(editor ? 0xe6e8eb : 0xffffff);
|
||
colors[ImGuiCol_TextDisabled] = rgb(editor ? 0x8a929c : 0xa0a0a2);
|
||
colors[ImGuiCol_WindowBg] = rgb(panel, Global.UIBgOpacity); // ui.bg.opacity from config file
|
||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||
colors[ImGuiCol_PopupBg] = rgb(base, 0.98f);
|
||
colors[ImGuiCol_Border] = rgb(border);
|
||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||
colors[ImGuiCol_FrameBg] = rgb(field);
|
||
colors[ImGuiCol_FrameBgHovered] = rgb(header);
|
||
colors[ImGuiCol_FrameBgActive] = rgb(border);
|
||
colors[ImGuiCol_TitleBg] = rgb(base);
|
||
colors[ImGuiCol_TitleBgActive] = rgb(header);
|
||
colors[ImGuiCol_TitleBgCollapsed] = rgb(base, 0.75f);
|
||
colors[ImGuiCol_MenuBarBg] = rgb(panel);
|
||
colors[ImGuiCol_ScrollbarBg] = rgb(base, 0.60f);
|
||
colors[ImGuiCol_ScrollbarGrab] = rgb(border);
|
||
colors[ImGuiCol_ScrollbarGrabHovered] = rgb(0x5a636c);
|
||
colors[ImGuiCol_ScrollbarGrabActive] = rgb(0x858585);
|
||
colors[ImGuiCol_CheckMark] = rgb(accentbright);
|
||
colors[ImGuiCol_SliderGrab] = rgb(accent);
|
||
colors[ImGuiCol_SliderGrabActive] = rgb(accentbright);
|
||
colors[ImGuiCol_Button] = rgb(header);
|
||
colors[ImGuiCol_ButtonHovered] = rgb(border);
|
||
colors[ImGuiCol_ButtonActive] = rgb(accent);
|
||
// shared by section headers and selected list items
|
||
colors[ImGuiCol_Header] = rgb(header);
|
||
colors[ImGuiCol_HeaderHovered] = rgb(accentdim);
|
||
colors[ImGuiCol_HeaderActive] = rgb(accent);
|
||
colors[ImGuiCol_Separator] = rgb(border);
|
||
colors[ImGuiCol_SeparatorHovered] = rgb(accent);
|
||
colors[ImGuiCol_SeparatorActive] = rgb(accentbright);
|
||
colors[ImGuiCol_ResizeGrip] = rgb(border, 0.50f);
|
||
colors[ImGuiCol_ResizeGripHovered] = rgb(accent);
|
||
colors[ImGuiCol_ResizeGripActive] = rgb(accentbright);
|
||
colors[ImGuiCol_Tab] = rgb(header);
|
||
colors[ImGuiCol_TabHovered] = rgb(border);
|
||
colors[ImGuiCol_TabActive] = rgb(accent); // also the line under the tab bar
|
||
colors[ImGuiCol_TabUnfocused] = rgb(header);
|
||
colors[ImGuiCol_TabUnfocusedActive] = rgb(accentdim);
|
||
// imgui 1.90+ marks the selected tab with a line over it as well, in the same colours
|
||
colors[ImGuiCol_TabSelectedOverline] = rgb(accent);
|
||
colors[ImGuiCol_TabDimmedSelectedOverline] = rgb(accentdim);
|
||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||
colors[ImGuiCol_TextSelectedBg] = rgb(accent, 0.60f);
|
||
colors[ImGuiCol_DragDropTarget] = rgb(accentbright, 0.90f);
|
||
colors[ImGuiCol_NavHighlight] = rgb(accentbright);
|
||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||
colors[ImGuiCol_ModalWindowDimBg] = rgb(base, 0.60f);
|
||
// where a dragged window would be docked, and an empty dock node
|
||
colors[ImGuiCol_DockingPreview] = rgb(accentbright, 0.70f);
|
||
colors[ImGuiCol_DockingEmptyBg] = rgb(base);
|
||
}
|
||
|
||
void ui_layer::imgui_style()
|
||
{
|
||
imgui_colors(0);
|
||
|
||
// flat, square elements with thin borders
|
||
auto &style = ImGui::GetStyle();
|
||
style.WindowRounding = 0.0f;
|
||
style.ChildRounding = 0.0f;
|
||
style.FrameRounding = 0.0f;
|
||
style.PopupRounding = 0.0f;
|
||
style.ScrollbarRounding = 0.0f;
|
||
style.GrabRounding = 0.0f;
|
||
style.TabRounding = 0.0f;
|
||
style.WindowBorderSize = 1.0f;
|
||
style.ChildBorderSize = 1.0f;
|
||
style.PopupBorderSize = 1.0f;
|
||
style.FrameBorderSize = 1.0f;
|
||
style.TabBorderSize = 0.0f;
|
||
|
||
style.ScaleAllSizes(Global.ui_scale);
|
||
}
|
||
|
||
bool ui_layer::init(GLFWwindow *Window)
|
||
{
|
||
m_window = Window;
|
||
|
||
IMGUI_CHECKVERSION();
|
||
ImGui::CreateContext();
|
||
m_imguiio = &ImGui::GetIO();
|
||
ImGui::GetCurrentContext()->ConfigNavWindowingKeyNext = ImGuiKey_None;
|
||
ImGui::GetCurrentContext()->ConfigNavWindowingKeyPrev = ImGuiKey_None;
|
||
// imgui.ini is read with the first frame, which may be of another mode than the editor
|
||
editor_ui::register_settings();
|
||
|
||
m_imguiio->ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||
// m_imguiio->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||
// m_imguiio->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||
|
||
static const ImWchar ranges[] =
|
||
{
|
||
0x0020, 0x00FF, // Basic Latin + Latin Supplement
|
||
0x0100, 0x017F, // Latin Extended-A
|
||
0x2070, 0x2079, // superscript
|
||
0x2500, 0x256C, // box drawings
|
||
0,
|
||
};
|
||
|
||
ImFontConfig config;
|
||
// config.FontDataOwnedByAtlas = false; // Avoid duplicate release of font data
|
||
|
||
if (FileExists("fonts/dejavusans.ttf")) {
|
||
// Load basic font first
|
||
font_default = m_imguiio->Fonts->AddFontFromFileTTF("fonts/dejavusans.ttf", Global.ui_fontsize, &config, &ranges[0]);
|
||
|
||
// Add support for Chinese character ranges
|
||
if (font_default) {
|
||
ImFontConfig chinese_config;
|
||
chinese_config.MergeMode = true;
|
||
chinese_config.PixelSnapH = true;
|
||
chinese_config.OversampleH = 1;
|
||
chinese_config.OversampleV = 1;
|
||
// Use NotoSansSC-Regular.ttf font file
|
||
if (FileExists("fonts/NotoSansSC-Regular.ttf")) {
|
||
const ImWchar* chinese_ranges = m_imguiio->Fonts->GetGlyphRangesChineseFull();
|
||
m_imguiio->Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.ttf", Global.ui_fontsize, &chinese_config, chinese_ranges);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (FileExists("fonts/dejavusansmono.ttf")) {
|
||
ImFontConfig mono_config;
|
||
mono_config.GlyphRanges = &ranges[0]; // Basic Latin characters
|
||
font_mono = m_imguiio->Fonts->AddFontFromFileTTF("fonts/dejavusansmono.ttf", Global.ui_fontsize, &mono_config, &ranges[0]);
|
||
|
||
// Add Chinese character support for monospace font as well
|
||
if (font_mono) {
|
||
ImFontConfig chinese_mono_config;
|
||
chinese_mono_config.OversampleH = 1;
|
||
chinese_mono_config.OversampleV = 1;
|
||
chinese_mono_config.MergeMode = true;
|
||
chinese_mono_config.PixelSnapH = true;
|
||
|
||
// Use NotoSansSC-Regular.ttf for monospace font as well
|
||
if (FileExists("fonts/NotoSansSC-Regular.ttf")) {
|
||
const ImWchar* chinese_ranges = m_imguiio->Fonts->GetGlyphRangesChineseFull();
|
||
m_imguiio->Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.ttf", Global.ui_fontsize, &chinese_mono_config, chinese_ranges);
|
||
}
|
||
}
|
||
}
|
||
for (auto const *bold : {"fonts/dejavusans-bold.ttf", "fonts/DejaVuSans-Bold.ttf"})
|
||
{
|
||
if (font_bold == nullptr && FileExists(bold))
|
||
{
|
||
ImFontConfig bold_config;
|
||
font_bold = m_imguiio->Fonts->AddFontFromFileTTF(bold, Global.ui_fontsize * 1.25f, &bold_config, &ranges[0]);
|
||
}
|
||
}
|
||
if (FileExists("fonts/bahnschrift.ttf")) {
|
||
ImFontConfig loading_config;
|
||
font_loading = m_imguiio->Fonts->AddFontFromFileTTF("fonts/bahnschrift.ttf", 48, &loading_config, &ranges[0]);
|
||
|
||
// Add Chinese character support for loading font as well
|
||
if (font_loading) {
|
||
ImFontConfig chinese_loading_config;
|
||
chinese_loading_config.OversampleH = 1;
|
||
chinese_loading_config.OversampleV = 1;
|
||
chinese_loading_config.MergeMode = true;
|
||
chinese_loading_config.PixelSnapH = true;
|
||
|
||
// Use NotoSansMonoCJKsc-Regular.ttf for loading font
|
||
if (FileExists("fonts/NotoSansMonoCJKsc-Regular.ttf")) {
|
||
const ImWchar* chinese_ranges = m_imguiio->Fonts->GetGlyphRangesChineseFull();
|
||
m_imguiio->Fonts->AddFontFromFileTTF("fonts/NotoSansMonoCJKsc-Regular.ttf", 48, &chinese_loading_config, chinese_ranges);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!font_default && !font_mono)
|
||
font_default = font_mono = m_imguiio->Fonts->AddFontDefault();
|
||
else if (!font_default)
|
||
font_default = font_mono;
|
||
else if (!font_mono)
|
||
font_mono = font_default;
|
||
if (!font_loading)
|
||
font_loading = font_default;
|
||
|
||
imgui_style();
|
||
|
||
ImGui_ImplGlfw_InitForOpenGL(m_window, false);
|
||
|
||
if (!GfxRenderer->GetImguiRenderer() || !GfxRenderer->GetImguiRenderer()->Init())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// panels can be docked together, and dragged out of the simulator window into windows of their own (e.g. on another monitor)
|
||
// when both backends can do it. glfw can't place windows under wayland. both flags have to be set before the first frame
|
||
m_imguiio->ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||
auto const viewportsupport{(m_imguiio->BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (m_imguiio->BackendFlags & ImGuiBackendFlags_RendererHasViewports) &&
|
||
glfwGetPlatform() != GLFW_PLATFORM_WAYLAND};
|
||
if (Global.ui_viewports && viewportsupport)
|
||
{
|
||
m_imguiio->ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
||
m_imguiio->ConfigViewportsNoTaskBarIcon = true;
|
||
// only dragging a panel with the cursor past the main window takes it out, not resizing it or placing it at the edge
|
||
m_imguiio->ConfigViewportsNoAutoDetach = true;
|
||
auto &platformio{ImGui::GetPlatformIO()};
|
||
imgui_create_window = platformio.Platform_CreateWindow;
|
||
platformio.Platform_CreateWindow = create_viewport_window;
|
||
}
|
||
WriteLog(std::string("ui: panels in separate windows ") + ((m_imguiio->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ? "enabled" : (viewportsupport ? "disabled in settings" : "not supported here")));
|
||
|
||
return true;
|
||
}
|
||
|
||
void ui_layer::shutdown()
|
||
{
|
||
ImGui::EndFrame();
|
||
|
||
GfxRenderer->GetImguiRenderer()->Shutdown();
|
||
ImGui_ImplGlfw_Shutdown();
|
||
ImGui::DestroyContext();
|
||
}
|
||
|
||
bool ui_layer::on_key(int const Key, int const Action)
|
||
{
|
||
if (Action == GLFW_PRESS)
|
||
{
|
||
if (Key == GLFW_KEY_PRINT_SCREEN)
|
||
{
|
||
Application.queue_screenshot();
|
||
return true;
|
||
}
|
||
|
||
if (Key == GLFW_KEY_F9)
|
||
{
|
||
m_logpanel.is_open = !m_logpanel.is_open;
|
||
return true;
|
||
}
|
||
|
||
if (Key == GLFW_KEY_F10)
|
||
{
|
||
m_quit_active = !m_quit_active;
|
||
return true;
|
||
}
|
||
|
||
if (m_quit_active)
|
||
{
|
||
if (Key == GLFW_KEY_Y)
|
||
{
|
||
Application.queue_quit(false);
|
||
return true;
|
||
}
|
||
else if (Key == GLFW_KEY_N)
|
||
{
|
||
m_quit_active = false;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool ui_layer::on_cursor_pos(double const Horizontal, double const Vertical)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
bool ui_layer::on_mouse_button(int const Button, int const Action)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
void ui_layer::on_window_resize(int w, int h)
|
||
{
|
||
for (auto *panel : m_panels)
|
||
panel->on_window_resize(w, h);
|
||
}
|
||
|
||
void ui_layer::update()
|
||
{
|
||
for (auto *panel : m_panels)
|
||
panel->update();
|
||
|
||
for (auto it = m_ownedpanels.rbegin(); it != m_ownedpanels.rend(); it++)
|
||
{
|
||
(*it)->update();
|
||
if (!(*it)->is_open)
|
||
m_ownedpanels.erase(std::next(it).base());
|
||
}
|
||
}
|
||
|
||
void ui_layer::render()
|
||
{
|
||
// the frame may have been started by the ui of another mode, when the mode changes; the windows docked in the dockspace
|
||
// of this one would come undocked if they were drawn in a frame without it
|
||
if (m_dockspaceframe != ImGui::GetFrameCount())
|
||
{
|
||
render_dockspace();
|
||
m_dockspaceframe = ImGui::GetFrameCount();
|
||
}
|
||
render_background();
|
||
render_panels();
|
||
render_tooltip();
|
||
render_menu();
|
||
render_quit_widget();
|
||
render_hierarchy();
|
||
// template method implementation
|
||
render_();
|
||
|
||
render_internal();
|
||
}
|
||
|
||
void ui_layer::render_internal()
|
||
{
|
||
ImGui::Render();
|
||
GfxRenderer->GetImguiRenderer()->Render();
|
||
|
||
if (m_imguiio->ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||
{
|
||
// panels outside of the main window are drawn in their own windows, whose gl contexts are shared with the main one
|
||
auto *context{glfwGetCurrentContext()};
|
||
ImGui::UpdatePlatformWindows();
|
||
ImGui::RenderPlatformWindowsDefault();
|
||
glfwMakeContextCurrent(context);
|
||
}
|
||
}
|
||
|
||
void ui_layer::begin_ui_frame()
|
||
{
|
||
// the colours follow the ui of the mode drawn
|
||
if (palette() != m_palette)
|
||
imgui_colors(palette());
|
||
begin_ui_frame_internal();
|
||
render_dockspace();
|
||
m_dockspaceframe = ImGui::GetFrameCount();
|
||
}
|
||
|
||
void ui_layer::begin_ui_frame_internal()
|
||
{
|
||
GfxRenderer->GetImguiRenderer()->BeginFrame();
|
||
ImGui_ImplGlfw_NewFrame();
|
||
ImGui::NewFrame();
|
||
}
|
||
|
||
void ui_layer::render_quit_widget()
|
||
{
|
||
if (!m_quit_active)
|
||
return;
|
||
|
||
ImGui::SetNextWindowSize(ImVec2(0, 0));
|
||
ImGui::Begin(STR_C("Quit"), &m_quit_active, ImGuiWindowFlags_NoResize);
|
||
ImGui::TextUnformatted(STR_C("Quit simulation?"));
|
||
if (ImGui::Button(STR_C("Yes")))
|
||
Application.queue_quit(false);
|
||
|
||
ImGui::SameLine();
|
||
if (ImGui::Button(STR_C("No")))
|
||
m_quit_active = false;
|
||
ImGui::End();
|
||
}
|
||
|
||
void ui_layer::render_hierarchy(){
|
||
if(!m_editor_hierarchy)
|
||
return;
|
||
|
||
// fitted to the contents when it first appears; no auto resize, which a docked window can't do
|
||
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver);
|
||
ImGui::Begin((std::string(STR_C("Scene Hierarchy")) + "###scenehierarchy").c_str(), &m_editor_hierarchy);
|
||
ImGui::Text("Registered nodes: %zu", scene::Hierarchy.size());
|
||
ImGui::BeginChild("hierarchy_list", ImVec2(500, 300), true);
|
||
|
||
for (auto &entry : scene::Hierarchy)
|
||
{
|
||
const std::string &uuid = entry.first;
|
||
scene::basic_node *node = entry.second;
|
||
|
||
if (node)
|
||
{
|
||
char buf[512];
|
||
std::snprintf(buf, sizeof(buf), "%s | %s (%.1f, %.1f, %.1f)",
|
||
node->name().c_str(),
|
||
uuid.c_str(),
|
||
node->location().x,
|
||
node->location().y,
|
||
node->location().z);
|
||
|
||
if (ImGui::Selectable(buf, false))
|
||
{
|
||
// Focus camera on selected node
|
||
auto const node_pos = node->location();
|
||
auto const camera_offset = glm::dvec3(0.0, 10.0, -20.0);
|
||
editor_mode::set_focus_active(false);
|
||
TCamera &camera = editor_mode::get_camera();
|
||
camera.Pos = node_pos + camera_offset;
|
||
|
||
}
|
||
|
||
if (ImGui::IsItemHovered())
|
||
{
|
||
ImGui::SetTooltip("UUID: %s\nNode type: %s", uuid.c_str(), typeid(*node).name());
|
||
}
|
||
}
|
||
}
|
||
|
||
ImGui::EndChild();
|
||
ImGui::End();
|
||
|
||
}
|
||
|
||
void ui_layer::set_cursor(int const Mode)
|
||
{
|
||
glfwSetInputMode(m_window, GLFW_CURSOR, Mode);
|
||
m_cursorvisible = Mode != GLFW_CURSOR_DISABLED;
|
||
}
|
||
|
||
void ui_layer::set_progress(std::string const &Text)
|
||
{
|
||
m_progresstext = Text;
|
||
}
|
||
|
||
void ui_layer::set_progress(float const progress, float const subtaskprogress)
|
||
{
|
||
m_progress = progress * 0.01f;
|
||
m_subtaskprogress = subtaskprogress * 0.01f;
|
||
}
|
||
|
||
void ui_layer::set_background(std::string const &Filename)
|
||
{
|
||
m_background = Filename.empty() ? null_handle : GfxRenderer->Fetch_Texture(Filename);
|
||
}
|
||
|
||
void ui_layer::clear_panels()
|
||
{
|
||
m_panels.clear();
|
||
m_ownedpanels.clear();
|
||
}
|
||
|
||
void ui_layer::add_owned_panel(ui_panel *Panel)
|
||
{
|
||
for (auto &panel : m_ownedpanels)
|
||
if (panel->name() == Panel->name())
|
||
{
|
||
delete Panel;
|
||
return;
|
||
}
|
||
|
||
Panel->is_open = true;
|
||
m_ownedpanels.emplace_back(Panel);
|
||
}
|
||
|
||
void ui_layer::render_panels()
|
||
{
|
||
for (auto *panel : m_panels)
|
||
panel->render();
|
||
for (auto &panel : m_ownedpanels)
|
||
panel->render();
|
||
|
||
if (m_imgui_demo)
|
||
ImGui::ShowDemoWindow(&m_imgui_demo);
|
||
}
|
||
|
||
void ui_layer::render_tooltip()
|
||
{
|
||
if (!m_cursorvisible || m_imguiio->WantCaptureMouse || m_tooltip.empty())
|
||
return;
|
||
|
||
ImGui::BeginTooltip();
|
||
ImGui::TextUnformatted(m_tooltip.c_str());
|
||
ImGui::EndTooltip();
|
||
}
|
||
|
||
void ui_layer::render_menu_contents()
|
||
{
|
||
if (ImGui::BeginMenu(STR_C("General")))
|
||
{
|
||
bool flag = DebugModeFlag;
|
||
if (ImGui::MenuItem(STR_C("Debug mode"), nullptr, &flag))
|
||
{
|
||
command_relay relay;
|
||
relay.post(user_command::debugtoggle, 0.0, 0.0, GLFW_RELEASE, 0);
|
||
}
|
||
ImGui::MenuItem(STR_C("Quit"), "F10", &m_quit_active);
|
||
ImGui::EndMenu();
|
||
}
|
||
if (ImGui::BeginMenu(STR_C("Tools")))
|
||
{
|
||
static bool log = Global.iWriteLogEnabled & 1;
|
||
|
||
ImGui::MenuItem(STR_C("Logging to log.txt"), nullptr, &log);
|
||
if (log)
|
||
Global.iWriteLogEnabled |= 1;
|
||
else
|
||
Global.iWriteLogEnabled &= ~1;
|
||
|
||
if (ImGui::MenuItem(STR_C("Screenshot"), "PrtScr"))
|
||
Application.queue_screenshot();
|
||
ImGui::EndMenu();
|
||
}
|
||
if (ImGui::BeginMenu(STR_C("Windows")))
|
||
{
|
||
ImGui::MenuItem(STR_C("Log"), "F9", &m_logpanel.is_open);
|
||
if (DebugModeFlag)
|
||
{
|
||
ImGui::MenuItem(STR_C("ImGui Demo"), nullptr, &m_imgui_demo);
|
||
bool ret = ImGui::MenuItem(STR_C("Headlight config"), nullptr, GfxRenderer->Debug_Ui_State(std::nullopt));
|
||
|
||
GfxRenderer->Debug_Ui_State(ret);
|
||
}
|
||
if(EditorModeFlag){
|
||
ImGui::MenuItem("Hierarchy", nullptr, &m_editor_hierarchy);
|
||
bool change_history_enabled = editor_mode::change_history();
|
||
if (ImGui::MenuItem("Change History", nullptr, &change_history_enabled))
|
||
{
|
||
editor_mode::set_change_history(change_history_enabled);
|
||
}
|
||
bool settings_open = editor_mode::settings_open();
|
||
if (ImGui::MenuItem("Editor Settings", nullptr, &settings_open))
|
||
{
|
||
editor_mode::set_settings_open(settings_open);
|
||
}
|
||
}
|
||
render_windows_menu();
|
||
ImGui::EndMenu();
|
||
}
|
||
}
|
||
|
||
void ui_layer::render_menu()
|
||
{
|
||
glm::dvec2 mousepos = Global.cursor_pos;
|
||
|
||
if (m_suppress_menu || (false == m_menu_always && !((Global.ControlPicking && mousepos.y < 50.0f) || m_imguiio->WantCaptureMouse)))
|
||
return;
|
||
|
||
if (ImGui::BeginMainMenuBar())
|
||
{
|
||
render_menu_contents();
|
||
ImGui::EndMainMenuBar();
|
||
}
|
||
}
|
||
|
||
void ui_layer::render_background()
|
||
{
|
||
if (m_background == 0)
|
||
return;
|
||
|
||
ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
||
ITexture &tex = GfxRenderer->Texture(m_background);
|
||
tex.create();
|
||
|
||
float tex_w = (float)tex.get_width();
|
||
float tex_h = (float)tex.get_height();
|
||
|
||
// skalowanie "cover" – wypełnia cały ekran, zachowując proporcje
|
||
float scale_factor = display_size.x / display_size.y > tex_w / tex_h ? display_size.x / tex_w : display_size.y / tex_h;
|
||
|
||
ImVec2 image_size(tex_w * scale_factor, tex_h * scale_factor);
|
||
|
||
// wyśrodkowanie obrazka
|
||
ImVec2 start_position((display_size.x - image_size.x) * 0.5f, (display_size.y - image_size.y) * 0.5f);
|
||
ImVec2 end_position(start_position.x + image_size.x, start_position.y + image_size.y);
|
||
|
||
// obrazek jest odwrócony w pionie – odwracamy UV
|
||
ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())->AddImage((ImTextureID)(intptr_t)(tex.get_id()), start_position, end_position, ImVec2(0, 1), ImVec2(1, 0));
|
||
}
|