diff --git a/application/editormode.cpp b/application/editormode.cpp index adfb1340..73493be0 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -487,6 +487,7 @@ editor_mode::editor_mode() { // the area fill settings live in the node bank window, in the tab of the fill 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_array_options([this]() { render_array(); }); ui()->set_bend_options([this]() { render_bend(); }); ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); }); @@ -1423,7 +1424,7 @@ bool editor_mode::update() void editor_mode::render_settings() { - ImGui::Begin(STR_C("Editor Settings"), &m_settings_open, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Begin((std::string(STR_C("Editor Settings")) + "###editorsettings").c_str(), &m_settings_open); if (ImGui::BeginTabBar("##editorsettings")) { @@ -2964,7 +2965,7 @@ void editor_mode::render_orthophoto_window() if (false == m_orthophoto_window) return; ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver); - if (ImGui::Begin(STR_C("Orthophoto##window"), &m_orthophoto_window, ImGuiWindowFlags_AlwaysAutoResize)) + if (ImGui::Begin((std::string(STR_C("Orthophoto##window")) + "###orthophoto").c_str(), &m_orthophoto_window)) render_orthophoto_ui(); ImGui::End(); } @@ -3623,6 +3624,51 @@ void editor_mode::render_array() ImGui::PopID(); } +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}}; + for (auto const &mode : modes) + { + if (ImGui::MenuItem(mode.first, nullptr, ui()->mode() == mode.second)) + { + m_track_window_open = false; + ui()->set_mode(mode.second); + } + } + ImGui::Separator(); + if (ImGui::MenuItem(STR_C("Tracks"), nullptr, m_track_window_open)) + { + if (m_track_window_open) + m_track_window_open = false; + else + show_track_tab(m_track_tab); + } + if (ImGui::MenuItem("Roads", nullptr, m_roadtool.window)) + { + m_roadtool.window = !m_roadtool.window; + if (m_roadtool.window) + m_track_window_open = false; + } + ImGui::Separator(); + if (m_gizmo_enabled) + { + std::pair const operations[] = { + {STR_C("Translate (Q)"), gizmo_operation::translate}, {STR_C("Rotate (W)"), gizmo_operation::rotate}, {STR_C("Scale (E)"), gizmo_operation::scale}}; + for (auto const &operation : operations) + { + if (ImGui::MenuItem(operation.first, nullptr, m_gizmo_op == operation.second)) + m_gizmo_op = operation.second; + } + if (m_gizmo_op != gizmo_operation::scale && ImGui::MenuItem(STR_C("Local space (R)"), nullptr, m_gizmo_local)) + m_gizmo_local = !m_gizmo_local; + ImGui::Separator(); + } + if (ImGui::MenuItem(STR_C("Top view, orthographic (O)"), nullptr, Global.EditorOrtho)) + toggle_ortho(); +} + void editor_mode::render_gizmo_options() { if (ImGui::Button(Global.EditorOrtho ? "3D view (O)" : "Top view, orthographic (O)")) @@ -4585,7 +4631,7 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const void editor_mode::render_change_history(){ - ImGui::Begin(STR_C("Editor History"), &m_change_history, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Begin((std::string(STR_C("Editor History")) + "###editorhistory").c_str(), &m_change_history); int maxsize = m_max_history_size; if (ImGui::InputInt(STR_C("Max history size"), &maxsize)) { diff --git a/application/editormode.h b/application/editormode.h index 95e9bc8f..9e14f293 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -425,6 +425,8 @@ class editor_mode : public application_mode, private editor_track::observer void render_gizmo(); // gizmo settings, drawn in the toolset window void render_gizmo_options(); + // edit modes, editor windows and gizmo settings in the toolbar under the menu + void render_toolbar(); bool m_gizmo_enabled{true}; // master switch for the in-viewport gizmo bool m_gizmo_using{false}; // tracks an ongoing drag, so a single undo snapshot is taken per drag bool m_gizmo_local{false}; // manipulate in the object's local space instead of world space diff --git a/application/editormodegauge.cpp b/application/editormodegauge.cpp index 2f64fb42..6d6883a0 100644 --- a/application/editormodegauge.cpp +++ b/application/editormodegauge.cpp @@ -708,7 +708,7 @@ void editor_mode::render_gauge_window() if (false == m_gauge.open) return; ImGui::SetNextWindowSize(ImVec2(380.0f, 420.0f), ImGuiCond_FirstUseEver); - if (false == ImGui::Begin(STR_C("Structure gauge"), &m_gauge.open)) + if (false == ImGui::Begin((std::string(STR_C("Structure gauge")) + "###structuregauge").c_str(), &m_gauge.open)) { ImGui::End(); return; diff --git a/application/editormoderoad.cpp b/application/editormoderoad.cpp index d66b6bcb..c7e7051c 100644 --- a/application/editormoderoad.cpp +++ b/application/editormoderoad.cpp @@ -2834,8 +2834,7 @@ void editor_mode::draw_road_overlay() const "LMB: select a piece, a junction, a point (white dot) or a marker"; } // shown above the place the track tools put their hints at - ImGuiIO const &io = ImGui::GetIO(); - ImVec2 const position{12.0f, io.DisplaySize.y - 56.0f}; + ImVec2 const position{editor_ui::view_min().x + 12.0f, editor_ui::view_max().y - 56.0f}; auto const size{ImGui::CalcTextSize(hint.c_str())}; drawlist->AddRectFilled(ImVec2(position.x - 6.0f, position.y - 4.0f), ImVec2(position.x + size.x + 6.0f, position.y + size.y + 4.0f), IM_COL32(0, 0, 0, 150), 4.0f); drawlist->AddText(position, IM_COL32(255, 255, 255, 230), hint.c_str()); diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 761b36ab..8d9f8a72 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -3657,8 +3657,10 @@ void editor_mode::draw_track_hints() auto hints{track_key_hints(false)}; if (hints.size() > 4) hints.resize(4); - auto const bottom{io.DisplaySize.y}; - auto const available{io.DisplaySize.x * 0.6f - 2.0f * margin}; + // in the part of the window the 3d view is seen in, between the docked windows + auto const left{editor_ui::view_min().x + margin}; + auto const bottom{editor_ui::view_max().y}; + auto const available{(editor_ui::view_max().x - editor_ui::view_min().x) * 0.6f - 2.0f * margin}; auto const mode{track_mode_name()}; std::vector> lines(1); float used{ImGui::CalcTextSize(mode.c_str()).x + 2.0f * pad + gap}; @@ -3679,13 +3681,13 @@ void editor_mode::draw_track_hints() float width{i == 0 ? ImGui::CalcTextSize(mode.c_str()).x + 2.0f * pad : 0.0f}; for (auto const *hint : lines[i]) width += (width > 0.0f ? gap : 0.0f) + width_of(*hint); - drawlist->AddRectFilled(ImVec2(margin - 6.0f, y - pad - 2.0f), ImVec2(margin + width + 6.0f, y + lineheight - pad - 2.0f), IM_COL32(0, 0, 0, 160), 4.0f); - auto x{margin}; + drawlist->AddRectFilled(ImVec2(left - 6.0f, y - pad - 2.0f), ImVec2(left + width + 6.0f, y + lineheight - pad - 2.0f), IM_COL32(0, 0, 0, 160), 4.0f); + auto x{left}; if (i == 0) x += chip(ImVec2(x, y), mode.c_str(), IM_COL32(60, 140, 230, 235), IM_COL32(255, 255, 255, 255)); for (auto const *hint : lines[i]) { - if (x > margin) + if (x > left) x += gap; x += chip(ImVec2(x, y), hint->key, IM_COL32(255, 210, 60, 220), IM_COL32(20, 20, 20, 255)) + 5.0f; drawlist->AddText(ImVec2(x, y), IM_COL32(255, 255, 255, 230), hint->action.c_str()); diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index 4f7ede62..dc38b282 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -16,6 +16,10 @@ http://mozilla.org/MPL/2.0/. #include "scene/scenelayers.h" #include "rendering/renderer.h" #include "utilities/translation.h" +#include "imgui/imgui_internal.h" + +glm::vec2 editor_ui::m_viewmin{0.0f}; +glm::vec2 editor_ui::m_viewmax{0.0f}; editor_ui::editor_ui() { @@ -29,6 +33,98 @@ editor_ui::editor_ui() 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; +} + +void editor_ui::render_dockspace() +{ + // windows of the editor are docked around the 3d view, which is left free in the middle + auto *viewport{ImGui::GetMainViewport()}; + auto const dockspace{ImGui::GetID("###editordockspace")}; + if (m_layoutreset || ImGui::DockBuilderGetNode(dockspace) == nullptr) + { + m_layoutreset = false; + build_default_layout(dockspace); + } + ImGui::DockSpaceOverViewport(dockspace, viewport, ImGuiDockNodeFlags_PassthruCentralNode); + if (auto const *central{ImGui::DockBuilderGetCentralNode(dockspace)}; central != nullptr) + { + m_viewmin = {central->Pos.x, central->Pos.y}; + m_viewmax = {central->Pos.x + central->Size.x, central->Pos.y + central->Size.y}; + } + else + { + m_viewmin = {viewport->WorkPos.x, viewport->WorkPos.y}; + m_viewmax = {viewport->WorkPos.x + viewport->WorkSize.x, viewport->WorkPos.y + viewport->WorkSize.y}; + } +} + +void editor_ui::build_default_layout(unsigned int const Dockspace) +{ + auto *viewport{ImGui::GetMainViewport()}; + ImGui::DockBuilderRemoveNode(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 + 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 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)}; + 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 bottom{ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, bottom_height / height, nullptr, ¢er)}; + // 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(window, right); + for (auto const *window : {"###profilestrip", "###editorhistory", "###modelsets"}) + ImGui::DockBuilderDockWindow(window, bottom); + ImGui::DockBuilderFinish(Dockspace); +} + +void editor_ui::render_() +{ + auto *viewport{ImGui::GetMainViewport()}; + auto const flags{ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar}; + // toolbar under the menu: the tools of the editor mode + if (ImGui::BeginViewportSideBar("##editortoolbar", viewport, ImGuiDir_Up, ImGui::GetFrameHeight(), flags)) + { + if (ImGui::BeginMenuBar()) + { + if (m_toolbaroptions) + m_toolbaroptions(); + ImGui::EndMenuBar(); + } + } + ImGui::End(); + // status bar: the outcome of the last operation, and the selected node + if (ImGui::BeginViewportSideBar("##editorstatusbar", viewport, ImGuiDir_Down, ImGui::GetFrameHeight(), flags)) + { + if (ImGui::BeginMenuBar()) + { + if (false == m_layerspanel.status.empty()) + { + if (m_layerspanel.status_error) + ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), "%s", m_layerspanel.status.c_str()); + else + ImGui::TextUnformatted(m_layerspanel.status.c_str()); + } + if (m_node != nullptr) + { + auto const name{m_node->name().empty() ? std::string{"(none)"} : m_node->name()}; + auto const label{std::string{STR_C("Selected:")} + " " + name}; + ImGui::SameLine(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() - ImGui::CalcTextSize(label.c_str()).x - ImGui::GetStyle().ItemSpacing.x * 2.0f)); + ImGui::TextUnformatted(label.c_str()); + } + ImGui::EndMenuBar(); + } + } + ImGui::End(); } // updates state of UI elements @@ -157,6 +253,9 @@ void editor_ui::render_menu_contents() if (ImGui::BeginMenu(STR_C("Mode windows"))) { + 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("Layers"), nullptr, &m_layerspanel.is_open); ImGui::MenuItem(STR_C("Include database"), nullptr, &m_includespanel.is_open); diff --git a/application/editoruilayer.h b/application/editoruilayer.h index f390d932..a1d4cd35 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -61,6 +61,11 @@ class editor_ui : public ui_layer { m_menuoptions = std::move(Renderer); } + // draws the tools of the editor mode in the toolbar under the menu + void set_toolbar_options(std::function Renderer) + { + m_toolbaroptions = std::move(Renderer); + } // draws the gizmo settings in the toolset window (the gizmo state lives in the editor mode) void set_gizmo_options(std::function Renderer) { @@ -83,6 +88,15 @@ class editor_ui : public ui_layer void set_file_actions(std::function Save, std::function Export); void set_new_scenery(std::function New) { m_newscenery = std::move(New); } void set_open_scenery(std::function Open) { m_openscenery = std::move(Open); } + // part of the main window between the docked windows, where the 3d view is seen; for the overlays placed in it + static glm::vec2 view_min() + { + return m_viewmin; + } + static glm::vec2 view_max() + { + return m_viewmax; + } // shows outcome of an operation in the layers window void set_status(std::string const &Status, bool const Error = false); @@ -91,6 +105,10 @@ class editor_ui : public ui_layer void render_mode_options(nodebank_panel::edit_mode const Mode); void render_header_sections(); void render_menu_contents() override; + void render_dockspace() override; + void render_() override; + // docks the windows of the editor in their default places + void build_default_layout(unsigned int const Dockspace); // 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 @@ -106,6 +124,10 @@ class editor_ui : public ui_layer model_set_ref m_insertset; std::function m_filloptions; std::function m_menuoptions; + std::function m_toolbaroptions; + bool m_layoutreset{false}; // the default layout is to be built again on the next frame + static glm::vec2 m_viewmin; + static glm::vec2 m_viewmax; std::function m_gizmooptions; std::function m_arrayoptions; std::function m_bendoptions; diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index 6eae78db..a36072ed 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -892,7 +892,7 @@ void nodebank_panel::open_sets_window(int const Setid) void nodebank_panel::render_sets_window() { ImGui::SetNextWindowSize(ImVec2S(600, 380), ImGuiCond_FirstUseEver); - if (false == ImGui::Begin(STR_C("Model sets"), &m_setsopen, ImGuiWindowFlags_NoCollapse)) + if (false == ImGui::Begin((std::string(STR_C("Model sets")) + "###modelsets").c_str(), &m_setsopen, ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; diff --git a/application/uilayer.cpp b/application/uilayer.cpp index 4f949054..0d99ae53 100644 --- a/application/uilayer.cpp +++ b/application/uilayer.cpp @@ -460,6 +460,13 @@ void ui_layer::update() void ui_layer::render() { + // the frame may have been started by the ui of another mode, when the mode changes; the windows docked in the dockspace + // of this one would come undocked if they were drawn in a frame without it + if (m_dockspaceframe != ImGui::GetFrameCount()) + { + render_dockspace(); + m_dockspaceframe = ImGui::GetFrameCount(); + } render_background(); render_panels(); render_tooltip(); @@ -490,6 +497,8 @@ void ui_layer::render_internal() void ui_layer::begin_ui_frame() { begin_ui_frame_internal(); + render_dockspace(); + m_dockspaceframe = ImGui::GetFrameCount(); } void ui_layer::begin_ui_frame_internal() @@ -520,8 +529,9 @@ void ui_layer::render_hierarchy(){ if(!m_editor_hierarchy) return; - ImGui::SetNextWindowSize(ImVec2(0, 0)); - ImGui::Begin(STR_C("Scene Hierarchy"), &m_editor_hierarchy, ImGuiWindowFlags_AlwaysAutoResize); + // fitted to the contents when it first appears; no auto resize, which a docked window can't do + ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver); + ImGui::Begin((std::string(STR_C("Scene Hierarchy")) + "###scenehierarchy").c_str(), &m_editor_hierarchy); ImGui::Text("Registered nodes: %zu", scene::Hierarchy.size()); ImGui::BeginChild("hierarchy_list", ImVec2(500, 300), true); @@ -683,7 +693,7 @@ void ui_layer::render_menu() { glm::dvec2 mousepos = Global.cursor_pos; - if (!((Global.ControlPicking && mousepos.y < 50.0f) || m_imguiio->WantCaptureMouse) || m_suppress_menu) + if (m_suppress_menu || (false == m_menu_always && !((Global.ControlPicking && mousepos.y < 50.0f) || m_imguiio->WantCaptureMouse))) return; if (ImGui::BeginMainMenuBar()) diff --git a/application/uilayer.h b/application/uilayer.h index 975c1c77..96cd407b 100644 --- a/application/uilayer.h +++ b/application/uilayer.h @@ -139,6 +139,8 @@ protected: virtual void render_menu_contents(); ui_log_panel m_logpanel { "Log", true }; bool m_suppress_menu = false; // if `true`, the menu at the top of the window will not be present + bool m_menu_always = false; // if `true`, the menu is shown all the time, not only when the mouse is at the top of the window + int m_dockspaceframe{-1}; // frame the dockspace was last submitted in // progress bar config float m_progress { 0.0f }; // percentage of filled progres bar, to indicate lengthy operations. float m_subtaskprogress{ 0.0f }; // percentage of filled progres bar, to indicate lengthy operations. @@ -148,6 +150,8 @@ protected: // methods // render() subclass details virtual void render_() {} + // submitted right after the start of the frame, before any window that may be docked in it + virtual void render_dockspace() {} // draws background quad with specified earlier texture void render_background(); void render_tooltip(); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 03c7c4b6..75c50c41 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -4593,3 +4593,9 @@ msgstr "Głowica na bramkę wisi nad osią toru tak wysoko nad główkami szyn" msgid " not read" msgstr " nieodczytywany" + +msgid "Reset window layout" +msgstr "Przywróć układ okien" + +msgid "Selected:" +msgstr "Zaznaczono:"