diff --git a/CMakeLists.txt b/CMakeLists.txt index a752fd72..2478f8ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,6 +208,7 @@ set(SOURCES "application/editormodetrackgizmo.cpp" "application/editormoderecords.cpp" "application/editormodeswitchdrive.cpp" +"application/editormodeswitchtool.cpp" "application/editormodeswitchslide.cpp" "application/editormodeswitchdesign.cpp" "application/editormodetrackbed.cpp" diff --git a/application/editormode.h b/application/editormode.h index aa0b250e..4e2e33f2 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -505,7 +505,6 @@ class editor_mode : public application_mode, private editor_track::observer // over the 3d view: the tools of the field of work along its left edge, the modes of the gizmo at its top, the axes of the // world and the top view in its top right corner void render_viewport_overlays(); - void render_viewport_tools(); // the tools of the field of work, in the order of the toolbar void work_area_tools(std::vector &Buttons); // the tools of the field of work in the toolbar, as icons with their names @@ -1026,6 +1025,7 @@ class editor_mode : public application_mode, private editor_track::observer slide_state slide; double slide_typed{0.0}; int connection{-1}; + int connection_kind{0}; // the kind of connection chosen last, taken again when the connections are chosen int connection_second{-1}; double connection_radius{0.0}; double connection_gap{6.0}; @@ -1034,9 +1034,25 @@ class editor_mode : public application_mode, private editor_track::observer double connection_stub{25.0}; }; switch_tool m_switch; + // what the switch tool places: a switch into a straight or a curve, a slip switch or a crossing at a crossing of two + // straights, or a connection of tracks laid whole + enum class switch_kind + { + single, + slip, + crossing, + connection + }; + switch_kind current_switch_kind() const; + void choose_switch_kind(switch_kind const Kind); + bool switch_kind_offered(switch_kind const Kind) const; bool plan_connection(connection::request &Request, connection::result &Result) const; void insert_connection(); - void render_connection_ui(); + void render_connection_options(); + // the name of a kind of connection of tracks, translated + static char const *connection_name(int const Kind); + // the name of a switch template for the lists: where it comes from and its name without the lengths + static std::string switch_template_label(editor_track::switch_template const &Template); std::string connection_readout(connection::result const &Plan) const; editor_track::straight straight_near(glm::dvec3 const &Point, std::vector const &Exclude, double const Reach) const; editor_track::straight straight_through(glm::dvec3 const &Point, glm::dvec2 const &Direction) const; @@ -1274,7 +1290,7 @@ class editor_mode : public application_mode, private editor_track::observer track_tab tab; char const *tooltip; }; - static std::array const &track_modes(); + static std::array const &track_modes(); bool track_plan_tab() const { return m_track_tab == track_tab::straights || m_track_tab == track_tab::route; } track_tab plan_tab_for(TTrack const *Track) const; // the objects along the track: a tab for each tool diff --git a/application/editormodeconnection.cpp b/application/editormodeconnection.cpp index b5f9142e..c2d344d3 100644 --- a/application/editormodeconnection.cpp +++ b/application/editormodeconnection.cpp @@ -32,17 +32,6 @@ namespace using geometry::cross; using geometry::plan_of; -char const *connection_name(int const Kind) -{ - switch (static_cast(Kind)) - { - case connection::kind::double_crossover: return STR_C("Double crossover"); - case connection::kind::scissors: return STR_C("Scissors crossover"); - case connection::kind::ladder: return STR_C("Switch ladder"); - default: return STR_C("Crossover"); - } -} - std::vector spacings_of(char const *Text) { std::string text{Text}; @@ -59,13 +48,6 @@ std::vector spacings_of(char const *Text) return result; } -std::string switch_label(editor_track::switch_template const &Template) -{ - if (Template.source != nullptr) - return STR("Scenery: ") + Template.label; - return (Template.own ? STR("Own: ") : std::string{"PLK: "}) + Template.label; -} - bool usable_in_connection(editor_track::switch_template const &Template) { return false == Template.double_slip && false == Template.twofold(); @@ -73,6 +55,17 @@ bool usable_in_connection(editor_track::switch_template const &Template) } // namespace +char const *editor_mode::connection_name(int const Kind) +{ + switch (static_cast(Kind)) + { + case connection::kind::double_crossover: return STR_C("Double crossover"); + case connection::kind::scissors: return STR_C("Scissors crossover"); + case connection::kind::ladder: return STR_C("Switch ladder"); + default: return STR_C("Crossover"); + } +} + editor_track::straight editor_mode::straight_near(glm::dvec3 const &Point, std::vector const &Exclude, double const Reach) const { TTrack *found{nullptr}; @@ -335,32 +328,19 @@ void editor_mode::insert_connection() ui()->set_status(tool.status, false); } -void editor_mode::render_connection_ui() +void editor_mode::render_connection_options() { auto &tool{m_switch}; - ImGui::PushItemWidth(220.0f); - if (ImGui::BeginCombo(STR_C("Connection##switchconnection"), tool.connection < 0 ? STR_C("Single switch") : connection_name(tool.connection))) - { - if (ImGui::Selectable(STR_C("Single switch"), tool.connection < 0)) - tool.connection = -1; - for (int i = 0; i < connection::kind_count; ++i) - if (ImGui::Selectable(connection_name(i), tool.connection == i)) - tool.connection = i; - ImGui::EndCombo(); - } - item_tooltip("A connection of tracks laid whole: press on a straight and drag onto the other track. Its switches are cut into the tracks with the inserts between them and get their drives; Shift+wheel while dragging: another kind, Ctrl+wheel: R of the aligning curve"); if (tool.connection < 0) - { - ImGui::PopItemWidth(); return; - } - auto const label = [&](int const Index) { return Index < 0 || Index >= static_cast(tool.templates.size()) ? std::string{STR_C("The same as the first one")} : switch_label(tool.templates[Index]); }; + ImGui::PushItemWidth(220.0f); + auto const label = [&](int const Index) { return Index < 0 || Index >= static_cast(tool.templates.size()) ? std::string{STR_C("The same as the first one")} : switch_template_label(tool.templates[Index]); }; if (ImGui::BeginCombo(STR_C("Other switches##connectionsecond"), label(tool.connection_second).c_str())) { if (ImGui::Selectable(STR_C("The same as the first one"), tool.connection_second < 0)) tool.connection_second = -1; for (int i = 0; i < static_cast(tool.templates.size()); ++i) - if (usable_in_connection(tool.templates[i]) && ImGui::Selectable((switch_label(tool.templates[i]) + "##connectionsecond" + std::to_string(i)).c_str(), tool.connection_second == i)) + if (usable_in_connection(tool.templates[i]) && ImGui::Selectable((switch_template_label(tool.templates[i]) + "##connectionsecond" + std::to_string(i)).c_str(), tool.connection_second == i)) tool.connection_second = i; ImGui::EndCombo(); } diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index d8725ee3..f2b0af08 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -1652,6 +1652,18 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com WriteLog("SELFTEST layhold: " + m_lay.status); return selftest_result::handled; } + if (command == "switchkind") + { + // what the switch tool places, as chosen in its panel: single, slip, crossing or connection with the kind of it + std::string word; + int sub{0}; + words >> word >> sub; + auto const kind{word == "slip" ? switch_kind::slip : word == "crossing" ? switch_kind::crossing : word == "connection" ? switch_kind::connection : switch_kind::single}; + m_switch.connection_kind = sub; + choose_switch_kind(kind); + WriteLog(format("SELFTEST switchkind %s: kind %d, connection %d, armed %d", word.c_str(), static_cast(current_switch_kind()), m_switch.connection, m_switch.armed)); + return selftest_result::handled; + } if (command == "connection" || command == "connectionplan" || command == "connectionhold") { std::string kindword; diff --git a/application/editormodeswitchdrive.cpp b/application/editormodeswitchdrive.cpp index e04d5ac2..56507c15 100644 --- a/application/editormodeswitchdrive.cpp +++ b/application/editormodeswitchdrive.cpp @@ -289,7 +289,7 @@ bool editor_mode::render_switch_drive_choice() scan_switch_drives(); if (tool.drives.empty()) { - ImGui::TextDisabled("%s", STR_C("No switch drives: no template is described with category: switch drive")); + ImGui::TextDisabled("%s", STR_C("No switch drive models in the scenery data")); return false; } auto const label = [&](int const Index) { return Index < 0 ? std::string{STR_C("no drive")} : tool.drives[Index].name; }; diff --git a/application/editormodeswitchtool.cpp b/application/editormodeswitchtool.cpp new file mode 100644 index 00000000..2b2f2714 --- /dev/null +++ b/application/editormodeswitchtool.cpp @@ -0,0 +1,201 @@ +/* +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/. +*/ + +// the panel of the switch tool: what it places, the templates and the options of that only, and how to place it + +#include "stdafx.h" +#include "application/editormode.h" +#include "application/editormodeshared.h" +#include "editor/editorConnections.hpp" +#include "utilities/translation.h" +#include "world/Track.h" + +#include "imgui/imgui.h" +#include +#include + +namespace +{ + +// the next radio button on the same line as the last item, if it fits there +void same_line_if_fits(char const *Label) +{ + auto const &style{ImGui::GetStyle()}; + auto const width{ImGui::GetFrameHeight() + style.ItemInnerSpacing.x + ImGui::CalcTextSize(Label, nullptr, true).x}; + auto const end{ImGui::GetItemRectMax().x + style.ItemSpacing.x + width}; + if (end <= ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x) + ImGui::SameLine(); +} + +// whether the template is one of those the kind places +bool of_kind(editor_track::switch_template const &Template, int const Kind) +{ + switch (Kind) + { + case 1: return Template.double_slip && Template.type != "Skrz"; + case 2: return Template.double_slip && Template.type == "Skrz"; + case 3: return false == Template.double_slip && false == Template.twofold(); + default: return false == Template.double_slip; + } +} + +} // namespace + +std::string editor_mode::switch_template_label(editor_track::switch_template const &Template) +{ + if (Template.source != nullptr) + return STR("Scenery: ") + Template.title() + " (" + std::to_string(Template.count) + "x)"; + return (Template.own ? STR("Own: ") : std::string{"PLK: "}) + Template.title(); +} + +editor_mode::switch_kind editor_mode::current_switch_kind() const +{ + auto const &tool{m_switch}; + if (tool.connection >= 0) + return switch_kind::connection; + if (tool.armed >= 0 && tool.armed < static_cast(tool.templates.size()) && tool.templates[tool.armed].double_slip) + return tool.templates[tool.armed].type == "Skrz" ? switch_kind::crossing : switch_kind::slip; + return switch_kind::single; +} + +bool editor_mode::switch_kind_offered(switch_kind const Kind) const +{ + auto const &templates{m_switch.templates}; + return std::any_of(templates.begin(), templates.end(), [&](editor_track::switch_template const &Template) { return of_kind(Template, static_cast(Kind)); }); +} + +void editor_mode::choose_switch_kind(switch_kind const Kind) +{ + auto &tool{m_switch}; + tool.connection = (Kind == switch_kind::connection ? std::clamp(tool.connection_kind, 0, connection::kind_count - 1) : -1); + // the template chosen stays if it's of the kind, else the first one of the kind + auto const fits = [&](int const Index) { return Index >= 0 && Index < static_cast(tool.templates.size()) && of_kind(tool.templates[Index], static_cast(Kind)); }; + if (fits(tool.armed)) + return; + for (int i = 0; i < static_cast(tool.templates.size()); ++i) + if (fits(i)) + { + tool.last = i; + tool.armed = i; + tool.status.clear(); + return; + } + tool.armed = -1; +} + +void editor_mode::render_switch_ui() +{ + auto &tool{m_switch}; + auto const kind{current_switch_kind()}; + + // what to place, each with the templates and the options of its own only + struct choice + { + switch_kind kind; + char const *label; + char const *tooltip; + }; + choice const choices[] = {{switch_kind::single, "Switch", "A switch put into a straight or a curve"}, + {switch_kind::slip, "Slip switch", "A single or double slip switch at a crossing of two straights"}, + {switch_kind::crossing, "Crossing", "A crossing of two straights"}, + {switch_kind::connection, "Connection", "Switches laid whole between two tracks: a crossover, a double or a scissors crossover, a switch ladder"}}; + ImGui::TextDisabled("%s", STR_C("What to place")); + auto first{true}; + for (auto const &entry : choices) + { + if (entry.kind != switch_kind::single && false == switch_kind_offered(entry.kind)) + continue; + if (false == first) + same_line_if_fits(STR_C(entry.label)); + first = false; + if (ImGui::RadioButton(STR_C(entry.label), kind == entry.kind) && kind != entry.kind) + choose_switch_kind(entry.kind); + item_tooltip(entry.tooltip); + } + if (kind == switch_kind::connection) + { + for (int i = 0; i < connection::kind_count; ++i) + { + if (i > 0) + same_line_if_fits(connection_name(i)); + ImGui::PushID(i); + if (ImGui::RadioButton(connection_name(i), tool.connection == i)) + { + tool.connection = i; + tool.connection_kind = i; + } + ImGui::PopID(); + } + } + + // how to place it with the mouse + char const *how{nullptr}; + switch (kind) + { + case switch_kind::slip: how = "Click at a crossing of two straights; a single slip on the side of the joined tracks"; break; + case switch_kind::crossing: how = "Click at a crossing of two straights"; break; + case switch_kind::connection: + how = tool.connection == static_cast(connection::kind::double_crossover) ? "Press on a straight and drag onto the other track. While dragging: Shift+wheel another kind, Ctrl+wheel R of the aligning curve, Alt+wheel where they meet" : + "Press on a straight and drag onto the other track. While dragging: Shift+wheel another kind, Ctrl+wheel R of the aligning curve"; + break; + default: how = "Click a straight or a curve, then press on it and drag: along for the direction, sideways for the side"; break; + } + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.3f, 1.0f)); + ImGui::TextWrapped("%s", STR_C(how)); + ImGui::PopStyleColor(); + + // the templates of the kind, by their names; the lengths and the rest of the label in the tooltip + ImGui::TextDisabled("%s", STR_C(kind == switch_kind::connection ? "The first switch" : "Template")); + auto const listed{static_cast(std::count_if(tool.templates.begin(), tool.templates.end(), [&](editor_track::switch_template const &Template) { return of_kind(Template, static_cast(kind)); }))}; + ImGui::BeginChild("##switchtemplates", ImVec2(0.0f, std::min(240.0f, (listed + 0.5f) * ImGui::GetTextLineHeightWithSpacing() + 8.0f)), true); + for (int i = 0; i < static_cast(tool.templates.size()); ++i) + { + auto const &entry{tool.templates[i]}; + if (false == of_kind(entry, static_cast(kind))) + continue; + if (ImGui::Selectable((switch_template_label(entry) + "##switch" + std::to_string(i)).c_str(), tool.armed == i)) + { + tool.last = i; + tool.armed = i; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", entry.label.c_str()); + } + ImGui::EndChild(); + if (kind == switch_kind::connection) + render_connection_options(); + + // the name and the drive of the next switch; a crossing has no moving parts + if (kind != switch_kind::crossing) + { + ImGui::PushItemWidth(120.0f); + ImGui::InputTextWithHint(STR_C("Name##switchname"), switch_name_for_new().c_str(), tool.name, sizeof(tool.name)); + ImGui::PopItemWidth(); + item_tooltip("Name of the next switch, its drive events are named after it; empty: the next free z1, z2...; a number at the end goes up with each switch"); + if (render_switch_drive_choice()) + if (auto *track{selected_track()}; track != nullptr && track->eType == tt_Switch && false == track->m_editorremoved) + apply_switch_drive_choice(*track); + } + if (false == tool.status.empty()) + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", tool.status.c_str()); + + if (ImGui::SmallButton(STR_C("Collect from the scenery"))) + { + tool.templates = editor_track::standard_switch_templates(); + auto const found{editor_track::find_switch_templates()}; + tool.templates.insert(tool.templates.end(), found.begin(), found.end()); + tool.collected = true; + arm_switch(); + } + item_tooltip("Looks for the switches of the scenery, to use their shapes as the templates"); + ImGui::SameLine(); + if (ImGui::SmallButton(STR_C("Template editor..."))) + m_switch_designer.open = true; + item_tooltip("Own switch templates: diverging tracks of straights, arcs and transition curves, double switches"); +} diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 3a470141..75f5211a 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -58,13 +58,6 @@ using geometry::level; using geometry::reversed; using geometry::straight_piece; -std::string template_label(editor_track::switch_template const &Template) -{ - if (Template.source != nullptr) - return STR("Scenery: ") + Template.label + " (" + std::to_string(Template.count) + "x)"; - return (Template.own ? STR("Own: ") : std::string{"PLK: "}) + Template.label; -} - double constexpr kParallelFindAngle{15.0}; // deg, neighbours counted as beside the straight double constexpr kParallelSnapAngle{5.0}; // deg, close enough to snap parallel double constexpr kParallelSnapWindow{0.45}; // m from 4.00 / 4.50 / 4.75 m @@ -556,9 +549,9 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve profile_restore(); } -std::array const &editor_mode::track_modes() +std::array const &editor_mode::track_modes() { - static std::array const modes{{ + static std::array const modes{{ {"Select", "Esc", track_tab::path, "LMB on a path selects it: its points, control vectors and parameters"}, {"Lay track", "L", track_tab::lay, "Lays new track through the clicked points, on the ground or from a free end of a track"}, {"Plan", "G", track_tab::straights, "The plan of the line laid: LMB on a straight drags its ends or the middle, on a curve its vertex and radius. C works too"}, @@ -570,7 +563,7 @@ std::array const &editor_mode::track_modes() {"Profile", "P", track_tab::profile, "Vertical profile (grade line) along the line"}, {"Speed", "V", track_tab::speed, "Speed limits of the paths against the speed their geometry allows"}, {"Joints", "J", track_tab::joints, "Ends of the paths which almost meet, steps, kinks, jumps of the cant and of the grade at the joints"}, - {"Infra", "I", track_tab::infra, "Infrastructure along the track, bound to follow its changes"}, + // the infrastructure tool (track_tab::infra) is left out: no button nor key }}; return modes; } @@ -994,8 +987,16 @@ void editor_mode::render_turnout_ui() } else ImGui::Text(STR_C("Length %.3f m, diverging %s, end offset %.3f m"), length, offset > 0.0 ? STR_C("right") : STR_C("left"), std::abs(offset)); - render_track_name(*track); - render_switch_record(*track); + if (auto const known{switch_store::of(*track)}; known.has_value()) + ImGui::TextUnformatted(switch_store::label(*known).c_str()); + // the name, the number, the rail and the kilometre of the switch, with what the scenery keeps of it + if (ImGui::CollapsingHeader(STR_C("Details##switchdetails"))) + { + render_track_name(*track); + render_switch_record(*track); + if (angle > 1e-6) + ImGui::Text(STR_C("Angle 1:%.2f (%.4f deg), R in the entry %.0f m"), 1.0 / std::tan(angle), glm::degrees(angle), diverging.radius); + } auto const drive_ui = [&](TTrack &Switch) { ImGui::PushID(&Switch); if (set.has_value()) @@ -1064,8 +1065,6 @@ void editor_mode::render_turnout_ui() } if (false == m_switch.status.empty()) ImGui::TextWrapped("%s", m_switch.status.c_str()); - if (angle > 1e-6) - ImGui::Text(STR_C("Angle 1:%.2f (%.4f deg), R in the entry %.0f m"), 1.0 / std::tan(angle), glm::degrees(angle), diverging.radius); auto &tool{m_switch}; if (false == tool.collected) @@ -1086,13 +1085,13 @@ void editor_mode::render_turnout_ui() } ImGui::Separator(); ImGui::PushItemWidth(-1.0f); - if (ImGui::BeginCombo("##turnouttemplate", tool.templates[m_turnout_template].label.c_str())) + if (ImGui::BeginCombo("##turnouttemplate", switch_template_label(tool.templates[m_turnout_template]).c_str())) { for (int i = 0; i < static_cast(tool.templates.size()); ++i) { if (false == fits(tool.templates[i])) continue; - auto const label{template_label(tool.templates[i])}; + auto const label{switch_template_label(tool.templates[i])}; if (ImGui::Selectable(label.c_str(), i == m_turnout_template)) m_turnout_template = i; } @@ -3709,7 +3708,13 @@ std::vector editor_mode::track_key_hints(bool const All) hints = {{"LMB", "start, on the ground or at a free end"}}; else if (m_track_tab == track_tab::turnout && (selected_track() == nullptr || selected_track()->eType == tt_Switch)) { - hints = {{"LMB on a track", "the straight or the curve for the switch"}}; + switch (current_switch_kind()) + { + case switch_kind::connection: hints = {{"Press on a straight", "and drag onto the other track"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}}; break; + case switch_kind::slip: + case switch_kind::crossing: hints = {{"LMB", "at a crossing of two straights"}}; break; + default: hints = {{"LMB on a track", "the straight or the curve for the switch"}}; break; + } if (selected != nullptr) { hints.push_back({"Gizmo", "move the selected switch"}); @@ -5784,63 +5789,6 @@ void editor_mode::draw_build_overlay() const draw_free_ends(track, track->iCategoryFlag & 15, std::vector(m_track_drag.begin(), m_track_drag.end())); } -void editor_mode::render_switch_ui() -{ - auto &tool{m_switch}; - auto const list = [&](bool const Slips) { - for (int i = 0; i < static_cast(tool.templates.size()); ++i) - { - auto const &entry{tool.templates[i]}; - if (entry.double_slip != Slips) - continue; - auto label{template_label(entry)}; - label += "##switch" + std::to_string(i); - if (ImGui::Selectable(label.c_str(), tool.armed == i)) - { - tool.last = i; - tool.armed = tool.last; - } - } - }; - ImGui::TextDisabled("%s", STR_C("Switch template")); - ImGui::BeginChild("##switchtemplates", ImVec2(0.0f, 240.0f), true); - list(false); - ImGui::EndChild(); - if (std::any_of(tool.templates.begin(), tool.templates.end(), [](editor_track::switch_template const &Template) { return Template.double_slip; })) - { - if (tool.armed >= 0 && tool.armed < static_cast(tool.templates.size()) && tool.templates[tool.armed].double_slip) - ImGui::SetNextItemOpen(true); - if (ImGui::CollapsingHeader(STR_C("Slip switches and crossings"))) - { - ImGui::TextDisabled("%s", STR_C("Only at a crossing of two straights")); - list(true); - } - } - render_connection_ui(); - ImGui::PushItemWidth(120.0f); - ImGui::InputTextWithHint(STR_C("Name##switchname"), switch_name_for_new().c_str(), tool.name, sizeof(tool.name)); - ImGui::PopItemWidth(); - item_tooltip("Name of the next switch, its drive events are named after it; empty: the next free z1, z2...; a number at the end goes up with each switch"); - if (render_switch_drive_choice()) - if (auto *track{selected_track()}; track != nullptr && track->eType == tt_Switch && false == track->m_editorremoved) - apply_switch_drive_choice(*track); - if (false == tool.status.empty()) - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", tool.status.c_str()); - if (ImGui::SmallButton(STR_C("Collect from the scenery"))) - { - tool.templates = editor_track::standard_switch_templates(); - auto const found{editor_track::find_switch_templates()}; - tool.templates.insert(tool.templates.end(), found.begin(), found.end()); - tool.collected = true; - arm_switch(); - } - item_tooltip("Looks for the switches of the scenery, to use their shapes as the templates"); - ImGui::SameLine(); - if (ImGui::SmallButton(STR_C("Template editor..."))) - m_switch_designer.open = true; - item_tooltip("Own switch templates: diverging tracks of straights, arcs and transition curves, double switches"); -} - std::vector editor_mode::detour_outline() const { auto const &state{m_straights}; diff --git a/application/editormodeviewport.cpp b/application/editormodeviewport.cpp index bfff5613..6d371e8e 100644 --- a/application/editormodeviewport.cpp +++ b/application/editormodeviewport.cpp @@ -127,7 +127,6 @@ void editor_mode::render_viewport_overlays() // a view squeezed between the docks has no room for them if (width < 8.0f * button_side() || height < 6.0f * button_side()) return; - render_viewport_tools(); render_viewport_gizmo(); render_view_axes(); } @@ -166,30 +165,31 @@ void editor_mode::work_area_tools(std::vector &Buttons) auto const chosen{(m_track_tab == Tab || (Tab == track_tab::straights && track_plan_tab())) && false == (Tab == track_tab::lineside && vehicle)}; Buttons.push_back({Picture, STR_C(found->label), found->key, STR_C(found->tooltip), chosen, [this, Tab, chosen]() { show_track_tab(chosen ? track_tab::path : Tab); }, Group}); }; + // the tools of laying track first, the others behind More, shown while one of them is in use add(track_tab::path, icon::select, false); add(track_tab::lay, icon::lay_track, false); add(track_tab::straights, icon::curve, false); add(track_tab::turnout, icon::turnout, false); add(track_tab::parallel, icon::parallel, false); - add(track_tab::turntable, icon::turntable, false); - add(track_tab::signals, icon::signal, true); - add(track_tab::lineside, icon::objects, false); - Buttons.push_back({icon::vehicle, STR_C("Trainset"), nullptr, STR_C("A trainset on the selected track: written to the scenery on save, and to drive at once"), vehicle, [this, vehicle]() { - show_track_tab(vehicle ? track_tab::path : track_tab::lineside); - m_vehicle.expand = false == vehicle; - }}); - auto const advanced{m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra || m_gauge.open}; + auto const advanced{m_track_tab == track_tab::turntable || m_track_tab == track_tab::signals || m_track_tab == track_tab::lineside || m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || + m_track_tab == track_tab::joints || m_gauge.open}; auto const more{EditorSettings.track_tools_more()}; - Buttons.push_back({icon::more, more ? STR_C("Less") : STR_C("More"), nullptr, STR_C("The checks of a line laid: profile, speed, joints, infrastructure, structure gauge"), more, [more]() { + Buttons.push_back({icon::more, more ? STR_C("Less") : STR_C("More"), nullptr, STR_C("Turntables, signals, objects along the track, trainsets and the checks of a line laid: profile, speed, joints, structure gauge"), more, [more]() { EditorSettings.track_tools_more(false == more); EditorSettings.save(); }, true}); if (false == more && false == advanced) break; + add(track_tab::turntable, icon::turntable, true); + add(track_tab::signals, icon::signal, false); + add(track_tab::lineside, icon::objects, false); + Buttons.push_back({icon::vehicle, STR_C("Trainset"), nullptr, STR_C("A trainset on the selected track: written to the scenery on save, and to drive at once"), vehicle, [this, vehicle]() { + show_track_tab(vehicle ? track_tab::path : track_tab::lineside); + m_vehicle.expand = false == vehicle; + }}); add(track_tab::profile, icon::profile, true); add(track_tab::speed, icon::speed, false); add(track_tab::joints, icon::joints, false); - add(track_tab::infra, icon::infra, false); Buttons.push_back({icon::gauge, STR_C("Structure gauge"), nullptr, STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)"), m_gauge.open, [this]() { m_gauge.open = !m_gauge.open; }}); break; @@ -238,48 +238,6 @@ void editor_mode::work_area_tools(std::vector &Buttons) } } -void editor_mode::render_viewport_tools() -{ - // the tools of the field of work which have icons, as in the toolbar - std::vector buttons; - buttons.reserve(24); - work_area_tools(buttons); - std::erase_if(buttons, [](tool_button const &Button) { return Button.picture == icon::count; }); - if (buttons.empty()) - return; - - // down the left edge, in more columns if the view is too low for them; the bottom is left to the hints of the tools - auto const side{button_side()}; - auto const gap{2.0f * scale()}; - auto const groupgap{8.0f * scale()}; - auto const margin{8.0f * scale()}; - ImVec2 const origin{editor_ui::view_min().x + margin, editor_ui::view_min().y + margin}; - auto const bottom{std::max(origin.y + 4.0f * side, editor_ui::view_max().y - 130.0f * scale())}; - std::vector places; - places.reserve(buttons.size()); - ImVec2 place{origin}; - for (auto const &button : buttons) - { - if (button.group && place.y > origin.y) - place.y += groupgap - gap; - if (place.y > origin.y && place.y + side > bottom) - place = ImVec2(place.x + side + gap, origin.y); - places.push_back(place); - place.y += side + gap; - } - ImVec2 extent{0.0f, 0.0f}; - for (auto const &at : places) - extent = ImVec2(std::max(extent.x, at.x + side - origin.x), std::max(extent.y, at.y + side - origin.y)); - auto const pad{3.0f * scale()}; - if (begin_overlay("##viewporttools", ImVec2(origin.x - pad, origin.y - pad), ImVec2(extent.x + 2.0f * pad, extent.y + 2.0f * pad))) - { - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(origin.x - pad, origin.y - pad), ImVec2(origin.x + extent.x + pad, origin.y + extent.y + pad), IM_COL32(16, 18, 21, 140), 6.0f * scale()); - for (std::size_t index = 0; index < buttons.size(); ++index) - icon_button(buttons[index], places[index], static_cast(index)); - } - ImGui::End(); -} - void editor_mode::render_toolbar_caption(char const *Text) { // as wide in both rows, so the buttons of the rows start one under the other. the rows are drawn with no spacing of items diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index 7152b404..0d0d4301 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -762,7 +762,7 @@ void editor_ui::render_tool_options() } else render_mode_options(current); - if (ImGui::CollapsingHeader(STR_C("Gizmo"), ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader(STR_C("Gizmo"))) { if (m_gizmooptions) m_gizmooptions(); diff --git a/application/editoruilayer.h b/application/editoruilayer.h index 020fd4be..f405bad5 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -58,6 +58,8 @@ class editor_ui : public ui_layer void set_track(bool const Track) { m_track = Track; + // the models of the node bank aren't laid with the track tools + m_nodebankpanel.suppressed = Track; } // draws menus of the editor mode in the menu bar, past the ones of the user interface void set_menu_options(std::function Renderer) diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index 79808c2d..bae698a9 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -915,7 +915,7 @@ void nodebank_panel::render_set_cards(int const Setid) void nodebank_panel::render() { - if (false == is_open) + if (false == is_open || suppressed) { return; } diff --git a/application/editoruipanels.h b/application/editoruipanels.h index 6ebf21aa..4b00b130 100644 --- a/application/editoruipanels.h +++ b/application/editoruipanels.h @@ -133,6 +133,7 @@ class nodebank_panel : public ui_panel TRACK }; edit_mode mode = MODIFY; + bool suppressed{false}; // the field of work has no use for it: not drawn, though it stays open for the others nodebank_panel(std::string const &Name, bool const Isopen); void nodebank_reload(); diff --git a/editor/editorSettings.cpp b/editor/editorSettings.cpp index 909e4465..e92679cf 100644 --- a/editor/editorSettings.cpp +++ b/editor/editorSettings.cpp @@ -104,7 +104,7 @@ bool editorSettings::load() parse(value, m_palette); else if (key == "first_steps") m_first_steps = (value == "1"); - else if (key == "track_tools_more") + else if (key == "track_tools_all") m_track_tools_more = (value == "1"); else if (key.rfind(origin_prefix, 0) == 0) { @@ -150,7 +150,7 @@ bool editorSettings::save() stream << "nodebank_card " << m_nodebank_card << "\n"; stream << "colours " << m_palette << "\n"; stream << "first_steps " << (m_first_steps ? 1 : 0) << "\n"; - stream << "track_tools_more " << (m_track_tools_more ? 1 : 0) << "\n"; + stream << "track_tools_all " << (m_track_tools_more ? 1 : 0) << "\n"; stream << std::fixed << std::setprecision(3); for (auto const &origin : m_orthophoto_origins) stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n"; diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 66d62a2e..7bafe19e 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -34,6 +34,7 @@ http: #include #include #include +#include #include #include #include @@ -3417,3 +3418,32 @@ void editor_track::rebuild_geometry(TTrack &Track) if (crossing != &Track && false == crossing->m_editorremoved) rebuild(*crossing); } + +std::string editor_track::switch_template::title() const +{ + std::istringstream words(label); + std::vector kept; + std::vector all; + for (std::string word; words >> word;) + all.push_back(word); + for (std::size_t i = 0; i < all.size(); ++i) + { + if (all[i].front() == '(') + break; + auto const number = [](std::string const &Word) { + char *end{nullptr}; + std::strtod(Word.c_str(), &end); + return end != Word.c_str() && *end == '\0'; + }; + if ((all[i] == "a" || all[i] == "b") && i + 1 < all.size() && number(all[i + 1])) + { + ++i; + continue; + } + kept.push_back(all[i]); + } + std::string result; + for (auto const &word : kept) + result += (result.empty() ? "" : " ") + word; + return result.empty() ? label : result; +} diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 584b00bf..cebde773 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -424,6 +424,9 @@ class editor_track int design{-1}; // in the list of the designs the template was built from double middle{0.0}; // a double slip with the blades outside of the diamond: the radius in the middle of its slip curves, below 0 a straight there bool twofold() const { return stagger > 0.0; } + // the name of the template for a list: the label without the lengths a and b and the hint in brackets, which + // the full label gives + std::string title() const; }; // the diverging curve of a slip turning by Angle: arcs of Radius at both ends, each Ends long, and the arc of Middle, // Between long, between them; a straight when Middle is 0. With blades cutting into the stock rails the curve starts and ends diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 4caf9766..7384ba55 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -6928,3 +6928,66 @@ msgstr "Skóra podrozjazdnic" msgid "Replaceable skin of the timber model, empty: the one of the model" msgstr "Wymienna skóra modelu podrozjazdnic, puste: skóra samego modelu" + +msgid "What to place" +msgstr "Co stawiasz" + +msgid "Slip switch" +msgstr "Rozjazd krzyżowy" + +msgid "Crossing" +msgstr "Skrzyżowanie" + +msgid "Connection" +msgstr "Połączenie" + +msgid "A switch put into a straight or a curve" +msgstr "Rozjazd wstawiany w prostą albo łuk" + +msgid "A single or double slip switch at a crossing of two straights" +msgstr "Rozjazd krzyżowy pojedynczy albo podwójny, na skrzyżowaniu dwóch prostych" + +msgid "A crossing of two straights" +msgstr "Skrzyżowanie dwóch prostych" + +msgid "Switches laid whole between two tracks: a crossover, a double or a scissors crossover, a switch ladder" +msgstr "Rozjazdy układane w całości między dwoma torami: przejście, przejście podwójne, nożycowe, drabinka rozjazdów" + +msgid "Click at a crossing of two straights; a single slip on the side of the joined tracks" +msgstr "Kliknij skrzyżowanie dwóch prostych; rozjazd pojedynczy po stronie łączonych torów" + +msgid "Click at a crossing of two straights" +msgstr "Kliknij skrzyżowanie dwóch prostych" + +msgid "Press on a straight and drag onto the other track. While dragging: Shift+wheel another kind, Ctrl+wheel R of the aligning curve, Alt+wheel where they meet" +msgstr "Wciśnij LPM na prostej i przeciągnij na drugi tor. W trakcie: Shift+kółko inny rodzaj, Ctrl+kółko R łuku dopasowującego, Alt+kółko miejsce spotkania" + +msgid "Press on a straight and drag onto the other track. While dragging: Shift+wheel another kind, Ctrl+wheel R of the aligning curve" +msgstr "Wciśnij LPM na prostej i przeciągnij na drugi tor. W trakcie: Shift+kółko inny rodzaj, Ctrl+kółko R łuku dopasowującego" + +msgid "Click a straight or a curve, then press on it and drag: along for the direction, sideways for the side" +msgstr "Kliknij prostą albo łuk, potem wciśnij na nim i przeciągnij: wzdłuż wybiera kierunek, w bok stronę toru zwrotnego" + +msgid "The first switch" +msgstr "Pierwszy rozjazd" + +msgid "Template" +msgstr "Wzorzec" + +msgid "Details##switchdetails" +msgstr "Szczegóły##switchdetails" + +msgid "No switch drive models in the scenery data" +msgstr "Brak modeli napędów zwrotnic w danych scenerii" + +msgid "Turntables, signals, objects along the track, trainsets and the checks of a line laid: profile, speed, joints, structure gauge" +msgstr "Obrotnice, sygnalizatory, obiekty wzdłuż toru, składy i sprawdzanie ułożonej linii: profil, prędkość, styki, skrajnia" + +msgid "Press on a straight" +msgstr "Wciśnij na prostej" + +msgid "and drag onto the other track" +msgstr "i przeciągnij na drugi tor" + +msgid "at a crossing of two straights" +msgstr "na skrzyżowaniu dwóch prostych"