diff --git a/application/editormode.cpp b/application/editormode.cpp index 73493be0..de441f6a 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -488,6 +488,24 @@ editor_mode::editor_mode() { ui()->set_fill_options([this]() { render_area_fill(); }); ui()->set_gizmo_options([this]() { render_gizmo_options(); }); ui()->set_toolbar_options([this]() { render_toolbar(); }); + ui()->set_track_options([this]() { render_track_tool_options(); }, [this]() { render_track_selection(); }); + ui()->set_workspace_options(editor_ui::workspace::roads, [this]() { render_road_tool_options(); }, [this]() { render_road_selection(); }); + ui()->set_workspace_options(editor_ui::workspace::terrain, [this]() { render_terrain_tool_options(); }); + // a node picked in the scene window is selected as if clicked in the view; a double click flies the camera to it + ui()->set_scene_select([this](scene::basic_node *Node, bool const Focus) { + if (Focus) + start_focus(Node); + std::string reason; + if (Node->from_template() || false == scene::Layers.editable(Node, &reason)) + { + ui()->set_status("\"" + (Node->name().empty() ? std::string{"(unnamed node)"} : Node->name()) + "\" can't be edited: " + reason, true); + return; + } + m_track_window_open = false; + ui()->set_mode(nodebank_panel::MODIFY); + m_node = Node; + ui()->set_node(m_node); + }); ui()->set_array_options([this]() { render_array(); }); ui()->set_bend_options([this]() { render_bend(); }); ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); }); @@ -1398,7 +1416,8 @@ bool editor_mode::update() // roads: trajectories of the lanes, and the tools of the road window update_road_tool(); draw_road_overlay(); - render_road_window(); + // the roads and the terrain are fields of work, their tools are drawn in the tool options window in place of the edit mode ones + ui()->set_workspace(m_terrain_open ? editor_ui::workspace::terrain : m_roadtool.window ? editor_ui::workspace::roads : editor_ui::workspace::none); // --- array: the one made last is kept in line with its settings --- update_array(); @@ -1407,7 +1426,6 @@ bool editor_mode::update() if (ui()->mode() == nodebank_panel::FILL) draw_area_fill_outline(); - render_orthophoto_window(); render_new_scenery_popup(); render_open_scenery_popup(); @@ -1446,13 +1464,6 @@ void editor_mode::render_settings() ImGui::Checkbox(STR_C("Transform gizmo (ImGuizmo)"), &m_gizmo_enabled); ImGui::EndTabItem(); } - bool const terrainwanted = m_terrain_tab_wanted; - m_terrain_tab_wanted = false; - if (ImGui::BeginTabItem(STR_C("Terrain"), nullptr, terrainwanted ? ImGuiTabItemFlags_SetSelected : 0)) - { - render_terrain_ui(); - ImGui::EndTabItem(); - } ImGui::EndTabBar(); } @@ -2810,7 +2821,14 @@ void editor_mode::render_map_menu() m_orthophoto.enabled(false == enabled); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery")); - ImGui::MenuItem(STR_C("Orthophoto settings..."), nullptr, &m_orthophoto_window); + // the settings are a part of the terrain tools + if (ImGui::MenuItem(STR_C("Orthophoto settings..."), nullptr, m_terrain_open)) + { + m_track_window_open = false; + m_roadtool.window = false; + terrain_workspace(true); + m_orthophoto_expand = true; + } ImGui::EndMenu(); } @@ -2960,14 +2978,27 @@ void editor_mode::render_open_scenery_popup() ImGui::EndPopup(); } -void editor_mode::render_orthophoto_window() +void editor_mode::terrain_workspace(bool const Open) { - if (false == m_orthophoto_window) - return; - ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver); - if (ImGui::Begin((std::string(STR_C("Orthophoto##window")) + "###orthophoto").c_str(), &m_orthophoto_window)) + m_terrain_open = Open; + if (false == Open) + m_terrain_sculpt = m_chunk_edit = false; +} + +void editor_mode::render_terrain_tool_options() +{ + render_terrain_ui(); + bool const expand{m_orthophoto_expand}; + m_orthophoto_expand = false; + if (expand) + ImGui::SetNextItemOpen(true); + if (ImGui::CollapsingHeader(STR_C("Orthophoto"))) + { + // opened from the map menu: brought into sight, the terrain tools above them take the most of the window + if (expand) + ImGui::SetScrollHereY(0.0f); render_orthophoto_ui(); - ImGui::End(); + } } void editor_mode::render_object_menu() @@ -3629,27 +3660,71 @@ void editor_mode::render_toolbar() // edit modes of the node bank, each of them takes the mouse over from the track window std::pair const modes[] = { {STR_C("Select"), nodebank_panel::MODIFY}, {STR_C("Insert"), nodebank_panel::ADD}, {STR_C("Brush"), nodebank_panel::BRUSH}, {STR_C("Area fill"), nodebank_panel::FILL}, {STR_C("Copy to bank"), nodebank_panel::COPY}}; + // the fields of work: an edit mode, or the tracks, signals, vehicle, roads or terrain, one at a time + bool const workspace{m_roadtool.window || m_terrain_open}; for (auto const &mode : modes) { - if (ImGui::MenuItem(mode.first, nullptr, ui()->mode() == mode.second)) + if (ImGui::MenuItem(mode.first, nullptr, false == workspace && ui()->mode() == mode.second)) { m_track_window_open = false; + m_roadtool.window = false; + terrain_workspace(false); ui()->set_mode(mode.second); } } ImGui::Separator(); - if (ImGui::MenuItem(STR_C("Tracks"), nullptr, m_track_window_open)) + bool const tracks{m_track_window_open && false == m_terrain_open}; + bool const vehicle{tracks && m_track_tab == track_tab::lineside && m_vehicle.open}; + if (ImGui::MenuItem(STR_C("Tracks"), nullptr, tracks && m_track_tab != track_tab::signals && false == vehicle)) { - if (m_track_window_open) + if (tracks && m_track_tab != track_tab::signals && false == vehicle) m_track_window_open = false; else - show_track_tab(m_track_tab); + { + terrain_workspace(false); + show_track_tab(m_track_tab == track_tab::signals || vehicle ? track_tab::path : m_track_tab); + } } - if (ImGui::MenuItem("Roads", nullptr, m_roadtool.window)) + // the signals are a tool of the track mode, but a field of work of their own + if (ImGui::MenuItem(STR_C("Signals"), nullptr, tracks && m_track_tab == track_tab::signals)) { - m_roadtool.window = !m_roadtool.window; - if (m_roadtool.window) + if (tracks && m_track_tab == track_tab::signals) m_track_window_open = false; + else + { + terrain_workspace(false); + show_track_tab(track_tab::signals); + } + } + // so is the vehicle to drive, put on a track by the objects along the track + if (ImGui::MenuItem(STR_C("Vehicle"), nullptr, vehicle)) + { + if (vehicle) + m_track_window_open = false; + else + { + terrain_workspace(false); + show_track_tab(track_tab::lineside); + m_vehicle.expand = true; + } + } + if (ImGui::MenuItem("Roads", nullptr, m_roadtool.window && false == m_terrain_open)) + { + m_roadtool.window = !(m_roadtool.window && false == m_terrain_open); + if (m_roadtool.window) + { + m_track_window_open = false; + terrain_workspace(false); + } + } + if (ImGui::MenuItem(STR_C("Terrain"), nullptr, m_terrain_open)) + { + terrain_workspace(false == m_terrain_open); + if (m_terrain_open) + { + m_track_window_open = false; + m_roadtool.window = false; + } } ImGui::Separator(); if (m_gizmo_enabled) diff --git a/application/editormode.h b/application/editormode.h index 9e14f293..e8d99cae 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -231,6 +231,10 @@ class editor_mode : public application_mode, private editor_track::observer // editable terrain patches created in the editor void render_terrain_ui(); + // the terrain as a field of work of its own: the terrain tools and the orthophoto in the tool options window + void render_terrain_tool_options(); + // opens or leaves the terrain; leaving it puts the sculpting and the chunk editing away, so the mouse doesn't stay with them + void terrain_workspace(bool const Open); // creates a large terrain as a grid of adjacent chunks (each its own editable patch) void create_chunked_terrain(); // manual grid-aligned chunks: add/remove single chunks for fine control @@ -289,7 +293,7 @@ class editor_mode : public application_mode, private editor_track::observer std::map, std::unique_ptr> m_grid_chunks; bool m_terrain_sculpt{false}; // when true, LMB sculpts terrain instead of picking bool m_terrain_brush_smooth{false}; // the brush evens the ground out instead of raising or lowering it - bool m_terrain_tab_wanted{false}; // the settings window brings the terrain tab forward the next time it's drawn + bool m_terrain_open{false}; // the terrain is the field of work, chosen in the toolbar bool m_chunk_edit{false}; // when true, LMB adds/removes whole chunks int m_terrain_cells{32}; // grid resolution (quads per side) int m_terrain_chunks{4}; // chunks per side for a chunked terrain @@ -311,7 +315,6 @@ class editor_mode : public application_mode, private editor_track::observer // geoportal orthophoto layer drawn under the other viewport overlays void render_orthophoto_ui(); void render_map_menu(); - void render_orthophoto_window(); bool m_newscenery_asked{false}; void render_new_scenery_popup(); bool restart_for_new_scenery(); @@ -326,7 +329,7 @@ class editor_mode : public application_mode, private editor_track::observer std::vector m_openscenery_list; std::string m_openscenery_choice; void render_open_scenery_popup(); - bool m_orthophoto_window{false}; + bool m_orthophoto_expand{false}; // the orthophoto settings of the terrain tools are opened the next time they're drawn // origin of the scenery in PUWG 1992 the scenery file gives in its //$g line, if it does bool m_georeference_read{false}; bool m_georeference{false}; @@ -834,7 +837,10 @@ class editor_mode : public application_mode, private editor_track::observer bool m_track_window_open{false}; // the inspector is pinned to the right edge of the screen, the profile strip to the bottom one, both resized from the inner edge void render_track_inspector(); - void render_track_modes(TTrack *Track); + // the track tools in the tool options window, and the selected path in the inspector + void render_track_tool_options(); + void render_track_selection(); + void render_track_modes(); void show_track_tab(track_tab const Tab); bool track_analysis_tab() const { return m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra; } bool track_shortcut(int const Key); @@ -1746,7 +1752,8 @@ class editor_mode : public application_mode, private editor_track::observer char islandtext[128]{"none"}; }; void render_road_menu(); - void render_road_window(); + void render_road_tool_options(); + void render_road_selection(); bool render_road_layout(road_node::state &State); bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value); void render_junction_layout(junction_node &Junction); diff --git a/application/editormodeprofile.cpp b/application/editormodeprofile.cpp index 974482cb..9c23bd56 100644 --- a/application/editormodeprofile.cpp +++ b/application/editormodeprofile.cpp @@ -852,13 +852,11 @@ void editor_mode::render_profile_earthworks() } if (false == active_terrains().empty()) { + // the terrain tools take the tool options window over, the profile stays open under them if (ImGui::Button(STR_C("Sculpting..."))) - { - set_settings_open(true); - m_terrain_tab_wanted = true; - } + terrain_workspace(true); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("Opens the terrain tab of the editor settings: raising, lowering and smoothing with a brush")); + ImGui::SetTooltip("%s", STR_C("Opens the terrain tools: raising, lowering and smoothing with a brush")); } ImGui::TreePop(); } diff --git a/application/editormoderoad.cpp b/application/editormoderoad.cpp index c7e7051c..1a57ed30 100644 --- a/application/editormoderoad.cpp +++ b/application/editormoderoad.cpp @@ -221,7 +221,10 @@ void editor_mode::render_road_menu() if (ImGui::BeginMenu("Roads")) { if (ImGui::MenuItem("Road editor", nullptr, &m_roadtool.window) && m_roadtool.window) + { m_track_window_open = false; + terrain_workspace(false); + } ImGui::MenuItem("Show lanes", nullptr, &m_roadtool.lanes); ImGui::EndMenu(); } @@ -2258,17 +2261,10 @@ bool editor_mode::render_road_layout(road_node::state &State) return changed; } -void editor_mode::render_road_window() +// the road tools and what the next pieces get, in the tool options window; the selection is drawn in the inspector by render_road_selection() +void editor_mode::render_road_tool_options() { auto &tool{m_roadtool}; - if (false == tool.window) - return; - ImGui::SetNextWindowSize(ImVec2(440.0f, 620.0f), ImGuiCond_FirstUseEver); - if (false == ImGui::Begin("Roads###roadeditor", &tool.window)) - { - ImGui::End(); - return; - } std::string reason; if (false == editor_road::available(&reason)) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", reason.c_str()); @@ -2296,11 +2292,6 @@ void editor_mode::render_road_window() ImGui::SameLine(); ImGui::Checkbox("Show lanes", &tool.lanes); - // typed values are taken when confirmed, so nothing is made anew with each digit - auto const number = [](char const *Label, float &Value, float const Step, char const *Format) { - ImGui::SetNextItemWidth(120.0f); - return ImGui::InputFloat(Label, &Value, Step, Step * 4.0f, Format, ImGuiInputTextFlags_EnterReturnsTrue); - }; if (tool.tool == 1) { if (ImGui::Checkbox("Roundabout", &tool.ring)) @@ -2378,6 +2369,26 @@ void editor_mode::render_road_window() placing.kind = point_kind(tool.placekind); render_roadpoint_layout(placing, nullptr); } + else + ImGui::TextDisabled("The selected road is set up in the inspector"); + if (false == tool.status.empty()) + { + ImGui::Separator(); + ImGui::TextWrapped("%s", tool.status.c_str()); + } +} + +// what the select tool of the roads picked, in the inspector +void editor_mode::render_road_selection() +{ + auto &tool{m_roadtool}; + // typed values are taken when confirmed, so nothing is made anew with each digit + auto const number = [](char const *Label, float &Value, float const Step, char const *Format) { + ImGui::SetNextItemWidth(120.0f); + return ImGui::InputFloat(Label, &Value, Step, Step * 4.0f, Format, ImGuiInputTextFlags_EnterReturnsTrue); + }; + if (tool.tool != 0) + ImGui::TextDisabled("The select tool shows the road picked in the view here"); else if (tool.marker != nullptr) { auto &point{*tool.marker}; @@ -2490,12 +2501,6 @@ void editor_mode::render_road_window() ImGui::TextDisabled("LMB: select a road piece, a junction, a point where pieces meet,"); ImGui::TextDisabled("a level crossing or a traffic point"); } - if (false == tool.status.empty()) - { - ImGui::Separator(); - ImGui::TextWrapped("%s", tool.status.c_str()); - } - ImGui::End(); } void editor_mode::draw_road_overlay() const diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 8d9f8a72..3fcd91db 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -745,104 +745,119 @@ void editor_mode::render_track_inspector() return; } - auto *track{selected_track()}; m_route_tab = m_track_tab != track_tab::path; - ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - 460.0f, ImGui::GetFrameHeight() + 8.0f), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(440.0f, std::min(720.0f, io.DisplaySize.y - 80.0f)), ImGuiCond_FirstUseEver); - std::string title{STR_C("Tracks")}; - if (track != nullptr && m_track_tab != track_tab::lay && false == track_analysis_tab()) - title += ": " + (track->name().empty() ? std::string{"(noname)"} : track->name()); - title += "###trackinspector"; - if (ImGui::Begin(title.c_str(), &m_track_window_open, ImGuiWindowFlags_NoCollapse)) + // the tools of the mode are drawn in the tool options window and the selected path in the inspector, see + // render_track_tool_options() and render_track_selection(); the checks list what they find in a window of their own + if (m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra) { - render_track_modes(track); - ImGui::BeginChild("##trackmode"); - switch (m_track_tab) + auto const *title{m_track_tab == track_tab::speed ? STR_C("Speed check") : m_track_tab == track_tab::joints ? STR_C("Joints") : STR_C("Infrastructure")}; + ImGui::SetNextWindowSize(ImVec2(600.0f, std::min(320.0f, io.DisplaySize.y - 80.0f)), ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string{title} + "###trackanalysis").c_str(), nullptr, ImGuiWindowFlags_NoCollapse)) { - case track_tab::lay: - render_lay_ui(); - break; - case track_tab::turnout: - if (track != nullptr && track->eType == tt_Switch) - render_turnout_ui(); - else - render_switch_ui(); - break; - case track_tab::straights: - render_straight_ui(); - if (ImGui::CollapsingHeader(STR_C("Straights in the scenery"))) - render_straights_ui(); - break; - case track_tab::route: - render_route_ui(); - break; - case track_tab::profile: render_profile_body(); break; - case track_tab::speed: render_speed_body(); break; - case track_tab::joints: render_joints_body(); break; - case track_tab::lineside: render_lineside_ui(); break; - case track_tab::signals: render_signal_ui(); break; - case track_tab::infra: render_infra_body(); break; - case track_tab::turntable: render_turntable_ui(); break; - default: - render_track_set_ui(); - render_track_spread_ui(); - if (track != nullptr) - render_path_ui(); - else - ImGui::TextDisabled("%s", STR_C("LMB on a path in the view selects it")); - break; + switch (m_track_tab) + { + case track_tab::speed: render_speed_body(); break; + case track_tab::joints: render_joints_body(); break; + default: render_infra_body(); break; + } } - ImGui::EndChild(); + ImGui::End(); } - ImGui::End(); - if (false == m_track_window_open) - ui()->set_track(false); if (m_track_tab == track_tab::profile && m_track_window_open && false == m_profile.route.spans.empty()) render_profile_strip(); } -void editor_mode::render_track_modes(TTrack *Track) +void editor_mode::render_track_tool_options() { - auto const &modes{track_modes()}; - auto const row = [&](char const *Label, int const From, int const To) { - ImGui::AlignTextToFramePadding(); - ImGui::TextDisabled("%s", STR_C(Label)); - for (int i = From; i < To; ++i) - { - auto const &mode{modes[i]}; - auto const label{std::string{STR_C(mode.label)} + "###trackmode" + std::to_string(i)}; - // the buttons which don't fit go on to the next line, under the first one - auto const width{ImGui::GetFrameHeight() + ImGui::GetStyle().ItemInnerSpacing.x + ImGui::CalcTextSize(STR_C(mode.label)).x}; - if (i == From) - ImGui::SameLine(72.0f); - else if (ImGui::GetItemRectMax().x + ImGui::GetStyle().ItemSpacing.x + width > ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x) - ImGui::SetCursorPosX(72.0f); - else - ImGui::SameLine(); - if (ImGui::RadioButton(label.c_str(), m_track_tab == mode.tab)) - show_track_tab(mode.tab); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s [%s]", STR_C(mode.tooltip), mode.key); - } - }; - row("Edit", 0, 7); - row("Analysis", 7, static_cast(modes.size())); - ImGui::SameLine(); - ImGui::Checkbox(STR_C("Structure gauge"), &m_gauge.open); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)")); + auto *track{selected_track()}; + render_track_modes(); render_track_search(); ImGui::Spacing(); render_track_guide(); - if (Track != nullptr && m_track_tab != track_tab::lay && false == track_analysis_tab()) + switch (m_track_tab) { - char const *type = Track->eType == tt_Normal ? "normal" : Track->eType == tt_Switch ? "switch" : Track->eType == tt_Cross ? "cross" : Track->eType == tt_Table ? "turntable" : Track->eType == tt_Tributary ? "tributary" : "unknown"; - ImGui::TextDisabled("%s, %.2f m", STR_C(type), Track->Length()); + case track_tab::lay: + render_lay_ui(); + break; + case track_tab::turnout: + if (track != nullptr && track->eType == tt_Switch) + render_turnout_ui(); + else + render_switch_ui(); + break; + case track_tab::straights: + render_straight_ui(); + if (ImGui::CollapsingHeader(STR_C("Straights in the scenery"))) + render_straights_ui(); + break; + case track_tab::route: + render_route_ui(); + break; + case track_tab::profile: render_profile_body(); break; + case track_tab::speed: + case track_tab::joints: + case track_tab::infra: + ImGui::TextDisabled("%s", STR_C("What the check finds is listed in its window")); + break; + case track_tab::lineside: render_lineside_ui(); break; + case track_tab::signals: render_signal_ui(); break; + case track_tab::turntable: render_turntable_ui(); break; + default: + render_track_set_ui(); + render_track_spread_ui(); + break; } } +void editor_mode::render_track_selection() +{ + auto *track{selected_track()}; + if (track == nullptr) + { + ImGui::TextDisabled("%s", STR_C("LMB on a path in the view selects it")); + return; + } + char const *type = track->eType == tt_Normal ? "normal" : track->eType == tt_Switch ? "switch" : track->eType == tt_Cross ? "cross" : track->eType == tt_Table ? "turntable" : track->eType == tt_Tributary ? "tributary" : "unknown"; + ImGui::TextUnformatted((track->name().empty() ? std::string{"(noname)"} : track->name()).c_str()); + ImGui::TextDisabled("%s, %.2f m", STR_C(type), track->Length()); + // the parameters of the path belong to the select tool, the other tools show the path they work with + if (m_track_tab == track_tab::path) + render_path_ui(); +} + +void editor_mode::render_track_modes() +{ + auto const &modes{track_modes()}; + // the tools in groups: building the track, what goes by it, the checks + auto const group = [&](char const *Label, std::initializer_list const Tabs) { + ImGui::SeparatorText(STR_C(Label)); + if (false == ImGui::BeginTable(Label, 3, ImGuiTableFlags_SizingStretchSame)) + return; + for (auto const tab : Tabs) + { + auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == tab; })}; + if (found == modes.end()) + continue; + ImGui::TableNextColumn(); + auto const index{static_cast(found - modes.begin())}; + auto const label{std::string{STR_C(found->label)} + "###trackmode" + std::to_string(index)}; + if (ImGui::Selectable(label.c_str(), m_track_tab == tab)) + show_track_tab(tab); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s [%s]", STR_C(found->tooltip), found->key); + } + ImGui::EndTable(); + }; + group("Build", {track_tab::path, track_tab::lay, track_tab::turnout, track_tab::straights, track_tab::route, track_tab::turntable}); + group("By the track", {track_tab::signals, track_tab::lineside}); + group("Checks", {track_tab::profile, track_tab::speed, track_tab::joints, track_tab::infra}); + ImGui::Checkbox(STR_C("Structure gauge"), &m_gauge.open); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)")); +} + void editor_mode::render_track_guide() { auto const hints{track_key_hints(false)}; diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index dc38b282..bc7fc757 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/utilities.h" #include "scene/scenenode.h" #include "scene/scenelayers.h" +#include "scene/scene.h" #include "rendering/renderer.h" #include "utilities/translation.h" #include "imgui/imgui_internal.h" @@ -31,8 +32,6 @@ editor_ui::editor_ui() add_external_panel(&m_layerspanel); add_external_panel(&m_includespanel); - m_nodebankpanel.mode_options = [this](nodebank_panel::edit_mode const Mode) { render_mode_options(Mode); }; - m_nodebankpanel.header_sections = [this]() { render_header_sections(); }; // the menu is a part of the layout of the editor, not a pop-up as in the driver mode m_menu_always = true; } @@ -67,22 +66,26 @@ void editor_ui::build_default_layout(unsigned int const Dockspace) ImGui::DockBuilderAddNode(Dockspace, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_PassthruCentralNode); ImGui::DockBuilderSetNodePos(Dockspace, viewport->WorkPos); ImGui::DockBuilderSetNodeSize(Dockspace, viewport->WorkSize); - // widths in pixels the current windows need, as parts of the room there is + // widths in pixels the windows need, as parts of the room there is auto const width{std::max(viewport->WorkSize.x, 1.0f)}; auto const height{std::max(viewport->WorkSize.y, 1.0f)}; - auto const left_width{std::min(440.0f * Global.ui_scale, width * 0.3f)}; + auto const left_width{std::min(400.0f * Global.ui_scale, width * 0.3f)}; auto const right_width{std::min(460.0f * Global.ui_scale, width * 0.3f)}; - auto const bottom_height{std::min(300.0f * Global.ui_scale, height * 0.35f)}; + auto const bottom_height{std::min(280.0f * Global.ui_scale, height * 0.35f)}; ImGuiID center{Dockspace}; - auto const left{ImGui::DockBuilderSplitNode(center, ImGuiDir_Left, left_width / width, nullptr, ¢er)}; - auto const right{ImGui::DockBuilderSplitNode(center, ImGuiDir_Right, right_width / (width - left_width), nullptr, ¢er)}; + auto const right{ImGui::DockBuilderSplitNode(center, ImGuiDir_Right, right_width / width, nullptr, ¢er)}; + auto const left{ImGui::DockBuilderSplitNode(center, ImGuiDir_Left, left_width / (width - right_width), nullptr, ¢er)}; auto const bottom{ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, bottom_height / height, nullptr, ¢er)}; + // left: settings of the tool in use above, the scenery below + ImGuiID lefttop{left}; + auto const leftbottom{ImGui::DockBuilderSplitNode(lefttop, ImGuiDir_Down, 0.5f, nullptr, &lefttop)}; // windows are found by the stable part of their names, after ### - for (auto const *window : {"###Toolset", "###Layers", "###scenehierarchy"}) - ImGui::DockBuilderDockWindow(window, left); - for (auto const *window : {"###trackinspector", "###roadeditor", "###editorsettings", "###Include database", "###orthophoto", "###structuregauge"}) + ImGui::DockBuilderDockWindow("###tooloptions", lefttop); + for (auto const *window : {"###scenetree", "###Layers", "###scenehierarchy"}) + ImGui::DockBuilderDockWindow(window, leftbottom); + for (auto const *window : {"###inspector", "###editorsettings", "###Include database"}) ImGui::DockBuilderDockWindow(window, right); - for (auto const *window : {"###profilestrip", "###editorhistory", "###modelsets"}) + for (auto const *window : {"###Node bank", "###profilestrip", "###trackanalysis", "###structuregauge", "###editorhistory", "###modelsets"}) ImGui::DockBuilderDockWindow(window, bottom); ImGui::DockBuilderFinish(Dockspace); } @@ -102,6 +105,9 @@ void editor_ui::render_() } } ImGui::End(); + render_tool_options(); + render_inspector(); + render_scene(); // status bar: the outcome of the last operation, and the selected node if (ImGui::BeginViewportSideBar("##editorstatusbar", viewport, ImGuiDir_Down, ImGui::GetFrameHeight(), flags)) { @@ -186,34 +192,245 @@ void editor_ui::render_mode_options(nodebank_panel::edit_mode const Mode) } } -void editor_ui::render_header_sections() +void editor_ui::render_tool_options() { - if (ImGui::CollapsingHeader(STR_C("Gizmo"), ImGuiTreeNodeFlags_DefaultOpen)) + if (false == m_tooloptionsopen) + return; + ImGui::SetNextWindowSize(ImVec2S(400, 300), ImGuiCond_FirstUseEver); + auto const current{mode()}; + auto const active{m_workspace == workspace::terrain || (m_workspace == workspace::roads && current != nodebank_panel::TRACK) ? m_workspace : workspace::none}; + // a change of the mode brings the window forward, in case it shares its dock with another one + auto const shown{active != workspace::none ? 100 + static_cast(active) : static_cast(current)}; + if (shown != m_toolmode) { - if (m_gizmooptions) - m_gizmooptions(); + m_toolmode = shown; + ImGui::SetNextWindowFocus(); } - if (ImGui::CollapsingHeader(STR_C("Node properties"), ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::Begin((std::string(STR_C("Tool options")) + "###tooloptions").c_str(), &m_tooloptionsopen, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse)) { - ImGui::Indent(); - m_itempropertiespanel.render_body(); - ImGui::Unindent(); + char const *name{STR_C("Select")}; + switch (current) + { + case nodebank_panel::ADD: + name = STR_C("Insert"); + break; + case nodebank_panel::BRUSH: + name = STR_C("Brush"); + break; + case nodebank_panel::FILL: + name = STR_C("Area fill"); + break; + case nodebank_panel::COPY: + name = STR_C("Copy to bank"); + break; + case nodebank_panel::TRACK: + name = STR_C("Tracks"); + break; + default: + break; + } + if (active == workspace::roads) + name = "Roads"; + else if (active == workspace::terrain) + name = STR_C("Terrain"); + ImGui::SeparatorText(name); + if (active != workspace::none) + { + auto const &tools{m_workspacetools[static_cast(active)]}; + if (tools) + tools(); + } + else if (current == nodebank_panel::TRACK) + { + if (m_tracktools) + m_tracktools(); + } + else + render_mode_options(current); + if (ImGui::CollapsingHeader(STR_C("Gizmo"), ImGuiTreeNodeFlags_DefaultOpen)) + { + if (m_gizmooptions) + m_gizmooptions(); + } } - if (ImGui::CollapsingHeader(STR_C("Array"))) + ImGui::End(); +} + +void editor_ui::render_inspector() +{ + if (false == m_inspectoropen) + return; + ImGui::SetNextWindowSize(ImVec2S(420, 480), ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(STR_C("Inspector")) + "###inspector").c_str(), &m_inspectoropen, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse)) { - if (m_arrayoptions) - m_arrayoptions(); + if (mode() == nodebank_panel::TRACK) + { + // in the track mode the selection is a path + if (m_trackselection) + m_trackselection(); + ImGui::End(); + return; + } + if (auto const &selection{m_workspaceselection[static_cast(m_workspace)]}) + { + selection(); + ImGui::End(); + return; + } + if (ImGui::CollapsingHeader(STR_C("Node properties"), ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(); + m_itempropertiespanel.render_body(); + ImGui::Unindent(); + } + if (ImGui::CollapsingHeader(STR_C("Array"))) + { + if (m_arrayoptions) + m_arrayoptions(); + } + if (m_bendexpand) + { + ImGui::SetNextItemOpen(true); + m_bendexpand = false; + } + if (ImGui::CollapsingHeader(STR_C("Bend along the track"))) + { + if (m_bendoptions) + m_bendoptions(); + } } - if (m_bendexpand) + ImGui::End(); +} + +void editor_ui::scene_rebuild() +{ + m_sceneentries.clear(); + m_sceneentries.reserve(scene::Hierarchy.size()); + for (auto const &entry : scene::Hierarchy) { - ImGui::SetNextItemOpen(true); - m_bendexpand = false; + if (entry.second == nullptr) + continue; + m_sceneentries.push_back({entry.first, entry.second->layer(), entry.second->group(), entry.second->name()}); } - if (ImGui::CollapsingHeader(STR_C("Bend along the track"))) + std::sort(m_sceneentries.begin(), m_sceneentries.end(), [](scene_entry const &Left, scene_entry const &Right) { + if (Left.layer != Right.layer) + return Left.layer < Right.layer; + if (Left.group != Right.group) + return Left.group < Right.group; + return Left.name < Right.name; + }); + m_scenesize = scene::Hierarchy.size(); + m_scenefilterused = "\x01"; // matches are found again +} + +void editor_ui::scene_row(std::size_t const Entry) +{ + auto const &entry{m_sceneentries[Entry]}; + // the node is looked up again, it may have been removed since the list was made + auto const lookup{scene::Hierarchy.find(entry.uuid)}; + auto *node{lookup != scene::Hierarchy.end() ? lookup->second : nullptr}; + if (node == nullptr) { - if (m_bendoptions) - m_bendoptions(); + ImGui::TextDisabled("%s", entry.name.c_str()); + return; } + auto const label{(node->name().empty() ? std::string{"(none)"} : node->name()) + "##" + entry.uuid}; + if (ImGui::Selectable(label.c_str(), node == m_node, ImGuiSelectableFlags_AllowDoubleClick) && m_sceneselect) + m_sceneselect(node, ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)); + if (ImGui::IsItemHovered()) + { + auto const location{node->location()}; + ImGui::SetTooltip("%s\n%.1f, %.1f, %.1f\n%s", entry.uuid.c_str(), location.x, location.y, location.z, STR_C("Double click: fly to it")); + } +} + +void editor_ui::render_scene() +{ + if (false == m_sceneopen) + return; + ImGui::SetNextWindowSize(ImVec2S(400, 420), ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(STR_C("Scene")) + "###scenetree").c_str(), &m_sceneopen, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse)) + { + if (m_scenesize != scene::Hierarchy.size()) + scene_rebuild(); + if (ImGui::Button(STR_C("Refresh"))) + scene_rebuild(); + ImGui::SameLine(); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::InputTextWithHint("##scenefilter", STR_C("Filter: name or uuid"), m_scenefilter, IM_ARRAYSIZE(m_scenefilter)); + ImGui::TextDisabled(STR_C("%zu models"), m_sceneentries.size()); + if (ImGui::BeginChild("##scenelist", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders)) + { + std::string const filter{m_scenefilter}; + if (false == filter.empty()) + { + // a flat list of what matches the filter + if (filter != m_scenefilterused) + { + m_scenematches.clear(); + for (std::size_t idx = 0; idx < m_sceneentries.size(); ++idx) + if (contains(m_sceneentries[idx].name, filter) || contains(m_sceneentries[idx].uuid, filter)) + m_scenematches.push_back(idx); + m_scenefilterused = filter; + } + ImGuiListClipper clipper; + clipper.Begin(static_cast(m_scenematches.size())); + while (clipper.Step()) + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) + scene_row(m_scenematches[row]); + } + else + { + // layers, in them the groups (made by the includes), in these the models + std::size_t layerstart{0}; + while (layerstart < m_sceneentries.size()) + { + auto const layer{m_sceneentries[layerstart].layer}; + auto layerend{layerstart}; + while (layerend < m_sceneentries.size() && m_sceneentries[layerend].layer == layer) + ++layerend; + auto const layername{scene::Layers.valid(layer) ? scene::Layers.layer(layer).name : std::string{STR_C("(no layer)")}}; + auto const layerlabel{layername + " (" + std::to_string(layerend - layerstart) + ")##layer" + std::to_string(layer)}; + if (ImGui::TreeNodeEx(layerlabel.c_str(), m_sceneentries.size() == layerend - layerstart ? ImGuiTreeNodeFlags_DefaultOpen : 0)) + { + auto groupstart{layerstart}; + while (groupstart < layerend) + { + auto const group{m_sceneentries[groupstart].group}; + auto groupend{groupstart}; + while (groupend < layerend && m_sceneentries[groupend].group == group) + ++groupend; + auto const rows = [&](std::size_t const From, std::size_t const To) { + ImGuiListClipper clipper; + clipper.Begin(static_cast(To - From)); + while (clipper.Step()) + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) + scene_row(From + row); + }; + if (group == null_handle) + { + rows(groupstart, groupend); + } + else + { + auto const grouplabel{std::string{STR_C("Group")} + " " + std::to_string(group) + " (" + std::to_string(groupend - groupstart) + ")##group" + std::to_string(group)}; + if (ImGui::TreeNode(grouplabel.c_str())) + { + rows(groupstart, groupend); + ImGui::TreePop(); + } + } + groupstart = groupend; + } + ImGui::TreePop(); + } + layerstart = layerend; + } + } + } + ImGui::EndChild(); + } + ImGui::End(); } void editor_ui::render_menu_contents() @@ -256,7 +473,10 @@ void editor_ui::render_menu_contents() if (ImGui::MenuItem(STR_C("Reset window layout"))) m_layoutreset = true; ImGui::Separator(); - ImGui::MenuItem(STR_C("Toolset"), nullptr, &m_nodebankpanel.is_open); + ImGui::MenuItem(STR_C("Tool options"), nullptr, &m_tooloptionsopen); + ImGui::MenuItem(STR_C("Inspector"), nullptr, &m_inspectoropen); + ImGui::MenuItem(STR_C("Scene"), nullptr, &m_sceneopen); + ImGui::MenuItem(STR_C("Node bank"), nullptr, &m_nodebankpanel.is_open); ImGui::MenuItem(STR_C("Layers"), nullptr, &m_layerspanel.is_open); ImGui::MenuItem(STR_C("Include database"), nullptr, &m_includespanel.is_open); ImGui::EndMenu(); @@ -330,7 +550,6 @@ nodebank_panel::edit_mode editor_ui::mode() } void editor_ui::set_mode(nodebank_panel::edit_mode const Mode) { - m_nodebankpanel.requested_mode = Mode; m_nodebankpanel.mode = Mode; m_nodebankpanel.is_open = true; } diff --git a/application/editoruilayer.h b/application/editoruilayer.h index a1d4cd35..f28484dd 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -97,13 +97,47 @@ class editor_ui : public ui_layer { return m_viewmax; } + // tools of the track mode in the tool options window, the selected path in the inspector + void set_track_options(std::function Tools, std::function Selection) + { + m_tracktools = std::move(Tools); + m_trackselection = std::move(Selection); + } + // fields of work of the editor mode besides the edit modes, chosen in its toolbar: their tools are drawn in the tool options window, + // what they have selected in the inspector. the terrain is shown over the track mode, the roads give way to it + enum class workspace + { + none, + roads, + terrain, + count_ + }; + void set_workspace(workspace const Workspace) + { + m_workspace = Workspace; + } + void set_workspace_options(workspace const Workspace, std::function Tools, std::function Selection = {}) + { + m_workspacetools[static_cast(Workspace)] = std::move(Tools); + m_workspaceselection[static_cast(Workspace)] = std::move(Selection); + } + // selection of a node in the scene window; Focus is set when the camera is to fly to it + void set_scene_select(std::function Select) + { + m_sceneselect = std::move(Select); + } // shows outcome of an operation in the layers window void set_status(std::string const &Status, bool const Error = false); private: // methods void render_mode_options(nodebank_panel::edit_mode const Mode); - void render_header_sections(); + // windows of the editor drawn by the user interface itself + void render_tool_options(); + void render_inspector(); + void render_scene(); + void scene_rebuild(); + void scene_row(std::size_t const Entry); void render_menu_contents() override; void render_dockspace() override; void render_() override; @@ -112,7 +146,7 @@ class editor_ui : public ui_layer // members itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window functions_panel m_functionspanel{"Functions", true}; // not a window of its own, its settings are drawn in the toolset tabs - nodebank_panel m_nodebankpanel{"Toolset", true}; // main editor window: gizmo, node properties, edit modes and the node bank + nodebank_panel m_nodebankpanel{"Node bank", true}; // templates to insert, paint or fill with layers_panel m_layerspanel{"Layers", true}; includes_panel m_includespanel{"Include database", false}; std::function m_save; @@ -126,6 +160,29 @@ class editor_ui : public ui_layer std::function m_menuoptions; std::function m_toolbaroptions; bool m_layoutreset{false}; // the default layout is to be built again on the next frame + bool m_tooloptionsopen{true}; + bool m_inspectoropen{true}; + bool m_sceneopen{true}; + std::function m_sceneselect; + std::function m_tracktools; + std::function m_trackselection; + int m_toolmode{-1}; // edit mode or field of work the tool options were drawn for the last time + workspace m_workspace{workspace::none}; + std::array, static_cast(workspace::count_)> m_workspacetools; + std::array, static_cast(workspace::count_)> m_workspaceselection; + // scene window: the models of the scenery by layer and group, rebuilt when their number changes + struct scene_entry + { + std::string uuid; // key in scene::Hierarchy, the node is looked up by it when drawn + scene::layer_handle layer; + scene::group_handle group; + std::string name; + }; + std::vector m_sceneentries; + std::size_t m_scenesize{static_cast(-1)}; + char m_scenefilter[64]{}; + std::string m_scenefilterused; + std::vector m_scenematches; static glm::vec2 m_viewmin; static glm::vec2 m_viewmax; std::function m_gizmooptions; diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index a36072ed..2381d40f 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -598,8 +598,7 @@ void nodebank_panel::render() } else { - // mode settings are drawn above the list, so start with a window tall enough for both - ImGui::SetNextWindowSize(ImVec2S(440, 640), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2S(440, 400), ImGuiCond_FirstUseEver); } if (size_min.x > 0) { @@ -619,32 +618,8 @@ void nodebank_panel::render() m_setsopen = !m_setsopen; } - if (header_sections) - { - header_sections(); - } - - // edit modes as tabs, each with its own settings - std::pair const modes[] = {{STR_C("Select"), MODIFY}, {STR_C("Insert"), ADD}, {STR_C("Brush"), BRUSH}, {STR_C("Area fill"), FILL}, {STR_C("Copy to bank"), COPY}}; - if (ImGui::BeginTabBar("##editmodes", ImGuiTabBarFlags_FittingPolicyResizeDown)) - { - for (auto const &tab : modes) - { - if (false == ImGui::BeginTabItem(tab.first, nullptr, requested_mode == tab.second ? ImGuiTabItemFlags_SetSelected : 0)) - { - continue; - } - mode = tab.second; - if (mode_options) - { - mode_options(mode); - } - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - requested_mode = -1; - } - + // the edit modes are chosen in the toolbar and set up in the tool options window, the gizmo and the node + // properties are in the inspector: what's left here is the node bank itself ImGui::Separator(); ImGui::PushItemWidth(-1); ImGui::InputTextWithHint(STR_C("Search"), STR_C("Search node bank"), m_nodesearch, IM_ARRAYSIZE(m_nodesearch)); diff --git a/application/editoruipanels.h b/application/editoruipanels.h index 7da83870..85d862b3 100644 --- a/application/editoruipanels.h +++ b/application/editoruipanels.h @@ -132,7 +132,6 @@ class nodebank_panel : public ui_panel TRACK }; edit_mode mode = MODIFY; - int requested_mode{-1}; // tab brought forward on the next frame nodebank_panel(std::string const &Name, bool const Isopen); void nodebank_reload(); @@ -157,10 +156,6 @@ class nodebank_panel : public ui_panel // shows the set manager window, optionally with specified user set selected void open_sets_window(int const Setid = 0); - // draws settings of the active edit mode inside its tab - std::function mode_options; - // draws sections attached above the mode tabs (gizmo, node properties) - std::function header_sections; private: // methods: diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 75c50c41..f632bc85 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -2027,8 +2027,8 @@ msgstr "Długość, na której skarpa przechodzi w koronę i w teren; 0: ostre k msgid "Sculpting..." msgstr "Rzeźbienie..." -msgid "Opens the terrain tab of the editor settings: raising, lowering and smoothing with a brush" -msgstr "Otwiera zakładkę terenu w ustawieniach edytora: podnoszenie, obniżanie i wygładzanie pędzlem" +msgid "Opens the terrain tools: raising, lowering and smoothing with a brush" +msgstr "Otwiera narzędzia terenu: podnoszenie, obniżanie i wygładzanie pędzlem" # scenery editor: track editor, selection, joints, parallel track, hectometre posts, fouling points @@ -2458,9 +2458,6 @@ msgstr "Ortofotomapa" msgid "Orthophoto settings..." msgstr "Ustawienia ortofotomapy..." -msgid "Orthophoto##window" -msgstr "Ortofotomapa##window" - msgid "Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery" msgstr "Zdjęcia lotnicze z geoportal.gov.pl pod scenerią, ułożone według punktu odniesienia scenerii" @@ -4599,3 +4596,48 @@ msgstr "Przywróć układ okien" msgid "Selected:" msgstr "Zaznaczono:" + +msgid "Tool options" +msgstr "Opcje narzędzia" + +msgid "Inspector" +msgstr "Inspektor" + +msgid "Scene" +msgstr "Scena" + +msgid "Node bank" +msgstr "Bank obiektów" + +msgid "Refresh" +msgstr "Odśwież" + +msgid "Filter: name or uuid" +msgstr "Filtr: nazwa lub uuid" + +msgid "%zu models" +msgstr "modeli: %zu" + +msgid "(no layer)" +msgstr "(bez warstwy)" + +msgid "Group" +msgstr "Grupa" + +msgid "Double click: fly to it" +msgstr "Dwuklik: kamera leci do niego" + +msgid "By the track" +msgstr "Przy torze" + +msgid "Checks" +msgstr "Kontrola" + +msgid "Infrastructure" +msgstr "Infrastruktura" + +msgid "Speed check" +msgstr "Kontrola prędkości" + +msgid "What the check finds is listed in its window" +msgstr "Wyniki kontroli są w jej oknie"