Reorganize source files into logical subdirectories

Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-14 19:01:57 +00:00
parent f981f81d55
commit 0531086bb9
221 changed files with 131 additions and 108 deletions

1382
application/application.cpp Normal file

File diff suppressed because it is too large Load Diff

131
application/application.h Normal file
View File

@@ -0,0 +1,131 @@
/*
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/.
*/
#pragma once
#include "applicationmode.h"
#include "PyInt.h"
#include "network/manager.h"
#include "headtrack.h"
#ifdef WITH_UART
#include "uart.h"
#endif
class eu07_application {
const int MAX_NETWORK_PER_FRAME = 1000;
public:
// types
enum mode {
launcher = 0,
scenarioloader,
driver,
editor,
count_
};
// constructors
eu07_application() = default;
// methods
int
init( int Argc, char *Argv[] );
int
run();
// issues request for a worker thread to perform specified task. returns: true if task was scheduled
#ifdef WITH_DISCORD_RPC
void DiscordRPCService(); // discord rich presence service function (runs as separate thread)
#endif
bool
request( python_taskqueue::task_request const &Task );
// ensures the main thread holds the python gil and can safely execute python calls
void
acquire_python_lock();
// frees the python gil and swaps out the main thread
void
release_python_lock();
void
exit();
void
render_ui();
void
begin_ui_frame();
// switches application to specified mode
bool
pop_mode();
bool
push_mode( eu07_application::mode const Mode );
void
set_title( std::string const &Title );
void
set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f );
void
set_tooltip( std::string const &Tooltip );
void
set_cursor( int const Mode );
void
set_cursor_pos( double const Horizontal, double const Vertical );
void queue_screenshot();
// input handlers
void on_key( int const Key, int const Scancode, int const Action, int const Mods );
void on_char( unsigned int const Char );
void on_cursor_pos( double const Horizontal, double const Vertical );
void on_mouse_button( int const Button, int const Action, int const Mods );
void on_scroll( double const Xoffset, double const Yoffset );
void on_focus_change(bool focus);
void on_window_resize(int w, int h);
// gives access to specified window, creates a new window if index == -1
GLFWwindow *
window( int const Windowindex = 0, bool visible = false, int width = 1, int height = 1, GLFWmonitor *monitor = nullptr, bool keep_ownership = true, bool share_ctx = true );
GLFWmonitor * find_monitor( const std::string &str ) const;
std::string describe_monitor( GLFWmonitor *monitor ) const;
// generate network sync verification number
double
generate_sync();
void
queue_quit(bool direct);
bool
is_server() const;
bool
is_client() const;
private:
// types
using modeptr_array = std::array<std::shared_ptr<application_mode>, static_cast<std::size_t>( mode::count_ )>;
using mode_stack = std::stack<mode>;
// methods
bool needs_ogl() const;
void init_debug();
void init_files();
int init_settings( int Argc, char *Argv[] );
int init_locale();
int init_glfw();
void init_callbacks();
int init_ogl();
int init_ui();
int init_gfx();
int init_audio();
int init_data();
int init_modes();
bool init_network();
int run_crashgui();
// members
bool m_screenshot_queued = false;
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
mode_stack m_modestack; // current behaviour mode
python_taskqueue m_taskqueue;
std::vector<GLFWwindow *> m_windows;
int m_glfwversion;
std::optional<network::manager> m_network;
std::optional<headtrack> m_headtrack;
};
extern eu07_application Application;

View File

@@ -0,0 +1,59 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
// component implementing specific mode of application behaviour
// base interface
class application_mode {
public:
// destructor
virtual ~application_mode() = default;
// methods;
// initializes internal data structures of the mode. returns: true on success, false otherwise
virtual bool init() = 0;
// mode-specific update of simulation data. returns: false on error, true otherwise
virtual bool update() = 0;
// draws mode-specific user interface
inline
void render_ui() {
if( m_userinterface != nullptr ) {
m_userinterface->render(); } }
inline
void begin_ui_frame() {
if( m_userinterface != nullptr ) {
m_userinterface->begin_ui_frame(); } }
inline
void set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f ) {
if( m_userinterface != nullptr ) {
m_userinterface->set_progress( Progress, Subtaskprogress ); } }
inline
void set_tooltip( std::string const &Tooltip ) {
if( m_userinterface != nullptr ) {
m_userinterface->set_tooltip( Tooltip ); } }
// maintenance method, called when the mode is activated
virtual void enter() = 0;
// maintenance method, called when the mode is deactivated
virtual void exit() = 0;
// input handlers
virtual void on_key( int Key, int Scancode, int Action, int Mods ) = 0;
virtual void on_cursor_pos( double X, double Y ) = 0;
virtual void on_mouse_button( int Button, int Action, int Mods ) = 0;
virtual void on_scroll( double Xoffset, double Yoffset ) = 0;
virtual void on_window_resize( int w, int h ) = 0;
virtual void on_event_poll() = 0;
virtual bool is_command_processor() const = 0;
protected:
// members
std::shared_ptr<ui_layer> m_userinterface;
};

1349
application/driverhints.cpp Normal file

File diff suppressed because it is too large Load Diff

21
application/driverhints.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef DRIVER_HINT_INCL
#define DRIVER_HINT_INCL
#define DRIVER_HINT_DEF(a, b) a,
enum class driver_hint {
#include "driverhints_def.h"
};
#undef DRIVER_HINT_DEF
#endif
#ifdef DRIVER_HINT_CONTENT
#define DRIVER_HINT_DEF(a, b) b,
const char *driver_hints_texts[] =
{
#include "driverhints_def.h"
};
#undef DRIVER_HINT_DEF
#endif

View File

@@ -0,0 +1,105 @@
DRIVER_HINT_DEF(batteryon, STRN("Switch on battery"))
DRIVER_HINT_DEF(batteryoff, STRN("Switch off battery"))
DRIVER_HINT_DEF(radioon, STRN("Switch on radio"))
DRIVER_HINT_DEF(radiooff, STRN("Switch off radio"))
DRIVER_HINT_DEF(radiochannel, STRN("Tune into channel %.0f"))
DRIVER_HINT_DEF(oilpumpon, STRN("Switch on oil pump"))
DRIVER_HINT_DEF(oilpumpoff, STRN("Switch off oil pump"))
DRIVER_HINT_DEF(fuelpumpon, STRN("Switch on fuel pump"))
DRIVER_HINT_DEF(fuelpumpoff, STRN("Switch off fuel pump"))
DRIVER_HINT_DEF(pantographairsourcesetmain, STRN("Switch pantograph 3-way valve to primary air source"))
DRIVER_HINT_DEF(pantographairsourcesetauxiliary, STRN("Switch pantograph 3-way valve to auxiliary air source"))
DRIVER_HINT_DEF(pantographcompressoron, STRN("Switch on pantograph compressor"))
DRIVER_HINT_DEF(pantographcompressoroff, STRN("Switch off pantograph compressor"))
DRIVER_HINT_DEF(pantographsvalveon, STRN("Enable pantographs valve"))
DRIVER_HINT_DEF(pantographsvalveoff, STRN("Disable pantographcs valve"))
DRIVER_HINT_DEF(frontpantographvalveon, STRN("Raise pantograph A"))
DRIVER_HINT_DEF(frontpantographvalveoff, STRN("Lower pantograph A"))
DRIVER_HINT_DEF(rearpantographvalveon, STRN("Raise pantograph B"))
DRIVER_HINT_DEF(rearpantographvalveoff, STRN("Lower pantograph B"))
DRIVER_HINT_DEF(converteron, STRN("Switch on converter"))
DRIVER_HINT_DEF(converteroff, STRN("Switch off converter"))
DRIVER_HINT_DEF(primaryconverteroverloadreset, STRN("Reset converter overload relay"))
DRIVER_HINT_DEF(maincircuitgroundreset, STRN("Reset main circuit ground relay"))
DRIVER_HINT_DEF(tractionnmotoroverloadreset, STRN("Reset traction motors overload relay"))
DRIVER_HINT_DEF(linebreakerclose, STRN("Close line breaker"))
DRIVER_HINT_DEF(linebreakeropen, STRN("Open line breaker"))
DRIVER_HINT_DEF(compressoron, STRN("Switch on compressor"))
DRIVER_HINT_DEF(compressoroff, STRN("Switch off compressor"))
DRIVER_HINT_DEF(frontmotorblowerson, STRN("Switch on front motor blowers"))
DRIVER_HINT_DEF(frontmotorblowersoff, STRN("Switch off front motor blowers"))
DRIVER_HINT_DEF(rearmotorblowerson, STRN("Switch on rear motor blowers"))
DRIVER_HINT_DEF(rearmotorblowersoff, STRN("Switch off rear motor blowers"))
DRIVER_HINT_DEF(springbrakeon, STRN("Apply spring brake"))
DRIVER_HINT_DEF(springbrakeoff, STRN("Release spring brake"))
DRIVER_HINT_DEF(manualbrakon, STRN("Apply manual brake"))
DRIVER_HINT_DEF(manualbrakoff, STRN("Release manual brake"))
DRIVER_HINT_DEF(mastercontrollersetidle, STRN("Set engine to idle"))
DRIVER_HINT_DEF(mastercontrollersetseriesmode, STRN("Set master controller to series mode"))
DRIVER_HINT_DEF(waterheateron, STRN("Switch on water heater"))
DRIVER_HINT_DEF(waterheateroff, STRN("Switch off water heater"))
DRIVER_HINT_DEF(waterheaterbreakeron, STRN("Switch on water heater breaker"))
DRIVER_HINT_DEF(waterheaterbreakeroff, STRN("Switch off water heater breaker"))
DRIVER_HINT_DEF(waterpumpon, STRN("Switch on water pump"))
DRIVER_HINT_DEF(waterpumpoff, STRN("Switch off water pump"))
DRIVER_HINT_DEF(waterpumpbreakeron, STRN("Switch on water pump breaker"))
DRIVER_HINT_DEF(waterpumpbreakeroff, STRN("Switch off water pump breaker"))
DRIVER_HINT_DEF(watercircuitslinkon, STRN("Link water circuits"))
DRIVER_HINT_DEF(watercircuitslinkoff, STRN("Unlink water circuits"))
DRIVER_HINT_DEF(waittemperaturetoolow, STRN("Wait for warm up to complete"))
DRIVER_HINT_DEF(mastercontrollersetzerospeed, STRN("Set master controller to neutral"))
DRIVER_HINT_DEF(mastercontrollersetreverserunlock, STRN("Set master controller to position %.0f"))
DRIVER_HINT_DEF(trainbrakesetpipeunlock, STRN("Set brake controller to pipe filling mode"))
DRIVER_HINT_DEF(trainbrakerelease, STRN("Release train brake"))
DRIVER_HINT_DEF(trainbrakeapply, STRN("Apply train brake"))
DRIVER_HINT_DEF(directionforward, STRN("Set reverser to forward"))
DRIVER_HINT_DEF(directionbackward, STRN("Set reverser to reverse"))
DRIVER_HINT_DEF(directionother, STRN("Switch reverser to opposite direction"))
DRIVER_HINT_DEF(directionnone, STRN("Set reverser to neutral"))
DRIVER_HINT_DEF(waitpressuretoolow, STRN("Wait for main reservoir to fill"))
DRIVER_HINT_DEF(waitpantographpressuretoolow, STRN("Wait for sufficient air pressure in pantograph subsystem"))
DRIVER_HINT_DEF(sandingon, STRN("Switch on sanding"))
DRIVER_HINT_DEF(sandingoff, STRN("Switch off sanding"))
DRIVER_HINT_DEF(consistdoorlockson, STRN("Switch on door locks"))
DRIVER_HINT_DEF(departuresignalon, STRN("Switch on departure signal"))
DRIVER_HINT_DEF(departuresignaloff, STRN("Switch off departure signal"))
DRIVER_HINT_DEF(doorrightopen, STRN("Open doors"))
DRIVER_HINT_DEF(doorrightclose, STRN("Close doors"))
DRIVER_HINT_DEF(doorleftopen, STRN("Open doors"))
DRIVER_HINT_DEF(doorleftclose, STRN("Close doors"))
DRIVER_HINT_DEF(doorrightpermiton, STRN("Grant permit to open doors"))
DRIVER_HINT_DEF(doorrightpermitoff, STRN("Revoke permit to open doors"))
DRIVER_HINT_DEF(doorleftpermiton, STRN("Grant permit to open doors"))
DRIVER_HINT_DEF(doorleftpermitoff, STRN("Revoke permit to open doors"))
DRIVER_HINT_DEF(hornon, STRN("Sound the horn"))
DRIVER_HINT_DEF(hornoff, STRN("Switch off horn"))
DRIVER_HINT_DEF(consistlightson, STRN("Switch on consist lights"))
DRIVER_HINT_DEF(consistlightsoff, STRN("Switch off consist lights"))
DRIVER_HINT_DEF(consistheatingon, STRN("Switch on consist heating"))
DRIVER_HINT_DEF(consistheatingoff, STRN("Switch off consist heating"))
DRIVER_HINT_DEF(securitysystemreset, STRN("Acknowledge alerter"))
DRIVER_HINT_DEF(shpsystemreset, STRN("Acknowledge SHP"))
DRIVER_HINT_DEF(couplingadapterattach, STRN("Attach coupling adapter"))
DRIVER_HINT_DEF(couplingadapterremove, STRN("Remove coupling adapter"))
DRIVER_HINT_DEF(secondcontrollersetzero, STRN("Switch off field shunting"))
DRIVER_HINT_DEF(tractiveforcedecrease, STRN("Reduce tractive force"))
DRIVER_HINT_DEF(tractiveforceincrease, STRN("Increase tractive force"))
DRIVER_HINT_DEF(brakingforcedecrease, STRN("Reduce braking force"))
DRIVER_HINT_DEF(brakingforceincrease, STRN("Increase braking force"))
DRIVER_HINT_DEF(brakingforcesetzero, STRN("Release train brakes"))
DRIVER_HINT_DEF(brakingforcelap, STRN("Lap train brake"))
DRIVER_HINT_DEF(independentbrakeapply, STRN("Apply independent brake"))
DRIVER_HINT_DEF(independentbrakerelease, STRN("Release independent brake"))
DRIVER_HINT_DEF(antislip, STRN("Apply anti slip brake"))
DRIVER_HINT_DEF(waitloadexchange, STRN("Wait for passenger exchange to complete"))
DRIVER_HINT_DEF(waitdeparturetime, STRN("Wait for departure time"))
DRIVER_HINT_DEF(headcodepc1, STRN("Switch on Pc 1 head lamp code"))
DRIVER_HINT_DEF(headcodepc2, STRN("Switch on Pc 2 head lamp code"))
DRIVER_HINT_DEF(headcodepc5, STRN("Switch on Pc 5 tail lamp code"))
DRIVER_HINT_DEF(headcodetb1, STRN("Switch on Tb 1 head lamp code"))
DRIVER_HINT_DEF(lightsoff, STRN("Switch off lights"))
DRIVER_HINT_DEF(releaseron, STRN("Actuate"))
DRIVER_HINT_DEF(releaseroff, STRN("Stop actuating"))
DRIVER_HINT_DEF(bufferscompress, STRN("Apply tractive force to compress buffers"))
DRIVER_HINT_DEF(cabactivation, STRN("Activate cabin"))
DRIVER_HINT_DEF(cabdeactivation, STRN("Deactivate cabin"))

1301
application/drivermode.cpp Normal file

File diff suppressed because it is too large Load Diff

119
application/drivermode.h Normal file
View File

@@ -0,0 +1,119 @@
/*
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/.
*/
#pragma once
#include "applicationmode.h"
#include "driverkeyboardinput.h"
#include "drivermouseinput.h"
#include "gamepadinput.h"
#include "Console.h"
#include "Camera.h"
#include "Classes.h"
#ifdef WITH_UART
#include "uart.h"
#endif
#ifdef WITH_ZMQ
#include "zmq_input.h"
#endif
class driver_mode : public application_mode {
public:
// constructors
driver_mode();
// methods
// initializes internal data structures of the mode. returns: true on success, false otherwise
bool init() override;
// mode-specific update of simulation data. returns: false on error, true otherwise
bool update() override;
// maintenance method, called when the mode is activated
void enter() override;
// maintenance method, called when the mode is deactivated
void exit() override;
// input handlers
void on_key( int Key, int Scancode, int Action, int Mods ) override;
void on_cursor_pos( double Horizontal, double Vertical ) override;
void on_mouse_button( int Button, int Action, int Mods ) override;
void on_scroll( double Xoffset, double Yoffset ) override;
void on_window_resize( int w, int h ) override { ; }
void on_event_poll() override;
bool is_command_processor() const override;
private:
// types
enum view {
consistfront,
consistrear,
bogie,
driveby,
count_
};
struct view_config {
TDynamicObject const *owner { nullptr };
Math3D::vector3 offset {};
Math3D::vector3 angle {};
};
struct drivermode_input {
gamepad_input gamepad;
drivermouse_input mouse;
glm::dvec2 mouse_pickmodepos; // stores last mouse position in control picking mode
driverkeyboard_input keyboard;
#ifdef _WIN32
Console console;
#endif
#ifdef WITH_UART
std::unique_ptr<uart_input> uart;
#endif
#ifdef WITH_ZMQ
std::unique_ptr<zmq_input> zmq;
#endif
std::unique_ptr<motiontelemetry> telemetry;
bool init();
void poll();
std::string
binding_hints( std::pair<user_command, user_command> const &Commands ) const;
std::pair<user_command, user_command>
command_fallback( user_command const Command ) const;
};
// methods
void update_camera( const double Deltatime );
// handles vehicle change flag
void OnKeyDown( int cKey );
void InOutKey();
void CabView();
void ExternalView();
void DistantView( bool const Near = false );
void set_picking( bool const Picking );
// members
drivermode_input m_input;
std::array<basic_event *, 10> KeyEvents { nullptr }; // eventy wyzwalane z klawiaury
TCamera Camera;
TCamera DebugCamera;
int m_externalviewmode { view::consistfront }; // selected external view mode
bool m_externalview { true };
std::array<view_config, view::count_> m_externalviewconfigs;
TDynamicObject *pDynamicNearest { nullptr }; // vehicle nearest to the active camera. TODO: move to camera
double fTime50Hz { 0.0 }; // bufor czasu dla komunikacji z PoKeys
double const m_primaryupdaterate { 1.0 / 100.0 };
double const m_secondaryupdaterate { 1.0 / 50.0 };
double m_primaryupdateaccumulator { m_secondaryupdaterate }; // keeps track of elapsed simulation time, for core fixed step routines
double m_secondaryupdateaccumulator { m_secondaryupdaterate }; // keeps track of elapsed simulation time, for less important fixed step routines
int iPause { 0 }; // wykrywanie zmian w zapauzowaniu
command_relay m_relay;
std::string change_train; // train name awaiting entering
};

View File

@@ -0,0 +1,279 @@
/*
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 "driveruilayer.h"
#include "Globals.h"
#include "application.h"
#include "translation.h"
#include "simulation.h"
#include "Train.h"
#include "AnimModel.h"
#include "renderer.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(&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;
}
}
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();
}
}

View File

@@ -0,0 +1,72 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
#include "driveruipanels.h"
#include "command.h"
#include "widgets/vehiclelist.h"
#include "widgets/vehicleparams.h"
#include "widgets/map.h"
#include "widgets/time.h"
#include "widgets/trainingcard.h"
#include "widgets/perfgraphs.h"
#include "widgets/cameraview_extcam.h"
class driver_ui : public ui_layer {
public:
// constructors
driver_ui();
// methods
void showDebugUI() override;
// potentially processes provided input key. returns: true if the input was processed, false otherwise
bool
on_key( int const Key, int const Action ) override;
// potentially processes provided mouse movement. returns: true if the input was processed, false otherwise
bool
on_cursor_pos( double const Horizontal, double const Vertical ) override;
// potentially processes provided mouse button. returns: true if the input was processed, false otherwise
bool
on_mouse_button( int const Button, int const Action ) override;
// updates state of UI elements
void
update() override;
protected:
void render_menu_contents() override;
private:
// methods
// sets visibility of the cursor
void
set_cursor( bool const Visible );
// render() subclass details
void
render_() override;
// members
drivingaid_panel m_aidpanel { "Driving Aid", false };
scenario_panel m_scenariopanel { "Scenario", false };
timetable_panel m_timetablepanel { "Timetable", false };
debug_panel m_debugpanel { "Debug Data", false };
transcripts_panel m_transcriptspanel { "Transcripts", true }; // voice transcripts
trainingcard_panel m_trainingcardpanel;
perfgraph_panel m_perfgraphpanel;
bool m_paused { false };
command_relay m_relay;
ui::vehiclelist_panel m_vehiclelist { ui::vehiclelist_panel(*this) };
ui::map_panel m_mappanel;
ui::time_panel m_timepanel;
ui::cameraview_panel m_cameraviewpanel;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
#include "Classes.h"
class drivingaid_panel : public ui_expandable_panel {
public:
drivingaid_panel( std::string const &Name, bool const Isopen )
: ui_expandable_panel( Name, Isopen )
{}
void update() override;
private:
// members
std::array<char, 256> m_buffer;
};
class timetable_panel : public ui_expandable_panel {
public:
timetable_panel( std::string const &Name, bool const Isopen )
: ui_expandable_panel( Name, Isopen ) {}
void update() override;
void render() override;
private:
// members
std::array<char, 256> m_buffer;
std::vector<text_line> m_tablelines;
};
class scenario_panel : public ui_panel {
public:
scenario_panel( std::string const &Name, bool const Isopen )
: ui_panel( Name, Isopen ) {}
void update() override;
void render() override;
bool is_expanded{ false };
private:
// members
std::array<char, 256> m_buffer;
TDynamicObject const *m_nearest { nullptr };
};
class debug_panel : public ui_panel {
public:
debug_panel( std::string const &Name, bool const Isopen )
: ui_panel( Name, Isopen ) {
m_eventsearch.fill( 0 ); }
void update() override;
void render() override;
private:
// types
struct input_data {
TTrain const *train;
TDynamicObject const *controlled;
TCamera const *camera;
TDynamicObject const *vehicle;
TMoverParameters *mover;
TController const *mechanik;
};
// methods
// generate and send section data to provided output
void update_section_vehicle( std::vector<text_line> &Output );
void update_section_engine( std::vector<text_line> &Output );
void update_section_ai( std::vector<text_line> &Output );
void update_section_scantable( std::vector<text_line> &Output );
void update_section_scenario( std::vector<text_line> &Output );
void update_section_eventqueue( std::vector<text_line> &Output );
void update_section_powergrid( std::vector<text_line> &Output );
void update_section_camera( std::vector<text_line> &Output );
void update_section_renderer( std::vector<text_line> &Output );
#ifdef WITH_UART
void update_section_uart( std::vector<text_line> &Output );
#endif
// section update helpers
std::string update_vehicle_coupler( int const Side );
std::string update_vehicle_brake() const;
// renders provided lines, under specified collapsing header
bool render_section( std::string const &Header, std::vector<text_line> const &Lines );
bool render_section( std::vector<text_line> const &Lines );
bool render_section_scenario();
bool render_section_eventqueue();
#ifdef WITH_UART
bool render_section_uart();
#endif
bool render_section_settings();
bool render_section_developer();
// members
std::array<char, 1024> m_buffer;
std::array<char, 128> m_eventsearch;
input_data m_input;
std::vector<text_line>
m_vehiclelines,
m_enginelines,
m_ailines,
m_scantablelines,
m_cameralines,
m_scenariolines,
m_eventqueuelines,
m_powergridlines,
m_rendererlines,
m_uartlines;
double last_time = std::numeric_limits<double>::quiet_NaN();
struct graph_data
{
double last_val = 0.0;
std::array<float, 150> data = { 0.0f };
size_t pos = 0;
float range = 25.0f;
void update(float data);
void render();
};
graph_data AccN_jerk_graph;
graph_data AccN_acc_graph;
float last_AccN;
std::array<char, 128> queue_event_buf = { 0 };
std::array<char, 128> queue_event_activator_buf = { 0 };
bool m_eventqueueactivevehicleonly { false };
};
class transcripts_panel : public ui_panel {
public:
transcripts_panel( std::string const &Name, bool const Isopen )
: ui_panel( Name, Isopen ) {}
void update() override;
void render() override;
};

913
application/editormode.cpp Normal file
View File

@@ -0,0 +1,913 @@
/*
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 "editormode.h"
#include "editoruilayer.h"
#include "application.h"
#include "Globals.h"
#include "simulation.h"
#include "simulationtime.h"
#include "simulationenvironment.h"
#include "Timer.h"
#include "Console.h"
#include "renderer.h"
#include "AnimModel.h"
#include "scene.h"
#include "imgui/imgui.h"
#include "Logs.h"
#include <cmath>
#include <functional>
#include <vector>
// Static member initialization
TCamera editor_mode::Camera;
bool editor_mode::m_focus_active = false;
bool editor_mode::m_change_history = true;
namespace
{
using vec3 = glm::vec3;
using dvec2 = glm::dvec2;
inline bool is_release(int state)
{
return state == GLFW_RELEASE;
}
inline bool is_press(int state)
{
return state == GLFW_PRESS;
}
}
bool editor_mode::editormode_input::init()
{
return (mouse.init() && keyboard.init());
}
void editor_mode::editormode_input::poll()
{
keyboard.poll();
}
editor_mode::editor_mode() {
m_userinterface = std::make_shared<editor_ui>();
}
editor_ui *editor_mode::ui() const
{
return static_cast<editor_ui *>(m_userinterface.get());
}
bool editor_mode::init()
{
Camera.Init({0, 15, 0}, {glm::radians(-30.0), glm::radians(180.0), 0}, nullptr);
return m_input.init();
}
void editor_mode::apply_rotation_for_new_node(scene::basic_node *node, int rotation_mode, float fixed_rotation_value)
{
if (!node)
return;
if (rotation_mode == functions_panel::RANDOM)
{
const vec3 rotation{0.0f, LocalRandom(0.0, 360.0), 0.0f};
m_editor.rotate(node, rotation, 1);
}
else if (rotation_mode == functions_panel::FIXED)
{
const vec3 rotation{0.0f, fixed_rotation_value, 0.0f};
m_editor.rotate(node, rotation, 0);
}
}
void editor_mode::start_focus(scene::basic_node *node, double duration)
{
if (!node)
return;
m_focus_active = true;
m_focus_time = 0.0;
m_focus_duration = duration;
m_focus_start_pos = Camera.Pos;
m_focus_start_lookat = Camera.LookAt;
m_focus_target_lookat = node->location();
glm::dvec3 dir = m_focus_start_pos - m_focus_start_lookat;
double dist = glm::length(dir);
m_focus_target_pos = m_focus_target_lookat + glm::dvec3(10.0, 3.0, 10.0);
}
void editor_mode::handle_brush_mouse_hold(int Action, int Button)
{
auto const mode = ui()->mode();
auto const rotation_mode = ui()->rot_mode();
auto const fixed_rotation_value = ui()->rot_val();
if(mode != nodebank_panel::BRUSH)
return;
GfxRenderer->Pick_Node_Callback(
[this, mode, rotation_mode, fixed_rotation_value, Action, Button](scene::basic_node * /*node*/) {
const std::string *src = ui()->get_active_node_template();
if (!src)
return;
std::string name = "editor_";
glm::dvec3 newPos = clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
double distance = glm::distance(newPos, oldPos);
if (distance < ui()->getSpacing())
return;
TAnimModel *cloned = simulation::State.create_model(*src, name, Camera.Pos + newPos);
oldPos = newPos;
if (!cloned)
return;
std::string new_name = "editor_" + cloned->uuid.to_string();
cloned->m_name = new_name;
std::string as_text;
cloned->export_as_text(as_text);
push_snapshot(cloned, EditorSnapshot::Action::Add, as_text);
m_node = cloned;
apply_rotation_for_new_node(m_node, rotation_mode, fixed_rotation_value);
ui()->set_node(m_node);
});
}
void editor_mode::add_to_hierarchy(scene::basic_node *node)
{
if (!node) return;
scene::Hierarchy[node->uuid.to_string()] = node;
}
void editor_mode::remove_from_hierarchy(scene::basic_node *node)
{
if (!node) return;
auto it = scene::Hierarchy.find(node->uuid.to_string());
if (it != scene::Hierarchy.end())
scene::Hierarchy.erase(it);
}
scene::basic_node* editor_mode::find_in_hierarchy(const std::string &uuid_str)
{
if (uuid_str.empty()) return nullptr;
auto it = scene::Hierarchy.find(uuid_str);
return (it != scene::Hierarchy.end()) ? it->second : nullptr;
}
scene::basic_node* editor_mode::find_node_by_any(scene::basic_node *node_ptr, const std::string &uuid_str, const std::string &name)
{
if (node_ptr) return node_ptr;
if (!uuid_str.empty()) {
auto *node = find_in_hierarchy(uuid_str);
if (node) return node;
}
if (!name.empty()) {
return simulation::Instances.find(name);
}
return nullptr;
}
void editor_mode::push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action, std::string const &Serialized)
{
if (!node)
return;
if(m_max_history_size >= 0 && (int)m_history.size() >= m_max_history_size)
{
m_history.erase(m_history.begin(), m_history.begin() + ((int)m_history.size() - m_max_history_size + 1));
}
EditorSnapshot snap;
snap.action = Action;
snap.node_name = node->name();
snap.position = node->location();
snap.uuid = node->uuid;
if (auto *model = dynamic_cast<TAnimModel *>(node))
{
snap.rotation = model->Angles();
}
else
{
snap.rotation = glm::vec3(0.0f);
}
if (Action == EditorSnapshot::Action::Delete || Action == EditorSnapshot::Action::Add)
{
if (!Serialized.empty())
snap.serialized = Serialized;
else
node->export_as_text(snap.serialized);
}
snap.node_ptr = node;
m_history.push_back(std::move(snap));
g_redo.clear();
}
glm::dvec3 editor_mode::clamp_mouse_offset_to_max(const glm::dvec3 &offset)
{
double len = glm::length(offset);
if (len <= static_cast<double>(kMaxPlacementDistance) || len <= 1e-6)
return offset;
return glm::normalize(offset) * static_cast<double>(kMaxPlacementDistance);
}
void editor_mode::nullify_history_pointers(scene::basic_node *node)
{
if (!node)
return;
for (auto &s : m_history)
{
if (s.node_ptr == node)
s.node_ptr = nullptr;
}
for (auto &s : g_redo)
{
if (s.node_ptr == node)
s.node_ptr = nullptr;
}
}
void editor_mode::undo_last()
{
if (m_history.empty())
return;
EditorSnapshot snap = m_history.back();
m_history.pop_back();
if (snap.action == EditorSnapshot::Action::Delete)
{
// undo delete -> recreate model
EditorSnapshot redoSnap;
redoSnap.action = EditorSnapshot::Action::Delete;
redoSnap.node_name = snap.node_name;
redoSnap.serialized = snap.serialized;
redoSnap.position = snap.position;
redoSnap.node_ptr = nullptr;
g_redo.push_back(std::move(redoSnap));
TAnimModel *created = simulation::State.create_model(snap.serialized, snap.node_name, snap.position);
if (created)
{
created->location(snap.position);
created->Angles(snap.rotation);
m_node = created;
m_node->uuid = snap.uuid; // restore original UUID for better tracking (not strictly necessary)
add_to_hierarchy(created);
ui()->set_node(m_node);
}
return;
}
scene::basic_node *target = find_node_by_any(snap.node_ptr, snap.uuid.to_string(), snap.node_name);
if (!target)
return;
EditorSnapshot current;
current.action = snap.action;
current.node_name = snap.node_name;
current.node_ptr = target;
current.position = target->location();
if (auto *model = dynamic_cast<TAnimModel *>(target))
current.rotation = model->Angles();
else
current.rotation = glm::vec3(0.0f);
g_redo.push_back(std::move(current));
if (snap.action == EditorSnapshot::Action::Add)
{
// undo add -> delete the instance
if (auto *model = dynamic_cast<TAnimModel *>(target))
{
nullify_history_pointers(model);
remove_from_hierarchy(model);
simulation::State.delete_model(model);
m_node = nullptr;
ui()->set_node(nullptr);
}
return;
}
target->location(snap.position);
if (auto *model = dynamic_cast<TAnimModel *>(target))
{
glm::vec3 cur = model->Angles();
glm::vec3 delta = snap.rotation - cur;
m_editor.rotate(target, delta, 0);
}
m_node = target;
ui()->set_node(m_node);
}
void editor_mode::redo_last()
{
if (g_redo.empty())
return;
EditorSnapshot snap = g_redo.back();
g_redo.pop_back();
// handle delete redo (re-delete) separately
if (snap.action == EditorSnapshot::Action::Delete)
{
EditorSnapshot hist;
hist.action = snap.action;
hist.node_name = snap.node_name;
hist.serialized = snap.serialized;
hist.position = snap.position;
hist.uuid = snap.uuid;
m_history.push_back(std::move(hist));
scene::basic_node *target = simulation::Instances.find(snap.node_name);
if (target)
{
if (auto *model = dynamic_cast<TAnimModel *>(target))
{
nullify_history_pointers(model);
remove_from_hierarchy(model);
simulation::State.delete_model(model);
m_node = nullptr;
ui()->set_node(nullptr);
}
}
return;
}
scene::basic_node *target = find_node_by_any(snap.node_ptr, snap.uuid.to_string(), snap.node_name);
EditorSnapshot hist;
hist.action = snap.action;
hist.node_name = snap.node_name;
hist.node_ptr = target;
if (target)
{
hist.position = target->location();
if (auto *model = dynamic_cast<TAnimModel *>(target))
hist.rotation = model->Angles();
hist.uuid = snap.uuid;
}
m_history.push_back(std::move(hist));
if (snap.action == EditorSnapshot::Action::Add)
{
TAnimModel *created = simulation::State.create_model(snap.serialized, snap.node_name, snap.position);
if (created)
{
created->location(snap.position);
created->Angles(snap.rotation);
m_node = created;
m_node->uuid = snap.uuid;
ui()->set_node(m_node);
if (!m_history.empty())
m_history.back().node_ptr = created;
}
return;
}
if (!target)
return;
// apply redo position
target->location(snap.position);
if (auto *model = dynamic_cast<TAnimModel *>(target))
{
glm::vec3 cur = model->Angles();
glm::vec3 delta = snap.rotation - cur;
m_editor.rotate(target, delta, 0);
}
m_node = target;
ui()->set_node(m_node);
}
bool editor_mode::update()
{
Timer::UpdateTimers(true);
simulation::State.update_clocks();
simulation::Environment.update();
auto const deltarealtime = Timer::GetDeltaRenderTime();
// fixed step render time routines (50 Hz)
fTime50Hz += deltarealtime; // accumulate even when paused to keep frame reads stable
while (fTime50Hz >= 1.0 / 50.0)
{
#ifdef _WIN32
Console::Update();
#endif
m_userinterface->update();
// update brush settings visibility depending on panel mode
ui()->toggleBrushSettings(ui()->mode() == nodebank_panel::BRUSH);
if (mouseHold)
{
// process continuous brush placement
if(ui()->mode() == nodebank_panel::BRUSH)
handle_brush_mouse_hold(GLFW_REPEAT, GLFW_MOUSE_BUTTON_LEFT);
}
// decelerate camera velocity with thresholding
Camera.Velocity *= 0.65f;
if (std::abs(Camera.Velocity.x) < 0.01)
Camera.Velocity.x = 0.0;
if (std::abs(Camera.Velocity.y) < 0.01)
Camera.Velocity.y = 0.0;
if (std::abs(Camera.Velocity.z) < 0.01)
Camera.Velocity.z = 0.0;
fTime50Hz -= 1.0 / 50.0;
}
// variable step routines
update_camera(deltarealtime);
simulation::Region->update_sounds();
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
GfxRenderer->Update(deltarealtime);
simulation::is_ready = true;
// --- ImGui: Editor History Window & Settings ---
if(!m_change_history) return true;
render_change_history();
return true;
}
void editor_mode::update_camera(double const Deltatime)
{
// account for keyboard-driven motion
// if focus animation active, interpolate camera toward target
if (m_focus_active)
{
m_focus_time += Deltatime;
double t = m_focus_duration > 0.0 ? (m_focus_time / m_focus_duration) : 1.0;
if (t >= 1.0)
t = 1.0;
// smoothstep easing
double s = t * t * (3.0 - 2.0 * t);
Camera.Pos = glm::mix(m_focus_start_pos, m_focus_target_pos, s);
Camera.LookAt = glm::mix(m_focus_start_lookat, m_focus_target_lookat, s);
if (t >= 1.0)
m_focus_active = false;
}
Camera.Update();
// reset window state (will be set again if UI requires it)
Global.CabWindowOpen = false;
// publish camera back to global copy
Global.pCamera = Camera;
}
void editor_mode::enter()
{
m_statebackup = {Global.pCamera, FreeFlyModeFlag, Global.ControlPicking};
Camera = Global.pCamera;
if (!FreeFlyModeFlag)
{
auto const *vehicle = Camera.m_owner;
if (vehicle)
{
auto const cab = (vehicle->MoverParameters->CabOccupied == 0 ? 1 : vehicle->MoverParameters->CabOccupied);
auto const left = vehicle->VectorLeft() * cab;
Camera.Pos = Math3D::vector3(Camera.Pos.x, vehicle->GetPosition().y, Camera.Pos.z) + left * vehicle->GetWidth() + Math3D::vector3(1.25f * left.x, 1.6f, 1.25f * left.z);
Camera.m_owner = nullptr;
Camera.LookAt = vehicle->GetPosition();
Camera.RaLook(); // single camera reposition
FreeFlyModeFlag = true;
}
}
Global.ControlPicking = true;
EditorModeFlag = true;
Application.set_cursor(GLFW_CURSOR_NORMAL);
}
void editor_mode::exit()
{
EditorModeFlag = false;
Global.ControlPicking = m_statebackup.picking;
FreeFlyModeFlag = m_statebackup.freefly;
Global.pCamera = m_statebackup.camera;
g_redo.clear();
m_history.clear();
Application.set_cursor((Global.ControlPicking ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED));
if (!Global.ControlPicking)
{
Application.set_cursor_pos(0, 0);
}
}
void editor_mode::on_key(int const Key, int const Scancode, int const Action, int const Mods)
{
#ifndef __unix__
Global.shiftState = (Mods & GLFW_MOD_SHIFT) ? true : false;
Global.ctrlState = (Mods & GLFW_MOD_CONTROL) ? true : false;
Global.altState = (Mods & GLFW_MOD_ALT) ? true : false;
#endif
bool anyModifier = Mods & (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT);
// first give UI a chance to handle the key
if (!anyModifier && m_userinterface->on_key(Key, Action))
return;
// then internal input handling
if (m_input.keyboard.key(Key, Action))
return;
if (Action == GLFW_RELEASE)
return;
// shortcuts: undo/redo
if (Global.ctrlState && Key == GLFW_KEY_Z && is_press(Action))
{
undo_last();
return;
}
if (Global.ctrlState && Key == GLFW_KEY_Y && is_press(Action))
{
redo_last();
return;
}
// legacy hardcoded keyboard commands
switch (Key)
{
case GLFW_KEY_F11:
if (Action != GLFW_PRESS)
break;
if (!Global.ctrlState && !Global.shiftState)
{
Application.pop_mode();
}
else if (Global.ctrlState && Global.shiftState)
{
simulation::State.export_as_text(Global.SceneryFile);
}
break;
case GLFW_KEY_F12:
if (Global.ctrlState && Global.shiftState && is_press(Action))
{
DebugModeFlag = !DebugModeFlag;
}
break;
case GLFW_KEY_DELETE:
if (is_press(Action))
{
TAnimModel *model = dynamic_cast<TAnimModel *>(m_node);
if (model)
{
// record deletion for undo (serialize full node)
std::string as_text;
model->export_as_text(as_text);
std::string debug = "Deleting node: " + as_text + "\nSerialized data:\n";
push_snapshot(model, EditorSnapshot::Action::Delete, as_text);
WriteLog(debug, logtype::generic);
// clear history pointers referencing this model before actually deleting it
nullify_history_pointers(model);
remove_from_hierarchy(model);
m_node = nullptr;
m_dragging = false;
ui()->set_node(nullptr);
simulation::State.delete_model(model);
}
}
break;
case GLFW_KEY_F:
if (is_press(Action))
{
if(!m_node)
break;
// start smooth focus camera on selected node
start_focus(m_node, 0.6);
}
break;
default:
break;
}
}
void editor_mode::on_cursor_pos(double const Horizontal, double const Vertical)
{
dvec2 const mousemove = dvec2{Horizontal, Vertical} - m_input.mouse.position();
m_input.mouse.position(Horizontal, Vertical);
if (m_input.mouse.button(GLFW_MOUSE_BUTTON_LEFT) == GLFW_RELEASE)
return;
if (!m_node)
return;
if (m_takesnapshot)
{
// record appropriate action type depending on current input mode
if (mode_rotationX() || mode_rotationY() || mode_rotationZ())
push_snapshot(m_node, EditorSnapshot::Action::Rotate);
else
push_snapshot(m_node, EditorSnapshot::Action::Move);
m_takesnapshot = false;
}
if (mode_translation())
{
if (mode_translation_vertical())
{
float const translation = static_cast<float>(mousemove.y * -0.01);
m_editor.translate(m_node, translation);
}
else
{
auto mouseOffset = clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
auto const mouseworldposition = Camera.Pos + mouseOffset;
m_editor.translate(m_node, mouseworldposition, mode_snap());
}
}
else if (mode_rotationY())
{
vec3 const rotation{0.0f, static_cast<float>(mousemove.x) * 0.25f, 0.0f};
float const quantization = (mode_snap() ? 5.0f : 0.0f);
m_editor.rotate(m_node, rotation, quantization);
}
else if (mode_rotationZ())
{
vec3 const rotation{0.0f, 0.0f, static_cast<float>(mousemove.x) * 0.25f};
float const quantization = (mode_snap() ? 5.0f : 0.0f);
m_editor.rotate(m_node, rotation, quantization);
}
else if (mode_rotationX())
{
vec3 const rotation{static_cast<float>(mousemove.y) * 0.25f, 0.0f, 0.0f};
float const quantization = (mode_snap() ? 5.0f : 0.0f);
m_editor.rotate(m_node, rotation, quantization);
}
}
void editor_mode::on_mouse_button(int const Button, int const Action, int const Mods)
{
// UI first
if (m_userinterface->on_mouse_button(Button, Action))
{
m_input.mouse.button(Button, Action);
return;
}
if (Button == GLFW_MOUSE_BUTTON_LEFT)
{
auto const mode = ui()->mode();
auto const rotation_mode = ui()->rot_mode();
auto const fixed_rotation_value = ui()->rot_val();
if (is_press(Action))
{
mouseHold = true;
m_node = nullptr;
// delegate node picking behaviour depending on current panel mode
GfxRenderer->Pick_Node_Callback(
[this, mode, rotation_mode, fixed_rotation_value](scene::basic_node *node) {
// ignore picks that are beyond allowed placement distance
if (node) {
double const dist = glm::distance(node->location(), glm::dvec3{Global.pCamera.Pos});
if (dist > static_cast<double>(kMaxPlacementDistance))
return;
}
if (mode == nodebank_panel::MODIFY)
{
if (!m_dragging)
return;
m_node = node;
ui()->set_node(m_node);
}
else if (mode == nodebank_panel::COPY)
{
if (node && typeid(*node) == typeid(TAnimModel))
{
std::string as_text;
node->export_as_text(as_text);
ui()->add_node_template(as_text);
}
m_dragging = false;
}
else if (mode == nodebank_panel::ADD)
{
const std::string *src = ui()->get_active_node_template();
if (!src)
return;
std::string name = "editor_";
glm::dvec3 mouseOffset = clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
TAnimModel *cloned = simulation::State.create_model(*src, name, Camera.Pos + mouseOffset);
if (!cloned)
return;
// record addition for undo
std::string as_text;
std::string new_name = "editor_" + cloned->uuid.to_string();
cloned->m_name = new_name;
cloned->export_as_text(as_text);
push_snapshot(cloned, EditorSnapshot::Action::Add, as_text);
if (!m_dragging)
return;
m_node = cloned;
apply_rotation_for_new_node(m_node, rotation_mode, fixed_rotation_value);
ui()->set_node(m_node);
}
});
m_dragging = true;
m_takesnapshot = true;
}
else
{
if (is_release(Action))
mouseHold = false;
m_dragging = false;
}
}
m_input.mouse.button(Button, Action);
}
void editor_mode::render_change_history(){
ImGui::Begin("Editor History", &m_change_history, ImGuiWindowFlags_AlwaysAutoResize);
int maxsize = m_max_history_size;
if (ImGui::InputInt("Max history size", &maxsize))
{
m_max_history_size = std::max(0, maxsize);
if ((int)m_history.size() > m_max_history_size && m_max_history_size >= 0)
{
auto remove_count = (int)m_history.size() - m_max_history_size;
m_history.erase(m_history.begin(), m_history.begin() + remove_count);
// adjust selected index
if (m_selected_history_idx >= (int)m_history.size())
m_selected_history_idx = (int)m_history.size() - 1;
}
}
float dist = kMaxPlacementDistance;
if (ImGui::InputFloat("Max placement distance", &dist))
{
kMaxPlacementDistance = std::max(0.0f, dist);
}
ImGui::Separator();
ImGui::Text("History (newest at end): %zu entries", m_history.size());
ImGui::BeginChild("history_list", ImVec2(400, 200), true);
for (int i = 0; i < (int)m_history.size(); ++i)
{
auto &s = m_history[i];
char buf[256];
std::snprintf(buf, sizeof(buf), "%3d: %s %s pos=(%.1f,%.1f,%.1f)", i,
(s.action == EditorSnapshot::Action::Add) ? "ADD" :
(s.action == EditorSnapshot::Action::Delete) ? "DEL" :
(s.action == EditorSnapshot::Action::Move) ? "MOV" :
(s.action == EditorSnapshot::Action::Rotate) ? "ROT" : "OTH",
s.node_name.empty() ? "(noname)" : s.node_name.c_str(),
s.position.x, s.position.y, s.position.z);
if (ImGui::Selectable(buf, m_selected_history_idx == i))
m_selected_history_idx = i;
}
ImGui::EndChild();
ImGui::Separator();
if (ImGui::Button("Clear History"))
{
m_history.clear();
g_redo.clear();
m_selected_history_idx = -1;
}
ImGui::SameLine();
ImGui::SameLine();
if (ImGui::Button("Undo Selected"))
{
if (m_selected_history_idx >= 0 && m_selected_history_idx < (int)m_history.size())
{
int target = m_selected_history_idx;
int undoCount = (int)m_history.size() - 1 - target;
for (int k = 0; k < undoCount; ++k)
undo_last();
m_selected_history_idx = -1;
}
}
ImGui::End();
}
void editor_mode::on_event_poll()
{
m_input.poll();
}
bool editor_mode::is_command_processor() const
{
return false;
}
bool editor_mode::mode_translation() const
{
return (false == Global.altState);
}
bool editor_mode::mode_translation_vertical() const
{
return (true == Global.shiftState);
}
bool editor_mode::mode_rotationY() const
{
return ((true == Global.altState) && (false == Global.ctrlState) && (false == Global.shiftState));
}
bool editor_mode::mode_rotationX() const
{
return ((true == Global.altState) && (true == Global.ctrlState) && (false == Global.shiftState));
}
bool editor_mode::mode_rotationZ() const
{
return ((true == Global.altState) && (true == Global.ctrlState) && (true == Global.shiftState));
}
bool editor_mode::mode_snap() const
{
return ((false == Global.altState) && (true == Global.ctrlState) && (false == Global.shiftState));
}
bool editor_mode::focus_active()
{
return m_focus_active;
}
void editor_mode::set_focus_active(bool isActive)
{
m_focus_active = isActive;
}

146
application/editormode.h Normal file
View File

@@ -0,0 +1,146 @@
/*
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/.
*/
#pragma once
#include "applicationmode.h"
#include "editormouseinput.h"
#include "editorkeyboardinput.h"
#include "Camera.h"
#include "sceneeditor.h"
#include "scenenode.h"
class editor_mode : public application_mode
{
public:
// constructors
editor_mode();
// methods
// initializes internal data structures of the mode. returns: true on success, false otherwise
bool init() override;
// mode-specific update of simulation data. returns: false on error, true otherwise
bool update() override;
// maintenance method, called when the mode is activated
void enter() override;
// maintenance method, called when the mode is deactivated
void exit() override;
// input handlers
void on_key(int Key, int Scancode, int Action, int Mods) override;
void on_cursor_pos(double Horizontal, double Vertical) override;
void on_mouse_button(int Button, int Action, int Mods) override;
void on_scroll(double const Xoffset, double const Yoffset) override
{
;
}
void on_window_resize(int w, int h) override
{
;
}
void on_event_poll() override;
bool is_command_processor() const override;
void undo_last();
static bool focus_active();
static void set_focus_active(bool isActive);
static TCamera& get_camera() { return Camera; }
static bool change_history() { return m_change_history; }
static void set_change_history(bool enabled) { m_change_history = enabled; }
private:
// types
struct editormode_input
{
editormouse_input mouse;
editorkeyboard_input keyboard;
bool init();
void poll();
};
struct state_backup
{
TCamera camera;
bool freefly;
bool picking;
};
struct EditorSnapshot
{
enum class Action { Move, Rotate, Add, Delete, Other };
Action action{Action::Other};
std::string node_name; // node identifier (basic_node::name())
// direct pointer to node when available; used for in-memory undo/redo lookup
scene::basic_node *node_ptr{nullptr};
std::string serialized; // full text for recreate (used for Add/Delete)
glm::dvec3 position{0.0, 0.0, 0.0};
glm::vec3 rotation{0.0f, 0.0f, 0.0f};
UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes
};
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
std::vector<EditorSnapshot> m_history; // history of changes to nodes, used for undo functionality
std::vector<EditorSnapshot> g_redo;
// methods
void update_camera(double const Deltatime);
bool mode_translation() const;
bool mode_translation_vertical() const;
bool mode_rotationY() const;
bool mode_rotationX() const;
bool mode_rotationZ() const;
bool mode_snap() const;
editor_ui *ui() const;
void redo_last();
void handle_brush_mouse_hold(int Action, int Button);
void apply_rotation_for_new_node(scene::basic_node *node, int rotation_mode, float fixed_rotation_value);
// members
state_backup m_statebackup; // helper, cached variables to be restored on mode exit
editormode_input m_input;
static TCamera Camera;
// focus (smooth camera fly-to) state
static bool m_focus_active;
glm::dvec3 m_focus_start_pos{0.0,0.0,0.0};
glm::dvec3 m_focus_target_pos{0.0,0.0,0.0};
glm::dvec3 m_focus_start_lookat{0.0,0.0,0.0};
glm::dvec3 m_focus_target_lookat{0.0,0.0,0.0};
double m_focus_time{0.0};
double m_focus_duration{0.6};
double fTime50Hz{0.0}; // bufor czasu dla komunikacji z PoKeys
scene::basic_editor m_editor;
scene::basic_node *m_node; // currently selected scene node
bool m_takesnapshot{true}; // helper, hints whether snapshot of selected node(s) should be taken before modification
bool m_dragging = false;
glm::dvec3 oldPos;
bool mouseHold{false};
float kMaxPlacementDistance = 200.0f;
static bool m_change_history;
// UI/history settings
int m_max_history_size{200};
int m_selected_history_idx{-1};
glm::dvec3 clamp_mouse_offset_to_max(const glm::dvec3 &offset);
// focus camera smoothly on specified node
void start_focus(scene::basic_node *node, double duration = 0.6);
// hierarchy management
void add_to_hierarchy(scene::basic_node *node);
void remove_from_hierarchy(scene::basic_node *node);
scene::basic_node* find_in_hierarchy(const std::string &uuid_str);
scene::basic_node* find_node_by_any(scene::basic_node *node_ptr, const std::string &uuid_str, const std::string &name);
// clear history/redo pointers that reference the given node (prevent dangling pointers)
void nullify_history_pointers(scene::basic_node *node);
void render_change_history();
};

View File

@@ -0,0 +1,96 @@
/*
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 "editoruilayer.h"
#include "Globals.h"
#include "scenenode.h"
#include "renderer.h"
editor_ui::editor_ui()
{
clear_panels();
// bind the panels with ui object. maybe not the best place for this but, eh
add_external_panel(&m_itempropertiespanel);
add_external_panel(&m_nodebankpanel);
add_external_panel(&m_functionspanel);
add_external_panel(&m_brushobjects);
}
// updates state of UI elements
void editor_ui::update()
{
set_tooltip("");
if (Global.ControlPicking && DebugModeFlag)
{
const auto sceneryNode = GfxRenderer->Pick_Node();
const std::string content = sceneryNode ? sceneryNode->tooltip() : "";
set_tooltip(content);
}
ui_layer::update();
m_itempropertiespanel.update(m_node);
m_functionspanel.update(m_node);
auto ptr = get_active_node_template(true);
if (ptr)
m_brushobjects.update(*ptr);
}
void editor_ui::toggleBrushSettings(bool isVisible)
{
if (m_brushobjects.is_open != isVisible)
m_brushobjects.is_open = isVisible;
}
void editor_ui::set_node(scene::basic_node *Node)
{
m_node = Node;
}
void editor_ui::add_node_template(const std::string &desc)
{
m_nodebankpanel.add_template(desc);
}
std::string const *editor_ui::get_active_node_template(bool bypassRandom)
{
if (!bypassRandom && m_brushobjects.is_open && m_brushobjects.useRandom && m_brushobjects.Objects.size() > 0)
{
return m_brushobjects.GetRandomObject();
}
return m_nodebankpanel.get_active_template();
}
nodebank_panel::edit_mode editor_ui::mode()
{
return m_nodebankpanel.mode;
}
float editor_ui::getSpacing()
{
return m_brushobjects.spacing;
}
functions_panel::rotation_mode editor_ui::rot_mode()
{
return m_functionspanel.rot_mode;
}
float editor_ui::rot_val()
{
return m_functionspanel.rot_value;
}
bool editor_ui::rot_from_last()
{
return m_functionspanel.rot_from_last;
}

View File

@@ -0,0 +1,48 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
#include "editoruipanels.h"
namespace scene
{
class basic_node;
}
class editor_ui : public ui_layer
{
public:
// constructors
editor_ui();
// methods
// updates state of UI elements
void update() override;
void set_node(scene::basic_node *Node);
void add_node_template(const std::string &desc);
float rot_val();
bool rot_from_last();
functions_panel::rotation_mode rot_mode();
const std::string *get_active_node_template(bool bypassRandom = false);
nodebank_panel::edit_mode mode();
float getSpacing();
void toggleBrushSettings(bool isVisible);
private:
// members
itemproperties_panel m_itempropertiespanel{"Node Properties", true};
functions_panel m_functionspanel{"Functions", true};
nodebank_panel m_nodebankpanel{"Node Bank", true};
brush_object_list m_brushobjects{"Brush properties", false};
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
};

View File

@@ -0,0 +1,618 @@
/*
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 "editoruipanels.h"
#include "scenenodegroups.h"
#include "Globals.h"
#include "Camera.h"
#include "AnimModel.h"
#include "Track.h"
#include "Event.h"
#include "MemCell.h"
#include "editoruilayer.h"
#include "renderer.h"
#include "utilities.h"
void itemproperties_panel::update(scene::basic_node const *Node)
{
m_node = Node;
if (false == is_open)
{
return;
}
text_lines.clear();
m_grouplines.clear();
std::string textline;
// scenario inspector
auto const *node{Node};
auto const &camera{Global.pCamera};
if (node == nullptr)
{
auto const mouseposition{camera.Pos + GfxRenderer->Mouse_Position()};
textline = "mouse location: [" + to_string(mouseposition.x, 2) + ", " + to_string(mouseposition.y, 2) + ", " + to_string(mouseposition.z, 2) + "]";
text_lines.emplace_back(textline, Global.UITextColor);
return;
}
/*
// TODO: bind receiver in the constructor
if( ( m_itemproperties != nullptr )
&& ( m_itemproperties->node != nullptr ) ) {
// fetch node data; skip properties which were changed until they're retrieved by the observer
auto const *node { m_itemproperties->node };
if( m_itemproperties->name.second == false ) {
m_itemproperties->name.first = ( node->name().empty() ? "(none)" : node->name() );
}
if( m_itemproperties->location.second == false ) {
m_itemproperties->location.first = node->location();
}
}
*/
textline = "name: " + (node->name().empty() ? "(none)" : Bezogonkow(node->name())) + "\ntype: " + node->node_type + "\nlocation: [" + to_string(node->location().x, 2) + ", " + to_string(node->location().y, 2) + ", " +
to_string(node->location().z, 2) + "]" +
" (distance: " + to_string(glm::length(glm::dvec3{node->location().x, 0.0, node->location().z} - glm::dvec3{camera.Pos.x, 0.0, camera.Pos.z}), 1) + " m)" + "\nUUID: " + node->uuid.to_string();
text_lines.emplace_back(textline, Global.UITextColor);
// subclass-specific data
// TBD, TODO: specialized data dump method in each node subclass, or data imports in the panel for provided subclass pointer?
if (typeid(*node) == typeid(TAnimModel))
{
auto const *subnode = static_cast<TAnimModel const *>(node);
textline = "angle_x: " + to_string(clamp_circular(subnode->vAngle.x, 360.f), 2) + " deg, " + "angle_y: " + to_string(clamp_circular(subnode->vAngle.y, 360.f), 2) + " deg, " +
"angle_z: " + to_string(clamp_circular(subnode->vAngle.z, 360.f), 2) + " deg";
textline += ";\nlights: ";
if (subnode->iNumLights > 0)
{
textline += '[';
for (int lightidx = 0; lightidx < subnode->iNumLights; ++lightidx)
{
textline += to_string(subnode->lsLights[lightidx]);
if (lightidx < subnode->iNumLights - 1)
{
textline += ", ";
}
}
textline += ']';
}
else
{
textline += "(none)";
}
text_lines.emplace_back(textline, Global.UITextColor);
// 3d shape
auto modelfile{((subnode->pModel != nullptr) ? subnode->pModel->NameGet() : "(none)")};
if (modelfile.find(szModelPath) == 0)
{
// don't include 'models/' in the path
modelfile.erase(0, std::string{szModelPath}.size());
}
// texture
auto texturefile{((subnode->Material()->replacable_skins[1] != null_handle) ? GfxRenderer->Material(subnode->Material()->replacable_skins[1])->GetName() : "(none)")};
if (texturefile.find(szTexturePath) == 0)
{
// don't include 'textures/' in the path
texturefile.erase(0, std::string{szTexturePath}.size());
}
text_lines.emplace_back("mesh: " + modelfile, Global.UITextColor);
text_lines.emplace_back("skin: " + texturefile, Global.UITextColor);
}
else if (typeid(*node) == typeid(TTrack))
{
auto const *subnode = static_cast<TTrack const *>(node);
std::string isolatedlist;
for (const TIsolated *iso : subnode->Isolated)
{
if (!isolatedlist.empty())
isolatedlist += ", ";
isolatedlist += iso->asName;
}
// basic attributes
textline = "isolated: " + (!isolatedlist.empty() ? isolatedlist : "(none)") + "\nvelocity: " + to_string(subnode->SwitchExtension ? subnode->SwitchExtension->fVelocity : subnode->fVelocity) +
"\nwidth: " + to_string(subnode->fTrackWidth) + " m" + "\nfriction: " + to_string(subnode->fFriction, 2) + "\nquality: " + to_string(subnode->iQualityFlag);
text_lines.emplace_back(textline, Global.UITextColor);
// textures
auto texturefile{((subnode->m_material1 != null_handle) ? GfxRenderer->Material(subnode->m_material1)->GetName() : "(none)")};
if (texturefile.find(szTexturePath) == 0)
{
texturefile.erase(0, std::string{szTexturePath}.size());
}
auto texturefile2{((subnode->m_material2 != null_handle) ? GfxRenderer->Material(subnode->m_material2)->GetName() : "(none)")};
if (texturefile2.find(szTexturePath) == 0)
{
texturefile2.erase(0, std::string{szTexturePath}.size());
}
textline = "skins:\n " + texturefile + "\n " + texturefile2;
text_lines.emplace_back(textline, Global.UITextColor);
// paths
textline = "paths: ";
for (auto const &path : subnode->m_paths)
{
textline += "\n [" + to_string(path.points[segment_data::point::start].x, 3) + ", " + to_string(path.points[segment_data::point::start].y, 3) + ", " +
to_string(path.points[segment_data::point::start].z, 3) + "]->" + " [" + to_string(path.points[segment_data::point::end].x, 3) + ", " +
to_string(path.points[segment_data::point::end].y, 3) + ", " + to_string(path.points[segment_data::point::end].z, 3) + "] ";
}
text_lines.emplace_back(textline, Global.UITextColor);
// events
textline.clear();
std::vector<std::pair<std::string, TTrack::event_sequence const *>> const eventsequences{{"ev0", &subnode->m_events0}, {"ev0all", &subnode->m_events0all},
{"ev1", &subnode->m_events1}, {"ev1all", &subnode->m_events1all},
{"ev2", &subnode->m_events2}, {"ev2all", &subnode->m_events2all}};
for (auto const &eventsequence : eventsequences)
{
if (eventsequence.second->empty())
{
continue;
}
textline += (textline.empty() ? "" : "\n") + eventsequence.first + ": [";
for (auto const &event : *(eventsequence.second))
{
if (textline.back() != '[')
{
textline += ", ";
}
textline += (event.second != nullptr ? Bezogonkow(event.second->m_name) : event.first + " (missing)");
}
textline += "] ";
}
text_lines.emplace_back(textline, Global.UITextColor);
}
else if (typeid(*node) == typeid(TMemCell))
{
auto const *subnode = static_cast<TMemCell const *>(node);
textline = "data: [" + subnode->Text() + "]" + " [" + to_string(subnode->Value1(), 2) + "]" + " [" + to_string(subnode->Value2(), 2) + "]";
text_lines.emplace_back(textline, Global.UITextColor);
textline = "track: " + (subnode->asTrackName.empty() ? "(none)" : Bezogonkow(subnode->asTrackName));
text_lines.emplace_back(textline, Global.UITextColor);
}
update_group();
}
void itemproperties_panel::update_group()
{
auto const grouphandle{m_node->group()};
if (grouphandle == null_handle)
{
m_grouphandle = null_handle;
m_groupprefix.clear();
return;
}
auto const &nodegroup{scene::Groups.group(grouphandle)};
if (m_grouphandle != grouphandle)
{
// calculate group name from shared prefix of item names
std::vector<std::reference_wrapper<std::string const>> names;
// build list of custom item and event names
for (auto const *node : nodegroup.nodes)
{
auto const &name{node->name()};
if (name.empty() || name == "none")
{
continue;
}
names.emplace_back(name);
}
for (auto const *event : nodegroup.events)
{
auto const &name{event->m_name};
if (name.empty() || name == "none")
{
continue;
}
names.emplace_back(name);
}
// find the common prefix
if (names.size() > 1)
{
m_groupprefix = names.front();
for (auto const &name : names)
{
// NOTE: first calculation runs over two instances of the same name, but, eh
auto const prefixlength{len_common_prefix(m_groupprefix, name)};
if (prefixlength > 0)
{
m_groupprefix = m_groupprefix.substr(0, prefixlength);
}
else
{
m_groupprefix.clear();
break;
}
}
}
else
{
// less than two names to compare means no prefix
m_groupprefix.clear();
}
m_grouphandle = grouphandle;
}
m_grouplines.emplace_back("nodes: " + to_string(static_cast<int>(nodegroup.nodes.size())) + "\nevents: " + to_string(static_cast<int>(nodegroup.events.size())), Global.UITextColor);
m_grouplines.emplace_back("names prefix: " + (m_groupprefix.empty() ? "(none)" : m_groupprefix), Global.UITextColor);
}
void itemproperties_panel::render()
{
if (false == is_open)
{
return;
}
if (true == text_lines.empty())
{
return;
}
auto flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse | (size.x > 0 ? ImGuiWindowFlags_NoResize : 0);
if (size.x > 0)
{
ImGui::SetNextWindowSize(ImVec2S(size.x, size.y));
}
if (size_min.x > 0)
{
ImGui::SetNextWindowSizeConstraints(ImVec2S(size_min.x, size_min.y), ImVec2(size_max.x, size_max.y));
}
auto const panelname{(title.empty() ? m_name : title) + "###" + m_name};
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
{
// header section
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());
}
// group section
render_group();
}
ImGui::End();
}
bool itemproperties_panel::render_group()
{
if (m_node == nullptr)
{
return false;
}
if (m_grouplines.empty())
{
return false;
}
if (false == ImGui::CollapsingHeader("Parent Group"))
{
return false;
}
for (auto const &line : m_grouplines)
{
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
}
return true;
}
brush_object_list::brush_object_list(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen)
{
size_min = {50, 100};
size_max = {1000, 500};
}
bool brush_object_list::VectorGetter(void *data, int idx, const char **out_text)
{
auto *vec = static_cast<std::vector<std::string> *>(data);
if (idx < 0 || idx >= vec->size())
return false;
*out_text = (*vec)[idx].c_str();
return true;
}
void brush_object_list::update(std::string nodeTemplate)
{
Template = nodeTemplate;
}
std::string *brush_object_list::GetRandomObject()
{
static std::string empty; // fallback
if (Objects.empty())
return &empty;
static std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<size_t> dist(0, Objects.size() - 1);
return &Objects[dist(rng)];
}
void brush_object_list::render()
{
if (false == is_open)
{
return;
}
auto flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse | (size.x > 0 ? ImGuiWindowFlags_NoResize : 0);
if (ImGui::Begin("Brush random set", nullptr, flags))
{
ImGui::SliderFloat("Spacing", &spacing, 0.1f, 20.0f, "%.1f m");
ImGui::Checkbox("Enable brush random from set", &useRandom);
if (useRandom)
{
ImGui::Text("Set of objects to choose from:");
ImGui::ListBox("", &idx, VectorGetter, (void *)&Objects, Objects.size(), 6);
if (ImGui::Button("Add"))
{
if (!Template.empty())
Objects.push_back(Template);
}
ImGui::SameLine();
if (ImGui::Button("Remove") && idx >= 0 && idx < Objects.size())
{
Objects.erase(Objects.begin() + idx);
}
if (ImGui::Button("Remove all") && idx >= 0 && idx < Objects.size())
{
Objects.clear();
}
}
ImGui::End();
}
}
nodebank_panel::nodebank_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen)
{
size_min = {100, 50};
size_max = {1000, 1000};
memset(m_nodesearch, 0, sizeof(m_nodesearch));
std::ifstream file;
file.open("nodebank.txt", std::ios_base::in | std::ios_base::binary);
std::string line;
while (std::getline(file, line))
{
if (line.size() < 4)
{
continue;
}
auto const labelend{line.find("node")};
auto const nodedata{(labelend == std::string::npos ? "" : labelend == 0 ? line : line.substr(labelend))};
auto const label{(labelend == std::string::npos ? line : labelend == 0 ? generate_node_label(nodedata) : line.substr(0, labelend))};
m_nodebank.push_back({label, std::make_shared<std::string>(nodedata)});
}
// sort alphabetically content of each group
auto groupbegin{m_nodebank.begin()};
auto groupend{groupbegin};
while (groupbegin != m_nodebank.end())
{
groupbegin = std::find_if(groupend, m_nodebank.end(), [](auto const &Entry) { return (false == Entry.second->empty()); });
groupend = std::find_if(groupbegin, m_nodebank.end(), [](auto const &Entry) { return (Entry.second->empty()); });
std::sort(groupbegin, groupend, [](auto const &Left, auto const &Right) { return (Left.first < Right.first); });
}
}
void nodebank_panel::nodebank_reload()
{
m_nodebank.clear();
std::ifstream file;
file.open("nodebank.txt", std::ios_base::in | std::ios_base::binary);
std::string line;
while (std::getline(file, line))
{
if (line.size() < 4)
{
continue;
}
auto const labelend{line.find("node")};
auto const nodedata{(labelend == std::string::npos ? "" : labelend == 0 ? line : line.substr(labelend))};
auto const label{(labelend == std::string::npos ? line : labelend == 0 ? generate_node_label(nodedata) : line.substr(0, labelend))};
m_nodebank.push_back({label, std::make_shared<std::string>(nodedata)});
}
// sort alphabetically content of each group
auto groupbegin{m_nodebank.begin()};
auto groupend{groupbegin};
while (groupbegin != m_nodebank.end())
{
groupbegin = std::find_if(groupend, m_nodebank.end(), [](auto const &Entry) { return (false == Entry.second->empty()); });
groupend = std::find_if(groupbegin, m_nodebank.end(), [](auto const &Entry) { return (Entry.second->empty()); });
std::sort(groupbegin, groupend, [](auto const &Left, auto const &Right) { return (Left.first < Right.first); });
}
}
void nodebank_panel::render()
{
if (false == is_open)
{
return;
}
auto flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse | (size.x > 0 ? ImGuiWindowFlags_NoResize : 0);
if (size.x > 0)
{
ImGui::SetNextWindowSize(ImVec2S(size.x, size.y));
}
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() ? name() : title) + "###" + name()};
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
{
ImGui::RadioButton("Modify node", (int *)&mode, MODIFY);
ImGui::SameLine();
ImGui::RadioButton("Insert from bank", (int *)&mode, ADD);
ImGui::SameLine();
ImGui::RadioButton("Brush mode", (int *)&mode, BRUSH);
ImGui::SameLine();
ImGui::RadioButton("Copy to bank", (int *)&mode, COPY);
ImGui::SameLine();
if (ImGui::Button("Reload Nodebank"))
{
nodebank_reload();
}
if (mode == BRUSH)
{
// ImGui::SliderFloat("Spacing", &spacing, 0.1f, 20.0f, "%.1f m");
}
ImGui::PushItemWidth(-1);
ImGui::InputTextWithHint("Search", "Search node bank", m_nodesearch, IM_ARRAYSIZE(m_nodesearch));
if (ImGui::ListBoxHeader("##nodebank", ImVec2(-1, -1)))
{
auto idx{0};
auto isvisible{false};
auto const searchfilter{std::string(m_nodesearch)};
for (auto const &entry : m_nodebank)
{
if (entry.second->empty())
{
// special case, header indicator
isvisible = ImGui::CollapsingHeader(entry.first.c_str());
}
else
{
if (false == isvisible)
{
continue;
}
if ((false == searchfilter.empty()) && (false == contains(entry.first, searchfilter)))
{
continue;
}
auto const label{" " + entry.first + "##" + std::to_string(idx)};
if (ImGui::Selectable(label.c_str(), entry.second == m_selectedtemplate))
m_selectedtemplate = entry.second;
++idx;
}
}
ImGui::ListBoxFooter();
}
}
ImGui::End();
}
void nodebank_panel::add_template(const std::string &desc)
{
auto const label{generate_node_label(desc)};
m_nodebank.push_back({label, std::make_shared<std::string>(desc)});
std::ofstream file;
file.open("nodebank.txt", std::ios_base::out | std::ios_base::app | std::ios_base::binary);
file << label << " " << desc;
}
const std::string *nodebank_panel::get_active_template()
{
return m_selectedtemplate.get();
}
std::string nodebank_panel::generate_node_label(std::string Input) const
{
auto tokenizer{cParser(Input)};
tokenizer.getTokens(9, false); // skip leading tokens
auto model{tokenizer.getToken<std::string>(false)};
auto texture{tokenizer.getToken<std::string>(false)};
replace_slashes(model);
erase_extension(model);
replace_slashes(texture);
return (texture == "none" ? model : model + " (" + texture + ")");
}
void functions_panel::update(scene::basic_node const *Node)
{
m_node = Node;
if (false == is_open)
{
return;
}
text_lines.clear();
m_grouplines.clear();
std::string textline;
// scenario inspector
auto const *node{Node};
auto const &camera{Global.pCamera};
}
void functions_panel::render()
{
if (false == is_open)
{
return;
}
auto flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse | (size.x > 0 ? ImGuiWindowFlags_NoResize : 0);
if (size.x > 0)
{
ImGui::SetNextWindowSize(ImVec2S(size.x, size.y));
}
if (size_min.x > 0)
{
ImGui::SetNextWindowSizeConstraints(ImVec2S(size_min.x, size_min.y), ImVec2(size_max.x, size_max.y));
}
auto const panelname{(title.empty() ? m_name : title) + "###" + m_name};
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
{
// header section
ImGui::RadioButton("Random rotation", (int *)&rot_mode, RANDOM);
ImGui::RadioButton("Fixed rotation", (int *)&rot_mode, FIXED);
if (rot_mode == FIXED)
{
// ImGui::Checkbox("Get rotation from last object", &rot_from_last);
ImGui::SliderFloat("Rotation Value", &rot_value, 0.0f, 360.0f, "%.1f");
};
ImGui::RadioButton("Default rotation", (int *)&rot_mode, DEFAULT);
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());
}
}
ImGui::End();
}

View File

@@ -0,0 +1,125 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
#include "Classes.h"
/*
// helper, associated bool is set when the primary value was changed and expects processing at the observer's leisure
template<typename Type_>
using changeable = std::pair<Type_, bool>;
// helper, holds a set of changeable properties for a scene node
struct item_properties {
scene::basic_node const *node { nullptr }; // properties' owner
changeable<std::string> name {};
changeable<glm::dvec3> location {};
changeable<glm::vec3> rotation {};
};
*/
class itemproperties_panel : public ui_panel
{
public:
itemproperties_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen) {}
void update(scene::basic_node const *Node);
void render() override;
private:
// methods
void update_group();
bool render_group();
// members
scene::basic_node const *m_node{nullptr}; // scene node bound to the panel
scene::group_handle m_grouphandle{null_handle}; // scene group bound to the panel
std::string m_groupprefix;
std::vector<text_line> m_grouplines;
};
class brush_object_list : public ui_panel
{
private:
int idx;
static bool VectorGetter(void *data, int idx, const char **out_text);
std::string Template;
public:
brush_object_list(std::string const &Name, bool const Isopen);
void render() override;
void update(std::string nodeTemplate);
// class use
std::vector<std::string> Objects;
std::string *GetRandomObject();
bool useRandom = {false};
float spacing{1.0f};
};
class nodebank_panel : public ui_panel
{
public:
enum edit_mode
{
MODIFY,
COPY,
ADD,
BRUSH
};
edit_mode mode = MODIFY;
nodebank_panel(std::string const &Name, bool const Isopen);
void nodebank_reload();
void render() override;
void add_template(const std::string &desc);
const std::string *get_active_template();
private:
// methods:
std::string generate_node_label(std::string Input) const;
// members:
std::vector<std::pair<std::string, std::shared_ptr<std::string>>> m_nodebank;
char m_nodesearch[128];
std::shared_ptr<std::string> m_selectedtemplate;
};
class functions_panel : public ui_panel
{
public:
enum rotation_mode
{
RANDOM,
FIXED,
DEFAULT
};
rotation_mode rot_mode = DEFAULT;
float rot_value = 0.0f;
bool rot_from_last = false;
functions_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen) {}
void update(scene::basic_node const *Node);
void render() override;
private:
// methods
// members
scene::basic_node const *m_node{nullptr}; // scene node bound to the panel
scene::group_handle m_grouphandle{null_handle}; // scene group bound to the panel
std::string m_groupprefix;
std::vector<text_line> m_grouplines;
};

View File

@@ -0,0 +1,87 @@
/*
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 "scenarioloadermode.h"
#include "Globals.h"
#include "simulation.h"
#include "simulationtime.h"
#include "simulationenvironment.h"
#include "application.h"
#include "scenarioloaderuilayer.h"
#include "renderer.h"
#include "Logs.h"
#include "translation.h"
scenarioloader_mode::scenarioloader_mode() {
m_userinterface = std::make_shared<scenarioloader_ui>();
}
// initializes internal data structures of the mode. returns: true on success, false otherwise
bool scenarioloader_mode::init() {
// nothing to do here
return true;
}
// mode-specific update of simulation data. returns: false on error, true otherwise
bool scenarioloader_mode::update() {
if (!Global.ready_to_load)
// waiting for network connection
return true;
if (!state) {
WriteLog("using simulation seed: " + std::to_string(Global.random_seed), logtype::generic);
WriteLog("using simulation starting timestamp: " + std::to_string(Global.starting_timestamp), logtype::generic);
Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" );
WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." );
timestart = std::chrono::system_clock::now();
state = simulation::State.deserialize_begin(Global.SceneryFile);
}
try {
if (simulation::State.deserialize_continue(state))
return true;
}
catch (invalid_scenery_exception &e) {
ErrorLog( "Bad init: scenario loading failed" );
Application.pop_mode();
}
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( ( std::chrono::system_clock::now() - timestart ) ).count() ) + " seconds" );
// TODO: implement and use next mode cue
Application.pop_mode();
Application.push_mode( eu07_application::mode::driver );
return true;
}
bool scenarioloader_mode::is_command_processor() const {
return false;
}
// maintenance method, called when the mode is activated
void scenarioloader_mode::enter() {
// TBD: hide cursor in fullscreen mode?
Application.set_cursor( GLFW_CURSOR_NORMAL );
simulation::is_ready = false;
Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" );
m_userinterface->set_progress(STR("Loading scenery"));
}
// maintenance method, called when the mode is deactivated
void scenarioloader_mode::exit() {
simulation::Time.init( Global.starting_timestamp );
simulation::Environment.init();
}

View File

@@ -0,0 +1,39 @@
/*
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/.
*/
#pragma once
#include "applicationmode.h"
#include "simulation.h"
class scenarioloader_mode : public application_mode {
std::shared_ptr<simulation::deserializer_state> state;
std::chrono::system_clock::time_point timestart;
public:
// constructors
scenarioloader_mode();
// methods
// initializes internal data structures of the mode. returns: true on success, false otherwise
bool init() override;
// mode-specific update of simulation data. returns: false on error, true otherwise
bool update() override;
// maintenance method, called when the mode is activated
void enter() override;
// maintenance method, called when the mode is deactivated
void exit() override;
// input handlers
void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override { ; }
void on_cursor_pos( double const Horizontal, double const Vertical ) override { ; }
void on_mouse_button( int const Button, int const Action, int const Mods ) override { ; }
void on_scroll( double const Xoffset, double const Yoffset ) override { ; }
void on_window_resize( int w, int h ) override { ; }
void on_event_poll() override { ; }
bool is_command_processor() const override;
};

View File

@@ -0,0 +1,327 @@
/*
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 "scenarioloaderuilayer.h"
#include "Globals.h"
#include "translation.h"
#include <nlohmann/json.hpp>
#include "Logs.h"
#include <sstream>
using json = nlohmann::json;
// Helper function to count UTF-8 characters (not bytes)
size_t utf8_char_count(const std::string& str)
{
size_t count = 0;
for (size_t i = 0; i < str.length(); )
{
unsigned char c = static_cast<unsigned char>(str[i]);
if ((c & 0x80) == 0x00)
{
// 1-byte character (ASCII)
i += 1;
}
else if ((c & 0xE0) == 0xC0)
{
// 2-byte character
i += 2;
}
else if ((c & 0xF0) == 0xE0)
{
// 3-byte character (most Chinese characters)
i += 3;
}
else if ((c & 0xF8) == 0xF0)
{
// 4-byte character
i += 4;
}
else
{
// Invalid UTF-8, skip
i += 1;
}
count++;
}
return count;
}
scenarioloader_ui::scenarioloader_ui()
{
m_suppress_menu = true;
generate_gradient_tex();
load_wheel_frames();
m_trivia = get_random_trivia();
}
std::vector<std::string> scenarioloader_ui::get_random_trivia()
{
WriteLog("Loading random trivia...");
std::vector<std::string> trivia = std::vector<std::string>();
if (!FileExists("lang/trivia_" + Global.asLang + ".json")
&& !FileExists("lang/trivia_en.json"))
{
ErrorLog("Trivia file not found!");
return trivia;
}
std::string triviaFile = FileExists("lang/trivia_" + Global.asLang + ".json") ? "lang/trivia_" + Global.asLang + ".json" : "lang/trivia_en.json";
//std::string lang = Global.asLang;
WriteLog("Selected language: " + Global.asLang);
// Read file in binary mode to preserve UTF-8 encoding
std::ifstream f(triviaFile, std::ios::binary);
std::stringstream buffer;
buffer << f.rdbuf();
std::string fileContent = buffer.str();
json triviaData = json::parse(fileContent);
// select random
int i = RandomInt(0, static_cast<int>(triviaData.size()) - 1);
std::string triviaStr = triviaData[i]["text"];
std::string background = triviaData[i]["background"];
// divide trivia into multiple lines - UTF-8 safe implementation
// Different languages need different character limits due to character width differences
int max_line_length = 100; // Default for Latin languages
if (Global.asLang == "zh")
{
max_line_length = 60; // Reduced for Chinese
}
// Find byte position corresponding to character count
auto get_byte_pos_for_char_count = [&](const std::string& str, size_t char_count) -> size_t {
size_t byte_pos = 0;
size_t current_chars = 0;
for (size_t i = 0; i < str.length() && current_chars < char_count; )
{
unsigned char c = static_cast<unsigned char>(str[i]);
if ((c & 0x80) == 0x00)
{
i += 1;
}
else if ((c & 0xE0) == 0xC0)
{
i += 2;
}
else if ((c & 0xF0) == 0xE0)
{
i += 3;
}
else if ((c & 0xF8) == 0xF0)
{
i += 4;
}
else
{
i += 1;
}
current_chars++;
byte_pos = i;
}
return byte_pos;
};
// UTF-8 safe find space within character limit
auto find_space_before_char_count = [&](const std::string& str, size_t max_chars) -> size_t {
size_t byte_limit = get_byte_pos_for_char_count(str, max_chars);
size_t search_limit = std::min(str.length(), byte_limit);
// Search backwards for a space that's not in the middle of a UTF-8 sequence
for (int idx = static_cast<int>(search_limit); idx >= 0; idx--) {
// Check if this position is a valid place to split (not in middle of UTF-8 char)
// A UTF-8 continuation byte has the form 10xxxxxx (0x80-0xBF)
if (idx == 0 || (static_cast<unsigned char>(str[idx - 1]) & 0xC0) != 0x80) {
// This is the start of a character (or beginning of string), check if it's a space
if (idx > 0 && str[idx - 1] == ' ') {
return idx - 1; // Position of the space
}
}
}
return std::string::npos; // No suitable space found
};
// Use character count for proper UTF-8 text wrapping
while (utf8_char_count(triviaStr) > static_cast<size_t>(max_line_length))
{
size_t split_pos = find_space_before_char_count(triviaStr, static_cast<size_t>(max_line_length));
if (split_pos == std::string::npos)
{
// No space found, split at max character count
split_pos = get_byte_pos_for_char_count(triviaStr, static_cast<size_t>(max_line_length));
}
trivia.push_back(triviaStr.substr(0, split_pos));
triviaStr = triviaStr.substr(split_pos);
// Skip leading whitespace in the next line
while (!triviaStr.empty() && triviaStr[0] == ' ')
{
triviaStr = triviaStr.substr(1);
}
}
// if triviaStr is not empty add this as last line
if (!triviaStr.empty())
trivia.push_back(triviaStr);
// now override background if trivia is set
if (!trivia.empty())
{
set_background("textures/ui/backgrounds/" + background);
}
return trivia;
}
void scenarioloader_ui::render_()
{
// For some reason, ImGui windows have some padding. Offset it.
// TODO: Find out a way to exactly adjust the position.
constexpr int padding = 12;
ImVec2 screen_size(Global.window_size.x, Global.window_size.y);
ImGui::SetNextWindowPos(ImVec2(-padding, -padding));
ImGui::SetNextWindowSize(ImVec2(Global.window_size.x + padding * 2, Global.window_size.y + padding * 2));
ImGui::Begin("Neo Loading Screen", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushFont(font_loading);
ImGui::SetWindowFontScale(1);
const float font_scale_mult = 48 / ImGui::GetFontSize();
// Gradient at the lower half of the screen
if (!Global.NvRenderer)
{
const auto tex = reinterpret_cast<ImTextureID>(m_gradient_overlay_tex);
draw_list->AddImage(tex, ImVec2(0, Global.window_size.y / 2), ImVec2(Global.window_size.x, Global.window_size.y), ImVec2(0, 0), ImVec2(1, 1));
}
// [O] Loading...
const float margin_left_icon = 35.0f;
const float margin_bottom_loading = 80.0f;
const float spacing = 10.0f; // odstęp między ikoną a tekstem
// Loading icon
const deferred_image *img = &m_loading_wheel_frames[38];
const auto loading_tex = img->get();
const auto loading_size = img->size();
ImVec2 icon_pos(margin_left_icon, screen_size.y - margin_bottom_loading - loading_size.y);
// Loading text
ImGui::SetWindowFontScale(font_scale_mult * 0.8f);
ImVec2 text_size = ImGui::CalcTextSize(m_progresstext.c_str());
// Vertical centering of text relative to icon
float icon_center_y = icon_pos.y + loading_size.y * 0.5f;
ImVec2 text_pos(icon_pos.x + loading_size.x + spacing, // tuż obok ikony
icon_center_y - text_size.y * 0.5f);
// Draw
//draw_list->AddImage(reinterpret_cast<ImTextureID>(loading_tex), icon_pos, ImVec2(icon_pos.x + loading_size.x, icon_pos.y + loading_size.y), ImVec2(0, 0), ImVec2(1, 1));
draw_list->AddText(text_pos, IM_COL32_WHITE, m_progresstext.c_str());
// Trivia
// draw only if we have any trivia loaded
if (m_trivia.size() > 0)
{
const float margin_right = 80.0f;
const float margin_bottom = 80.0f;
const float line_spacing = 25.0f;
const float header_gap = 10.0f;
// Measure width of trivia lines
ImGui::SetWindowFontScale(font_scale_mult * 0.6f);
float max_width = 0.0f;
for (const std::string &line : m_trivia)
{
ImVec2 size = ImGui::CalcTextSize(line.c_str());
if (size.x > max_width)
max_width = size.x;
}
// Measure header width
ImGui::SetWindowFontScale(font_scale_mult * 1.0f);
ImVec2 header_size = ImGui::CalcTextSize(STR_C("Did you know?"));
if (header_size.x > max_width)
max_width = header_size.x; // blok musi też pomieścić nagłówek
// Calculate block position
float content_height = (float)m_trivia.size() * line_spacing;
float total_height = header_size.y + header_gap + content_height;
float block_left = screen_size.x - margin_right - max_width;
float block_top = screen_size.y - margin_bottom - total_height;
// Draw header
ImVec2 header_pos(block_left + (max_width - header_size.x) * 0.5f, block_top);
draw_list->AddText(header_pos, IM_COL32_WHITE, STR_C("Did you know?"));
// Draw trivia lines
ImGui::SetWindowFontScale(font_scale_mult * 0.6f);
for (int i = 0; i < m_trivia.size(); i++)
{
const std::string &line = m_trivia[i];
ImVec2 text_size = ImGui::CalcTextSize(line.c_str());
ImVec2 text_pos(block_left + (max_width - text_size.x) * 0.5f, block_top + header_size.y + header_gap + i * line_spacing);
draw_list->AddText(text_pos, IM_COL32_WHITE, line.c_str());
}
}
// Progress bar at the bottom of the screen
const auto p1 = ImVec2(0, Global.window_size.y - 2);
const auto p2 = ImVec2(Global.window_size.x * m_progress, Global.window_size.y);
draw_list->AddRectFilled(p1, p2, ImColor(40, 210, 60, 255));
ImGui::PopFont();
ImGui::End();
}
void scenarioloader_ui::generate_gradient_tex()
{
if (Global.NvRenderer)
return;
constexpr int image_width = 1;
constexpr int image_height = 256;
const auto image_data = new char[image_width * image_height * 4];
for (int x = 0; x < image_width; x++)
for (int y = 0; y < image_height; y++)
{
image_data[(y * image_width + x) * 4] = 0;
image_data[(y * image_width + x) * 4 + 1] = 0;
image_data[(y * image_width + x) * 4 + 2] = 0;
image_data[(y * image_width + x) * 4 + 3] = clamp(static_cast<int>(pow(y / 255.f, 0.7) * 255), 0, 255);
}
// Create a OpenGL texture identifier
GLuint image_texture;
glGenTextures(1, &image_texture);
glBindTexture(GL_TEXTURE_2D, image_texture);
// Setup filtering parameters for display
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Upload pixels into texture
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
delete[] image_data;
m_gradient_overlay_width = image_width;
m_gradient_overlay_height = image_height;
m_gradient_overlay_tex = image_texture;
}
void scenarioloader_ui::load_wheel_frames()
{
for (int i = 0; i < 60; i++)
m_loading_wheel_frames[i] = deferred_image("ui/loading_wheel/" + std::to_string(i + 1));
}

View File

@@ -0,0 +1,33 @@
/*
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/.
*/
#pragma once
#include "uilayer.h"
#include "launcher/deferred_image.h"
class scenarioloader_ui : public ui_layer {
int m_gradient_overlay_width;
int m_gradient_overlay_height;
GLuint m_gradient_overlay_tex;
deferred_image m_loading_wheel_frames[60];
std::vector<std::string> m_trivia;
public:
scenarioloader_ui();
void render_() override;
private:
/// <summary>
/// Loads random trivia from lang/trivia.json
/// and returns it as a vector of strings. Also performs background selection.
/// </summary>
std::vector<std::string> get_random_trivia();
void generate_gradient_tex();
void load_wheel_frames();
};

622
application/uilayer.cpp Normal file
View File

@@ -0,0 +1,622 @@
/*
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 "uilayer.h"
#include <utility>
#include "Globals.h"
#include "renderer.h"
#include "Logs.h"
#include "simulation.h"
#include "translation.h"
#include "application.h"
#include "editormode.h"
#include "imgui/imgui_impl_glfw.h"
GLFWwindow *ui_layer::m_window{nullptr};
ImGuiIO *ui_layer::m_imguiio{nullptr};
GLint ui_layer::m_textureunit{GL_TEXTURE0};
bool ui_layer::m_cursorvisible;
ImFont *ui_layer::font_default{nullptr};
ImFont *ui_layer::font_mono{nullptr};
ImFont *ui_layer::font_loading{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_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;
}
static ImVec4 imvec_lerp(const ImVec4 &a, const ImVec4 &b, float t)
{
return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t);
}
void ui_layer::imgui_style()
{
ImVec4 *colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.04f, 0.04f, 0.04f, 0.94f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.31f, 0.34f, 0.31f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.21f, 0.28f, 0.17f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.64f, 0.23f, 0.67f);
colors[ImGuiCol_TitleBg] = ImVec4(0.03f, 0.03f, 0.03f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.21f, 0.28f, 0.17f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.01f, 0.01f, 0.01f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.37f, 0.53f, 0.25f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.42f, 0.64f, 0.23f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.37f, 0.54f, 0.19f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.42f, 0.64f, 0.23f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.80f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.29f, 0.41f, 0.18f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.29f, 0.41f, 0.18f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.42f, 0.64f, 0.23f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.42f, 0.64f, 0.23f, 0.95f);
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.42f, 0.64f, 0.23f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 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);
colors[ImGuiCol_Separator] = colors[ImGuiCol_Border];
colors[ImGuiCol_Tab] = imvec_lerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f);
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered];
colors[ImGuiCol_TabActive] = imvec_lerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);
colors[ImGuiCol_TabUnfocused] = imvec_lerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f);
colors[ImGuiCol_TabUnfocusedActive] = imvec_lerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f);
ImGui::GetStyle().ScaleAllSizes(Global.ui_scale);
}
bool ui_layer::init(GLFWwindow *Window)
{
m_window = Window;
IMGUI_CHECKVERSION();
ImGui::CreateContext();
m_imguiio = &ImGui::GetIO();
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);
}
}
}
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;
}
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()
{
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();
}
void ui_layer::begin_ui_frame()
{
begin_ui_frame_internal();
}
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;
ImGui::SetNextWindowSize(ImVec2(0, 0));
ImGui::Begin(STR_C("Scene Hierarchy"), &m_editor_hierarchy, ImGuiWindowFlags_AlwaysAutoResize);
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);
}
}
ImGui::EndMenu();
}
}
void ui_layer::render_menu()
{
glm::dvec2 mousepos = Global.cursor_pos;
if (!((Global.ControlPicking && mousepos.y < 50.0f) || m_imguiio->WantCaptureMouse) || m_suppress_menu)
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()->AddImage(reinterpret_cast<ImTextureID>(tex.get_id()), start_position, end_position, ImVec2(0, 1), ImVec2(1, 0));
}

164
application/uilayer.h Normal file
View File

@@ -0,0 +1,164 @@
/*
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/.
*/
#pragma once
#include <string>
#include "Texture.h"
#include "widgets/popup.h"
// GuiLayer -- basic user interface class. draws requested information on top of openGL screen
class ui_panel {
public:
// constructor
ui_panel( std::string Identifier, bool Isopen );
virtual ~ui_panel() = default;
// methods
virtual void update() {}
virtual void on_window_resize(int w, int h) {}
virtual void render();
virtual void render_contents();
// temporary access
// types
struct text_line {
std::string data;
glm::vec4 color;
text_line( std::string const &Data, glm::vec4 const &Color) : data(Data), color(Color) {}
};
// members
std::string title;
bool is_open;
glm::ivec2 pos { -1, -1 };
glm::ivec2 size { -1, -1 };
glm::ivec2 size_min { -1, -1 };
glm::ivec2 size_max { -1, -1 };
std::deque<text_line> text_lines;
int window_flags = -1;
bool no_title_bar = false;
const std::string& name() { return m_name; }
void register_popup(std::unique_ptr<ui::popup> &&popup);
protected:
// members
std::string m_name;
std::list<std::unique_ptr<ui::popup>> popups;
};
class ui_expandable_panel : public ui_panel {
public:
using ui_panel::ui_panel;
bool is_expanded { false };
void render_contents() override;
};
class ui_log_panel : public ui_panel {
using ui_panel::ui_panel;
void render_contents() override;
};
struct ImFont;
class ui_layer {
public:
// constructors
ui_layer();
// destructor
virtual ~ui_layer();
// methods
static bool init( GLFWwindow *Window );
static void imgui_style();
// assign texturing hardware unit
static void set_unit( GLint const Textureunit ) { m_textureunit = GL_TEXTURE0 + Textureunit; }
static void shutdown();
virtual void showDebugUI() {};
// potentially processes provided input key. returns: true if the input was processed, false otherwise
virtual bool on_key( int Key, int Action );
// potentially processes provided mouse movement. returns: true if the input was processed, false otherwise
virtual bool on_cursor_pos( double Horizontal, double Vertical );
// potentially processes provided mouse button. returns: true if the input was processed, false otherwise
virtual bool on_mouse_button( int Button, int Action );
// processes window resize.
virtual void on_window_resize(int w, int h);
// updates state of UI elements
virtual void update();
// draws requested UI elements
void render();
static void render_internal();
// begins new UI frame
// (this is separate from render() to allow for debug GUI outside of proper UI framework)
void begin_ui_frame();
static void begin_ui_frame_internal();
//
static void set_cursor( int Mode );
// loading
void set_progress(std::string const &Text);
void set_progress(float progress, float subtaskprogress);
// sets the ui background texture, if any
void set_background( std::string const &Filename = "" );
void set_tooltip( std::string const &Tooltip ) { m_tooltip = Tooltip; }
void clear_panels();
void add_external_panel( ui_panel *Panel ) { m_panels.emplace_back( Panel ); }
void add_owned_panel( ui_panel *Panel );
// callback functions for imgui input
// returns true if input is consumed
static bool key_callback(int key, int scancode, int action, int mods);
static bool char_callback(unsigned int c);
static bool scroll_callback(double xoffset, double yoffset);
static bool mouse_button_callback(int button, int action, int mods);
static ImFont *font_default;
static ImFont *font_mono;
static ImFont *font_loading;
protected:
// members
static GLFWwindow *m_window;
static ImGuiIO *m_imguiio;
static bool m_cursorvisible;
virtual void render_menu_contents();
ui_log_panel m_logpanel { "Log", true };
bool m_suppress_menu = false; // if `true`, the menu at the top of the window will not be present
// progress bar config
float m_progress { 0.0f }; // percentage of filled progres bar, to indicate lengthy operations.
float m_subtaskprogress{ 0.0f }; // percentage of filled progres bar, to indicate lengthy operations.
std::string m_progresstext; // label placed over the progress bar
private:
// methods
// render() subclass details
virtual void render_() {}
// draws background quad with specified earlier texture
void render_background();
void render_tooltip();
void render_panels();
void render_menu();
void render_quit_widget();
void render_hierarchy();
// draws a quad between coordinates x,y and z,w with uv-coordinates spanning 0-1
void quad( glm::vec4 const &Coordinates, glm::vec4 const &Color );
// members
static GLint m_textureunit;
texture_handle m_background { null_handle }; // path to texture used as the background. size depends on mAspect.
std::vector<ui_panel *> m_panels;
std::vector<std::unique_ptr<ui_panel>> m_ownedpanels;
std::string m_tooltip;
bool m_quit_active = false;
bool m_imgui_demo = false;
bool m_editor_hierarchy = false;
bool m_editor_change_history = false;
};

View File

@@ -0,0 +1,91 @@
#include "stdafx.h"
#include "uitranscripts.h"
#include "Globals.h"
#include "parser.h"
#include "utilities.h"
namespace ui {
TTranscripts Transcripts;
// dodanie linii do tabeli, (show) i (hide) w [s] od aktualnego czasu
void
TTranscripts::AddLine( std::string const &txt, float show, float hide, bool it ) {
if( show == hide ) { return; } // komentarz jest ignorowany
// TODO: replace the timeangledeg mess with regular time points math
show = Global.fTimeAngleDeg + show / 240.0; // jeśli doba to 360, to 1s będzie równe 1/240
hide = Global.fTimeAngleDeg + hide / 240.0;
TTranscript transcript;
transcript.asText = ExchangeCharInString( txt, '|', ' ' ); // NOTE: legacy transcript lines use | as new line mark
transcript.fShow = show;
transcript.fHide = hide;
transcript.bItalic = it;
aLines.emplace_back( transcript );
// set the next refresh time while at it
// TODO, TBD: sort the transcript lines? in theory, they should be coming arranged in the right order anyway
// short of cases with multiple sounds overleaping
fRefreshTime = aLines.front().fHide;
}
// dodanie tekstów, długość dźwięku, czy istotne
void
TTranscripts::Add( std::string const &txt, bool backgorund ) {
if( true == txt.empty() ) { return; }
std::string asciitext{ txt }; win1250_to_ascii( asciitext ); // TODO: launch relevant conversion table based on language
cParser parser( asciitext );
while( true == parser.getTokens( 3, false, "[]\n" ) ) {
float begin, end;
std::string transcript;
parser
>> begin
>> end
>> transcript;
AddLine( transcript, 0.10 * begin, 0.12 * end, false );
}
// try to handle malformed(?) cases with no show/hide times
std::string transcript; parser >> transcript;
while( false == transcript.empty() ) {
// WriteLog( "Transcript text with no display/hide times: \"" + transcript + "\"" );
AddLine( transcript, 0.0, 0.12 * transcript.size(), false );
transcript = ""; parser >> transcript;
}
}
// usuwanie niepotrzebnych (nie częściej niż 10 razy na sekundę)
void
TTranscripts::Update() {
// HACK: detect day change
if( fRefreshTime - Global.fTimeAngleDeg > 180 ) {
fRefreshTime -= 360;
}
if( Global.fTimeAngleDeg < fRefreshTime ) { return; } // nie czas jeszcze na zmiany
// remove expired lines
while( false == aLines.empty() ) {
// HACK: detect day change
if( aLines.front().fHide - Global.fTimeAngleDeg > 180 ) {
aLines.front().fShow -= 360;
aLines.front().fHide -= 360;
}
if( Global.fTimeAngleDeg <= aLines.front().fHide ) {
// no expired lines yet
break;
}
aLines.pop_front(); // this line expired, discard it and start anew with the next one
}
// update next refresh time
if( false == aLines.empty() ) { fRefreshTime = aLines.front().fHide; }
else { fRefreshTime = 360.0f; }
}
} // namespace ui

View File

@@ -0,0 +1,41 @@
#pragma once
#include <deque>
#include <string>
namespace ui {
// klasa obsługująca linijkę napisu do dźwięku
struct TTranscript {
float fShow; // czas pokazania
float fHide; // czas ukrycia/usunięcia
std::string asText; // tekst gotowy do wyświetlenia (usunięte znaczniki czasu)
bool bItalic; // czy kursywa (dźwięk nieistotny dla prowadzącego)
};
// klasa obsługująca napisy do dźwięków
class TTranscripts {
public:
// constructors
TTranscripts() = default;
// methods
void AddLine( std::string const &txt, float show, float hide, bool it );
// dodanie tekstów, długość dźwięku, czy istotne
void Add( std::string const &txt, bool background = false );
// usuwanie niepotrzebnych (ok. 10 razy na sekundę)
void Update();
// members
std::deque<TTranscript> aLines;
private:
// members
float fRefreshTime { 360 }; // wartośc zaporowa
};
extern TTranscripts Transcripts;
} // namespace ui