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

feat(editor): undo and redo take back the kept alignments and switches together with the paths: each step of the history keeps the //$h and //$r records from before it; a deleted switch or slip forgets its record in the same step, so undoing the deletion brings it back; edits of the switch number, rail and km and forgetting a kept design stay outside the history; self-test commands deleting paths

This commit is contained in:
maj00r
2026-10-11 16:45:42 +02:00
parent b466755a0f
commit ddb637a3f5
8 changed files with 160 additions and 0 deletions

View File

@@ -2907,6 +2907,7 @@ bool editor_mode::save()
if (m_profile.changed)
profile_store();
switch_store::tidy();
switch_store::settle();
infra_store();
if (false == m_saved_camera.has_value() || glm::distance(m_saved_camera->first, glm::dvec3{Camera.Pos}) > 0.5 || glm::distance(m_saved_camera->second, glm::vec3{Camera.Angle}) > 0.01f)
{

View File

@@ -21,6 +21,8 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorOrthophoto.hpp"
#include "editor/editorTrack.hpp"
#include "editor/editorAlignment.hpp"
#include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorSpeed.hpp"
#include "editor/editorRoad.hpp"
#include "editor/editorProfile.hpp"
@@ -168,6 +170,8 @@ class editor_mode : public application_mode, private editor_track::observer
std::vector<infra::object_state> infra; // objects which followed the paths
bool profiles{false}; // the stored grade line designs changed too, these were the ones before
std::vector<stored_profile> profile_library;
std::optional<std::vector<alignment_store::record>> alignments; // the kept horizontal alignments changed too, these were the ones before
std::optional<std::vector<switch_store::record>> switch_records; // the kept switches changed too, these were the ones before
std::vector<array_copy> copies; // models an array added to the one it was made of
bool joined{false}; // undone and redone together with the change before it
std::shared_ptr<terrain_streamer::change const> terrain; // the heightmap terrain from before the change (Action::Terrain)
@@ -630,6 +634,8 @@ class editor_mode : public application_mode, private editor_track::observer
static bool tracks_editable(std::vector<TTrack *> const &Tracks, std::string &Reason);
void trim_history();
void restore_track_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo);
// changes of the kept alignments and switches go into the last step of the history, made just before them
void records_to_history();
editor_track::point_ref m_track_point;
std::vector<TTrack *> m_track_drag;
std::vector<std::pair<TTrack *, editor_track::point_ref>> m_track_drag_points;

View File

@@ -104,6 +104,7 @@ http://mozilla.org/MPL/2.0/.
// switchplace <template> <x> <z> <direction> <side> the catalogue template whose label starts so put into the straight
// nearest the point, or into the curve (switchcurve); slipplace <template> <x> <z>
// 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
// screenshot
// save
@@ -1199,6 +1200,36 @@ void editor_mode::selftest_step()
}
WriteLog("SELFTEST " + command + ": " + tool.status + " " + m_straights.status);
}
else if (command == "deletetrack" || command == "deletekind")
{
std::string name;
words >> name;
TTrack *chosen{nullptr};
if (command == "deletetrack")
chosen = simulation::Paths.find(name);
else
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
continue;
auto const found{alignment_store::on_track(*track)};
if (found.has_value() && alignment_store::describe(alignment_store::records()[found->record].result.elements[found->element]).kind == name)
{
chosen = track;
break;
}
}
if (chosen == nullptr || chosen->m_editorremoved)
{
WriteLog("SELFTEST " + command + ": no path " + name);
continue;
}
m_node = chosen;
ui()->set_node(m_node);
auto const deleted{chosen->name()};
delete_selected_track();
WriteLog("SELFTEST " + command + ": " + deleted + " deleted");
}
else if (command == "btfcheck")
{
std::string name;

View File

@@ -1233,10 +1233,25 @@ void editor_mode::push_track_snapshot(std::vector<std::pair<TTrack *, editor_tra
snap.created = std::move(Created);
snap.removed = std::move(Removed);
infra_attach(snap);
snap.alignments = alignment_store::take_changes();
snap.switch_records = switch_store::take_changes();
m_history.push_back(std::move(snap));
g_redo.clear();
}
void editor_mode::records_to_history()
{
auto alignments{alignment_store::take_changes()};
auto switches{switch_store::take_changes()};
if (m_history.empty() || m_history.back().action != EditorSnapshot::Action::TrackEdit)
return;
auto &last{m_history.back()};
if (alignments.has_value() && false == last.alignments.has_value())
last.alignments = std::move(alignments);
if (switches.has_value() && false == last.switch_records.has_value())
last.switch_records = std::move(switches);
}
bool editor_mode::tracks_editable(std::vector<TTrack *> const &Tracks, std::string &Reason)
{
for (auto const *track : Tracks)
@@ -1282,6 +1297,18 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
m_profiles = Snapshot.profile_library;
profile_library_mark(std::move(layers));
}
alignment_store::settle();
switch_store::settle();
if (Snapshot.alignments.has_value())
{
current.alignments = alignment_store::records();
alignment_store::restore(*Snapshot.alignments);
}
if (Snapshot.switch_records.has_value())
{
current.switch_records = switch_store::records();
switch_store::restore(*Snapshot.switch_records);
}
Opposite.push_back(std::move(current));
if (Undo)
@@ -1835,6 +1862,7 @@ void editor_mode::render_turnout_ui()
editor_track::follow(*track, following);
editor_track::commit(changed);
record_switch(*track, Template);
records_to_history();
if (false == loose.empty())
ui()->set_status(STR_C("Too far from the new ends of the switch, left unjoined: ") + loose, true);
};
@@ -2552,6 +2580,7 @@ void editor_mode::render_route_stored()
if (ImGui::SmallButton(STR_C("Forget the kept design")))
{
alignment_store::forget(route.departed);
alignment_store::settle();
route.departed = -1;
route.status = STR_C("The kept design will be removed from ") + layer + STR_C(" on save");
}
@@ -2711,6 +2740,7 @@ void editor_mode::route_apply()
route_update();
}
route_store();
records_to_history();
route.status = STR_C("Re-laid ") + std::to_string(chain.tracks.size()) + STR_C(" paths") + (added > 0 ? STR_C(", added ") + std::to_string(added) : std::string{});
if (startextension > 0.0 || endextension > 0.0)
route.status += format(", the curve took over %.2f / %.2f m of the adjoining straights", startextension, endextension);
@@ -4313,6 +4343,8 @@ void editor_mode::delete_selected_track()
return;
}
// retired first, so the infrastructure it carried and moves elsewhere is in the same undo step
if (auto const known{switch_store::of(*track)})
switch_store::forget(known->name);
editor_track::retire(*track);
// a switch leaves its main track behind, so the line isn't broken, unless Shift asks for all of it
std::vector<TTrack *> created;
@@ -6265,7 +6297,10 @@ void editor_mode::render_switch_record(TTrack &Track)
item_tooltip("Chainage of the start of the switch; taken from the horizontal alignment kept in the scenery when the switch lies on one");
ImGui::PopItemWidth();
if (changed)
{
switch_store::put(entry, entry.layer);
switch_store::settle();
}
}
void editor_mode::switch_placed(std::vector<TTrack *> const &Created)

View File

@@ -33,6 +33,8 @@ std::string const kMark{"//$h"};
double const kStep{1.0};
std::vector<record> g_records;
std::vector<record> g_base; // as they were before the changes not yet in the undo history
bool g_changed{false};
bool g_loaded{false};
// format of the lines, coordinates in the space of the file:
@@ -257,6 +259,8 @@ stored_element describe(alignment::element const &Element)
void reset()
{
g_records.clear();
g_base.clear();
g_changed = false;
g_loaded = false;
}
@@ -363,6 +367,8 @@ void load()
}
}
}
g_base = g_records;
g_changed = false;
if (false == g_records.empty())
WriteLog("Editor: " + std::to_string(g_records.size()) + " horizontal alignment(s) read from the scenery", logtype::generic);
}
@@ -585,6 +591,7 @@ int store(alignment::design const &Design, alignment::result const &Result, doub
g_records.push_back(std::move(entry));
index = static_cast<int>(g_records.size()) - 1;
}
g_changed = true;
mark(g_records[index].layer);
return index;
}
@@ -596,6 +603,7 @@ void forget(int const Index)
return;
auto const layer{g_records[Index].layer};
g_records.erase(g_records.begin() + Index);
g_changed = true;
mark(layer);
}
@@ -607,4 +615,32 @@ std::optional<double> chainage_at(glm::dvec3 const &Point)
return g_records[found->record].origin + found->chainage;
}
std::optional<std::vector<record>> take_changes()
{
if (false == g_changed)
return std::nullopt;
auto before{std::move(g_base)};
g_base = g_records;
g_changed = false;
return before;
}
void settle()
{
g_base = g_records;
g_changed = false;
}
void restore(std::vector<record> Records)
{
std::set<scene::layer_handle> layers;
for (auto const *list : {&g_records, &Records})
for (auto const &entry : *list)
layers.insert(entry.layer);
g_records = std::move(Records);
for (auto const layer : layers)
mark(layer);
settle();
}
} // namespace alignment_store

View File

@@ -87,5 +87,11 @@ int store(alignment::design const &Design, alignment::result const &Result, doub
void forget(int const Index);
// km of the point on the stored alignments, if it lies on one
std::optional<double> chainage_at(glm::dvec3 const &Point);
// the records as they were before the changes made since the last call, if there were any; the current ones become the base
std::optional<std::vector<record>> take_changes();
// the current records become the base, the changes made so far aren't a part of the undo history
void settle();
// puts the records back as they were, for undo and redo
void restore(std::vector<record> Records);
} // namespace alignment_store

View File

@@ -33,6 +33,8 @@ std::string const kMark{"//$r"};
double const kTolerance{0.02};
std::vector<record> g_records;
std::vector<record> g_base; // as they were before the changes not yet in the undo history
bool g_changed{false};
bool g_loaded{false};
// format of the lines, coordinates in the space of the file:
@@ -100,6 +102,8 @@ void mark(scene::layer_handle const Layer)
void reset()
{
g_records.clear();
g_base.clear();
g_changed = false;
g_loaded = false;
}
@@ -143,6 +147,8 @@ void load()
g_records.push_back(std::move(entry));
}
}
g_base = g_records;
g_changed = false;
if (false == g_records.empty())
WriteLog("Editor: " + std::to_string(g_records.size()) + " switch record(s) read from the scenery", logtype::generic);
}
@@ -189,11 +195,13 @@ void put(record Entry, scene::layer_handle const Layer)
if (Entry.number == "-")
Entry.number = existing->number;
*existing = std::move(Entry);
g_changed = true;
mark(existing->layer);
return;
}
Entry.layer = Layer;
g_records.push_back(std::move(Entry));
g_changed = true;
mark(g_records.back().layer);
}
@@ -204,7 +212,9 @@ void forget(std::string const &Name)
for (auto const &entry : g_records)
if (entry.name == Name)
layers.insert(entry.layer);
auto const count{g_records.size()};
g_records.erase(std::remove_if(g_records.begin(), g_records.end(), [&](record const &Entry) { return Entry.name == Name; }), g_records.end());
g_changed = g_changed || g_records.size() != count;
for (auto const layer : layers)
mark(layer);
}
@@ -234,6 +244,7 @@ void tidy()
g_records.erase(std::remove_if(g_records.begin(), g_records.end(), gone), g_records.end());
for (auto const layer : layers)
mark(layer);
g_changed = g_changed || false == layers.empty();
}
std::string label(record const &Record)
@@ -254,4 +265,32 @@ std::string label(record const &Record)
return text;
}
std::optional<std::vector<record>> take_changes()
{
if (false == g_changed)
return std::nullopt;
auto before{std::move(g_base)};
g_base = g_records;
g_changed = false;
return before;
}
void settle()
{
g_base = g_records;
g_changed = false;
}
void restore(std::vector<record> Records)
{
std::set<scene::layer_handle> layers;
for (auto const *list : {&g_records, &Records})
for (auto const &entry : *list)
layers.insert(entry.layer);
g_records = std::move(Records);
for (auto const layer : layers)
mark(layer);
settle();
}
} // namespace switch_store

View File

@@ -57,5 +57,11 @@ void forget(std::string const &Name);
void tidy();
// "Rz 60E1-500-1:12 L", as the type is written on the lists of the switches
std::string label(record const &Record);
// the records as they were before the changes made since the last call, if there were any; the current ones become the base
std::optional<std::vector<record>> take_changes();
// the current records become the base, the changes made so far aren't a part of the undo history
void settle();
// puts the records back as they were, for undo and redo
void restore(std::vector<record> Records);
} // namespace switch_store