From 4e69d8f86ccaa05859cdc8f889c305bae96ee0ca Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Thu, 8 Oct 2026 22:41:51 +0200 Subject: [PATCH] feat(editor): fields of work chosen in the toolbar, one at a time: edit modes, tracks, signals, vehicle, roads, terrain; Toolset split into Tool options (tools of the field of work and the gizmo), Inspector (what it has selected: node with array and bend, path, road) and Node bank (list only); Scene window with the models of the scenery by layer and group, filtered by name or uuid, a click selects, a double click flies the camera to it; track window replaced by a palette of the track tools in groups (build, by the track, checks) with the tools under it, the selected path in the inspector, results of the speed, joints and infrastructure checks in a docked window; road window split into the tools in tool options and the selection in the inspector; terrain tools moved out of the editor settings into tool options together with the orthophoto settings (map menu opens them), leaving the terrain puts sculpting and chunk editing away; default layout: tool options and scene on the left, inspector on the right, node bank and checks at the bottom; Polish translation --- application/editormode.cpp | 121 ++++++++++--- application/editormode.h | 17 +- application/editormodeprofile.cpp | 8 +- application/editormoderoad.cpp | 45 ++--- application/editormodetrack.cpp | 173 +++++++++--------- application/editoruilayer.cpp | 279 ++++++++++++++++++++++++++---- application/editoruilayer.h | 61 ++++++- application/editoruipanels.cpp | 31 +--- application/editoruipanels.h | 5 - lang/editor_pl.po | 52 +++++- 10 files changed, 590 insertions(+), 202 deletions(-) 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"