mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
1382
application/application.cpp
Normal file
1382
application/application.cpp
Normal file
File diff suppressed because it is too large
Load Diff
131
application/application.h
Normal file
131
application/application.h
Normal 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;
|
||||
59
application/applicationmode.h
Normal file
59
application/applicationmode.h
Normal 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
1349
application/driverhints.cpp
Normal file
File diff suppressed because it is too large
Load Diff
21
application/driverhints.h
Normal file
21
application/driverhints.h
Normal 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
|
||||
105
application/driverhints_def.h
Normal file
105
application/driverhints_def.h
Normal 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
1301
application/drivermode.cpp
Normal file
File diff suppressed because it is too large
Load Diff
119
application/drivermode.h
Normal file
119
application/drivermode.h
Normal 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
|
||||
};
|
||||
279
application/driveruilayer.cpp
Normal file
279
application/driveruilayer.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
72
application/driveruilayer.h
Normal file
72
application/driveruilayer.h
Normal 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;
|
||||
};
|
||||
1632
application/driveruipanels.cpp
Normal file
1632
application/driveruipanels.cpp
Normal file
File diff suppressed because it is too large
Load Diff
154
application/driveruipanels.h
Normal file
154
application/driveruipanels.h
Normal 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
913
application/editormode.cpp
Normal 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
146
application/editormode.h
Normal 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();
|
||||
};
|
||||
96
application/editoruilayer.cpp
Normal file
96
application/editoruilayer.cpp
Normal 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;
|
||||
}
|
||||
48
application/editoruilayer.h
Normal file
48
application/editoruilayer.h
Normal 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
|
||||
};
|
||||
618
application/editoruipanels.cpp
Normal file
618
application/editoruipanels.cpp
Normal 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 ∅
|
||||
|
||||
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();
|
||||
}
|
||||
125
application/editoruipanels.h
Normal file
125
application/editoruipanels.h
Normal 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;
|
||||
};
|
||||
87
application/scenarioloadermode.cpp
Normal file
87
application/scenarioloadermode.cpp
Normal 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();
|
||||
}
|
||||
39
application/scenarioloadermode.h
Normal file
39
application/scenarioloadermode.h
Normal 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;
|
||||
};
|
||||
327
application/scenarioloaderuilayer.cpp
Normal file
327
application/scenarioloaderuilayer.cpp
Normal 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));
|
||||
}
|
||||
33
application/scenarioloaderuilayer.h
Normal file
33
application/scenarioloaderuilayer.h
Normal 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
622
application/uilayer.cpp
Normal 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
164
application/uilayer.h
Normal 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;
|
||||
};
|
||||
91
application/uitranscripts.cpp
Normal file
91
application/uitranscripts.cpp
Normal 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
|
||||
41
application/uitranscripts.h
Normal file
41
application/uitranscripts.h
Normal 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
|
||||
Reference in New Issue
Block a user