17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

feat(tracks): track editor in one movable Tracks window with exclusive modes, a guide of what can be done in the current step and the rarely used fields folded; vertical profile window with the ground of the scenery triangles; plain pieces added at a free end, freehand only on request; curve mode edits the single curve clicked

This commit is contained in:
maj00r
2026-10-05 01:21:11 +02:00
parent 220fd0f37e
commit 3731520e33
13 changed files with 2769 additions and 924 deletions

View File

@@ -27,6 +27,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/Traction.h"
#include "imgui/imgui.h"
#include "utilities/translation.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
@@ -707,7 +708,7 @@ void editor_mode::infra_recognize()
state.tracks = infra_scope();
if (state.tracks.empty())
{
state.error = (state.scope == 2 ? "Open the vertical profile of a route first" : state.scope == 3 ? "The scenery has no paths the editor can handle" : "Select a path first");
state.error = (state.scope == 2 ? STR_C("Open the vertical profile of a route first") : state.scope == 3 ? STR_C("The scenery has no paths the editor can handle") : STR_C("Select a path first"));
return;
}
track_index index;
@@ -803,7 +804,7 @@ void editor_mode::infra_recognize()
add(binding, "nearby", corridor);
}
std::stable_sort(state.candidates.begin(), state.candidates.end(), [](infra_candidate const &A, infra_candidate const &B) { return A.binding.group < B.binding.group; });
state.status = format("%d object(s) found along %d path(s)", static_cast<int>(state.candidates.size()), static_cast<int>(state.tracks.size()));
state.status = format(STR_C("%d object(s) found along %d path(s)"), static_cast<int>(state.candidates.size()), static_cast<int>(state.tracks.size()));
}
void editor_mode::infra_bind_chosen()
@@ -823,7 +824,7 @@ void editor_mode::infra_bind_chosen()
candidate.chosen = false;
++count;
}
state.status = format("%d object(s) bound, they follow the changes of the track. The bindings are kept in the scenery files on save", count);
state.status = format(STR_C("%d object(s) bound, they follow the changes of the track. The bindings are kept in the scenery files on save"), count);
}
void editor_mode::infra_unbind(std::vector<std::size_t> Indices)
@@ -1108,7 +1109,7 @@ void editor_mode::infra_attach(EditorSnapshot &Snapshot)
return true;
for (auto const &anchor : State.saved.anchors)
{
if (contains(Snapshot.created, anchor.at.track))
if (contains(Snapshot.created, anchor.at.track) || contains(Snapshot.removed, anchor.at.track))
return true;
for (auto const &entry : Snapshot.tracks)
if (entry.first == anchor.at.track)
@@ -1122,37 +1123,29 @@ void editor_mode::infra_attach(EditorSnapshot &Snapshot)
m_infra_buffer.clear();
}
void editor_mode::render_infra_window()
void editor_mode::render_infra_body()
{
auto &state{m_infra};
if (false == state.open)
return;
ImGui::SetNextWindowSize(ImVec2(560, 620), ImGuiCond_FirstUseEver);
if (false == ImGui::Begin("Infrastructure along the track###trackinfra", &state.open))
{
ImGui::End();
return;
}
infra_load();
Live.refresh();
auto follow{state.follow};
if (ImGui::Checkbox("Bound objects follow the changes of the track", &follow))
if (ImGui::Checkbox(STR_C("Bound objects follow the changes of the track"), &follow))
{
state.follow = follow;
if (follow)
infra_rebase();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Bound objects keep their chainage along the path, the offset from its axis and the height above the rail top;\n"
"models and includes turn with the path and stay upright. The move is a part of the undo step of the change.");
ImGui::SetTooltip(STR_C("Bound objects keep their chainage along the path, the offset from its axis and the height above the rail top;\n"
"models and includes turn with the path and stay upright. The move is a part of the undo step of the change."));
ImGui::SameLine();
ImGui::Checkbox("Show in 3D", &state.show);
ImGui::Text("Objects bound in the scenery: %d", static_cast<int>(m_bindings.size()));
ImGui::Checkbox(STR_C("Show in 3D"), &state.show);
ImGui::Text(STR_C("Objects bound in the scenery: %d"), static_cast<int>(m_bindings.size()));
if (false == m_bindings_unresolved.empty())
{
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.f, 0.8f, 0.3f, 1.f), "(%d stored binding(s) not found, kept as they are)", static_cast<int>(m_bindings_unresolved.size()));
ImGui::TextColored(ImVec4(1.f, 0.8f, 0.3f, 1.f), STR_C("(%d stored binding(s) not found, kept as they are)"), static_cast<int>(m_bindings_unresolved.size()));
}
ImGui::Separator();
@@ -1163,7 +1156,6 @@ void editor_mode::render_infra_window()
ImGui::TextColored(ImVec4(1.f, 0.4f, 0.35f, 1.f), "%s", state.error.c_str());
else if (false == state.status.empty())
ImGui::TextWrapped("%s", state.status.c_str());
ImGui::End();
}
// what changed while the bindings did not follow the track becomes their new starting point
@@ -1199,15 +1191,15 @@ void editor_mode::render_infra_search()
if (state.scope == 1)
{
ImGui::SetNextItemWidth(120.f);
ImGui::InputFloat("Reach each way, m", &state.reach, 100.f, 500.f, "%.0f");
ImGui::InputFloat(STR_C("Reach each way, m"), &state.reach, 100.f, 500.f, "%.0f");
state.reach = std::clamp(state.reach, 10.f, 20000.f);
}
ImGui::SetNextItemWidth(120.f);
ImGui::InputFloat("Corridor each side of the axis, m", &state.corridor, 0.5f, 2.f, "%.1f");
ImGui::InputFloat(STR_C("Corridor each side of the axis, m"), &state.corridor, 0.5f, 2.f, "%.1f");
state.corridor = std::clamp(state.corridor, 0.5f, 50.f);
if (ImGui::TreeNode("Rules for names of the files"))
if (ImGui::TreeNode(STR_C("Rules for names of the files")))
{
ImGui::TextDisabled("Parts of the names, separated by spaces; the first rule which matches decides");
ImGui::TextDisabled(STR_C("Parts of the names, separated by spaces; the first rule which matches decides"));
for (std::size_t i = 0; i < state.rules.size(); ++i)
{
auto &rule{state.rules[i]};
@@ -1217,13 +1209,13 @@ void editor_mode::render_infra_search()
if (ImGui::InputText((std::string{infra::name(rule.group)} + "##rule" + std::to_string(i)).c_str(), buffer, sizeof(buffer)))
rule.keywords = buffer;
}
if (ImGui::SmallButton("Default rules"))
if (ImGui::SmallButton(STR_C("Default rules")))
state.rules = infra::default_rules();
ImGui::TextDisabled("Includes are matched by the template name and its //$e description, models by the file and node name;\n"
"traction is always the catenary, memory cells and event launchers the logic");
ImGui::TextDisabled(STR_C("Includes are matched by the template name and its //$e description, models by the file and node name;\n"
"traction is always the catenary, memory cells and event launchers the logic"));
ImGui::TreePop();
}
if (ImGui::Button("Find objects"))
if (ImGui::Button(STR_C("Find objects")))
infra_recognize();
}
@@ -1244,13 +1236,13 @@ void editor_mode::render_infra_candidates()
for (auto const &candidate : candidates)
chosen += candidate.chosen ? 1 : 0;
ImGui::SameLine();
if (ImGui::Button(format("Bind the chosen (%d)", chosen).c_str()))
if (ImGui::Button(format(STR_C("Bind the chosen (%d)"), chosen).c_str()))
infra_bind_chosen();
ImGui::SameLine();
if (ImGui::SmallButton("All"))
if (ImGui::SmallButton(STR_C("All")))
choose({}, true);
ImGui::SameLine();
if (ImGui::SmallButton("None"))
if (ImGui::SmallButton(STR_C("None")))
choose({}, false);
ImGui::BeginChild("##infracandidates", ImVec2(0.f, 260.f), true);
@@ -1263,10 +1255,10 @@ void editor_mode::render_infra_candidates()
auto const open{ImGui::TreeNodeEx(format("%s (%d)", infra::name(group), static_cast<int>(count)).c_str(), group == infra::category::other ? 0 : ImGuiTreeNodeFlags_DefaultOpen)};
ImGui::PopStyleColor();
ImGui::SameLine();
if (ImGui::SmallButton((std::string{"all##g"} + infra::name(group)).c_str()))
if (ImGui::SmallButton((std::string{STR_C("all##g")} + infra::name(group)).c_str()))
choose(group, true);
ImGui::SameLine();
if (ImGui::SmallButton((std::string{"none##g"} + infra::name(group)).c_str()))
if (ImGui::SmallButton((std::string{STR_C("none##g")} + infra::name(group)).c_str()))
choose(group, false);
if (false == open)
continue;
@@ -1281,7 +1273,7 @@ void editor_mode::render_infra_candidates()
ImGui::PushID(static_cast<int>(i));
if (candidate.bound)
{
ImGui::TextDisabled("[bound]");
ImGui::TextDisabled(STR_C("[bound]"));
}
else
{
@@ -1292,7 +1284,7 @@ void editor_mode::render_infra_candidates()
if (ImGui::IsItemHovered())
{
state.hovered = static_cast<int>(i);
ImGui::SetTooltip("%s, %s\n%.2f m %s of the axis, %.2f m above the rail top\n%s", infra::name(candidate.binding.type), candidate.binding.anchors.front().at.track->name().c_str(),
ImGui::SetTooltip(STR_C("%s, %s\n%.2f m %s of the axis, %.2f m above the rail top\n%s"), infra::name(candidate.binding.type), candidate.binding.anchors.front().at.track->name().c_str(),
std::abs(candidate.offset), candidate.offset >= 0.0 ? "right" : "left", candidate.binding.anchors.front().height, candidate.reason.c_str());
}
ImGui::SameLine();
@@ -1307,7 +1299,7 @@ void editor_mode::render_infra_candidates()
void editor_mode::render_infra_bound()
{
if (ImGui::TreeNode("Bound objects along the scope"))
if (ImGui::TreeNode(STR_C("Bound objects along the scope")))
{
auto const scope{infra_scope()};
std::unordered_set<TTrack const *> const tracks(scope.begin(), scope.end());
@@ -1315,11 +1307,11 @@ void editor_mode::render_infra_bound()
for (std::size_t i = 0; i < m_bindings.size(); ++i)
if (std::any_of(m_bindings[i].anchors.begin(), m_bindings[i].anchors.end(), [&](infra::anchor const &Anchor) { return tracks.count(Anchor.at.track) > 0; }))
along.push_back(i);
ImGui::Text("%d of %d", static_cast<int>(along.size()), static_cast<int>(m_bindings.size()));
ImGui::Text(STR_C("%d of %d"), static_cast<int>(along.size()), static_cast<int>(m_bindings.size()));
if (false == along.empty())
{
ImGui::SameLine();
if (ImGui::SmallButton("Unbind all of these"))
if (ImGui::SmallButton(STR_C("Unbind all of these")))
{
infra_unbind(along);
along.clear();
@@ -1334,11 +1326,11 @@ void editor_mode::render_infra_bound()
auto const index{along[row]};
auto const &binding{m_bindings[index]};
ImGui::PushID(static_cast<int>(index));
if (ImGui::SmallButton("unbind"))
if (ImGui::SmallButton(STR_C("unbind")))
unbind = index;
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(binding.lost ? IM_COL32(255, 80, 70, 255) : colour(binding.group)));
ImGui::Text("%s%s", binding.label.c_str(), binding.lost ? " (no path to follow)" : "");
ImGui::Text("%s%s", binding.label.c_str(), binding.lost ? STR_C(" (no path to follow)") : "");
ImGui::PopStyleColor();
ImGui::PopID();
}