17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 19:47:30 +01:00

fix(editor): a signal goes to the other side of the track and turns around

Dragging a standing signal over the track takes it to the other side;
the side used to come from where the signal stood, so it never crossed.
The selected signal gets "To the other side" and "Turn around": the
first mirrors it over the axis facing the same trains, the second turns
it to the trains of the other direction and moves its event to the
other event list of the path. Either one swaps a head leaning to the
track for its counterpart, and an include keeps the template its
directive names, so undo brings the old file back too.
This commit is contained in:
maj00r
2026-10-21 10:44:58 +02:00
parent 87bacede74
commit 7da0432dca
8 changed files with 251 additions and 78 deletions

View File

@@ -3058,7 +3058,12 @@ void editor_mode::select_include(scene::instance_handle const Instance)
void editor_mode::set_include_directive(scene::instance_handle const Instance, std::string const &Directive) void editor_mode::set_include_directive(scene::instance_handle const Instance, std::string const &Directive)
{ {
if (false == scene::Layers.modify(Instance, Directive)) std::string file;
std::vector<std::string> values;
if (scene::Layers.tracked(Instance) && editor_includes::parse_directive(Directive, file, values) && scene::Layers.instance(Instance).file != nullptr &&
editor_includes::normalized(file) == editor_includes::normalized(*scene::Layers.instance(Instance).file))
file.clear();
if (false == scene::Layers.modify(Instance, Directive, file))
return; return;
std::vector<TAnimModel *> retired; std::vector<TAnimModel *> retired;
simulation::State.rebuild_include(Instance, retired); simulation::State.rebuild_include(Instance, retired);

View File

@@ -1796,8 +1796,6 @@ class editor_mode : public application_mode, private editor_track::observer, pri
std::string read; std::string read;
TTrack *track{nullptr}; TTrack *track{nullptr};
int list{0}; int list{0};
signal_spot spot;
bool on_track{false};
}; };
struct signal_tool struct signal_tool
{ {
@@ -1821,6 +1819,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri
int hover{-1}; int hover{-1};
scene::instance_handle selected{0}; scene::instance_handle selected{0};
bool moving{false}; bool moving{false};
glm::dvec2 grab{0.0};
signal_spot home;
bool home_valid{false};
signal_spot moved; signal_spot moved;
bool moved_valid{false}; bool moved_valid{false};
std::string status; std::string status;
@@ -1836,7 +1837,12 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void signal_release(); void signal_release();
bool signal_place(signal_spot const &Spot, signal_template const &Template); bool signal_place(signal_spot const &Spot, signal_template const &Template);
void signal_move_update(); void signal_move_update();
void signal_move_finish(); bool signal_standing_spot(signal_standing const &Standing, signal_spot &Spot) const;
bool signal_drop_at(signal_standing const &Standing, TTrack &Track, int const Path, glm::dvec3 const &Point, int const Side, signal_spot &Spot) const;
bool signal_move_to(signal_standing const &Standing, signal_spot const &Spot, char const *Read, char const *Unread);
bool signal_flip(scene::instance_handle const Instance);
bool signal_turn(scene::instance_handle const Instance);
signal_standing const *signal_standing_of(scene::instance_handle const Instance) const;
bool signal_delete(scene::instance_handle const Instance); bool signal_delete(scene::instance_handle const Instance);
void signal_detach_events(std::string const &Read, std::vector<std::pair<TTrack *, editor_track::state>> &States); void signal_detach_events(std::string const &Read, std::vector<std::pair<TTrack *, editor_track::state>> &States);
void signal_renamed(std::string const &Before, std::string const &After, EditorSnapshot &Snapshot); void signal_renamed(std::string const &Before, std::string const &After, EditorSnapshot &Snapshot);

View File

@@ -50,7 +50,7 @@ http://mozilla.org/MPL/2.0/.
editor_mode::selftest_result editor_mode::selftest_signal(std::string const &command, std::istringstream &words) editor_mode::selftest_result editor_mode::selftest_signal(std::string const &command, std::istringstream &words)
{ {
if (command == "signal" || command == "signalmove" || command == "signaldelete" || command == "signalrename" || command == "signalprobe") if (command == "signal" || command == "signalmove" || command == "signaldelete" || command == "signalrename" || command == "signalprobe" || command == "signalflip" || command == "signalturn")
{ {
auto values{selftest_values(words)}; auto values{selftest_values(words)};
show_track_tab(track_tab::signals); show_track_tab(track_tab::signals);
@@ -107,29 +107,40 @@ editor_mode::selftest_result editor_mode::selftest_signal(std::string const &com
else if (command == "signalmove") else if (command == "signalmove")
{ {
auto const *standing{standing_named(values["name"])}; auto const *standing{standing_named(values["name"])};
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr}; auto *track{simulation::Paths.find(values["track"])};
if (item == nullptr) if (standing == nullptr || track == nullptr || track->m_paths.empty())
{ {
WriteLog("SELFTEST signalmove: no signal " + values["name"]); WriteLog("SELFTEST signalmove: no signal " + values["name"] + " or no track " + values["track"]);
return selftest_result::handled; return selftest_result::handled;
} }
auto const yaw{glm::radians(standing->include.yaw + 180.0)}; auto const path{std::clamp(values.count("path") ? std::stoi(values["path"]) : 0, 0, static_cast<int>(track->m_paths.size()) - 1)};
glm::dvec2 const travel{std::sin(yaw), std::cos(yaw)}; auto const at{values.count("at") ? std::stod(values["at"]) : 0.5};
signal_spot probe; geometry::bezier const curve{track->m_paths[path]};
if (false == spot_on(*item, 1, 1, probe)) auto point{curve.point(at)};
auto const sided{values.count("side") != 0};
auto side{values["side"] == "left" ? -1 : 1};
if (values.count("off"))
{ {
WriteLog("SELFTEST signalmove: no such track " + values["track"]); auto const tangent{glm::normalize(glm::dvec2{curve.first(at).x, curve.first(at).z})};
return selftest_result::handled; auto const off{std::stod(values["off"])};
point += glm::dvec3{-tangent.y * off, 0.0, tangent.x * off};
side = 0;
} }
auto const facing{glm::dot(probe.travel, travel) >= 0.0 ? 1 : -1}; else if (signal_spot home; false == sided && signal_standing_spot(*standing, home))
auto const sideoftravel{glm::dot(geometry::plan_of(standing->include.location) - geometry::plan_of(standing->spot.axis), glm::dvec2{-travel.y, travel.x}) >= 0.0 ? 1 : -1}; side = home.side * (home.list == 2 ? 1 : -1);
signal_select(standing->include.instance); signal_select(standing->include.instance);
tool.moving = true; tool.moving = true;
tool.moved_valid = spot_on(*item, facing, sideoftravel * facing, tool.moved); tool.moved_valid = signal_drop_at(*standing, *track, path, point, side, tool.moved);
tool.pressed = {-1000.f, -1000.f}; tool.pressed = {-1000.f, -1000.f};
signal_release(); signal_release();
WriteLog("SELFTEST signalmove: " + tool.status); WriteLog("SELFTEST signalmove: " + tool.status);
} }
else if (command == "signalflip" || command == "signalturn")
{
auto const *standing{standing_named(values["name"])};
auto const done{standing != nullptr && (command == "signalflip" ? signal_flip(standing->include.instance) : signal_turn(standing->include.instance))};
WriteLog("SELFTEST " + command + " " + values["name"] + (done ? ": " : ": failed, ") + tool.status);
}
else if (command == "signaldelete") else if (command == "signaldelete")
{ {
auto const *standing{standing_named(values["name"])}; auto const *standing{standing_named(values["name"])};

View File

@@ -46,6 +46,7 @@ float const kSignalPick{12.0f};
float const kDragStart{6.0f}; float const kDragStart{6.0f};
double const kDragTravel{1.0}; double const kDragTravel{1.0};
double const kRefreshPeriod{2.0}; double const kRefreshPeriod{2.0};
double const kSignalReach{10.0};
char const *const kKinds[] = {"main", "block", "warning", "shunt", "stop", "substitute", "repeater"}; char const *const kKinds[] = {"main", "block", "warning", "shunt", "stop", "substitute", "repeater"};
char const *const kKindLabels[] = {"Semaphore", "Automatic block", "Warning signal", "Shunting signal", "Stop signal", "Substitute signal", "Repeater"}; char const *const kKindLabels[] = {"Semaphore", "Automatic block", "Warning signal", "Shunting signal", "Stop signal", "Substitute signal", "Repeater"};
@@ -88,6 +89,17 @@ glm::dvec2 right_of(glm::dvec2 const &Direction)
return {-Direction.y, Direction.x}; return {-Direction.y, Direction.x};
} }
glm::dvec2 travel_of(double const Yaw)
{
auto const yaw{glm::radians(Yaw + 180.0)};
return {std::sin(yaw), std::cos(yaw)};
}
int travel_side(int const Side, int const List)
{
return Side * (List == 2 ? 1 : -1);
}
int lean_wanted(int const Side) int lean_wanted(int const Side)
{ {
return Side > 0 ? 1 : 2; return Side > 0 ? 1 : 2;
@@ -430,9 +442,12 @@ void editor_mode::signal_press(track_intent const &Intent)
{ {
if (Intent.index < 0 || Intent.index >= static_cast<int>(tool.standing.size())) if (Intent.index < 0 || Intent.index >= static_cast<int>(tool.standing.size()))
return; return;
signal_select(tool.standing[Intent.index].include.instance); auto const &standing{tool.standing[Intent.index]};
tool.grab = plan_of(standing.include.location) - plan_of(cursor_level(standing.include.location.y));
tool.home_valid = signal_standing_spot(standing, tool.home);
tool.moving = true; tool.moving = true;
tool.moved_valid = false; tool.moved_valid = false;
signal_select(standing.include.instance);
return; return;
} }
auto const *armed{signal_armed()}; auto const *armed{signal_armed()};
@@ -473,39 +488,84 @@ void editor_mode::signal_move_update()
return; return;
if (false == dragged) if (false == dragged)
return; return;
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.include.instance == tool.selected; })}; auto const *found{signal_standing_of(tool.selected)};
if (found == tool.standing.end()) if (found == nullptr)
{ {
tool.moved_valid = false; tool.moved_valid = false;
return; return;
} }
// the track under the cursor; off every track, as it is when the signal is dragged beside its track, the place of the auto const &location{found->include.location};
// track the signal stands by nearest the cursor auto const cursor{cursor_level(location.y)};
glm::dvec3 const held{cursor.x + tool.grab.x, location.y, cursor.z + tool.grab.y};
// the track under the cursor; off every track, as it is when the signal is dragged beside its track, the track the
// signal stands by
auto *track{m_hover.track}; auto *track{m_hover.track};
auto path{m_hover.path}; auto path{m_hover.path};
auto point{m_hover.point}; if (track == nullptr && tool.home_valid)
if (track == nullptr && found->track != nullptr && false == found->track->m_editorremoved && false == found->track->m_paths.empty())
{ {
track = found->track; track = tool.home.track;
path = std::clamp(found->spot.path, 0, static_cast<int>(track->m_paths.size()) - 1); path = tool.home.path;
point = bezier{track->m_paths[path]}.point(nearest_on(*track, path, cursor_level(found->spot.axis.y)));
} }
tool.moved_valid = track != nullptr && signal_drop_at(*found, *track, path, held, 0, tool.moved);
}
editor_mode::signal_standing const *editor_mode::signal_standing_of(scene::instance_handle const Instance) const
{
auto const &standing{m_signal.standing};
auto const found{std::find_if(standing.begin(), standing.end(), [&](signal_standing const &Standing) { return Standing.include.instance == Instance; })};
return found != standing.end() ? &*found : nullptr;
}
bool editor_mode::signal_drop_at(signal_standing const &Standing, TTrack &Track, int const Path, glm::dvec3 const &Point, int const Side, signal_spot &Spot) const
{
auto const *item{signal_template_of(Standing.include.file)};
if (item == nullptr || Track.m_paths.empty() || Track.m_editorremoved)
return false;
auto const path{std::clamp(Path, 0, static_cast<int>(Track.m_paths.size()) - 1)};
auto const forward{forward_at(Track, path, nearest_on(Track, path, Point))};
auto const facing{glm::dot(forward, travel_of(Standing.include.yaw)) >= 0.0 ? 1 : -1};
return signal_spot_at(Track, path, Point, Point, facing, Side * facing, *item, Spot);
}
bool editor_mode::signal_standing_spot(signal_standing const &Standing, signal_spot &Spot) const
{
auto const &location{Standing.include.location};
TTrack *track{nullptr};
int path{0};
auto best{kSignalReach};
auto const consider = [&](TTrack *Track) {
if (Track == nullptr || Track->m_editorremoved)
return;
for (int p = 0; p < static_cast<int>(Track->m_paths.size()); ++p)
{
bezier const curve{Track->m_paths[p]};
auto const low{glm::min(glm::min(plan_of(curve.p0), plan_of(curve.p1)), glm::min(plan_of(curve.p2), plan_of(curve.p3)))};
auto const high{glm::max(glm::max(plan_of(curve.p0), plan_of(curve.p1)), glm::max(plan_of(curve.p2), plan_of(curve.p3)))};
if (glm::length(glm::max(glm::max(low - plan_of(location), plan_of(location) - high), glm::dvec2{0.0})) >= best)
continue;
auto const distance{geometry::plan_distance(curve.point(nearest_on(*Track, p, location)), location)};
if (distance < best)
{
best = distance;
track = Track;
path = p;
}
}
};
consider(Standing.track);
if (track == nullptr) if (track == nullptr)
{ for (auto *candidate : simulation::Paths.sequence())
tool.moved_valid = false; consider(candidate);
return; if (track == nullptr)
} return false;
auto const *item{signal_template_of(found->include.file)}; auto const axis{bezier{track->m_paths[path]}.point(nearest_on(*track, path, location))};
if (item == nullptr) auto const side{glm::dot(plan_of(location) - plan_of(axis), right_of(travel_of(Standing.include.yaw))) >= 0.0 ? 1 : -1};
return; if (false == signal_drop_at(Standing, *track, path, location, side, Spot))
path = std::clamp(path, 0, static_cast<int>(track->m_paths.size()) - 1); return false;
auto const yaw{glm::radians(found->include.yaw + 180.0)}; Spot.offset = geometry::plan_distance(location, axis);
glm::dvec2 const travel{std::sin(yaw), std::cos(yaw)}; Spot.position = location;
auto const parameter{nearest_on(*track, path, point)}; Spot.yaw = Standing.include.yaw;
auto const forward{forward_at(*track, path, parameter)}; return true;
auto const facing{glm::dot(forward, travel) >= 0.0 ? 1 : -1};
auto const sideoftravel{glm::dot(plan_of(found->include.location) - plan_of(point), right_of(travel)) >= 0.0 ? 1 : -1};
tool.moved_valid = signal_spot_at(*track, path, point, point, facing, sideoftravel * facing, *item, tool.moved);
} }
void editor_mode::signal_release() void editor_mode::signal_release()
@@ -530,40 +590,53 @@ void editor_mode::signal_release()
return; return;
} }
tool.moved_valid = false; tool.moved_valid = false;
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.include.instance == tool.selected; })}; if (auto const *found{signal_standing_of(tool.selected)})
if (found == tool.standing.end()) signal_move_to(*found, tool.moved, STR_C("Signal %s moved, read by %s %s of %s, Ctrl+Z takes it back"), STR_C("Signal %s moved, Ctrl+Z takes it back"));
return; }
if (false == scene::Layers.tracked(found->include.instance) || false == scene::Layers.removable(found->include.instance))
bool editor_mode::signal_move_to(signal_standing const &Standing, signal_spot const &Spot, char const *Read, char const *Unread)
{
auto &tool{m_signal};
auto const instance{Standing.include.instance};
if (false == scene::Layers.tracked(instance) || false == scene::Layers.removable(instance))
{ {
tool.status = STR_C("This signal is in a file the editor doesn't change, it can't be moved"); tool.status = STR_C("This signal is in a file the editor doesn't change, it can't be moved");
return; return false;
} }
auto const &spot{tool.moved}; auto const &included{scene::Layers.instance(instance)};
auto const &included{scene::Layers.instance(found->include.instance)}; auto file{*included.file};
auto values{found->include.values}; signal_spot before;
auto const local{spot.position - included.context.offset}; auto const *item{signal_template_of(Standing.include.file)};
if (item != nullptr && signal_standing_spot(Standing, before) && travel_side(before.side, before.list) != travel_side(Spot.side, Spot.list))
file = signal_variant(*item, Spot);
auto values{Standing.include.values};
auto const local{Spot.position - included.context.offset};
values[1] = editor_includes::number(local.x); values[1] = editor_includes::number(local.x);
values[2] = editor_includes::number(local.y); values[2] = editor_includes::number(local.y);
values[3] = editor_includes::number(local.z); values[3] = editor_includes::number(local.z);
values[4] = editor_includes::number(spot.yaw); values[4] = editor_includes::number(Spot.yaw);
auto before{scene::Layers.directive(found->include.instance)}; auto const *next{signal_template_of(file)};
auto const read{next != nullptr && false == next->read.empty() ? editor_includes::substitute(next->read, values) : Standing.read};
auto directive{scene::Layers.directive(instance)};
std::vector<std::pair<TTrack *, editor_track::state>> states; std::vector<std::pair<TTrack *, editor_track::state>> states;
if (false == found->read.empty() && spot.track->Dynamics.empty()) if (false == Standing.read.empty() && Spot.track->Dynamics.empty())
{ {
signal_detach_events(found->read, states); signal_detach_events(Standing.read, states);
if (std::none_of(states.begin(), states.end(), [&](auto const &State) { return State.first == spot.track; })) if (std::none_of(states.begin(), states.end(), [&](auto const &State) { return State.first == Spot.track; }))
states.emplace_back(spot.track, editor_track::capture(*spot.track)); states.emplace_back(Spot.track, editor_track::capture(*Spot.track));
editor_track::events(*spot.track, spot.list).emplace_back(ToLower(found->read), nullptr); editor_track::events(*Spot.track, Spot.list).emplace_back(ToLower(read), nullptr);
std::string missing; std::string missing;
editor_track::bind_events(*spot.track, missing); editor_track::bind_events(*Spot.track, missing);
spot.track->mark_dirty(); Spot.track->mark_dirty();
} }
set_include_directive(found->include.instance, editor_includes::compose_directive(*included.file, values)); auto const name{Standing.name};
auto const node{Standing.include.file};
set_include_directive(instance, editor_includes::compose_directive(file, values));
if (states.empty()) if (states.empty())
{ {
EditorSnapshot snap; EditorSnapshot snap;
snap.directives.emplace_back(found->include.instance, std::move(before)); snap.directives.emplace_back(instance, std::move(directive));
snap.node_name = found->include.file; snap.node_name = node;
trim_history(); trim_history();
m_history.push_back(std::move(snap)); m_history.push_back(std::move(snap));
g_redo.clear(); g_redo.clear();
@@ -571,13 +644,47 @@ void editor_mode::signal_release()
else else
{ {
push_track_snapshot(std::move(states)); push_track_snapshot(std::move(states));
m_history.back().directives.emplace_back(found->include.instance, std::move(before)); m_history.back().directives.emplace_back(instance, std::move(directive));
} }
tool.status = found->read.empty() ? format(STR_C("Signal %s moved, Ctrl+Z takes it back"), found->name.c_str())
: format(STR_C("Signal %s moved, read by %s %s of %s, Ctrl+Z takes it back"), found->name.c_str(), editor_track::event_keyword(spot.list), found->read.c_str(), name_of(*spot.track).c_str());
auto const instance{found->include.instance};
signal_refresh(true); signal_refresh(true);
signal_select(instance); signal_select(instance);
auto const *moved{signal_standing_of(instance)};
tool.status = moved != nullptr && moved->track != nullptr ? format(Read, name.c_str(), editor_track::event_keyword(moved->list), moved->read.c_str(), name_of(*moved->track).c_str()) : format(Unread, name.c_str());
return true;
}
bool editor_mode::signal_flip(scene::instance_handle const Instance)
{
auto const *standing{signal_standing_of(Instance)};
signal_spot spot;
if (standing == nullptr || false == signal_standing_spot(*standing, spot))
{
m_signal.status = STR_C("No track by the signal to take it across");
return false;
}
auto const across{plan_of(spot.position) - plan_of(spot.axis)};
auto const mirrored{plan_of(spot.axis) + spot.travel * glm::dot(across, spot.travel) - right_of(spot.travel) * glm::dot(across, right_of(spot.travel))};
spot.side = -spot.side;
spot.position = {mirrored.x, spot.position.y, mirrored.y};
return signal_move_to(*standing, spot, STR_C("Signal %s moved to the other side of the track, read by %s %s of %s, Ctrl+Z takes it back"), STR_C("Signal %s moved to the other side of the track, Ctrl+Z takes it back"));
}
bool editor_mode::signal_turn(scene::instance_handle const Instance)
{
auto const *standing{signal_standing_of(Instance)};
signal_spot spot;
if (standing == nullptr || false == signal_standing_spot(*standing, spot))
{
m_signal.status = STR_C("No track by the signal to turn it to");
return false;
}
auto const *item{signal_template_of(standing->include.file)};
auto const position{spot.position};
if (item == nullptr || false == signal_spot_at(*spot.track, spot.path, spot.axis, spot.axis, spot.list == 2 ? -1 : 1, spot.side, *item, spot))
return false;
spot.position = position;
spot.yaw = std::fmod(standing->include.yaw + 180.0, 360.0);
return signal_move_to(*standing, spot, STR_C("Signal %s turned to the trains of the other direction, read by %s %s of %s, Ctrl+Z takes it back"), STR_C("Signal %s turned to the trains of the other direction, Ctrl+Z takes it back"));
} }
std::string editor_mode::signal_variant(signal_template const &Template, signal_spot const &Spot) const std::string editor_mode::signal_variant(signal_template const &Template, signal_spot const &Spot) const
@@ -876,9 +983,8 @@ void editor_mode::render_signal_palette()
void editor_mode::render_signal_selected() void editor_mode::render_signal_selected()
{ {
auto &tool{m_signal}; auto const *found{signal_standing_of(m_signal.selected)};
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.include.instance == tool.selected; })}; if (found == nullptr)
if (found == tool.standing.end())
return; return;
ImGui::Separator(); ImGui::Separator();
ImGui::TextUnformatted(format(STR_C("Selected: %s (%s)"), found->name.c_str(), found->include.file.c_str()).c_str()); ImGui::TextUnformatted(format(STR_C("Selected: %s (%s)"), found->name.c_str(), found->include.file.c_str()).c_str());
@@ -889,6 +995,15 @@ void editor_mode::render_signal_selected()
ImGui::TextDisabled(STR_C("Read by %s %s of %s"), editor_track::event_keyword(found->list), found->read.c_str(), name_of(*found->track).c_str()); ImGui::TextDisabled(STR_C("Read by %s %s of %s"), editor_track::event_keyword(found->list), found->read.c_str(), name_of(*found->track).c_str());
else else
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.3f, 1.0f), STR_C("No path has %s among its events: the train doesn't see it"), found->read.c_str()); ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.3f, 1.0f), STR_C("No path has %s among its events: the train doesn't see it"), found->read.c_str());
auto const instance{found->include.instance};
auto const width{(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f};
if (ImGui::Button(STR_C("To the other side"), ImVec2(width, 0.0f)))
signal_flip(instance);
item_tooltip("Takes the signal across the track, it keeps facing the same trains. Dragging it over the track does the same");
ImGui::SameLine();
if (ImGui::Button(STR_C("Turn around"), ImVec2(width, 0.0f)))
signal_turn(instance);
item_tooltip("Turns the signal to the trains of the other direction, they read it by the other event of the path");
} }
void editor_mode::render_signal_ui() void editor_mode::render_signal_ui()
@@ -1038,10 +1153,7 @@ void editor_mode::draw_signal_overlay() const
drawlist->AddText(ImVec2(head.x + 6.0f, head.y - 6.0f), colour, label.c_str()); drawlist->AddText(ImVec2(head.x + 6.0f, head.y - 6.0f), colour, label.c_str());
} }
if (selected || hovered) if (selected || hovered)
{ arrow(location, travel_of(standing.include.yaw), colour);
auto const yaw{glm::radians(standing.include.yaw + 180.0)};
arrow(location, glm::dvec2{std::sin(yaw), std::cos(yaw)}, colour);
}
} }
auto const preview = [&](signal_spot const &Spot, ImU32 const Colour) { auto const preview = [&](signal_spot const &Spot, ImU32 const Colour) {
projection.line(drawlist, Spot.axis + glm::dvec3{0.0, kRailTop, 0.0}, Spot.position, Colour, 2.0f); projection.line(drawlist, Spot.axis + glm::dvec3{0.0, kRailTop, 0.0}, Spot.position, Colour, 2.0f);

View File

@@ -1641,8 +1641,10 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
hints = {{"LMB on a track", "the line the objects go along"}}; hints = {{"LMB on a track", "the line the objects go along"}};
else if (m_track_window_open && m_track_tab == track_tab::parallel) else if (m_track_window_open && m_track_tab == track_tab::parallel)
hints = {{"LMB on a track", "the new track goes beside it"}, {"Click on a side in the panel", "the side of the new track"}}; hints = {{"LMB on a track", "the new track goes beside it"}, {"Click on a side in the panel", "the side of the new track"}};
else if (m_track_window_open && m_track_tab == track_tab::signals && (m_signal.placing || m_signal.moving)) else if (m_track_window_open && m_track_tab == track_tab::signals && m_signal.moving)
hints = {{"Drag along", m_signal.moving ? "moves the signal along the track" : "the trains it faces run that way"}, {"Release", "done"}, {"Esc", "cancel"}}; hints = {{"Drag along", "moves the signal along the track"}, {"Drag over the track", "to the other side"}, {"Release", "done"}, {"Esc", "cancel"}};
else if (m_track_window_open && m_track_tab == track_tab::signals && m_signal.placing)
hints = {{"Drag along", "the trains it faces run that way"}, {"Release", "done"}, {"Esc", "cancel"}};
else if (m_track_window_open && m_track_tab == track_tab::signals && signal_armed() == nullptr) else if (m_track_window_open && m_track_tab == track_tab::signals && signal_armed() == nullptr)
hints = {{"List in the panel", "the type of the signal"}, {"Drag a signal", "moves it along the track"}, {"LMB on a signal", "selects it"}}; hints = {{"List in the panel", "the type of the signal"}, {"Drag a signal", "moves it along the track"}, {"LMB on a signal", "selects it"}};
else if (m_track_window_open && m_track_tab == track_tab::signals) else if (m_track_window_open && m_track_tab == track_tab::signals)

View File

@@ -9955,3 +9955,36 @@ msgstr "Gdy tor wychodzi poza teren, NMT dobudowuje się wokół kursora na bie
msgid "The ends have to be numbers, from 0.1 m to 100 km apart" msgid "The ends have to be numbers, from 0.1 m to 100 km apart"
msgstr "Końce muszą być liczbami, od 0,1 m do 100 km od siebie" msgstr "Końce muszą być liczbami, od 0,1 m do 100 km od siebie"
msgid "To the other side"
msgstr "Na drugą stronę"
msgid "Takes the signal across the track, it keeps facing the same trains. Dragging it over the track does the same"
msgstr "Przenosi sygnalizator na drugą stronę toru, dalej stoi przodem do tych samych pociągów. To samo robi przeciągnięcie go przez tor"
msgid "Turns the signal to the trains of the other direction, they read it by the other event of the path"
msgstr "Obraca sygnalizator do pociągów z przeciwnego kierunku, odczytują go przez drugi event toru"
msgid "No track by the signal to take it across"
msgstr "Przy sygnalizatorze nie ma toru, na którego drugą stronę można go przenieść"
msgid "No track by the signal to turn it to"
msgstr "Przy sygnalizatorze nie ma toru, do którego można go obrócić"
msgid "Signal %s moved to the other side of the track, read by %s %s of %s, Ctrl+Z takes it back"
msgstr "Sygnalizator %s przeniesiony na drugą stronę toru, pociąg odczytuje go przez %s %s toru %s, Ctrl+Z cofa"
msgid "Signal %s moved to the other side of the track, Ctrl+Z takes it back"
msgstr "Sygnalizator %s przeniesiony na drugą stronę toru, Ctrl+Z cofa"
msgid "Signal %s turned to the trains of the other direction, read by %s %s of %s, Ctrl+Z takes it back"
msgstr "Sygnalizator %s obrócony do pociągów z przeciwnego kierunku, pociąg odczytuje go przez %s %s toru %s, Ctrl+Z cofa"
msgid "Signal %s turned to the trains of the other direction, Ctrl+Z takes it back"
msgstr "Sygnalizator %s obrócony do pociągów z przeciwnego kierunku, Ctrl+Z cofa"
msgid "Drag over the track"
msgstr "Przeciągnij przez tor"
msgid "to the other side"
msgstr "na drugą stronę"

View File

@@ -1090,13 +1090,17 @@ std::string node_layers::directive(instance_handle const Instance) const
} }
// replaces the directive of specified include, the scenery file receives it on save // replaces the directive of specified include, the scenery file receives it on save
bool node_layers::modify(instance_handle const Instance, std::string const &Directive) bool node_layers::modify(instance_handle const Instance, std::string const &Directive, std::string const &File)
{ {
if (false == tracked(Instance) || m_instances[Instance - 1].dead || Directive.empty()) if (false == tracked(Instance) || m_instances[Instance - 1].dead || Directive.empty())
{ {
return false; return false;
} }
m_instances[Instance - 1].directive = Directive; m_instances[Instance - 1].directive = Directive;
if (false == File.empty())
{
m_instances[Instance - 1].file = &(*m_templates.emplace(File).first);
}
return true; return true;
} }

View File

@@ -335,7 +335,7 @@ class node_layers
// returns: empty text if the directive can't be read // returns: empty text if the directive can't be read
std::string directive(instance_handle Instance) const; std::string directive(instance_handle Instance) const;
// replaces the directive of specified include, the scenery file receives it on save. NOTE: the nodes aren't touched // replaces the directive of specified include, the scenery file receives it on save. NOTE: the nodes aren't touched
bool modify(instance_handle Instance, std::string const &Directive); bool modify(instance_handle Instance, std::string const &Directive, std::string const &File = {});
// takes the models specified include was loaded with out of the scene, once the editor shows its own in their place // takes the models specified include was loaded with out of the scene, once the editor shows its own in their place
void rebuilt(instance_handle Instance); void rebuilt(instance_handle Instance);
// true if specified node was replaced by the editor with a node showing the current state of its include // true if specified node was replaced by the editor with a node showing the current state of its include