mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 07:59:30 +01:00
feat(editor): presets of the track properties, applied on the selected tracks
(cherry picked from commit 83c8c23c80)
This commit is contained in:
@@ -182,6 +182,7 @@ set(SOURCES
|
||||
"input/editorkeyboardinput.cpp"
|
||||
"editor/editorSettings.cpp"
|
||||
"editor/editorModelSets.cpp"
|
||||
"editor/editorTrackPresets.cpp"
|
||||
"editor/editorIncludeInfo.cpp"
|
||||
"editor/editorTerrain.cpp"
|
||||
"editor/editorTrack.cpp"
|
||||
|
||||
@@ -28,6 +28,7 @@ extern char **environ;
|
||||
#include "application/application.h"
|
||||
#include "editor/editorSettings.hpp"
|
||||
#include "editor/editorModelSets.hpp"
|
||||
#include "editor/editorTrackPresets.hpp"
|
||||
#include "editor/editorIncludeInfo.hpp"
|
||||
#include "editor/editorGroundMesh.hpp"
|
||||
#include "editor/editorFormat.hpp"
|
||||
@@ -542,6 +543,7 @@ editor_mode::editor_mode() {
|
||||
ui()->set_new_scenery([this]() { m_newscenery_asked = true; });
|
||||
ui()->set_open_scenery([this]() { m_openscenery_asked = true; });
|
||||
ui()->set_edit_menu([this]() { render_edit_menu(); });
|
||||
ui()->set_tools_menu([this]() { render_track_presets_menu(); });
|
||||
ui()->set_menu_options([this]() {
|
||||
render_object_menu();
|
||||
render_view_menu();
|
||||
@@ -648,6 +650,7 @@ bool editor_mode::init()
|
||||
Global.EditorOrthoTracks = EditorSettings.top_tracks();
|
||||
set_norms_free(EditorSettings.norms() == editorSettings::track_norms::free_laying);
|
||||
EditorModelSets.load();
|
||||
EditorTrackPresets.load();
|
||||
Camera.Init({0, 15, 0}, {glm::radians(-30.0), glm::radians(180.0), 0}, nullptr);
|
||||
return m_input.init();
|
||||
}
|
||||
|
||||
@@ -1837,6 +1837,15 @@ class editor_mode : public application_mode, private editor_track::observer, pri
|
||||
void render_track_style(std::vector<TTrack *> const &Tracks);
|
||||
void copy_track_style(TTrack const &Track);
|
||||
std::size_t paste_track_style(std::vector<TTrack *> const &Tracks);
|
||||
// which groups of the properties are pasted or put on the tracks by a preset, in the order of the groups of editormodetrackprops.cpp
|
||||
using track_groups = std::array<bool, 11>;
|
||||
// the values of the properties of the chosen groups put on the tracks, in one step of the undo. returns: the number of the
|
||||
// tracks changed, none when no value is of the chosen groups
|
||||
std::optional<std::size_t> apply_track_values(std::vector<std::pair<std::string, std::string>> const &Values, track_groups const &Groups, std::vector<TTrack *> const &Tracks, char const *What);
|
||||
// presets of the properties of the tracks (editorTrackPresets), made from a track and put on the selected ones
|
||||
void render_track_presets(std::vector<TTrack *> const &Tracks);
|
||||
void render_track_presets_menu();
|
||||
std::size_t apply_track_preset(int const Id, std::vector<TTrack *> const &Tracks);
|
||||
std::vector<TTrack *> similar_tracks(int const Property, std::vector<TTrack *> const &Tracks) const;
|
||||
// values of the properties over the selected tracks, each with the number of the tracks having it
|
||||
struct track_values
|
||||
@@ -1882,12 +1891,19 @@ class editor_mode : public application_mode, private editor_track::observer, pri
|
||||
{
|
||||
std::string from;
|
||||
std::vector<std::pair<std::string, std::string>> values;
|
||||
// speed, environment, overhead line, track circuits, rails, trackbed, its shape, sleepers, ride and sound, frogs
|
||||
std::array<bool, 10> groups{false, false, false, false, true, true, true, true, false, true};
|
||||
// speed, environment, overhead line, track circuits, rails, trackbed, its shape, sleepers, ride and sound, frogs, sleeper placement
|
||||
track_groups groups{false, false, false, false, true, true, true, true, false, true, true};
|
||||
bool open{false};
|
||||
bool picking{false}; // the next click on a track in the view copies its properties
|
||||
};
|
||||
track_style m_track_style;
|
||||
struct track_presets_panel
|
||||
{
|
||||
int selected{0}; // id of the preset chosen in the inspector
|
||||
int nameid{0}; // the preset whose name is in the field
|
||||
char name[128]{};
|
||||
};
|
||||
track_presets_panel m_track_presets;
|
||||
std::vector<std::vector<TTrack *>> m_track_similar;
|
||||
TTrack const *m_track_list_hover{nullptr}; // in the list of the selected tracks
|
||||
int m_track_list_hover_frame{-1};
|
||||
|
||||
@@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "application/editormodeshared.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "editor/editorTrackPresets.hpp"
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Track.h"
|
||||
@@ -219,10 +220,13 @@ enum group : int
|
||||
sleepers,
|
||||
ride_sound,
|
||||
frogs,
|
||||
sleeper_placement, // spacing and shift of the sleepers and of the trackbed under them, apart from their models
|
||||
group_count
|
||||
};
|
||||
|
||||
char const *const kGroupLabels[group_count] = {"Speed", "Environment", "Overhead line", "Track circuits", "Rails", "Trackbed", "Shape of the trackbed", "Sleepers", "Ride and sound", "Frog and check rails"};
|
||||
char const *const kGroupLabels[group_count] = {"Speed", "Environment", "Overhead line", "Track circuits", "Rails", "Trackbed", "Shape of the trackbed", "Sleepers", "Ride and sound", "Frog and check rails", "Sleeper placement"};
|
||||
// the groups as the presets keep them in their file, by names which don't change with the order of the groups
|
||||
char const *const kGroupKeys[group_count] = {"speed", "environment", "overhead", "circuits", "rails", "trackbed", "bedshape", "sleepers", "ride", "frogs", "sleeperplacement"};
|
||||
|
||||
struct track_property
|
||||
{
|
||||
@@ -630,13 +634,13 @@ std::vector<track_property> make_properties()
|
||||
};
|
||||
}
|
||||
}
|
||||
add_float(list, "sleeperspacing", section::look_sleepers, group::sleepers, "Sleeper spacing (m)", "Distance between the sleepers along the track", &TTrack::m_sleeper_frequency, 3, 0.1f, true);
|
||||
add_float(list, "sleeperspacing", section::look_sleepers, group::sleeper_placement, "Sleeper spacing (m)", "Distance between the sleepers along the track", &TTrack::m_sleeper_frequency, 3, 0.1f, true);
|
||||
char const *const offsets[][3] = {{"sleeperside", "Sleeper shift sideways (m)", "Shift of each model sideways, to the right of the track"},
|
||||
{"sleeperalong", "Sleeper shift along (m)", "Shift of each model along the track"},
|
||||
{"sleeperup", "Sleeper shift up (m)", "Shift of each model up"}};
|
||||
for (int axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
auto &property{add(list, offsets[axis][0], field::number, section::look_sleepers, group::sleepers, offsets[axis][1], offsets[axis][2])};
|
||||
auto &property{add(list, offsets[axis][0], field::number, section::look_sleepers, group::sleeper_placement, offsets[axis][1], offsets[axis][2])};
|
||||
property.rebuild = true;
|
||||
property.get = [axis](TTrack const &Track) { return decimal(Track.m_sleeper_offset[axis], 3); };
|
||||
property.set = [axis](TTrack &Track, std::string const &Text) {
|
||||
@@ -647,7 +651,7 @@ std::vector<track_property> make_properties()
|
||||
return true;
|
||||
};
|
||||
}
|
||||
add_float(list, "bedshift", section::look_sleepers, group::sleepers, "Trackbed shift (m)", "Vertical shift of the drawn trackbed under the sleepers, negative lowers it", &TTrack::m_sleeper_ballast_z, 3, lowest, true);
|
||||
add_float(list, "bedshift", section::look_sleepers, group::sleeper_placement, "Trackbed shift (m)", "Vertical shift of the drawn trackbed under the sleepers, negative lowers it", &TTrack::m_sleeper_ballast_z, 3, lowest, true);
|
||||
|
||||
// ride and sound
|
||||
add_float(list, "friction", section::ride, group::ride_sound, "Friction", "Friction coefficient of the rails, 0.15 for the dry rails; lower values make the wheels slip", &TTrack::fFriction, 3, 0.f, false);
|
||||
@@ -748,6 +752,62 @@ void row_tooltip(track_property const &Property, value_counts const &Values, std
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
// the values of the properties of the track, as they're copied and kept by the presets
|
||||
std::vector<std::pair<std::string, std::string>> values_of_track(TTrack const &Track)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> values;
|
||||
for (auto const &property : properties())
|
||||
if (false == property.off && property.applies(Track))
|
||||
values.emplace_back(property.key, property.get(Track));
|
||||
return values;
|
||||
}
|
||||
|
||||
// the groups some of the values are of
|
||||
std::array<bool, group_count> groups_in(std::vector<std::pair<std::string, std::string>> const &Values)
|
||||
{
|
||||
std::array<bool, group_count> groups{};
|
||||
for (auto const &[key, value] : Values)
|
||||
for (auto const &property : properties())
|
||||
if (key == property.key)
|
||||
groups[property.group] = true;
|
||||
return groups;
|
||||
}
|
||||
|
||||
// the groups the preset overwrites, from the names it keeps them by
|
||||
std::array<bool, group_count> groups_of(editor_track_preset const &Preset)
|
||||
{
|
||||
std::array<bool, group_count> groups{};
|
||||
for (auto const &key : Preset.groups)
|
||||
for (int group = 0; group < group_count; ++group)
|
||||
if (key == kGroupKeys[group])
|
||||
groups[group] = true;
|
||||
return groups;
|
||||
}
|
||||
|
||||
std::vector<std::string> group_keys(std::array<bool, group_count> const &Groups)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
for (int group = 0; group < group_count; ++group)
|
||||
if (Groups[group])
|
||||
keys.emplace_back(kGroupKeys[group]);
|
||||
return keys;
|
||||
}
|
||||
|
||||
// what the preset puts on the tracks, for its tooltip
|
||||
std::string preset_summary(editor_track_preset const &Preset)
|
||||
{
|
||||
auto const groups{groups_of(Preset)};
|
||||
auto const available{groups_in(Preset.values)};
|
||||
std::string overwrites;
|
||||
for (int group = 0; group < group_count; ++group)
|
||||
if (groups[group] && available[group])
|
||||
overwrites += (overwrites.empty() ? "" : ", ") + std::string{STR_C(kGroupLabels[group])};
|
||||
auto text{overwrites.empty() ? std::string{STR_C("Overwrites nothing: choose what it overwrites in the inspector")} : format(STR_C("Overwrites: %s"), overwrites.c_str())};
|
||||
if (false == Preset.from.empty())
|
||||
text += "\n" + format(STR_C("Taken from %s"), Preset.from.c_str());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<TTrack *> editor_mode::selected_tracks()
|
||||
@@ -1297,6 +1357,7 @@ void editor_mode::render_track_properties(std::vector<TTrack *> const &Tracks)
|
||||
if (any_in(section::look_sleepers, false) && ImGui::TreeNode(STR_C("Sleeper models")))
|
||||
{
|
||||
rows(section::look_sleepers, false);
|
||||
render_track_presets(Tracks);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -1307,50 +1368,230 @@ void editor_mode::render_track_properties(std::vector<TTrack *> const &Tracks)
|
||||
void editor_mode::copy_track_style(TTrack const &Track)
|
||||
{
|
||||
auto &style{m_track_style};
|
||||
style.values.clear();
|
||||
style.from = name_of(Track);
|
||||
for (auto const &property : properties())
|
||||
if (false == property.off && property.applies(Track))
|
||||
style.values.emplace_back(property.key, property.get(Track));
|
||||
style.values = values_of_track(Track);
|
||||
ui()->set_status(format(STR_C("Properties of %s copied: Ctrl+Shift+V pastes them on the selected tracks"), style.from.c_str()), false);
|
||||
}
|
||||
|
||||
std::size_t editor_mode::paste_track_style(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
static_assert(group_count == std::tuple_size_v<decltype(track_style::groups)>, "a choice for each group of the pasted properties");
|
||||
auto const &style{m_track_style};
|
||||
if (style.values.empty() || Tracks.empty())
|
||||
return 0;
|
||||
auto const changed{apply_track_values(style.values, style.groups, Tracks, "Pasted properties")};
|
||||
if (false == changed.has_value())
|
||||
{
|
||||
ui()->set_status(STR_C("Nothing chosen to paste"), true);
|
||||
return 0;
|
||||
}
|
||||
if (Tracks.size() == 1)
|
||||
ui()->set_status(*changed > 0 ? format(STR_C("Properties of %s pasted, Ctrl+Z takes them back"), style.from.c_str()) : std::string{STR_C("The track has these properties already")}, false);
|
||||
return *changed;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> editor_mode::apply_track_values(std::vector<std::pair<std::string, std::string>> const &Values, track_groups const &Groups, std::vector<TTrack *> const &Tracks, char const *What)
|
||||
{
|
||||
static_assert(group_count == std::tuple_size_v<track_groups>, "a choice for each group of the pasted properties");
|
||||
auto const &list{properties()};
|
||||
std::vector<std::pair<track_property const *, std::string>> pasted;
|
||||
bool rebuild{false};
|
||||
for (auto const late : {false, true})
|
||||
for (auto const &[key, value] : style.values)
|
||||
for (auto const &[key, value] : Values)
|
||||
{
|
||||
auto const index{track_property_index(key)};
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto const &property{list[index]};
|
||||
if (property.off || property.late != late || false == style.groups[property.group])
|
||||
if (property.off || property.late != late || false == Groups[property.group])
|
||||
continue;
|
||||
pasted.emplace_back(&property, value);
|
||||
rebuild |= property.rebuild;
|
||||
}
|
||||
if (pasted.empty())
|
||||
{
|
||||
ui()->set_status(STR_C("Nothing chosen to paste"), true);
|
||||
return 0;
|
||||
}
|
||||
auto const changed{tracks_apply(Tracks, [&](TTrack &Track) {
|
||||
return std::nullopt;
|
||||
return tracks_apply(Tracks, [&](TTrack &Track) {
|
||||
bool any{false};
|
||||
for (auto const &[property, value] : pasted)
|
||||
if (property->applies(Track))
|
||||
any |= property->set(Track, value);
|
||||
return any;
|
||||
}, rebuild, "Pasted properties")};
|
||||
if (Tracks.size() == 1)
|
||||
ui()->set_status(changed > 0 ? format(STR_C("Properties of %s pasted, Ctrl+Z takes them back"), style.from.c_str()) : std::string{STR_C("The track has these properties already")}, false);
|
||||
return changed;
|
||||
}, rebuild, What);
|
||||
}
|
||||
|
||||
std::size_t editor_mode::apply_track_preset(int const Id, std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
auto const *preset{EditorTrackPresets.find(Id)};
|
||||
if (preset == nullptr || Tracks.empty())
|
||||
return 0;
|
||||
auto const name{preset->name};
|
||||
auto const changed{apply_track_values(preset->values, groups_of(*preset), Tracks, "Applied a track preset")};
|
||||
if (false == changed.has_value())
|
||||
{
|
||||
ui()->set_status(format(STR_C("The preset \"%s\" overwrites nothing: choose what it overwrites"), name.c_str()), true);
|
||||
return 0;
|
||||
}
|
||||
if (*changed > 0)
|
||||
ui()->set_status(format(STR_P("Preset \"%s\" applied on %d track, Ctrl+Z takes it back", "Preset \"%s\" applied on %d tracks, Ctrl+Z takes it back", static_cast<long long>(*changed)), name.c_str(), static_cast<int>(*changed)), false);
|
||||
else
|
||||
ui()->set_status(STR_C("The tracks have the properties of the preset already"), false);
|
||||
return *changed;
|
||||
}
|
||||
|
||||
void editor_mode::render_track_presets(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
if (Tracks.empty() || false == ImGui::TreeNode(STR_C("Presets")))
|
||||
return;
|
||||
auto &panel{m_track_presets};
|
||||
auto const &presets{EditorTrackPresets.presets()};
|
||||
if (EditorTrackPresets.find(panel.selected) == nullptr)
|
||||
panel.selected = presets.empty() ? 0 : presets.front().id;
|
||||
auto const single{Tracks.size() == 1};
|
||||
auto const *selected{EditorTrackPresets.find(panel.selected)};
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (ImGui::BeginCombo("##trackpreset", selected != nullptr ? selected->name.c_str() : STR_C("No presets yet")))
|
||||
{
|
||||
for (auto const &preset : presets)
|
||||
{
|
||||
if (ImGui::Selectable(preset.name.c_str(), preset.id == panel.selected))
|
||||
panel.selected = preset.id;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", preset_summary(preset).c_str());
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (selected != nullptr && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", preset_summary(*selected).c_str());
|
||||
|
||||
auto const width{button_width(2)};
|
||||
ImGui::BeginDisabled(selected == nullptr);
|
||||
if (ImGui::Button(format(STR_P("Apply on %d track", "Apply on %d tracks", static_cast<long long>(Tracks.size())), static_cast<int>(Tracks.size())).c_str(), ImVec2(width, 0.0f)))
|
||||
apply_track_preset(panel.selected, Tracks);
|
||||
ImGui::EndDisabled();
|
||||
item_tooltip("Puts the values of the preset on the selected tracks, of what it overwrites; Ctrl+Z takes them back.\nTools in the menu applies the presets too");
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(false == single);
|
||||
if (ImGui::Button(STR_C("New from the track"), ImVec2(width, 0.0f)))
|
||||
{
|
||||
// rails, trackbed, its shape, sleepers, frogs and sleeper placement at first, the look of the track
|
||||
std::array<bool, group_count> groups{};
|
||||
for (auto const group : {group::rails, group::trackbed, group::trackbed_shape, group::sleepers, group::frogs, group::sleeper_placement})
|
||||
groups[group] = true;
|
||||
panel.selected = EditorTrackPresets.create(STR_C("New preset"), name_of(*Tracks.front()), values_of_track(*Tracks.front()), group_keys(groups));
|
||||
panel.nameid = 0;
|
||||
ui()->set_status(format(STR_C("Properties of %s saved as a preset: give it a name and choose what it overwrites"), name_of(*Tracks.front()).c_str()), false);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", STR_C(single ? "Saves the properties of this track as a new preset" : "Select one track to save its properties as a preset"));
|
||||
|
||||
// pointers to the presets don't outlive a change of their list
|
||||
selected = EditorTrackPresets.find(panel.selected);
|
||||
if (selected == nullptr)
|
||||
{
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", STR_C("A preset keeps the properties of a track: its rails, trackbed, sleepers... to put them on other tracks,\nwith the choice of what it overwrites"));
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::TreePop();
|
||||
return;
|
||||
}
|
||||
auto const id{selected->id};
|
||||
|
||||
// the name
|
||||
if (panel.nameid != id)
|
||||
{
|
||||
std::snprintf(panel.name, sizeof(panel.name), "%s", selected->name.c_str());
|
||||
panel.nameid = id;
|
||||
}
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(STR_C("Rename")).x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x);
|
||||
auto const entered{ImGui::InputText("##presetname", panel.name, IM_ARRAYSIZE(panel.name), ImGuiInputTextFlags_EnterReturnsTrue)};
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(STR_C("Rename")) || entered)
|
||||
{
|
||||
EditorTrackPresets.rename(id, panel.name);
|
||||
panel.nameid = 0; // reload, the name could have been adjusted to stay unique
|
||||
selected = EditorTrackPresets.find(id);
|
||||
}
|
||||
|
||||
// what it overwrites, of the groups it has values of
|
||||
ImGui::TextDisabled("%s", STR_C("Overwrites:"));
|
||||
// its own id scope: the group checkboxes share their labels with the sections of the properties
|
||||
ImGui::PushID("presetgroups");
|
||||
auto const available{groups_in(selected->values)};
|
||||
auto groups{groups_of(*selected)};
|
||||
auto changed{false};
|
||||
int column{0};
|
||||
auto const half{ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x * 0.5f};
|
||||
for (int group = 0; group < group_count; ++group)
|
||||
{
|
||||
if (false == available[group])
|
||||
continue;
|
||||
if (column % 2 == 1)
|
||||
ImGui::SameLine(half);
|
||||
changed |= ImGui::Checkbox(STR_C(kGroupLabels[group]), &groups[group]);
|
||||
++column;
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (changed)
|
||||
{
|
||||
// the groups it has no values of are kept as they were, for a track they're taken again from
|
||||
auto kept{groups_of(*selected)};
|
||||
for (int group = 0; group < group_count; ++group)
|
||||
if (available[group])
|
||||
kept[group] = groups[group];
|
||||
EditorTrackPresets.set_groups(id, group_keys(kept));
|
||||
selected = EditorTrackPresets.find(id);
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(false == single);
|
||||
if (ImGui::Button(STR_C("Update from the track"), ImVec2(width, 0.0f)))
|
||||
{
|
||||
EditorTrackPresets.update(id, name_of(*Tracks.front()), values_of_track(*Tracks.front()));
|
||||
ui()->set_status(format(STR_C("Preset \"%s\" takes the properties of %s now"), selected->name.c_str(), name_of(*Tracks.front()).c_str()), false);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", STR_C(single ? "The preset takes the properties of this track in place of the ones it had; what it overwrites stays" : "Select one track to take its properties into the preset"));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(STR_C("Delete"), ImVec2(width, 0.0f)))
|
||||
ImGui::OpenPopup("##deletepreset");
|
||||
if (ImGui::BeginPopup("##deletepreset"))
|
||||
{
|
||||
ImGui::Text(STR_C("Delete preset \"%s\"?"), selected->name.c_str());
|
||||
if (ImGui::Button(STR_C("Delete##confirm")))
|
||||
{
|
||||
EditorTrackPresets.remove(id);
|
||||
panel.selected = 0;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(STR_C("Cancel")))
|
||||
ImGui::CloseCurrentPopup();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
void editor_mode::render_track_presets_menu()
|
||||
{
|
||||
if (false == ImGui::BeginMenu(STR_C("Track presets")))
|
||||
return;
|
||||
auto const &presets{EditorTrackPresets.presets()};
|
||||
if (presets.empty())
|
||||
ImGui::TextDisabled("%s", STR_C("No presets: save one in the inspector of a track, Look, Sleeper models, Presets"));
|
||||
auto const tracks{selected_tracks()};
|
||||
int chosen{0};
|
||||
for (auto const &preset : presets)
|
||||
{
|
||||
if (ImGui::MenuItem(preset.name.c_str(), nullptr, false, false == tracks.empty()))
|
||||
chosen = preset.id;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", tracks.empty() ? STR_C("Select the tracks to apply the preset on first") : (format(STR_P("Applies the preset on %d selected track", "Applies the preset on %d selected tracks", static_cast<long long>(tracks.size())), static_cast<int>(tracks.size())) + "\n" + preset_summary(preset)).c_str());
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
if (chosen != 0)
|
||||
apply_track_preset(chosen, tracks);
|
||||
}
|
||||
|
||||
void editor_mode::render_track_style(std::vector<TTrack *> const &Tracks)
|
||||
@@ -1623,7 +1864,7 @@ editor_mode::selftest_result editor_mode::selftest_props(std::string const &Comm
|
||||
}
|
||||
else if (action == "groups")
|
||||
{
|
||||
// the groups pasted, as their numbers: 0 speed ... 9 frogs
|
||||
// the groups pasted, as their numbers: 0 speed ... 9 frogs, 10 sleeper placement
|
||||
m_track_style.groups.fill(false);
|
||||
for (int group; Words >> group;)
|
||||
if (group >= 0 && group < group_count)
|
||||
|
||||
@@ -990,6 +990,15 @@ void editor_ui::render_menu_contents()
|
||||
m_menuoptions();
|
||||
}
|
||||
|
||||
// the tools of the editor mode, in the tools menu after the common ones (logging, screenshot)
|
||||
void editor_ui::render_tools_menu()
|
||||
{
|
||||
if (!m_toolsmenu)
|
||||
return;
|
||||
ImGui::Separator();
|
||||
m_toolsmenu();
|
||||
}
|
||||
|
||||
// the windows of the editor and their layout, in the windows menu after the common ones (log, hierarchy, history, settings)
|
||||
void editor_ui::render_windows_menu()
|
||||
{
|
||||
|
||||
@@ -80,6 +80,11 @@ class editor_ui : public ui_layer
|
||||
{
|
||||
m_editmenu = std::move(Renderer);
|
||||
}
|
||||
// draws the items of the editor mode in the tools menu, after the common ones
|
||||
void set_tools_menu(std::function<void()> Renderer)
|
||||
{
|
||||
m_toolsmenu = std::move(Renderer);
|
||||
}
|
||||
// draws the fields of work of the editor mode in a row above the toolbar
|
||||
void set_workspace_bar(std::function<void()> Renderer)
|
||||
{
|
||||
@@ -177,6 +182,7 @@ class editor_ui : public ui_layer
|
||||
void scene_row(std::size_t const Entry);
|
||||
void render_menu_contents() override;
|
||||
void render_windows_menu() override;
|
||||
void render_tools_menu() override;
|
||||
int palette() const override;
|
||||
void render_dockspace() override;
|
||||
void render_() override;
|
||||
@@ -210,6 +216,7 @@ class editor_ui : public ui_layer
|
||||
std::function<void()> m_toolbaroptions;
|
||||
std::function<void()> m_workspacebar;
|
||||
std::function<void()> m_editmenu;
|
||||
std::function<void()> m_toolsmenu;
|
||||
bool m_layoutreset{false}; // the default layout is to be built again on the next frame
|
||||
std::string m_refusal;
|
||||
double m_refusaltime{-100.0};
|
||||
|
||||
@@ -696,6 +696,7 @@ void ui_layer::render_menu_contents()
|
||||
|
||||
if (ImGui::MenuItem(STR_C("Screenshot"), "PrtScr"))
|
||||
Application.queue_screenshot();
|
||||
render_tools_menu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu(STR_C("Windows")))
|
||||
|
||||
@@ -143,6 +143,8 @@ protected:
|
||||
virtual void render_menu_contents();
|
||||
// items of the windows menu which belong to the ui of a mode, after the common ones
|
||||
virtual void render_windows_menu() {}
|
||||
// items of the tools menu which belong to the ui of a mode, after the common ones
|
||||
virtual void render_tools_menu() {}
|
||||
// colours this ui is drawn in, see imgui_colors()
|
||||
virtual int palette() const
|
||||
{
|
||||
|
||||
233
editor/editorTrackPresets.cpp
Normal file
233
editor/editorTrackPresets.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
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 "editor/editorTrackPresets.hpp"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
editorTrackPresets EditorTrackPresets;
|
||||
|
||||
// file layout:
|
||||
// preset <name>
|
||||
// from <track name>
|
||||
// groups <group key> <group key>...
|
||||
// value <property key> <value, the rest of the line>
|
||||
// ...
|
||||
// endpreset
|
||||
namespace
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path presets_path()
|
||||
{
|
||||
fs::path p = user_config_path("eu07_editor_track_presets.txt");
|
||||
return p.empty() ? fs::path("eu07_editor_track_presets.txt") : p;
|
||||
}
|
||||
|
||||
std::string trimmed(std::string Text)
|
||||
{
|
||||
auto const first = Text.find_first_not_of(" \t");
|
||||
if (first == std::string::npos)
|
||||
return {};
|
||||
auto const last = Text.find_last_not_of(" \t");
|
||||
return Text.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
// one value per line in the file
|
||||
std::string single_line(std::string Text)
|
||||
{
|
||||
std::replace_if(Text.begin(), Text.end(), [](char const Character) { return Character == '\r' || Character == '\n' || Character == '\t'; }, ' ');
|
||||
return trimmed(Text);
|
||||
}
|
||||
|
||||
// the line past its first word, when it starts with the word
|
||||
bool keyword(std::string const &Line, char const *Word, std::string &Rest)
|
||||
{
|
||||
auto const length{std::char_traits<char>::length(Word)};
|
||||
if (Line.compare(0, length, Word) != 0 || (Line.size() > length && Line[length] != ' '))
|
||||
return false;
|
||||
Rest = Line.size() > length ? trimmed(Line.substr(length + 1)) : std::string{};
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool editorTrackPresets::load()
|
||||
{
|
||||
m_presets.clear();
|
||||
|
||||
const fs::path path = presets_path();
|
||||
std::error_code ec;
|
||||
if (!fs::exists(path, ec))
|
||||
return false;
|
||||
|
||||
std::ifstream stream(path, std::ios_base::in | std::ios_base::binary);
|
||||
if (!stream.is_open())
|
||||
return false;
|
||||
|
||||
editor_track_preset *current{nullptr};
|
||||
std::string line;
|
||||
std::string rest;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
line = single_line(line);
|
||||
if (line.empty() || line.rfind("//", 0) == 0)
|
||||
continue;
|
||||
|
||||
// a preset header also closes a preset with missing "endpreset"
|
||||
if (keyword(line, "preset", rest))
|
||||
{
|
||||
editor_track_preset preset;
|
||||
preset.id = m_nextid++;
|
||||
preset.name = unique_name(rest);
|
||||
m_presets.push_back(std::move(preset));
|
||||
current = &m_presets.back();
|
||||
continue;
|
||||
}
|
||||
if (line == "endpreset")
|
||||
{
|
||||
current = nullptr;
|
||||
continue;
|
||||
}
|
||||
if (current == nullptr)
|
||||
continue;
|
||||
if (keyword(line, "from", rest))
|
||||
current->from = rest;
|
||||
else if (keyword(line, "groups", rest))
|
||||
{
|
||||
std::istringstream words{rest};
|
||||
current->groups.clear();
|
||||
for (std::string group; words >> group;)
|
||||
current->groups.push_back(group);
|
||||
}
|
||||
else if (keyword(line, "value", rest) && false == rest.empty())
|
||||
{
|
||||
auto const split{rest.find(' ')};
|
||||
if (split == std::string::npos)
|
||||
current->values.emplace_back(rest, std::string{});
|
||||
else
|
||||
current->values.emplace_back(rest.substr(0, split), trimmed(rest.substr(split + 1)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editorTrackPresets::save() const
|
||||
{
|
||||
const fs::path path = presets_path();
|
||||
std::error_code ec;
|
||||
fs::create_directories(path.parent_path(), ec);
|
||||
|
||||
std::ofstream stream(path, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary);
|
||||
if (!stream.is_open())
|
||||
{
|
||||
ErrorLog("failed to save editor track presets");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream << "// MaSzyna editor: user-defined presets of the track properties\n";
|
||||
for (auto const &preset : m_presets)
|
||||
{
|
||||
stream << "\npreset " << preset.name << "\n";
|
||||
if (false == preset.from.empty())
|
||||
stream << "from " << preset.from << "\n";
|
||||
stream << "groups";
|
||||
for (auto const &group : preset.groups)
|
||||
stream << ' ' << group;
|
||||
stream << "\n";
|
||||
for (auto const &[key, value] : preset.values)
|
||||
stream << "value " << key << ' ' << single_line(value) << "\n";
|
||||
stream << "endpreset\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
editor_track_preset const *editorTrackPresets::find(int const Id) const
|
||||
{
|
||||
auto const lookup = std::find_if(m_presets.begin(), m_presets.end(), [Id](auto const &Preset) { return Preset.id == Id; });
|
||||
return lookup != m_presets.end() ? &(*lookup) : nullptr;
|
||||
}
|
||||
|
||||
editor_track_preset *editorTrackPresets::find_(int const Id)
|
||||
{
|
||||
auto const lookup = std::find_if(m_presets.begin(), m_presets.end(), [Id](auto const &Preset) { return Preset.id == Id; });
|
||||
return lookup != m_presets.end() ? &(*lookup) : nullptr;
|
||||
}
|
||||
|
||||
int editorTrackPresets::create(std::string const &Name, std::string const &From, std::vector<std::pair<std::string, std::string>> const &Values, std::vector<std::string> const &Groups)
|
||||
{
|
||||
editor_track_preset preset;
|
||||
preset.id = m_nextid++;
|
||||
preset.name = unique_name(Name);
|
||||
preset.from = single_line(From);
|
||||
preset.values = Values;
|
||||
preset.groups = Groups;
|
||||
m_presets.push_back(std::move(preset));
|
||||
save();
|
||||
return m_presets.back().id;
|
||||
}
|
||||
|
||||
void editorTrackPresets::remove(int const Id)
|
||||
{
|
||||
auto const lookup = std::find_if(m_presets.begin(), m_presets.end(), [Id](auto const &Preset) { return Preset.id == Id; });
|
||||
if (lookup == m_presets.end())
|
||||
return;
|
||||
m_presets.erase(lookup);
|
||||
save();
|
||||
}
|
||||
|
||||
void editorTrackPresets::rename(int const Id, std::string const &Name)
|
||||
{
|
||||
auto *preset = find_(Id);
|
||||
if (preset == nullptr)
|
||||
return;
|
||||
auto const name = unique_name(Name, Id);
|
||||
if (name == preset->name)
|
||||
return;
|
||||
preset->name = name;
|
||||
save();
|
||||
}
|
||||
|
||||
void editorTrackPresets::update(int const Id, std::string const &From, std::vector<std::pair<std::string, std::string>> const &Values)
|
||||
{
|
||||
auto *preset = find_(Id);
|
||||
if (preset == nullptr)
|
||||
return;
|
||||
preset->from = single_line(From);
|
||||
preset->values = Values;
|
||||
save();
|
||||
}
|
||||
|
||||
void editorTrackPresets::set_groups(int const Id, std::vector<std::string> const &Groups)
|
||||
{
|
||||
auto *preset = find_(Id);
|
||||
if (preset == nullptr || preset->groups == Groups)
|
||||
return;
|
||||
preset->groups = Groups;
|
||||
save();
|
||||
}
|
||||
|
||||
std::string editorTrackPresets::unique_name(std::string const &Base, int const IgnoreId) const
|
||||
{
|
||||
auto base = single_line(Base);
|
||||
if (base.empty())
|
||||
base = "New preset";
|
||||
auto const taken = [&](std::string const &Name) {
|
||||
return std::any_of(m_presets.begin(), m_presets.end(), [&](auto const &Preset) { return Preset.id != IgnoreId && Preset.name == Name; });
|
||||
};
|
||||
auto name = base;
|
||||
for (int suffix = 2; taken(name); ++suffix)
|
||||
name = base + " " + std::to_string(suffix);
|
||||
return name;
|
||||
}
|
||||
58
editor/editorTrackPresets.hpp
Normal file
58
editor/editorTrackPresets.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// user-defined presets of the properties of tracks (rails, trackbed, sleepers...), taken from a track and put on others
|
||||
// with the choice of what they overwrite. stored next to the user's ini files, like the model sets
|
||||
struct editor_track_preset
|
||||
{
|
||||
int id{0}; // runtime identifier, stable while the editor runs (renames don't break references)
|
||||
std::string name;
|
||||
std::string from; // name of the track the values were taken from
|
||||
// the values of the properties as the track inspector reads and writes them, by their keys
|
||||
std::vector<std::pair<std::string, std::string>> values;
|
||||
// the groups of the properties the preset overwrites, by their keys; the other values of it are kept but left out
|
||||
std::vector<std::string> groups;
|
||||
};
|
||||
|
||||
class editorTrackPresets
|
||||
{
|
||||
public:
|
||||
bool load();
|
||||
bool save() const;
|
||||
|
||||
std::vector<editor_track_preset> const &presets() const
|
||||
{
|
||||
return m_presets;
|
||||
}
|
||||
editor_track_preset const *find(int Id) const;
|
||||
// all modifying methods save the presets right away
|
||||
// returns: id of the new preset
|
||||
int create(std::string const &Name, std::string const &From, std::vector<std::pair<std::string, std::string>> const &Values, std::vector<std::string> const &Groups);
|
||||
void remove(int Id);
|
||||
void rename(int Id, std::string const &Name);
|
||||
// the values taken again, from another track or the same one changed
|
||||
void update(int Id, std::string const &From, std::vector<std::pair<std::string, std::string>> const &Values);
|
||||
void set_groups(int Id, std::vector<std::string> const &Groups);
|
||||
// name not used by any other preset, built from specified base
|
||||
std::string unique_name(std::string const &Base, int IgnoreId = 0) const;
|
||||
|
||||
private:
|
||||
editor_track_preset *find_(int Id);
|
||||
|
||||
std::vector<editor_track_preset> m_presets;
|
||||
int m_nextid{1};
|
||||
};
|
||||
|
||||
extern editorTrackPresets EditorTrackPresets;
|
||||
@@ -11532,3 +11532,98 @@ msgstr "Każdy kliknięty punkt na wysokości terenu, między punktami równe po
|
||||
|
||||
msgid "The track goes on at the height it's laid from, whatever the ground; the first point on the ground"
|
||||
msgstr "Tor idzie dalej na wysokości, z której go kładziesz, bez względu na teren; pierwszy punkt na terenie"
|
||||
|
||||
msgid "new, not saved"
|
||||
msgstr "nowy, niezapisany"
|
||||
|
||||
# scenery editor: presets of the track properties
|
||||
|
||||
msgid "Sleeper placement"
|
||||
msgstr "Rozmieszczenie podkładów"
|
||||
|
||||
msgid "Overwrites nothing: choose what it overwrites in the inspector"
|
||||
msgstr "Niczego nie nadpisuje: wybierz w inspektorze, co ma nadpisywać"
|
||||
|
||||
msgid "Overwrites: %s"
|
||||
msgstr "Nadpisuje: %s"
|
||||
|
||||
msgid "Taken from %s"
|
||||
msgstr "Wzięty z %s"
|
||||
|
||||
msgid "The preset \"%s\" overwrites nothing: choose what it overwrites"
|
||||
msgstr "Zestaw \"%s\" niczego nie nadpisuje: wybierz, co ma nadpisywać"
|
||||
|
||||
msgid "Preset \"%s\" applied on %d track, Ctrl+Z takes it back"
|
||||
msgid_plural "Preset \"%s\" applied on %d tracks, Ctrl+Z takes it back"
|
||||
msgstr[0] "Zestaw \"%s\" zastosowany na %d torze, Ctrl+Z go cofa"
|
||||
msgstr[1] "Zestaw \"%s\" zastosowany na %d torach, Ctrl+Z go cofa"
|
||||
msgstr[2] "Zestaw \"%s\" zastosowany na %d torach, Ctrl+Z go cofa"
|
||||
|
||||
msgid "The tracks have the properties of the preset already"
|
||||
msgstr "Tory mają już właściwości tego zestawu"
|
||||
|
||||
msgid "Presets"
|
||||
msgstr "Zestawy"
|
||||
|
||||
msgid "No presets yet"
|
||||
msgstr "Brak zestawów"
|
||||
|
||||
msgid "Apply on %d track"
|
||||
msgid_plural "Apply on %d tracks"
|
||||
msgstr[0] "Zastosuj na %d torze"
|
||||
msgstr[1] "Zastosuj na %d torach"
|
||||
msgstr[2] "Zastosuj na %d torach"
|
||||
|
||||
msgid "Puts the values of the preset on the selected tracks, of what it overwrites; Ctrl+Z takes them back.\nTools in the menu applies the presets too"
|
||||
msgstr "Nadaje zaznaczonym torom wartości zestawu, z tego, co nadpisuje; Ctrl+Z je cofa.\nZestawy można też zastosować z menu Narzędzia"
|
||||
|
||||
msgid "New from the track"
|
||||
msgstr "Nowy z toru"
|
||||
|
||||
msgid "New preset"
|
||||
msgstr "Nowy zestaw"
|
||||
|
||||
msgid "Properties of %s saved as a preset: give it a name and choose what it overwrites"
|
||||
msgstr "Właściwości %s zapisane jako zestaw: nadaj mu nazwę i wybierz, co ma nadpisywać"
|
||||
|
||||
msgid "Saves the properties of this track as a new preset"
|
||||
msgstr "Zapisuje właściwości tego toru jako nowy zestaw"
|
||||
|
||||
msgid "Select one track to save its properties as a preset"
|
||||
msgstr "Zaznacz jeden tor, żeby zapisać jego właściwości jako zestaw"
|
||||
|
||||
msgid "A preset keeps the properties of a track: its rails, trackbed, sleepers... to put them on other tracks,\nwith the choice of what it overwrites"
|
||||
msgstr "Zestaw przechowuje właściwości toru: szyny, podsypkę, podkłady... żeby nadać je innym torom,\nz wyborem tego, co nadpisuje"
|
||||
|
||||
msgid "Overwrites:"
|
||||
msgstr "Nadpisuje:"
|
||||
|
||||
msgid "Update from the track"
|
||||
msgstr "Aktualizuj z toru"
|
||||
|
||||
msgid "Preset \"%s\" takes the properties of %s now"
|
||||
msgstr "Zestaw \"%s\" ma teraz właściwości %s"
|
||||
|
||||
msgid "The preset takes the properties of this track in place of the ones it had; what it overwrites stays"
|
||||
msgstr "Zestaw przejmuje właściwości tego toru w miejsce dotychczasowych; wybór tego, co nadpisuje, zostaje"
|
||||
|
||||
msgid "Select one track to take its properties into the preset"
|
||||
msgstr "Zaznacz jeden tor, żeby przenieść jego właściwości do zestawu"
|
||||
|
||||
msgid "Delete preset \"%s\"?"
|
||||
msgstr "Usunąć zestaw \"%s\"?"
|
||||
|
||||
msgid "Track presets"
|
||||
msgstr "Zestawy torów"
|
||||
|
||||
msgid "No presets: save one in the inspector of a track, Look, Sleeper models, Presets"
|
||||
msgstr "Brak zestawów: zapisz zestaw w inspektorze toru, Wygląd, Modele podkładów, Zestawy"
|
||||
|
||||
msgid "Select the tracks to apply the preset on first"
|
||||
msgstr "Najpierw zaznacz tory, na których zastosować zestaw"
|
||||
|
||||
msgid "Applies the preset on %d selected track"
|
||||
msgid_plural "Applies the preset on %d selected tracks"
|
||||
msgstr[0] "Stosuje zestaw na %d zaznaczonym torze"
|
||||
msgstr[1] "Stosuje zestaw na %d zaznaczonych torach"
|
||||
msgstr[2] "Stosuje zestaw na %d zaznaczonych torach"
|
||||
|
||||
Reference in New Issue
Block a user