mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 15:04:54 +02:00
feat(editor): scripted mouse and keys for the self-tests and the launcher: mouse, mouseat, glide, button, wheel, press, release, tap, type and softcursor drive the window as the user does, EU07_LAUNCHER_SELFTEST runs a script in the launcher and the scenery wizard and hands an editor script on with editorscript; selectat selects the path nearest a point, switchlink no longer takes over a crossover left in the tool, joints without points logs every issue with free ends
This commit is contained in:
@@ -214,6 +214,7 @@ set(SOURCES
|
||||
"application/editormodesweep.cpp"
|
||||
"application/editormodeturntable.cpp"
|
||||
"application/editormodeselftest.cpp"
|
||||
"application/scriptedinput.cpp"
|
||||
"input/editormouseinput.cpp"
|
||||
"application/editoruilayer.cpp"
|
||||
"application/editoruipanels.cpp"
|
||||
|
||||
@@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "editor/editorProfile.hpp"
|
||||
#include "editor/editorInfra.hpp"
|
||||
#include "editor/editorGauge.hpp"
|
||||
#include "application/scriptedinput.h"
|
||||
#include "world/Sweep.h"
|
||||
|
||||
#include <array>
|
||||
@@ -1915,6 +1916,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
std::size_t next{0};
|
||||
int wait{0};
|
||||
bool quitting{false};
|
||||
scripted_input input;
|
||||
};
|
||||
selftest m_selftest;
|
||||
enum class selftest_result
|
||||
|
||||
@@ -69,6 +69,29 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
|
||||
m_focus_active = false;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "selectat")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
m_node = distance < 3.0 ? nearest : nullptr;
|
||||
m_track_point = {};
|
||||
ui()->set_node(m_node);
|
||||
WriteLog(format("SELFTEST selectat %.1f %.1f: %s", point.x, point.z, m_node != nullptr ? m_node->name().c_str() : "nothing"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "select")
|
||||
{
|
||||
std::string name;
|
||||
@@ -139,6 +162,48 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "mouseat")
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
words >> at.x >> at.y >> at.z;
|
||||
ImVec2 projected;
|
||||
if (false == screen_projection{}.project(at, projected))
|
||||
{
|
||||
WriteLog("SELFTEST mouseat: behind the camera");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
test.input.mouse_to(glm::dvec2{projected.x, projected.y});
|
||||
WriteLog(format("SELFTEST mouse %.0f %.0f", projected.x, projected.y));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "glide")
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
int steps{8};
|
||||
int every{2};
|
||||
std::string shots;
|
||||
words >> at.x >> at.y >> at.z >> steps >> every >> shots;
|
||||
ImVec2 projected;
|
||||
if (false == screen_projection{}.project(at, projected))
|
||||
return selftest_result::handled;
|
||||
glm::dvec2 const from{test.input.mouse().value_or(glm::dvec2{projected.x, projected.y})};
|
||||
glm::dvec2 const to{projected.x, projected.y};
|
||||
std::vector<std::string> expanded;
|
||||
for (int step = 1; step <= std::max(1, steps); ++step)
|
||||
{
|
||||
auto const t{static_cast<double>(step) / std::max(1, steps)};
|
||||
auto const eased{t * t * (3.0 - 2.0 * t)};
|
||||
auto const point{glm::mix(from, to, eased)};
|
||||
expanded.push_back(format("mouse %.1f %.1f", point.x, point.y));
|
||||
expanded.push_back("wait " + std::to_string(std::max(1, every)));
|
||||
if (shots == "shot")
|
||||
expanded.push_back("screenshot");
|
||||
}
|
||||
test.lines.insert(test.lines.begin() + static_cast<std::ptrdiff_t>(test.next), expanded.begin(), expanded.end());
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (test.input.dispatch(command, words))
|
||||
return selftest_result::handled;
|
||||
if (command == "screenshot")
|
||||
{
|
||||
Application.queue_screenshot();
|
||||
@@ -1697,6 +1762,7 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
|
||||
tool.templates = editor_track::standard_switch_templates();
|
||||
tool.collected = true;
|
||||
tool.armed = -1;
|
||||
tool.connection = -1;
|
||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||
if (tool.templates[i].label.rfind(label, 0) == 0)
|
||||
{
|
||||
@@ -2174,9 +2240,9 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
|
||||
int around{0};
|
||||
for (auto const &issue : m_joints.issues)
|
||||
{
|
||||
if (issue.type == joint_issue::kind::open)
|
||||
if (false == points.empty() && issue.type == joint_issue::kind::open)
|
||||
continue;
|
||||
if (std::none_of(points.begin(), points.end(), [&](glm::dvec3 const &Point) { return geometry::plan_distance(Point, issue.position) < 5.0; }))
|
||||
if (false == points.empty() && std::none_of(points.begin(), points.end(), [&](glm::dvec3 const &Point) { return geometry::plan_distance(Point, issue.position) < 5.0; }))
|
||||
continue;
|
||||
++around;
|
||||
WriteLog(format("SELFTEST joints: %s %.3f at %.1f %.1f, %s / %s", kinds[static_cast<int>(issue.type)], issue.value, issue.position.x, issue.position.z, issue.track != nullptr ? issue.track->name().c_str() : "-",
|
||||
@@ -3095,6 +3161,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm
|
||||
// wait <frames>
|
||||
// camera <x> <y> <z> <x> <y> <z> from the first point, looking at the second
|
||||
// select <track name>
|
||||
// selectat <x> <z> the path nearest the point, within 3 m, as a click on it
|
||||
// blade [name] logs the blades of each switch cut from the rails: tip, 50 mm head, full head, heel, drop of the head, open flangeway, the gauge lines apart against x^2/2R, vertices in both positions
|
||||
// frog [name] logs where the inner rails of each switch cross: distance on both paths, 1:n, throat, nose, check rails
|
||||
// guards [name] logs each guard rail and what each of its ends and breaks is cut along; a fault when a cut is not along a rail or a glued end not on the guard rail of the other track
|
||||
@@ -3152,7 +3219,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm
|
||||
// join <x> <z> <x> <z> [radius] [variant] [transitions 0|1] the free ends at the points joined, as a drag from one to the other
|
||||
// layto <x> <z> <x> <z> <layout 0..3> <radius> from the free end at the first point towards the second, as a drag in the layout
|
||||
// layclicks <layout 0..3> <radius> <x> <z>... clicks of the lay tool at the points, a free end within 3 m joined
|
||||
// joints <x> <z>... the joints check over the scenery, the issues around the points logged
|
||||
// joints <x> <z>... the joints check over the scenery, the issues around the points logged, every issue with free ends without points
|
||||
// route <x> <z> <single 0|1> the route design of the path nearest the point, a curve or the whole line
|
||||
// routevertex <i> <R> <L in> <L out> <cant>, routearc <i> <R> <L> <share>, routeshare <i> <share>, routekm <km>
|
||||
// routeapply, routetake as the buttons of the route design do it
|
||||
@@ -3166,6 +3233,12 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm
|
||||
// switches logs the switches kept in the scenery and the speed of each diverging track
|
||||
// deletetrack <name>, deletekind <kind> the path deleted as Del does it, or the first path over an element of the kind
|
||||
// btfcheck <terrain file> the .txtf converted to .btf, the editor lines the binary file keeps logged
|
||||
// mouse <x> <y>, mouseat <x> <y> <z> the cursor moved to the point of the window, or to where the point of the scene is seen, as the mouse moves it
|
||||
// glide <x> <y> <z> <steps> <frames> [shot] the cursor led to where the point is seen in steps, frames apart, a screenshot after each step with shot
|
||||
// button <left|right|middle> <press|release>, wheel <offset> the buttons and the wheel of the mouse, with the keys held
|
||||
// press <key>, release <key>, tap <key> a key held, let go, or both (ctrl, shift, alt, esc, enter, backspace, del, space, tab, a..z, 0..9, f1..f12)
|
||||
// softcursor <0|1> the cursor drawn into the picture, for screenshots
|
||||
// type <text> the characters typed into the text field with the focus
|
||||
// screenshot
|
||||
// save
|
||||
// log <text>
|
||||
|
||||
218
application/scriptedinput.cpp
Normal file
218
application/scriptedinput.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/scriptedinput.h"
|
||||
|
||||
#include "application/application.h"
|
||||
#include "application/uilayer.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int key_named(std::string const &Name)
|
||||
{
|
||||
static std::map<std::string, int> const names{{"ctrl", GLFW_KEY_LEFT_CONTROL}, {"shift", GLFW_KEY_LEFT_SHIFT}, {"alt", GLFW_KEY_LEFT_ALT}, {"esc", GLFW_KEY_ESCAPE}, {"enter", GLFW_KEY_ENTER}, {"backspace", GLFW_KEY_BACKSPACE},
|
||||
{"del", GLFW_KEY_DELETE}, {"space", GLFW_KEY_SPACE}, {"tab", GLFW_KEY_TAB}};
|
||||
if (auto const found{names.find(Name)}; found != names.end())
|
||||
return found->second;
|
||||
if (Name.size() == 1 && std::isalpha(static_cast<unsigned char>(Name[0])))
|
||||
return GLFW_KEY_A + (std::toupper(static_cast<unsigned char>(Name[0])) - 'A');
|
||||
if (Name.size() == 1 && std::isdigit(static_cast<unsigned char>(Name[0])))
|
||||
return GLFW_KEY_0 + (Name[0] - '0');
|
||||
if (Name.size() > 1 && (Name[0] == 'f' || Name[0] == 'F') && std::isdigit(static_cast<unsigned char>(Name[1])))
|
||||
return GLFW_KEY_F1 + std::stoi(Name.substr(1)) - 1;
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
int modifier_of(int const Key)
|
||||
{
|
||||
return Key == GLFW_KEY_LEFT_CONTROL ? GLFW_MOD_CONTROL : Key == GLFW_KEY_LEFT_SHIFT ? GLFW_MOD_SHIFT : Key == GLFW_KEY_LEFT_ALT ? GLFW_MOD_ALT : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void scripted_input::modifiers() const
|
||||
{
|
||||
auto &io{ImGui::GetIO()};
|
||||
io.AddKeyEvent(ImGuiMod_Ctrl, (m_mods & GLFW_MOD_CONTROL) != 0);
|
||||
io.AddKeyEvent(ImGuiMod_Shift, (m_mods & GLFW_MOD_SHIFT) != 0);
|
||||
io.AddKeyEvent(ImGuiMod_Alt, (m_mods & GLFW_MOD_ALT) != 0);
|
||||
}
|
||||
|
||||
void scripted_input::mouse_to(glm::dvec2 const &Screen)
|
||||
{
|
||||
if (false == m_mouse.has_value())
|
||||
ui_layer::cursor_enter_callback(1);
|
||||
m_mouse = Screen;
|
||||
ui_layer::cursor_pos_callback(Screen.x, Screen.y);
|
||||
Global.cursor_pos = glm::ivec2(Screen);
|
||||
Application.on_cursor_pos(Screen.x, Screen.y);
|
||||
}
|
||||
|
||||
bool scripted_input::dispatch(std::string const &Command, std::istringstream &Words)
|
||||
{
|
||||
if (Command == "mouse")
|
||||
{
|
||||
glm::dvec2 screen{0.0};
|
||||
Words >> screen.x >> screen.y;
|
||||
mouse_to(screen);
|
||||
WriteLog(format("SELFTEST mouse %.0f %.0f", screen.x, screen.y));
|
||||
return true;
|
||||
}
|
||||
if (Command == "button")
|
||||
{
|
||||
std::string which;
|
||||
std::string action;
|
||||
Words >> which >> action;
|
||||
auto const button{which == "right" ? GLFW_MOUSE_BUTTON_RIGHT : which == "middle" ? GLFW_MOUSE_BUTTON_MIDDLE : GLFW_MOUSE_BUTTON_LEFT};
|
||||
Application.on_mouse_button(button, action == "release" ? GLFW_RELEASE : GLFW_PRESS, m_mods);
|
||||
modifiers();
|
||||
return true;
|
||||
}
|
||||
if (Command == "wheel")
|
||||
{
|
||||
double offset{0.0};
|
||||
Words >> offset;
|
||||
Application.on_scroll(0.0, offset);
|
||||
modifiers();
|
||||
return true;
|
||||
}
|
||||
if (Command == "press" || Command == "release" || Command == "tap")
|
||||
{
|
||||
std::string name;
|
||||
Words >> name;
|
||||
auto const key{key_named(name)};
|
||||
if (key == GLFW_KEY_UNKNOWN)
|
||||
{
|
||||
WriteLog("SELFTEST " + Command + ": unknown key " + name);
|
||||
return true;
|
||||
}
|
||||
if (Command != "release")
|
||||
{
|
||||
m_mods |= modifier_of(key);
|
||||
Application.on_key(key, 0, GLFW_PRESS, m_mods);
|
||||
modifiers();
|
||||
}
|
||||
if (Command != "press")
|
||||
{
|
||||
m_mods &= ~modifier_of(key);
|
||||
Application.on_key(key, 0, GLFW_RELEASE, m_mods);
|
||||
modifiers();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Command == "type")
|
||||
{
|
||||
std::string text;
|
||||
std::getline(Words >> std::ws, text);
|
||||
for (std::size_t index = 0; index < text.size();)
|
||||
{
|
||||
auto const lead{static_cast<unsigned char>(text[index])};
|
||||
auto const extra{lead >= 0xF0 ? 3 : lead >= 0xE0 ? 2 : lead >= 0xC0 ? 1 : 0};
|
||||
unsigned int code{extra == 0 ? lead : lead & (0x3Fu >> extra)};
|
||||
for (int k = 1; k <= extra && index + k < text.size(); ++k)
|
||||
code = (code << 6) | (static_cast<unsigned char>(text[index + k]) & 0x3Fu);
|
||||
Application.on_char(code);
|
||||
index += extra + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Command == "softcursor")
|
||||
{
|
||||
int on{1};
|
||||
Words >> on;
|
||||
ImGui::GetIO().MouseDrawCursor = on != 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void input_script::step()
|
||||
{
|
||||
if (false == m_loaded)
|
||||
{
|
||||
m_loaded = true;
|
||||
auto const *file{std::getenv(m_variable)};
|
||||
if (file == nullptr)
|
||||
return;
|
||||
std::ifstream stream(file);
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
if (false == line.empty() && line.front() != '#')
|
||||
m_lines.push_back(line);
|
||||
WriteLog("SELFTEST: " + std::to_string(m_lines.size()) + " steps from " + file);
|
||||
}
|
||||
if (m_wait > 0)
|
||||
{
|
||||
--m_wait;
|
||||
return;
|
||||
}
|
||||
while (m_next < m_lines.size())
|
||||
{
|
||||
std::istringstream words(m_lines[m_next++]);
|
||||
std::string command;
|
||||
words >> command;
|
||||
if (command == "wait")
|
||||
{
|
||||
words >> m_wait;
|
||||
return;
|
||||
}
|
||||
if (command == "screenshot")
|
||||
{
|
||||
Application.queue_screenshot();
|
||||
m_wait = 3;
|
||||
return;
|
||||
}
|
||||
if (command == "log")
|
||||
{
|
||||
std::string text;
|
||||
std::getline(words >> std::ws, text);
|
||||
WriteLog("SELFTEST " + text);
|
||||
continue;
|
||||
}
|
||||
if (command == "editorscript")
|
||||
{
|
||||
std::string file;
|
||||
words >> file;
|
||||
#ifdef _WIN32
|
||||
_putenv_s("EU07_EDITOR_SELFTEST", file.c_str());
|
||||
#else
|
||||
setenv("EU07_EDITOR_SELFTEST", file.c_str(), 1);
|
||||
#endif
|
||||
WriteLog("SELFTEST editor script " + file);
|
||||
continue;
|
||||
}
|
||||
if (command == "quit")
|
||||
{
|
||||
if (false == m_quitting)
|
||||
{
|
||||
WriteLog("SELFTEST done");
|
||||
m_quitting = true;
|
||||
m_wait = 30;
|
||||
--m_next;
|
||||
return;
|
||||
}
|
||||
Application.queue_quit(true);
|
||||
return;
|
||||
}
|
||||
if (false == m_input.dispatch(command, words))
|
||||
WriteLog("SELFTEST unknown command " + command);
|
||||
}
|
||||
}
|
||||
49
application/scriptedinput.h
Normal file
49
application/scriptedinput.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class scripted_input
|
||||
{
|
||||
public:
|
||||
bool dispatch(std::string const &Command, std::istringstream &Words);
|
||||
void mouse_to(glm::dvec2 const &Screen);
|
||||
std::optional<glm::dvec2> const &mouse() const
|
||||
{
|
||||
return m_mouse;
|
||||
}
|
||||
|
||||
private:
|
||||
void modifiers() const;
|
||||
std::optional<glm::dvec2> m_mouse;
|
||||
int m_mods{0};
|
||||
};
|
||||
|
||||
class input_script
|
||||
{
|
||||
public:
|
||||
explicit input_script(char const *Variable) : m_variable{Variable} {}
|
||||
void step();
|
||||
|
||||
private:
|
||||
char const *m_variable;
|
||||
bool m_loaded{false};
|
||||
std::vector<std::string> m_lines;
|
||||
std::size_t m_next{0};
|
||||
int m_wait{0};
|
||||
bool m_quitting{false};
|
||||
scripted_input m_input;
|
||||
};
|
||||
@@ -17,6 +17,7 @@ bool launcher_mode::init()
|
||||
|
||||
bool launcher_mode::update()
|
||||
{
|
||||
m_script.step();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#pragma once
|
||||
|
||||
#include "application/applicationmode.h"
|
||||
#include "application/scriptedinput.h"
|
||||
|
||||
class launcher_mode : public application_mode {
|
||||
|
||||
@@ -33,4 +34,7 @@ public:
|
||||
void on_window_resize( int w, int h ) override;
|
||||
void on_event_poll() override { /* not handled by the launcher */ }
|
||||
bool is_command_processor() const override { return false; }
|
||||
|
||||
private:
|
||||
input_script m_script{"EU07_LAUNCHER_SELFTEST"};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user