/* 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/driveruilayer.h" #include "utilities/Globals.h" #include "utilities/utilities.h" #include "application/application.h" #include "utilities/translation.h" #include "simulation/simulation.h" #include "vehicle/Train.h" #include "model/AnimModel.h" #include "rendering/renderer.h" #include "DevConsole/devconsole.h" #include "entitysystem/ECScene.h" #include "entitysystem/components/BasicComponents.h" #include "entitysystem/components/RenderComponents.h" driver_ui::driver_ui() { clear_panels(); // bind the panels with ui object. maybe not the best place for this but, eh add_external_panel(&m_aidpanel); add_external_panel(&m_scenariopanel); add_external_panel(&m_timetablepanel); add_external_panel(&m_debugpanel); if (Global.gui_showtranscripts) add_external_panel(&m_transcriptspanel); add_external_panel(&m_trainingcardpanel); add_external_panel(&m_vehiclelist); add_external_panel(&m_timepanel); add_external_panel(&m_mappanel); add_external_panel(&m_logpanel); add_external_panel(&m_perfgraphpanel); add_external_panel(&dev::Console); add_external_panel(&m_cameraviewpanel); m_logpanel.is_open = false; m_aidpanel.title = STR("Driving Aid"); m_scenariopanel.title = STR("Scenario"); m_scenariopanel.size_min = {435, 85}; m_scenariopanel.size_max = {Global.fb_size.x * 0.95f, Global.fb_size.y * 0.95}; m_timetablepanel.title = STR("%-*.*s Time: %d:%02d:%02d"); m_timetablepanel.size_min = {435, 70}; m_timetablepanel.size_max = {435, Global.fb_size.y * 0.95}; m_transcriptspanel.title = STR("Transcripts"); m_transcriptspanel.size_min = {435, 85}; m_transcriptspanel.size_max = {Global.fb_size.x * 0.95, Global.fb_size.y * 0.95}; if (Global.gui_defaultwindows) { m_aidpanel.is_open = true; m_scenariopanel.is_open = true; } if (Global.gui_trainingdefault) { m_mappanel.is_open = true; m_trainingcardpanel.is_open = true; m_vehiclelist.is_open = true; } register_driver_commands(); } void driver_ui::register_driver_commands() { auto &con = dev::Console; // --- UI toggles --- con.register_command("debug", "Toggle debug panel", [this](const dev::args_t &) { m_debugpanel.is_open = !m_debugpanel.is_open; dev::Console.print_ok(m_debugpanel.is_open ? "Debug panel opened" : "Debug panel closed"); }); con.register_command("map", "Toggle map panel", [this](const dev::args_t &) { m_mappanel.is_open = !m_mappanel.is_open; }); con.register_command("aid", "Toggle driving-aid panel", [this](const dev::args_t &) { m_aidpanel.is_open = !m_aidpanel.is_open; }); con.register_command("timetable", "Toggle timetable panel", [this](const dev::args_t &) { m_timetablepanel.is_open = !m_timetablepanel.is_open; }); // --- Simulation control --- con.register_command("pause", "Pause or unpause simulation", [](const dev::args_t &) { command_relay relay; relay.post(user_command::pausetoggle, 0.0, 0.0, GLFW_PRESS, 0); }); con.register_command("debugmode", "Toggle debug mode (enable/disable)", [](const dev::args_t &args) { command_relay relay; relay.post(user_command::debugtoggle, 0.0, 0.0, GLFW_RELEASE, 0); dev::Console.print_ok(std::string("Debug mode: ") + (DebugModeFlag ? "on" : "off")); }); // --- Weather --- con.register_command("overcast", "Set overcast level: overcast <0.0-1.0>", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_info("Current overcast: " + std::to_string(Global.Overcast)); return; } try { float v = std::stof(args[1]); v = std::max(0.0f, std::min(1.0f, v)); Global.Overcast = v; dev::Console.print_ok("Overcast set to " + std::to_string(v)); } catch (...) { dev::Console.print_error("Expected a float 0.0-1.0"); } }); // --- Screenshot --- con.register_command("screenshot", "Take a screenshot", [](const dev::args_t &) { Application.queue_screenshot(); dev::Console.print_ok("Screenshot queued"); }); // --- Log --- con.register_command("log", "Enable or disable file logging: log [on|off]", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_info(std::string("File logging: ") + ((Global.iWriteLogEnabled & 1) ? "on" : "off")); return; } if (args[1] == "on") { Global.iWriteLogEnabled |= 1; dev::Console.print_ok("File logging enabled"); } else if (args[1] == "off") { Global.iWriteLogEnabled &= ~1; dev::Console.print_ok("File logging disabled"); } else { dev::Console.print_error("Usage: log [on|off]"); } }); // --- ECS --- con.register_command("ecs.list", "List all ECS entities (optional filter: ecs.list [name_fragment])", [](const dev::args_t &args) { ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); std::string filter = args.size() >= 2 ? args[1] : ""; int count = 0; for (auto entity : world.GetEntities()) { std::string name = std::to_string(static_cast(entity)); if (auto *id = world.GetComponent(entity)) name = id->Name.ToString(); if (!filter.empty() && name.find(filter) == std::string::npos) continue; dev::Console.print(" [" + std::to_string(static_cast(entity)) + "] " + name); ++count; } dev::Console.print_info("Total: " + std::to_string(count) + " entities"); }); con.register_command("ecs.info", "Show components of entity by name: ecs.info ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.info "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } dev::Console.print_ok("Entity: " + args[1]); if (auto *t = world.GetComponent(entity)) { dev::Console.print(" Transform: pos=(" + std::to_string(t->Position.x) + ", " + std::to_string(t->Position.y) + ", " + std::to_string(t->Position.z) + ")"); } if (auto *v = world.GetComponent(entity)) { dev::Console.print(" Velocity: (" + std::to_string(v->Value.x) + ", " + std::to_string(v->Value.y) + ", " + std::to_string(v->Value.z) + ")"); } if (world.HasComponent(entity)) dev::Console.print(" MeshRenderer: yes"); if (world.HasComponent(entity)) dev::Console.print(" ParticleEmitter: yes"); if (auto *id = world.GetComponent(entity)) dev::Console.print(" ID: " + id->Name.ToString() + " #" + std::to_string(id->Id)); }); con.register_command("ecs.setvel", "Set entity velocity: ecs.setvel ", [](const dev::args_t &args) { if (args.size() < 5) { dev::Console.print_error("Usage: ecs.setvel "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } try { glm::vec3 vel{ std::stof(args[2]), std::stof(args[3]), std::stof(args[4]) }; if (auto *v = world.GetComponent(entity)) { v->Value = vel; dev::Console.print_ok("Velocity set"); } else { world.AddComponent(entity).Value = vel; dev::Console.print_ok("Velocity component added and set"); } } catch (...) { dev::Console.print_error("Invalid numbers"); } }); con.register_command("ecs.kill", "Destroy entity by name: ecs.kill ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.kill "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } world.DestroyEntity(entity); dev::Console.print_ok("Entity destroyed: " + args[1]); }); con.register_command("ecs.count", "Show total entity count in current scene", [](const dev::args_t &) { ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } dev::Console.print_info("Entities: " + std::to_string(scene->World().GetEntityCount())); }); con.register_command("ecs.setpos", "Teleport entity to position: ecs.setpos ", [](const dev::args_t &args) { if (args.size() < 5) { dev::Console.print_error("Usage: ecs.setpos "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } try { glm::dvec3 pos{ std::stod(args[2]), std::stod(args[3]), std::stod(args[4]) }; if (auto *t = world.GetComponent(entity)) { t->Position = pos; dev::Console.print_ok("Position set: (" + args[2] + ", " + args[3] + ", " + args[4] + ")"); } else { dev::Console.print_error("Entity has no Transform component"); } } catch (...) { dev::Console.print_error("Invalid numbers"); } }); con.register_command("ecs.spawn", "Spawn a named entity with Transform at position: ecs.spawn ", [](const dev::args_t &args) { if (args.size() < 5) { dev::Console.print_error("Usage: ecs.spawn "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); try { glm::dvec3 pos{ std::stod(args[2]), std::stod(args[3]), std::stod(args[4]) }; auto entity = world.CreateEntity(); auto &id = world.AddComponent(entity); id.Name = args[1]; auto &t = world.AddComponent(entity); t.Position = pos; dev::Console.print_ok("Spawned entity '" + args[1] + "' at (" + args[2] + ", " + args[3] + ", " + args[4] + ")"); } catch (...) { dev::Console.print_error("Invalid position arguments"); } }); con.register_command("ecs.find", "Find entities whose name contains a substring: ecs.find ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.find "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); const std::string &fragment = args[1]; int found = 0; for (auto entity : world.GetEntities()) { auto *id = world.GetComponent(entity); if (!id) continue; std::string name = id->Name.ToString(); if (name.find(fragment) != std::string::npos) { dev::Console.print(" [" + std::to_string(static_cast(entity)) + "] " + name); ++found; } } if (found == 0) dev::Console.print_warn("No entities found matching: " + fragment); else dev::Console.print_info(std::to_string(found) + " match(es)"); }); con.register_command("ecs.disable", "Disable entity (systems will skip it): ecs.disable ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.disable "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } if (!world.HasComponent(entity)) world.AddComponent(entity); dev::Console.print_ok("Disabled: " + args[1]); }); con.register_command("ecs.enable", "Re-enable a disabled entity: ecs.enable ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.enable "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } world.RemoveComponent(entity); dev::Console.print_ok("Enabled: " + args[1]); }); con.register_command("ecs.addparticles", "Add a particle emitter to an entity: ecs.addparticles ", [](const dev::args_t &args) { if (args.size() < 2) { dev::Console.print_error("Usage: ecs.addparticles "); return; } ECScene *scene = Application.sceneManager.CurrentScene(); if (!scene) { dev::Console.print_warn("No active scene"); return; } ECWorld &world = scene->World(); entt::entity entity = world.FindEntityByName(args[1]); if (entity == entt::null) { dev::Console.print_error("Entity not found: " + args[1]); return; } auto& emitter = world.AddComponent(entity); emitter.isActive = true; emitter.spawnRate = 20.0f; emitter.particleLifetime = 2.5f; emitter.gravity = glm::vec3(0.0f, 0.4f, 0.0f); emitter.airResistance = 0.15f; emitter.colorFade = glm::vec4(0.0f, 0.0f, 0.0f, -0.5f); if (auto *t = world.GetComponent(entity)) emitter.emitterLocation = glm::vec3(t->Position); dev::Console.print_ok("ParticleEmitter added to: " + args[1]); }); con.print_info("Driver console ready. Type 'help' for commands."); } void driver_ui::render_menu_contents() { ui_layer::render_menu_contents(); if (ImGui::BeginMenu(STR_C("Mode windows"))) { ImGui::MenuItem(m_aidpanel.title.c_str(), "F1", &m_aidpanel.is_open); ImGui::MenuItem(m_scenariopanel.title.c_str(), "F1", &m_aidpanel.is_open); ImGui::MenuItem(STR_C("Timetable"), "F2", &m_timetablepanel.is_open); ImGui::MenuItem(m_debugpanel.name().c_str(), "F12", &m_debugpanel.is_open); ImGui::MenuItem(m_mappanel.name().c_str(), "Tab", &m_mappanel.is_open); ImGui::MenuItem(m_vehiclelist.name().c_str(), nullptr, &m_vehiclelist.is_open); ImGui::MenuItem(m_trainingcardpanel.name().c_str(), nullptr, &m_trainingcardpanel.is_open); ImGui::MenuItem(m_cameraviewpanel.name().c_str(), nullptr, &m_cameraviewpanel.is_open); if (DebugModeFlag) ImGui::MenuItem(m_perfgraphpanel.name().c_str(), nullptr, &m_perfgraphpanel.is_open); if (ImGui::MenuItem(m_timepanel.name().c_str())) m_timepanel.open(); ImGui::EndMenu(); } } void driver_ui::showDebugUI() { m_debugpanel.is_open = !m_debugpanel.is_open; } // potentially processes provided input key. returns: true if key was processed, false otherwise bool driver_ui::on_key(int const Key, int const Action) { if (ui_layer::on_key(Key, Action)) return true; switch (Key) { case GLFW_KEY_TAB: case GLFW_KEY_F1: case GLFW_KEY_F2: case GLFW_KEY_F3: case GLFW_KEY_F10: case GLFW_KEY_F12: { // ui mode selectors if ((true == Global.ctrlState) || (true == Global.shiftState)) { // only react to keys without modifiers return false; } if (Action != GLFW_PRESS) { return true; } // recognized, but ignored } default: { // everything else break; } } switch (Key) { case GLFW_KEY_TAB: { m_mappanel.is_open = !m_mappanel.is_open; return true; } case GLFW_KEY_F1: { // basic consist info auto state = ((m_aidpanel.is_open == false) ? 0 : (m_aidpanel.is_expanded == false) ? 1 : 2); state = clamp_circular(++state, 3); m_aidpanel.is_open = (state > 0); m_aidpanel.is_expanded = (state > 1); return true; } case GLFW_KEY_F2: { // timetable auto state = ((m_timetablepanel.is_open == false) ? 0 : (m_timetablepanel.is_expanded == false) ? 1 : 2); state = clamp_circular(++state, 3); m_timetablepanel.is_open = (state > 0); m_timetablepanel.is_expanded = (state > 1); return true; } case GLFW_KEY_F3: { // debug panel m_scenariopanel.is_open = !m_scenariopanel.is_open; return true; } case GLFW_KEY_F12: { // debug panel if (Global.shiftState) { m_debugpanel.is_open = !m_debugpanel.is_open; return true; } } default: { break; } } return false; } // potentially processes provided mouse movement. returns: true if the input was processed, false otherwise bool driver_ui::on_cursor_pos(double const Horizontal, double const Vertical) { // intercept mouse movement when the pause window is on return m_paused; } // potentially processes provided mouse button. returns: true if the input was processed, false otherwise bool driver_ui::on_mouse_button(int const Button, int const Action) { // intercept mouse movement when the pause window is on return m_paused; } // updates state of UI elements void driver_ui::update() { auto const pausemask{1 | 2}; auto ispaused{(false == DebugModeFlag) && ((Global.iPause & pausemask) != 0)}; if ((ispaused != m_paused) && (false == Global.ControlPicking)) { set_cursor(ispaused); } m_paused = ispaused; ui_layer::update(); } void driver_ui::set_cursor(bool const Visible) { if (Visible) { Application.set_cursor(GLFW_CURSOR_NORMAL); Application.set_cursor_pos(Global.window_size.x / 2, Global.window_size.y / 2); } else { Application.set_cursor(GLFW_CURSOR_DISABLED); Application.set_cursor_pos(0, 0); } } // render() subclass details void driver_ui::render_() { const std::string *rec_name = m_trainingcardpanel.is_recording(); if (rec_name && m_cameraviewpanel.set_state(true)) { m_cameraviewpanel.rec_name = *rec_name; m_cameraviewpanel.is_open = true; } else if (!rec_name) m_cameraviewpanel.set_state(false); // pause/quit modal auto const popupheader{STR_C("Simulation Paused")}; ImGui::SetNextWindowSize(ImVec2(-1, -1)); if (ImGui::BeginPopupModal(popupheader, nullptr, 0)) { if ((ImGui::Button(STR_C("Resume"), ImVec2(150, 0))) || (ImGui::IsKeyReleased(ImGui::GetKeyIndex(ImGuiKey_Escape)))) { m_relay.post(user_command::pausetoggle, 0.0, 0.0, GLFW_RELEASE, 0); } if (ImGui::Button(STR_C("Quit"), ImVec2(150, 0))) { Application.queue_quit(false); } if (!m_paused) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } else if (m_paused) { ImGui::OpenPopup(popupheader); } if (Global.desync != 0.0f) { ImGui::SetNextWindowSize(ImVec2(-1, -1)); if (ImGui::Begin("network", nullptr, ImGuiWindowFlags_NoCollapse)) ImGui::Text("desync: %0.2f", Global.desync); ImGui::End(); } }