17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 15:54:53 +01:00

refactor(editor): the trainset is a tool of its own

The fourth tool out of editor_mode. The track under the cursor and the
first steps of the tutorial become types of their own, which the editor
gives to the tools, and a tool shown as a tab of the lineside panel keeps
its open and expanded flags in a tool_tab. The trainsets waiting for the
save are handed to the editor by the tool, which hears back when they
were written. The self-test command trainset moves with it.
This commit is contained in:
maj00r
2026-10-21 09:44:17 +02:00
parent 8ba8345faf
commit c45c4713d6
11 changed files with 188 additions and 121 deletions

View File

@@ -1286,9 +1286,8 @@ bool editor_mode::update()
{
Global.editor_tracks = (current_work_area() == work_area::tracks);
selftest_step();
if (m_vehicle.leave)
if (m_vehicle.take_leave())
{
m_vehicle.leave = false;
Application.pop_mode();
return true;
}
@@ -1456,7 +1455,7 @@ bool editor_mode::update()
{
draw_hekto_overlay();
draw_fouling_overlay();
draw_vehicle_marker();
m_vehicle.draw_overlay();
}
render_track_context();
update_build_tools();
@@ -2937,13 +2936,7 @@ bool editor_mode::save()
auto const controls{turntable_controls(rootstatements)};
auto const noses{nose_controls(rootstatements)};
std::vector<std::string> trailing;
std::erase_if(m_vehicle.trainsets, [](auto const &Trainset) {
auto const *track{simulation::Paths.find(Trainset.track)};
return track == nullptr || track->m_editorremoved;
});
auto const placed{m_vehicle.trainsets.size()};
for (auto const &trainset : m_vehicle.trainsets)
trailing.insert(trailing.end(), trainset.lines.begin(), trainset.lines.end());
auto const placed{m_vehicle.trainset_lines(trailing)};
if (placed == 0)
starter_trainset(trailing);
auto const result = scene::Layers.save(rootstatements, trailing);
@@ -2952,10 +2945,7 @@ bool editor_mode::save()
if (result.success && noses)
m_nose_included = true;
if (result.success && placed > 0)
{
m_vehicle.trainsets.clear();
m_vehicle.status = format(STR_C("%d trainset(s) written to the scenery"), static_cast<int>(placed));
}
m_vehicle.trainsets_written();
if (result.success && placed == 0 && trailing.size() == 3)
{
std::istringstream header{trailing.front()};
@@ -5301,12 +5291,12 @@ void editor_mode::render_first_steps()
}
case trial::trainset:
{
ImGui::BeginDisabled(ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::lineside && m_vehicle.open);
ImGui::BeginDisabled(ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::lineside && m_vehicle.tab().open);
if (ImGui::Button(STR_C("The trainset tool")))
{
choose_work_area(work_area::tracks);
show_track_tab(track_tab::lineside);
m_vehicle.expand = true;
m_vehicle.tab().expand = true;
}
ImGui::EndDisabled();
break;

View File

@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/editormodegauge.h"
#include "application/editormodejoints.h"
#include "application/editormodespeed.h"
#include "application/editormodevehicle.h"
#include "application/editortool.h"
#include "scene/sceneeditor.h"
#include "scene/scenenode.h"
@@ -55,12 +56,6 @@ class TAnimModel;
// a tool of a field of work of the editor, a button in the toolbar and over the 3d view
struct editor_tool_button;
class TTrack;
namespace ui
{
class vehicles_bank;
struct vehicle_desc;
struct skin_set;
}
class editor_mode : public application_mode, private editor_track::observer, private editor_services
{
@@ -240,9 +235,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void render_help_menu();
void render_shortcuts_window();
bool m_shortcuts_open{false};
enum class first_step { tracks, lay, laid, wheel, join, turnout, undo, save, drive, look, top, select, shape, profile, profile_point, profile_apply, terrain, nmt, plan, plan_band, plan_apply, signal, posts, count };
using first_step = editor_first_step;
void render_first_steps();
void first_step_done(first_step const Step);
void first_step_done(first_step const Step) override;
bool m_first_steps_open{false};
std::array<bool, static_cast<std::size_t>(first_step::count)> m_first_steps_done{};
int m_tutorial_page{0};
@@ -718,6 +713,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
bool track_editing() const override;
std::size_t history_size() const override;
glm::dvec3 camera_position() const override;
track_hover const &hovered_track() const override;
void render_path_ui();
void draw_track_overlay() const;
editor_track::point_ref track_handle_hit() const;
@@ -1516,13 +1512,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
// a scenery with no tracks yet: in the middle of the view, how the first one is laid
void draw_empty_scenery_hint();
// path under the cursor, outlined before a click so it's known which one the click takes
struct track_hover
{
TTrack *track{nullptr};
int path{0};
glm::dvec3 point{0.0};
double since{0.0};
};
using track_hover = ::track_hover;
track_hover m_hover;
track_hover track_under_cursor(TTrack const *Only = nullptr, float const Pixels = 0.0f) const;
track_hover split_spot() const;
@@ -1925,49 +1915,6 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void render_fouling_ui();
void draw_fouling_overlay() const;
// a trainset put on the selected path, written to the scenery on save, or only to be driven
struct vehicle_tool
{
bool open{false};
bool expand{false};
std::shared_ptr<ui::vehicles_bank> bank;
char search[64]{};
bool controllable_only{true};
std::shared_ptr<ui::vehicle_desc> vehicle;
std::shared_ptr<ui::skin_set> skin;
int driver{0}; // 0: a driver, to be driven; 1: nobody, parked
TTrack *track{nullptr};
glm::dvec3 point{0.0};
std::string placed;
std::string status;
bool leave{false}; // the editor gives way to the driving at the start of the next frame
struct consist_vehicle
{
std::shared_ptr<ui::vehicle_desc> vehicle;
std::shared_ptr<ui::skin_set> skin;
bool turned{false};
};
std::vector<consist_vehicle> consist;
int picked{-1}; // vehicle of the train whose buttons are shown
std::set<ui::skin_set const *> minis; // skins whose side view was asked for
int facing{1};
bool write{true};
char timetable[64]{"none"};
double velocity{0.0};
struct trainset
{
std::string head;
std::string track;
std::vector<std::string> lines;
};
std::vector<trainset> trainsets;
};
vehicle_tool m_vehicle;
void vehicle_start(TTrack &Track, glm::dvec3 const &Point);
bool vehicle_place();
void render_vehicle_ui();
void draw_vehicle_marker() const;
struct signal_template
{
std::string file;
@@ -2445,7 +2392,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri
gauge_tool m_gauge{*this};
speed_tool m_speed{*this};
joints_tool m_joints{*this};
std::vector<editor_tool *> m_tools{&m_gauge, &m_speed, &m_joints};
vehicle_tool m_vehicle{*this};
std::vector<editor_tool *> m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle};
void infra_rebase();
void render_infra_search();

View File

@@ -293,29 +293,6 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
WriteLog(format("SELFTEST mapstate orthophoto %d top %d extent %.0f mark %s camera %.1f %.1f %.1f", m_orthophoto.enabled() ? 1 : 0, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent, marked.empty() ? "-" : marked.front().c_str(), Camera.Pos.x, Camera.Pos.y, Camera.Pos.z));
return selftest_result::handled;
}
if (command == "trainset")
{
std::string name;
std::string vehicle;
std::string skin;
int facing{1};
words >> name >> vehicle >> skin >> facing >> m_vehicle.velocity;
auto *track{simulation::Paths.find(name)};
if (track == nullptr)
return selftest_result::handled;
auto desc{std::make_shared<ui::vehicle_desc>()};
desc->path = vehicle;
auto set{std::make_shared<ui::skin_set>()};
set->skin = skin;
m_vehicle.consist.clear();
m_vehicle.vehicle = desc;
m_vehicle.skin = set;
vehicle_start(*track, geometry::bezier{track->m_paths.front()}.point(0.5));
m_vehicle.facing = facing;
auto const placed{vehicle_place()};
WriteLog(format("SELFTEST trainset %s placed %d pending %d: %s", name.c_str(), placed ? 1 : 0, static_cast<int>(m_vehicle.trainsets.size()), m_vehicle.status.c_str()));
return selftest_result::handled;
}
if (command == "selectmodel")
{
glm::dvec3 point{0.0};

View File

@@ -388,7 +388,7 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm
std::string tab;
words >> tab;
show_track_tab(tab == "parallel" ? track_tab::parallel : track_tab::lineside);
(tab == "vehicle" ? m_vehicle.expand : tab == "hekto" ? m_hekto.expand : tab == "fouling" ? m_fouling.expand : m_parallel.expand) = true;
(tab == "vehicle" ? m_vehicle.tab().expand : tab == "hekto" ? m_hekto.expand : tab == "fouling" ? m_fouling.expand : m_parallel.expand) = true;
return selftest_result::handled;
}
if (command == "bendpanel")

View File

@@ -142,7 +142,7 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
m_hekto.expand = tab == 1;
m_fouling.expand = tab == 2;
m_parallel.expand = tab == 3;
m_vehicle.expand = tab == 4;
m_vehicle.tab().expand = tab == 4;
return selftest_result::handled;
}
if (command == "curve")

View File

@@ -67,6 +67,11 @@ glm::dvec3 editor_mode::camera_position() const
return Camera.Pos;
}
editor_mode::track_hover const &editor_mode::hovered_track() const
{
return m_hover;
}
void editor_mode::set_current_node(scene::basic_node *Node)
{
m_node = Node;
@@ -554,7 +559,7 @@ void editor_mode::render_track_inspector()
m_hekto.open = false;
m_fouling.open = false;
m_parallel.open = false;
m_vehicle.open = false;
m_vehicle.tab().open = false;
}
ui()->set_track(m_track_window_open);
if (false == m_track_window_open)

View File

@@ -793,7 +793,7 @@ void editor_mode::render_track_context()
if (ImGui::MenuItem(STR_C("Place a vehicle here")))
{
show_track_tab(track_tab::lineside);
vehicle_start(*track, point);
m_vehicle.start(*track, point);
}
if (ImGui::MenuItem(STR_C("Hectometre posts from here")))
{
@@ -1479,7 +1479,7 @@ void editor_mode::render_lineside_ui()
std::array<tab, 3> const tabs{{
{"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }},
{"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_ui(); }},
{"Trainset", &m_vehicle.open, &m_vehicle.expand, [this]() { render_vehicle_ui(); }},
{"Trainset", &m_vehicle.tab().open, &m_vehicle.tab().expand, [this]() { m_vehicle.render_options(); }},
}};
if (selected_track() == nullptr)
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: the line the objects go along"));

View File

@@ -8,8 +8,7 @@ http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "application/editormodevehicle.h"
#include "application/editorprojection.h"
#include "application/editormodeshared.h"
#include "application/application.h"
@@ -154,18 +153,18 @@ double distance_along(TTrack const &Track, int const Path, glm::dvec3 const &Poi
} // namespace
void editor_mode::vehicle_start(TTrack &Track, glm::dvec3 const &Point)
void vehicle_tool::start(TTrack &Track, glm::dvec3 const &Point)
{
m_vehicle.track = &Track;
m_vehicle.point = Point;
m_vehicle.expand = true;
m_tab.expand = true;
m_vehicle.status.clear();
auto const heading{heading_at(Track, Track.eType == tt_Switch ? path_under(Track, Point) : 0, Point)};
glm::dvec2 const away{Point.x - Global.pCamera.Pos.x, Point.z - Global.pCamera.Pos.z};
m_vehicle.facing = glm::dot(heading, away) >= 0.0 ? 1 : -1;
}
bool editor_mode::vehicle_place()
bool vehicle_tool::place()
{
auto &tool{m_vehicle};
if (tool.track == nullptr || tool.track->m_editorremoved)
@@ -210,7 +209,7 @@ bool editor_mode::vehicle_place()
return false;
}
tool.placed = head;
first_step_done(first_step::drive);
m_services.first_step_done(editor_first_step::drive);
auto const trackname{tool.track->name().empty() ? std::string{STR("(no name)")} : tool.track->name()};
auto const behind{static_cast<int>(vehicles.size()) - 1};
tool.status = behind > 0 ? format(STR_P("%s and %d vehicle behind it stand on %s", "%s and %d vehicles behind it stand on %s", behind), head.c_str(), behind, trackname.c_str()) : format(STR_C("%s stands on %s"), head.c_str(), trackname.c_str());
@@ -220,7 +219,7 @@ bool editor_mode::vehicle_place()
{
std::string timetable{tool.timetable};
timetable.erase(std::remove_if(timetable.begin(), timetable.end(), [](unsigned char const Character) { return std::isspace(Character) != 0; }), timetable.end());
vehicle_tool::trainset entry{head, tool.track->name(), {}};
vehicle_state::trainset entry{head, tool.track->name(), {}};
entry.lines.push_back("trainset " + (timetable.empty() ? std::string{"none"} : timetable) + " " + tool.track->name() + " " + to_string(offset, 2) + " " + to_string(tool.velocity, 0));
if (against)
entry.lines.push_back("reversed");
@@ -238,14 +237,15 @@ bool editor_mode::vehicle_place()
return true;
}
void editor_mode::render_vehicle_ui()
void vehicle_tool::render_options()
{
auto &tool{m_vehicle};
auto *selected{selected_track()};
if (selected != nullptr && selected == tool.track && m_hover.track == selected && ImGui::IsMouseClicked(0) && false == ImGui::GetIO().WantCaptureMouse)
vehicle_start(*selected, m_hover.point);
auto *selected{m_services.selected_track()};
auto const &hover{m_services.hovered_track()};
if (selected != nullptr && selected == tool.track && hover.track == selected && ImGui::IsMouseClicked(0) && false == ImGui::GetIO().WantCaptureMouse)
start(*selected, hover.point);
else if (selected != nullptr && selected != tool.track)
vehicle_start(*selected, m_hover.track == selected ? m_hover.point : bezier{selected->m_paths.front()}.point(0.5));
start(*selected, hover.track == selected ? hover.point : bezier{selected->m_paths.front()}.point(0.5));
if (tool.track == nullptr)
{
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: where the vehicle stands"));
@@ -430,11 +430,11 @@ void editor_mode::render_vehicle_ui()
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.65f, 0.3f, 1.0f));
if (ImGui::Button(STR_C("Place"), ImVec2(half, tall)))
vehicle_place();
place();
item_tooltip("A trainset on the track, placed where the track was clicked, or in the middle of the path");
ImGui::PopStyleColor(2);
ImGui::SameLine();
if (ImGui::Button(STR_C("Place and drive"), ImVec2(-1.0f, tall)) && vehicle_place())
if (ImGui::Button(STR_C("Place and drive"), ImVec2(-1.0f, tall)) && place())
{
Global.editor_enter_vehicle = tool.placed;
tool.leave = true;
@@ -461,10 +461,10 @@ void editor_mode::render_vehicle_ui()
tool.trainsets.erase(tool.trainsets.begin() + static_cast<std::ptrdiff_t>(*drop));
}
void editor_mode::draw_vehicle_marker() const
void vehicle_tool::draw_overlay() const
{
auto const &tool{m_vehicle};
if (false == tool.open || tool.track == nullptr)
if (false == m_tab.open || tool.track == nullptr)
return;
screen_projection const projection;
ImVec2 screen;
@@ -490,3 +490,53 @@ void editor_mode::draw_vehicle_marker() const
ImVec2 const side{-unit.y, unit.x};
drawlist->AddTriangleFilled(ImVec2(end.x + unit.x * 10.0f, end.y + unit.y * 10.0f), ImVec2(end.x + side.x * 7.0f, end.y + side.y * 7.0f), ImVec2(end.x - side.x * 7.0f, end.y - side.y * 7.0f), colour);
}
bool vehicle_tool::take_leave()
{
return std::exchange(m_vehicle.leave, false);
}
std::size_t vehicle_tool::trainset_lines(std::vector<std::string> &Lines)
{
std::erase_if(m_vehicle.trainsets, [](auto const &Trainset) {
auto const *track{simulation::Paths.find(Trainset.track)};
return track == nullptr || track->m_editorremoved;
});
for (auto const &trainset : m_vehicle.trainsets)
Lines.insert(Lines.end(), trainset.lines.begin(), trainset.lines.end());
return m_vehicle.trainsets.size();
}
void vehicle_tool::trainsets_written()
{
m_vehicle.status = format(STR_C("%d trainset(s) written to the scenery"), static_cast<int>(m_vehicle.trainsets.size()));
m_vehicle.trainsets.clear();
}
editor_selftest_result vehicle_tool::selftest(std::string const &Command, std::istringstream &Words)
{
if (Command == "trainset")
{
std::string name;
std::string vehicle;
std::string skin;
int facing{1};
Words >> name >> vehicle >> skin >> facing >> m_vehicle.velocity;
auto *track{simulation::Paths.find(name)};
if (track == nullptr)
return editor_selftest_result::handled;
auto desc{std::make_shared<ui::vehicle_desc>()};
desc->path = vehicle;
auto set{std::make_shared<ui::skin_set>()};
set->skin = skin;
m_vehicle.consist.clear();
m_vehicle.vehicle = desc;
m_vehicle.skin = set;
start(*track, geometry::bezier{track->m_paths.front()}.point(0.5));
m_vehicle.facing = facing;
auto const placed{place()};
WriteLog(format("SELFTEST trainset %s placed %d pending %d: %s", name.c_str(), placed ? 1 : 0, static_cast<int>(m_vehicle.trainsets.size()), m_vehicle.status.c_str()));
return editor_selftest_result::handled;
}
return editor_selftest_result::unhandled;
}

View File

@@ -0,0 +1,79 @@
/*
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 "application/editortool.h"
#include <memory>
#include <set>
#include <string>
#include <vector>
namespace ui
{
class vehicles_bank;
struct vehicle_desc;
struct skin_set;
}
// a trainset put on the selected path, written to the scenery on save, or only to be driven
class vehicle_tool final : public editor_tool
{
public:
explicit vehicle_tool(editor_services &Services) : m_services{Services} {}
void draw_overlay() const override;
void render_options() override;
editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override;
tool_tab &tab() { return m_tab; }
void start(TTrack &Track, glm::dvec3 const &Point);
bool take_leave();
std::size_t trainset_lines(std::vector<std::string> &Lines);
void trainsets_written();
private:
editor_services &m_services;
tool_tab m_tab;
struct vehicle_state
{
std::shared_ptr<ui::vehicles_bank> bank;
char search[64]{};
bool controllable_only{true};
std::shared_ptr<ui::vehicle_desc> vehicle;
std::shared_ptr<ui::skin_set> skin;
int driver{0}; // 0: a driver, to be driven; 1: nobody, parked
TTrack *track{nullptr};
glm::dvec3 point{0.0};
std::string placed;
std::string status;
bool leave{false}; // the editor gives way to the driving at the start of the next frame
struct consist_vehicle
{
std::shared_ptr<ui::vehicle_desc> vehicle;
std::shared_ptr<ui::skin_set> skin;
bool turned{false};
};
std::vector<consist_vehicle> consist;
int picked{-1}; // vehicle of the train whose buttons are shown
std::set<ui::skin_set const *> minis; // skins whose side view was asked for
int facing{1};
bool write{true};
char timetable[64]{"none"};
double velocity{0.0};
struct trainset
{
std::string head;
std::string track;
std::vector<std::string> lines;
};
std::vector<trainset> trainsets;
};
vehicle_state m_vehicle;
bool place();
};

View File

@@ -160,7 +160,7 @@ void editor_mode::work_area_tools(std::vector<tool_button> &Buttons)
}
case work_area::tracks:
{
auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.open};
auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.tab().open};
auto const add = [&](track_tab const Tab, bool const Group) {
auto const &modes{track_modes()};
auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == Tab; })};
@@ -191,7 +191,7 @@ void editor_mode::work_area_tools(std::vector<tool_button> &Buttons)
add(track_tab::lineside, false);
Buttons.push_back({icon::vehicle, STR_C("Trainset"), nullptr, STR_C("A trainset on the selected track: written to the scenery on save, and to drive at once"), vehicle, [this, vehicle]() {
show_track_tab(vehicle ? track_tab::path : track_tab::lineside);
m_vehicle.expand = false == vehicle;
m_vehicle.tab().expand = false == vehicle;
}});
Buttons.back().accent = family_colour(1);
add(track_tab::profile, true);

View File

@@ -34,6 +34,22 @@ enum class editor_selftest_result
again
};
enum class editor_first_step { tracks, lay, laid, wheel, join, turnout, undo, save, drive, look, top, select, shape, profile, profile_point, profile_apply, terrain, nmt, plan, plan_band, plan_apply, signal, posts, count };
struct track_hover
{
TTrack *track{nullptr};
int path{0};
glm::dvec3 point{0.0};
double since{0.0};
};
struct tool_tab
{
bool open{false};
bool expand{false};
};
struct nmt_place
{
glm::dvec3 position{0.0}; // on the axis of the path
@@ -52,6 +68,8 @@ class editor_services
virtual bool track_editing() const = 0;
virtual std::size_t history_size() const = 0;
virtual glm::dvec3 camera_position() const = 0;
virtual track_hover const &hovered_track() const = 0;
virtual void first_step_done(editor_first_step const Step) = 0;
virtual void fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration = 0.6) = 0;
virtual void focus_track(TTrack &Track, int const Path, glm::dvec3 const &Point) = 0;
virtual void set_current_node(scene::basic_node *Node) = 0;