#include "UI.hpp" #include #include #include "../state.hpp" #include "Window.hpp" #include "core/utils/os.hpp" #include "api/Buffers.hpp" #include "core/world/Elements.hpp" #include "version.h" #include using namespace render; UI *UI::sInstance = nullptr; constexpr auto UI_MARGIN = 5; UI::UI() { // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); { ImGui::StyleColorsDark(); ImGuiStyle &style = ImGui::GetStyle(); style.TabRounding = 1; style.WindowRounding = 2; style.GrabRounding = 2; style.FrameRounding = 4; style.ScrollbarRounding = 4; style.WindowMenuButtonPosition = ImGuiDir_Right; // Purple dark theme /*colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.56f, 0.56f, 0.56f, 1.00f); colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); colors[ImGuiCol_Border] = ImVec4(0.53f, 0.46f, 0.54f, 0.50f); colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_FrameBg] = ImVec4(0.38f, 0.34f, 0.40f, 0.54f); colors[ImGuiCol_FrameBgHovered] = ImVec4(0.83f, 0.26f, 0.98f, 0.40f); colors[ImGuiCol_FrameBgActive] = ImVec4(0.73f, 0.26f, 0.98f, 0.67f); colors[ImGuiCol_TitleBg] = ImVec4(0.14f, 0.00f, 0.21f, 1.00f); colors[ImGuiCol_TitleBgActive] = ImVec4(0.39f, 0.16f, 0.48f, 1.00f); colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.13f, 0.00f, 0.21f, 0.51f); colors[ImGuiCol_MenuBarBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); colors[ImGuiCol_CheckMark] = ImVec4(0.74f, 0.27f, 0.83f, 1.00f); colors[ImGuiCol_SliderGrab] = ImVec4(0.55f, 0.21f, 0.80f, 1.00f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.75f, 0.27f, 0.83f, 1.00f); colors[ImGuiCol_Button] = ImVec4(0.85f, 0.26f, 0.98f, 0.40f); colors[ImGuiCol_ButtonHovered] = ImVec4(0.76f, 0.26f, 0.98f, 1.00f); colors[ImGuiCol_ButtonActive] = ImVec4(0.63f, 0.06f, 0.98f, 1.00f); colors[ImGuiCol_Header] = ImVec4(0.81f, 0.26f, 0.98f, 0.31f); colors[ImGuiCol_HeaderHovered] = ImVec4(0.84f, 0.26f, 0.98f, 0.80f); colors[ImGuiCol_HeaderActive] = ImVec4(0.79f, 0.26f, 0.98f, 1.00f); colors[ImGuiCol_Separator] = ImVec4(0.38f, 0.35f, 0.40f, 0.54f); colors[ImGuiCol_SeparatorHovered] = ImVec4(0.64f, 0.10f, 0.75f, 0.78f); colors[ImGuiCol_SeparatorActive] = ImVec4(0.53f, 0.10f, 0.75f, 1.00f); colors[ImGuiCol_ResizeGrip] = ImVec4(0.77f, 0.26f, 0.98f, 0.25f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.83f, 0.26f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.76f, 0.26f, 0.98f, 0.95f); colors[ImGuiCol_Tab] = ImVec4(0.48f, 0.18f, 0.58f, 0.86f); colors[ImGuiCol_TabHovered] = ImVec4(0.79f, 0.26f, 0.98f, 0.80f); colors[ImGuiCol_TabActive] = ImVec4(0.54f, 0.20f, 0.68f, 1.00f); colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.07f, 0.15f, 0.97f); colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22f, 0.14f, 0.42f, 1.00f); 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] = ImVec4(0.55f, 0.26f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavHighlight] = ImVec4(0.55f, 0.26f, 0.98f, 1.00f); 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] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);*/ } for(auto file: std::filesystem::directory_iterator("content/textures/")) { if(file.is_directory() && file.path().filename() != "ui") texturePacks.push_back(file.path().filename().string()); } } UI::~UI() { ImGui::DestroyContext(); } UI::Actions drawCommon(config::client::options &options, state::state &state, const std::vector& packs); UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector& packs); UI::Actions drawInGame(config::client::options &options, state::state &state, const state::reports &reports, intptr_t aim, const std::vector& packs); UI::Actions UI::draw(config::client::options &options, state::state &state, const state::reports &reports, intptr_t aim) { Actions actions = Actions::None; ImGui::NewFrame(); if (state.playing) { actions = drawInGame(options, state, reports, aim, texturePacks); } else { actions = drawMenu(options, state, texturePacks); } ImGui::Render(); return actions; } bool UI::IsFocus() { return ImGui::IsAnyItemActive(); } void UI::Unload() { delete sInstance; sInstance = nullptr; } template void drawOverlay(config::client::options::overlay_t& overlay, F inner) { if (overlay.visible) { const ImGuiIO &io = ImGui::GetIO(); if (overlay.corner != -1) { ImVec2 window_pos = ImVec2((overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN); ImVec2 window_pos_pivot = ImVec2((overlay.corner & 1) ? 1.0f : 0.0f, (overlay.corner & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); } ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background ImGui::Begin("Overlay", &overlay.visible, (overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); inner(); ImGui::Text("RAM: %.3f/%.3f GB", os::GetProcess().memused / 1000000000.f, os::GetGlobal().ram.total / 1000000000.); if (ImGui::BeginPopupContextWindow()) { if (ImGui::MenuItem("Custom", NULL, overlay.corner == -1)) overlay.corner = -1; if (ImGui::MenuItem("Top-left", NULL, overlay.corner == 0)) overlay.corner = 0; if (ImGui::MenuItem("Top-right", NULL, overlay.corner == 1)) overlay.corner = 1; if (ImGui::MenuItem("Bottom-left", NULL, overlay.corner == 2)) overlay.corner = 2; if (ImGui::MenuItem("Bottom-right", NULL, overlay.corner == 3)) overlay.corner = 3; if (overlay.visible && ImGui::MenuItem("Close")) overlay.visible = false; ImGui::EndPopup(); } ImGui::End(); } } UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector& packs) { drawOverlay(options.overlay, []{}); auto actions = drawCommon(options, state, packs); const ImGuiIO &io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y / 2), ImGuiCond_Always, ImVec2(.5f, .5f)); ImGui::Begin((std::string("Univerxel ") + UNIVERXEL_VERSION).c_str(), NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse); state.login.name.resize(128); state.login.token.resize(1024); if (ImGui::InputText("Login", state.login.name.data(), state.login.name.size()-1, ImGuiInputTextFlags_EnterReturnsTrue)) ImGui::SetKeyboardFocusHere(); const auto tryPlay = ImGui::InputText("Token", state.login.token.data(), state.login.token.size() - 1, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_EnterReturnsTrue); ImGui::Separator(); bool useLocal = !options.connection.has_value(); if (state.srvContainer) { ImGui::Columns(2, NULL, false); if (ImGui::Checkbox("Use local", &useLocal)) { options.connection = !useLocal ? std::make_optional(world::client::Universe::connection{}) : std::nullopt; } ImGui::NextColumn(); if (!useLocal) { ImGui::Checkbox("Run server", &state.useSrv); } ImGui::Columns(); } if (!useLocal) { ImGui::InputText("Host", &options.connection.value().host); ImGui::InputInt("Port", &options.connection.value().port); } { const auto canPlay = std::string::traits_type::length(state.login.name.data()) > 0; if (!canPlay) ImGui::PushStyleVarDisabled(); if ((ImGui::Button("Play", ImVec2(ImGui::GetWindowContentRegionWidth(), 30)) || tryPlay) && canPlay) { state.login.name.resize(std::string::traits_type::length(state.login.name.data())); state.login.token.resize(std::string::traits_type::length(state.login.token.data())); state.playing = true; } if (!canPlay) ImGui::PopStyleVar(); } ImGui::Separator(); if (ImGui::CollapsingHeader("Options")) { ImGui::Columns(3, NULL, false); ImGui::Checkbox("Render", &options.debugMenu.render); ImGui::NextColumn(); ImGui::Checkbox("World", &options.debugMenu.world); ImGui::NextColumn(); ImGui::Checkbox("Console", &options.console.visible); ImGui::Columns(); } if (ImGui::Button("Quit", ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) { actions |= UI::Actions::Quit; } ImGui::End(); return actions; } #define ABS_POS_FMT "(%.2Lf, %.2Lf, %.2Lf)" #define IDX_FMT "%d(%u, %u)" #define REL_POS_FMT "(" IDX_FMT ": %.2Lf, %.2Lf, %.2Lf)" #define REL_CPOS_FMT "(" IDX_FMT ": %lld, %lld, %lld)" #define POS_VAL(v) v.x, v.y, v.z #define ABS_POS_VAL(tf) POS_VAL(tf.position) #define IDX_VAL(i) i.val.index(), i.val.generation(), i.val.flag() #define REL_POS_VAL(rtf) IDX_VAL(rtf.parent), POS_VAL(rtf.relative.position) UI::Actions drawInGame(config::client::options &options, state::state &state, const state::reports &reports, intptr_t aim, const std::vector& texturePacks) { UI::Actions actions = UI::Actions::None; const ImGuiIO &io = ImGui::GetIO(); if(state.capture_mouse) { ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y / 2), ImGuiCond_Always, ImVec2(.5f, .5f)); ImGui::Begin("Aim", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoInputs); ImGui::Image((void *)aim, ImVec2(32, 32)); ImGui::End(); } if (options.debugMenu.bar) { ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("Debug")) { ImGui::Checkbox("Render", &options.debugMenu.render); ImGui::Checkbox("World", &options.debugMenu.world); ImGui::Checkbox("Contouring", &options.debugMenu.contouring); ImGui::Checkbox("Controls", &options.debugMenu.controls); ImGui::EndMenu(); } ImGui::Checkbox("Editor", &options.editor.visible); ImGui::Checkbox("Console", &options.console.visible); if(ImGui::MenuItem("Close")) options.debugMenu.bar = false; ImGui::EndMainMenuBar(); } actions |= drawCommon(options, state, texturePacks); if (options.debugMenu.contouring) { ImGui::Begin("Options: Contouring", &options.debugMenu.contouring, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderInt("Culling", &options.culling, -1, 10, options.culling > 0 ? "Occlusion %d" : (options.culling < 0 ? "None" : "Frustum")); state.contouring->onGui(); ImGui::End(); } { const auto farRange = state.contouring->getFarRange(); if (options.debugMenu.controls) { ImGui::Begin("Debug: Controls", &options.debugMenu.controls, ImGuiWindowFlags_AlwaysAutoResize); //MAYBE: add rotation ImGui::Text("Position: " ABS_POS_FMT "\n" REL_POS_FMT, ABS_POS_VAL(state.position.absolute), REL_POS_VAL(state.position.relative)); ImGui::Separator(); { if (ImGui::SliderFloat("Move speed", &options.control.speed, 0.1, 50) | ImGui::SliderInt("Sensibility", &options.control.sensibility, 1, 100, "%d%%")) { actions |= UI::Actions::Control; } ImGui::Checkbox("Collide", &options.control.collide); } ImGui::Separator(); { bool changePerspective = false; changePerspective |= ImGui::SliderAngle("FoV", &options.camera.fov, 30, 110); changePerspective |= ImGui::SliderFloat("Near", &options.camera.near_dist, 0.01, 10); changePerspective |= ImGui::SliderFloat("Far", &options.camera.far_dist, farRange.first, farRange.second); if(changePerspective) { actions |= UI::Actions::Camera; } } ImGui::End(); } const auto far_dist = std::clamp(options.camera.far_dist, farRange.first, farRange.second); if(far_dist != options.camera.far_dist) { options.camera.far_dist = far_dist; actions |= UI::Actions::Camera; } } if (options.editor.visible) { ImGui::Begin("Editor", &options.editor.visible, ImGuiWindowFlags_AlwaysAutoResize); if (const auto voxel = std::get_if(&state.look_at)) { ImGui::Text("Look at: voxel (%s, %.1f)\n" REL_CPOS_FMT, voxel->value.name().c_str(), voxel->value.density_ratio(), IDX_VAL(voxel->pos.node), POS_VAL(voxel->pos.voxel)); } else if (const auto element = std::get_if(&state.look_at)) { //TODO: if world::Elements::Is(element->parent) get model_id; ImGui::Text("Look at: entity " IDX_FMT "\n" ABS_POS_FMT, IDX_VAL(element->parent), POS_VAL(element->position)); } else { ImGui::Text("Look at: none"); } ImGui::Separator(); if (ImGui::BeginCombo("Material", world::Voxel::Name(options.editor.tool.material).c_str())) { for (size_t i = 0; i < world::module::Registry::Get()->getMaterials().names.size(); i++) { if (world::Voxel::IsVisible(i)) { const bool is_selected = (options.editor.tool.material == i); if (ImGui::Selectable(world::Voxel::Name(i).c_str(), is_selected)) options.editor.tool.material = i; if (is_selected) ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } ImGui::SliderInt("Radius", &options.editor.tool.radius, 1, 15); const auto shapeId = (size_t)options.editor.tool.shape; if (ImGui::BeginCombo("Shape", world::action::SHAPES[shapeId].c_str())) { for (size_t i = 0; i < world::action::SHAPES.size(); i++) { const bool is_selected = (shapeId == i); if (ImGui::Selectable(world::action::SHAPES[i].c_str(), is_selected)) options.editor.tool.shape = (world::action::Shape)i; if (is_selected) ImGui::SetItemDefaultFocus(); if (i == (size_t)world::action::Shape::SmoothSphere && ImGui::IsItemHovered()) ImGui::SetTooltip("Asymmetrical with small radius"); } ImGui::EndCombo(); } ImGui::Checkbox("Empty air", &options.editor.tool.emptyAir); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Is cleaned area breathable"); ImGui::End(); } drawOverlay(options.overlay, [&]{ ImGui::Text("%ld tris(%ld models)", reports.tris_count, reports.models_count); }); return actions; } UI::Actions drawCommon(config::client::options &options, state::state& state, const std::vector& texturePacks) { UI::Actions actions = UI::Actions::None; if (options.debugMenu.render) { ImGui::Begin("Options: Render", &options.debugMenu.render, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Checkbox("Overlay", &options.overlay.visible); { #ifdef RENDER_VK ImGui::Combo("Driver", (int*)&options.preferVulkan, "OpenGL\0Vulkan\0"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Vulkan driver is unrecommended and WIP (requires restart)"); #endif auto samples = std::to_string(options.window.getSamples()) + "x"; ImGui::SliderInt("MSAA", &options.window.sampling, -1, 7, options.window.sampling > 0 ? samples.c_str() : (options.window.sampling < 0 ? "System default" : "Minimun")); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Anti-aliasing (requires restart).\nActual value may be smaller than requested"); ImGui::Separator(); } if (ImGui::SliderInt("FPS", &options.window.targetFPS, Window::MIN_FPS-1, Window::MAX_FPS+1, options.window.targetFPS > Window::MIN_FPS ? (options.window.targetFPS < Window::MAX_FPS ? "%d" : "UNLIMITED") : "VSYNC")){ actions |= UI::Actions::FPS; } #ifdef _WINDOWS if (ImGui::IsItemHovered()) ImGui::SetTooltip("Due to windows clock inaccuracy prefer vsync or unlimited."); #endif if (ImGui::Checkbox("Fullscreen", &options.window.fullscreen)){ actions |= UI::Actions::FullScreen; } { bool changeRenderer = false; { int planarIdx = static_cast(options.renderer.voxel.planar) - 1; changeRenderer |= ImGui::Combo("Mapping", &planarIdx, "Cheap\0Biplanar\0Triplanar\0"); options.renderer.voxel.planar = static_cast(planarIdx + 1); } ImGui::Columns(2, NULL, false); changeRenderer |= ImGui::Checkbox("PBR", &options.renderer.voxel.pbr); ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Stochastic", &options.renderer.voxel.stochastic); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Hide textures tiling\nMay cause visible inconsistances on chunk borders"); ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Geometry", &options.renderer.voxel.geometry); ImGui::NextColumn(); if (options.renderer.voxel.geometry) { changeRenderer |= (ImGui::Checkbox("Blend", &options.renderer.voxel.blend) && options.renderer.voxel.geometry); } else { ImGui::TextDisabled("Blend"); } ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Curvature", &options.renderer.voxel.curvature); ImGui::NextColumn(); if (options.renderer.voxel.curvature) { changeRenderer |= ImGui::Checkbox("Depth", &options.renderer.voxel.curv_depth); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Planets may look way bigger than expected without it"); } else { ImGui::TextDisabled("Depth"); } ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Transparency", &options.renderer.voxel.transparency); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Requires transparency option in Contouring"); ImGui::NextColumn(); ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Fog", &options.renderer.voxel.fog); ImGui::NextColumn(); changeRenderer |= ImGui::Checkbox("Skybox", &options.renderer.skybox); if (changeRenderer) { actions |= UI::Actions::RendererSharders; } ImGui::Columns(); } if (ImGui::ColorEdit3("Fog color", &options.renderer.clear_color[0])) { actions |= UI::Actions::ClearColor; } if (ImGui::Checkbox("Wireframe", &options.renderer.wireframe)) { actions |= UI::Actions::FillMode; } if (ImGui::BeginCombo("Textures", options.renderer.textures.c_str())) { for (auto& pack: texturePacks) { const bool is_selected = (pack == options.renderer.textures); if (ImGui::Selectable(pack.c_str(), is_selected)) { options.renderer.textures = pack; actions |= UI::Actions::RendererTextures; } if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } std::string anisotropy = std::to_string(options.renderer.getAnisotropy()) + "x"; if (ImGui::SliderInt("Quality", &options.renderer.textureQuality, 0, 200, "%d%%") | ImGui::SliderInt("Sharpness", &options.renderer.textureSharpness, 0, 8, anisotropy.c_str())) { actions |= UI::Actions::RendererTextures; } if(ImGui::IsItemHovered()) ImGui::SetTooltip("Better texture quality especially at low angles"); ImGui::End(); } if (options.debugMenu.world) { ImGui::Begin("Options: World", &options.debugMenu.world, ImGuiWindowFlags_AlwaysAutoResize); const auto runServer = state.srvContainer && (!options.connection || state.useSrv); if (runServer) { state.srvContainer->onGui(); } bool useNet = options.connection.has_value(); if (runServer && useNet) { useNet = ImGui::CollapsingHeader("Local", ImGuiTreeNodeFlags_DefaultOpen); } if (useNet) { { int load = options.world.loadDistance; int keep = options.world.keepDistance; if (ImGui::SliderInt("Load distance", &load, 1, options.world.keepDistance) | ImGui::SliderInt("Keep distance", &keep, options.world.loadDistance + 1, 21)) { options.world.loadDistance = load; options.world.keepDistance = keep; actions |= UI::Actions::World; } } ImGui::Columns(2, NULL, false); ImGui::Checkbox("Use averages", &options.world.useAverages); ImGui::NextColumn(); ImGui::Checkbox("Trust majorant", &options.world.trustMajorant); ImGui::NextColumn(); ImGui::Checkbox("Edit prediction", &options.world.editPrediction); ImGui::NextColumn(); ImGui::Checkbox("Edit handling", &options.world.editHandling); ImGui::NextColumn(); ImGui::Checkbox("Move prediction", &options.world.movePrediction); ImGui::NextColumn(); ImGui::Checkbox("Move collision", &options.world.moveCollision); ImGui::Columns(); } if (ImGui::CollapsingHeader("Indicators")) { // MAYBE: move to Debug: Indicators ImGui::Columns(3, NULL, false); ImGui::Checkbox("OBB", &state.indicators.obb); ImGui::NextColumn(); ImGui::Checkbox("AABB", &state.indicators.aabb); ImGui::NextColumn(); ImGui::Checkbox("Chunks", &state.indicators.chunk); ImGui::Columns(); } ImGui::End(); } if (options.console.visible) { { const auto SIZE = ImVec2(300, 200); ImGui::SetNextWindowSize(SIZE, ImGuiCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(UI_MARGIN, ImGui::GetIO().DisplaySize.y - SIZE.y - UI_MARGIN), ImGuiCond_FirstUseEver); } ImGui::PushStyleVar(ImGuiStyleVar_Alpha, options.console.opacity); ImGui::Begin("Console", &options.console.visible, ImGuiWindowFlags_MenuBar); ImGui::BeginMenuBar(); if(ImGui::BeginMenu("Options")) { ImGui::Checkbox("Auto-scrool", &options.console.scroll); ImGui::SliderFloat("Opacity", &options.console.opacity, 0.1, 1); ImGui::EndMenu(); } if (ImGui::Button("Clear")) state.console.lines.fill(state::state::line{}); const bool copy_to_clipboard = ImGui::Button("Copy"); ImGui::EndMenuBar(); // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); /*if (ImGui::BeginPopupContextWindow()) { if (ImGui::Selectable("Clear")) ClearLog(); ImGui::EndPopup(); }*/ // Display every line as a separate entry so we can change their color or add custom widgets. // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping // to only process visible items. The clipper will automatically measure the height of your first item and then // "seek" to display only items in the visible area. // To use the clipper we can replace your standard loop: // for (int i = 0; i < Items.Size; i++) // With: // ImGuiListClipper clipper(Items.Size); // while (clipper.Step()) // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) // - That your items are evenly spaced (same height) // - That you have cheap random access to your elements (you can access them given their index, // without processing all the ones before) // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. // We would need random-access on the post-filtered list. // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, // and appending newly elements as they are inserted. This is left as a task to the user until we can manage // to improve this example code! // If your items are of variable height: // - Split them into same height items would be simpler and facilitate random-seeking into your list. // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing if (copy_to_clipboard) ImGui::LogToClipboard(); state.console.lines.iter([&](const state::state::line& item) { if (item.text.empty()) return; /*if (!Filter.PassFilter(item)) continue;*/ if (item.color.has_value()) ImGui::PushStyleColor(ImGuiCol_Text, item.color.value()); ImGui::TextUnformatted(item.text.c_str()); if (item.color.has_value()) ImGui::PopStyleColor(); }); if (copy_to_clipboard) ImGui::LogFinish(); if (options.console.scroll || ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f); ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::Separator(); // Command-line const auto flags = ImGuiInputTextFlags_EnterReturnsTrue; // | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; ImGui::GetColumnWidth(); if (ImGui::InputText("", state.console.buffer.data(), state.console.buffer.size(), flags /*TODO: completion (, callback, context)*/) && strlen(state.console.buffer.data()) > 0) actions |= UI::Actions::Message; // Auto-focus on window apparition ImGui::SetItemDefaultFocus(); if (actions && UI::Actions::Message) ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget ImGui::End(); ImGui::PopStyleVar(); } return actions; } void ImGui::PushStyleVarDisabled() { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); }