From 791d5d052cc9f14ae72189d20842ac8acd552767 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Fri, 9 Oct 2026 00:42:17 +0200 Subject: [PATCH] feat(editor): fields of work in a row of their own over the toolbar (surroundings, tracks, roads, terrain), twice as high, with bigger bold text, and the tools of the one chosen in the toolbar under it: the edit modes of the node bank, the track tools in groups with the vehicle and the structure gauge, the road tools with what the place tool puts, the terrain tools (sculpt, smooth, chunks, orthophoto); the tool options window keeps the settings of the tool; the node bank shows a big preview of the entry pointed at for a moment, with its model and skin; the green colours by default again; Polish translation --- application/editormode.cpp | 213 ++++++++++++++++++++++---------- application/editormode.h | 19 ++- application/editormoderoad.cpp | 80 +++++++----- application/editormodetrack.cpp | 56 +++++---- application/editoruilayer.cpp | 14 ++- application/editoruilayer.h | 6 + application/editoruipanels.cpp | 57 ++++++++- application/editoruipanels.h | 3 + application/uilayer.cpp | 9 ++ application/uilayer.h | 2 + editor/editorPreviews.cpp | 38 +++--- editor/editorPreviews.hpp | 6 +- editor/editorSettings.cpp | 4 +- editor/editorSettings.hpp | 4 +- lang/editor_pl.po | 39 ++++++ 15 files changed, 407 insertions(+), 143 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 3198308f..05e1c736 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -488,6 +488,7 @@ 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_workspace_bar([this]() { render_workspaces(); }); 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(); }); @@ -3674,77 +3675,157 @@ void editor_mode::choose_edit_mode(nodebank_panel::edit_mode const Mode) ui()->set_mode(Mode); } +editor_mode::work_area editor_mode::current_work_area() const +{ + // the terrain is shown over the tracks, see the tool options window + if (m_terrain_open) + return work_area::terrain; + if (m_track_window_open) + return work_area::tracks; + if (m_roadtool.window) + return work_area::roads; + return work_area::surroundings; +} + +void editor_mode::choose_work_area(work_area const Area) +{ + switch (Area) + { + case work_area::surroundings: + // the edit mode used last + choose_edit_mode(ui()->nodebank().mode); + break; + case work_area::tracks: + terrain_workspace(false); + show_track_tab(m_track_tab); + break; + case work_area::roads: + m_track_window_open = false; + terrain_workspace(false); + m_roadtool.window = true; + break; + case work_area::terrain: + m_track_window_open = false; + m_roadtool.window = false; + terrain_workspace(true); + break; + } +} + +void editor_mode::render_workspaces() +{ + struct area + { + char const *label; + work_area which; + char const *tooltip; + }; + area const areas[] = { + {STR_C("Surroundings"), work_area::surroundings, STR_C("Models around the line: select, insert, brush, area fill, copy to the node bank")}, + {STR_C("Tracks"), work_area::tracks, STR_C("Tracks and switches, signals, objects along the track, checks of the line")}, + {STR_C("Roads"), work_area::roads, STR_C("Roads and junctions, level crossings, the points where the traffic comes and goes")}, + {STR_C("Terrain"), work_area::terrain, STR_C("Terrain patches and chunks, sculpting, streaming, orthophoto")}, + }; + auto const current{current_work_area()}; + auto const &style{ImGui::GetStyle()}; + auto *list{ImGui::GetWindowDrawList()}; + // bigger and bold, with the font made for it if there's one, or drawn twice a pixel apart + auto *font{ui_layer::font_bold != nullptr ? ui_layer::font_bold : ImGui::GetFont()}; + auto const fontsize{ui_layer::font_bold != nullptr ? ui_layer::font_bold->FontSize : ImGui::GetFontSize() * 1.25f}; + auto const height{ImGui::GetWindowHeight()}; + auto const padding{style.FramePadding.x * 3.0f}; + ImGui::SetCursorPos(ImVec2(style.ItemSpacing.x, 0.0f)); + for (auto const &entry : areas) + { + auto const text{font->CalcTextSizeA(fontsize, FLT_MAX, 0.0f, entry.label)}; + ImVec2 const size{text.x + padding * 2.0f, height}; + auto const position{ImGui::GetCursorScreenPos()}; + ImGui::PushID(static_cast(entry.which)); + if (ImGui::InvisibleButton("##area", size)) + choose_work_area(entry.which); + ImGui::PopID(); + auto const hovered{ImGui::IsItemHovered()}; + auto const chosen{entry.which == current}; + ImVec2 const end{position.x + size.x, position.y + size.y}; + if (chosen || hovered) + list->AddRectFilled(position, end, ImGui::GetColorU32(chosen ? ImGuiCol_Header : ImGuiCol_HeaderHovered)); + if (chosen) + list->AddRectFilled(ImVec2(position.x, end.y - 3.0f), end, ImGui::GetColorU32(ImGuiCol_CheckMark)); + ImVec2 const textposition{position.x + padding, position.y + (height - text.y) * 0.5f}; + auto const color{ImGui::GetColorU32(chosen ? ImGuiCol_Text : ImGuiCol_TextDisabled)}; + list->AddText(font, fontsize, textposition, color, entry.label); + if (ui_layer::font_bold == nullptr) + list->AddText(font, fontsize, ImVec2(textposition.x + 1.0f, textposition.y), color, entry.label); + if (hovered) + ImGui::SetTooltip("%s", entry.tooltip); + ImGui::SameLine(0.0f, style.ItemSpacing.x); + } +} + +void editor_mode::render_terrain_toolbar() +{ + // what the left button does on the terrain: picks the models as it does elsewhere, raises and lowers, evens out, adds chunks + struct tool + { + char const *label; + bool chosen; + bool sculpt; + bool smooth; + bool chunks; + char const *tooltip; + }; + tool const tools[] = { + {STR_C("Select"), false == m_terrain_sculpt && false == m_chunk_edit, false, m_terrain_brush_smooth, false, STR_C("LMB picks the models, as in the surroundings")}, + {STR_C("Sculpt"), m_terrain_sculpt && false == m_terrain_brush_smooth, true, false, false, STR_C("LMB raises the terrain under the brush, Shift+LMB lowers it")}, + {STR_C("Smooth"), m_terrain_sculpt && m_terrain_brush_smooth, true, true, false, STR_C("LMB evens the terrain out under the brush")}, + {STR_C("Chunks"), m_chunk_edit, false, m_terrain_brush_smooth, true, STR_C("LMB adds a chunk next to the clicked one, Shift+LMB deletes it")}, + }; + for (auto const &entry : tools) + { + if (ImGui::MenuItem(entry.label, nullptr, entry.chosen)) + { + m_terrain_sculpt = entry.sculpt; + m_terrain_brush_smooth = entry.smooth; + m_chunk_edit = entry.chunks; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", entry.tooltip); + } + ImGui::Separator(); + auto const orthophoto{m_orthophoto.enabled()}; + if (ImGui::MenuItem(STR_C("Orthophoto"), nullptr, orthophoto)) + m_orthophoto.enabled(false == orthophoto); + 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")); +} + 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 (std::size_t index = 0; index < std::size(modes); ++index) + auto const area{current_work_area()}; + switch (area) { - auto const &mode{modes[index]}; - if (ImGui::MenuItem(mode.first, nullptr, false == workspace && ui()->mode() == mode.second)) - choose_edit_mode(mode.second); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s (%d)", mode.first, static_cast(index + 1)); + case work_area::surroundings: + { + // edit modes of the node bank + 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 (std::size_t index = 0; index < std::size(modes); ++index) + { + auto const &mode{modes[index]}; + if (ImGui::MenuItem(mode.first, nullptr, ui()->mode() == mode.second)) + choose_edit_mode(mode.second); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s (%d)", mode.first, static_cast(index + 1)); + } + break; + } + case work_area::tracks: render_track_toolbar(); break; + case work_area::roads: render_road_toolbar(); break; + case work_area::terrain: render_terrain_toolbar(); break; } ImGui::Separator(); - 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 (tracks && m_track_tab != track_tab::signals && false == vehicle) - m_track_window_open = false; - else - { - terrain_workspace(false); - show_track_tab(m_track_tab == track_tab::signals || vehicle ? track_tab::path : m_track_tab); - } - } - // 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)) - { - 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) + // the gizmo moves the models and the tracks; on the terrain the mouse is the brush's + if (m_gizmo_enabled && area != work_area::terrain) { 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}}; diff --git a/application/editormode.h b/application/editormode.h index 7f72e006..4251a36c 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -233,6 +233,8 @@ class editor_mode : public application_mode, private editor_track::observer 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(); + // tools of the terrain in the toolbar: picking, sculpting, smoothing, chunks, orthophoto + void render_terrain_toolbar(); // 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) @@ -432,6 +434,17 @@ class editor_mode : public application_mode, private editor_track::observer void render_toolbar(); // an edit mode of the node bank as the field of work, from the toolbar or its key (1-5) void choose_edit_mode(nodebank_panel::edit_mode const Mode); + // fields of work of the editor, in the row above the toolbar: the models around, the tracks, the roads, the terrain + enum class work_area + { + surroundings, + tracks, + roads, + terrain + }; + work_area current_work_area() const; + void choose_work_area(work_area const Area); + void render_workspaces(); 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 @@ -842,7 +855,8 @@ class editor_mode : public application_mode, private editor_track::observer // 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(); + // tools of the track mode in the toolbar, in groups: building, by the track, checks + void render_track_toolbar(); 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); @@ -1755,6 +1769,9 @@ class editor_mode : public application_mode, private editor_track::observer }; void render_road_menu(); void render_road_tool_options(); + // tools of the roads in the toolbar, and the change of the tool + void render_road_toolbar(); + void road_choose_tool(int const Tool); 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); diff --git a/application/editormoderoad.cpp b/application/editormoderoad.cpp index 1a57ed30..5e0f3bff 100644 --- a/application/editormoderoad.cpp +++ b/application/editormoderoad.cpp @@ -2261,6 +2261,57 @@ bool editor_mode::render_road_layout(road_node::state &State) return changed; } +void editor_mode::road_choose_tool(int const Tool) +{ + auto &tool{m_roadtool}; + if (Tool == tool.tool) + return; + tool.tool = Tool; + road_cancel(); + if (tool.tool != 0) + { + tool.junction = nullptr; + tool.points.clear(); + tool.marker = nullptr; + // the fields for the names of materials show the layout for the roads to be built again + road_select(nullptr); + } +} + +void editor_mode::render_road_toolbar() +{ + auto &tool{m_roadtool}; + struct entry + { + char const *label; + char const *tooltip; + }; + entry const tools[] = {{"Select", "LMB: a road piece, a junction, a point where pieces meet, a level crossing or a traffic point"}, + {"Build", "LMB: start a road, then each next point; on a loose end, the side of a road or a junction it's joined to them"}, + {"Place", "Level crossings, and the points where vehicles appear on the roads or are taken off them"}}; + for (int index = 0; index < static_cast(std::size(tools)); ++index) + { + if (ImGui::MenuItem(tools[index].label, nullptr, tool.tool == index)) + road_choose_tool(index); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", tools[index].tooltip); + } + if (tool.tool == 2) + { + // what the place tool puts + ImGui::Separator(); + char const *const kinds[] = {"Level crossing", "Spawn point", "Removal point", "Pedestrian crossing"}; + for (int index = 0; index < static_cast(std::size(kinds)); ++index) + { + if (ImGui::MenuItem(kinds[index], nullptr, tool.placekind == index)) + tool.placekind = index; + } + } + ImGui::Separator(); + if (ImGui::MenuItem("Show lanes", nullptr, tool.lanes)) + tool.lanes = !tool.lanes; +} + // 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() { @@ -2269,28 +2320,7 @@ void editor_mode::render_road_tool_options() if (false == editor_road::available(&reason)) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", reason.c_str()); - auto const previoustool{tool.tool}; - ImGui::RadioButton("Select", &tool.tool, 0); - ImGui::SameLine(); - ImGui::RadioButton("Build", &tool.tool, 1); - ImGui::SameLine(); - ImGui::RadioButton("Place", &tool.tool, 2); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", "Level crossings, and the points where vehicles appear on the roads or are taken off them"); - if (tool.tool != previoustool) - { - road_cancel(); - if (tool.tool != 0) - { - tool.junction = nullptr; - tool.points.clear(); - tool.marker = nullptr; - // the fields for the names of materials show the layout for the roads to be built again - road_select(nullptr); - } - } - ImGui::SameLine(); - ImGui::Checkbox("Show lanes", &tool.lanes); + // the tools and what the place tool puts are chosen in the toolbar if (tool.tool == 1) { @@ -2338,12 +2368,6 @@ void editor_mode::render_road_tool_options() } else if (tool.tool == 2) { - ImGui::RadioButton("Level crossing", &tool.placekind, 0); - ImGui::SameLine(); - ImGui::RadioButton("Spawn point", &tool.placekind, 1); - ImGui::SameLine(); - ImGui::RadioButton("Removal point", &tool.placekind, 2); - ImGui::RadioButton("Pedestrian crossing", &tool.placekind, 3); tool.placekind = std::clamp(tool.placekind, 0, 3); switch (tool.placekind) { diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 3fcd91db..689ee15f 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -772,7 +772,7 @@ void editor_mode::render_track_inspector() void editor_mode::render_track_tool_options() { auto *track{selected_track()}; - render_track_modes(); + // the tools themselves are in the toolbar render_track_search(); ImGui::Spacing(); render_track_guide(); @@ -827,33 +827,41 @@ void editor_mode::render_track_selection() render_path_ui(); } -void editor_mode::render_track_modes() +void editor_mode::render_track_toolbar() { 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)) + auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.open}; + // a tool chosen again gives way to the select one + auto const tool = [&](track_tab const Tab) { + auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == Tab; })}; + if (found == modes.end()) 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(); + auto const chosen{m_track_tab == Tab && false == (Tab == track_tab::lineside && vehicle)}; + auto const label{std::string{STR_C(found->label)} + "###tracktool" + std::to_string(static_cast(found - modes.begin()))}; + if (ImGui::MenuItem(label.c_str(), nullptr, chosen)) + show_track_tab(chosen ? track_tab::path : Tab); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s [%s]", STR_C(found->tooltip), found->key); }; - 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); + // building the track, what goes by it, the checks + for (auto const tab : {track_tab::path, track_tab::lay, track_tab::turnout, track_tab::straights, track_tab::route, track_tab::turntable}) + tool(tab); + ImGui::Separator(); + tool(track_tab::signals); + tool(track_tab::lineside); + // the vehicle to drive is one of the objects along the track + if (ImGui::MenuItem(STR_C("Vehicle"), nullptr, vehicle)) + { + show_track_tab(vehicle ? track_tab::path : track_tab::lineside); + m_vehicle.expand = false == vehicle; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("A vehicle to drive, put on the selected track; it isn't written to the scenery files")); + ImGui::Separator(); + for (auto const tab : {track_tab::profile, track_tab::speed, track_tab::joints, track_tab::infra}) + tool(tab); + if (ImGui::MenuItem(STR_C("Structure gauge"), nullptr, m_gauge.open)) + m_gauge.open = !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)")); } diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index 863f1b56..8fc55fc9 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -586,7 +586,19 @@ 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 + // the fields of work of the editor under the menu, in a row twice as high; the tools of the one chosen in the toolbar under it + if (m_workspacebar) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + if (ImGui::BeginViewportSideBar("##editorworkspaces", viewport, ImGuiDir_Up, ImGui::GetFrameHeight() * 2.0f, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse)) + { + m_workspacebar(); + } + ImGui::End(); + ImGui::PopStyleVar(2); + } + // toolbar under them: the tools of the field of work if (ImGui::BeginViewportSideBar("##editortoolbar", viewport, ImGuiDir_Up, ImGui::GetFrameHeight(), flags)) { if (ImGui::BeginMenuBar()) diff --git a/application/editoruilayer.h b/application/editoruilayer.h index 2ab2f8d4..f95af4c2 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -69,6 +69,11 @@ class editor_ui : public ui_layer { m_toolbaroptions = std::move(Renderer); } + // draws the fields of work of the editor mode in a row above the toolbar + void set_workspace_bar(std::function Renderer) + { + m_workspacebar = 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) { @@ -182,6 +187,7 @@ class editor_ui : public ui_layer std::function m_filloptions; std::function m_menuoptions; std::function m_toolbaroptions; + std::function m_workspacebar; bool m_layoutreset{false}; // the default layout is to be built again on the next frame bool m_tooloptionsopen{true}; bool m_inspectoropen{true}; diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index b7f8d803..bc157b80 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -568,6 +568,10 @@ void nodebank_panel::nodebank_reload() { m_previews->clear(); } + if (m_largepreviews) + { + m_largepreviews->clear(); + } std::ifstream file; file.open("nodebank.txt", std::ios_base::in | std::ios_base::binary); std::string line; @@ -619,6 +623,52 @@ void nodebank_panel::index_previews() } } +void nodebank_panel::preview_popup(std::pair> const &Entry) +{ + if (false == ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) + { + return; + } + // the generator makes the images 1024 pixels big; they're shown half as big, as much as the window has room for + auto const size{std::clamp(static_cast(512.0f * Global.ui_scale), 256, 1024)}; + if (m_largepreviews == nullptr) + { + m_largepreviews = std::make_unique(size, 16); + } + auto const *label{Entry.first.c_str()}; + while (*label == ' ') + { + ++label; + } + ImGui::BeginTooltip(); + ImGui::TextUnformatted(label); + std::string model, skin; + if (editor_previews::entry_model(*Entry.second, model, skin)) + { + ImGui::TextDisabled("%s%s%s", model.c_str(), (skin.empty() || skin == "none") ? "" : " ", (skin.empty() || skin == "none") ? "" : skin.c_str()); + } + auto const lookup{m_previewpaths.find(Entry.second.get())}; + if (lookup != m_previewpaths.end()) + { + auto const side{std::min(static_cast(size), ImGui::GetMainViewport()->WorkSize.y * 0.6f)}; + auto texture{m_largepreviews->image(lookup->second)}; + if (texture == 0 && m_previews) + { + // the small image, until the big one is read + texture = m_previews->image(lookup->second); + } + if (texture != 0) + { + ImGui::Image(static_cast(texture), ImVec2(side, side)); + } + else + { + ImGui::TextDisabled("%s", m_largepreviews->missing(lookup->second) ? STR_C("no preview") : "..."); + } + } + ImGui::EndTooltip(); +} + void nodebank_panel::render_cards(std::vector> const *> const &Entries) { auto const &style{ImGui::GetStyle()}; @@ -690,7 +740,7 @@ void nodebank_panel::render_cards(std::vectorupdate(); + } if (ImGui::Button(STR_C("Reload node bank"))) { nodebank_reload(); @@ -833,6 +887,7 @@ void nodebank_panel::render() auto const label{" " + entry.first + "##" + std::to_string(idx)}; if (list_item(label.c_str(), entry.second == m_selectedtemplate)) m_selectedtemplate = entry.second; + preview_popup(entry); ++idx; } } diff --git a/application/editoruipanels.h b/application/editoruipanels.h index e2b081aa..a826d5b4 100644 --- a/application/editoruipanels.h +++ b/application/editoruipanels.h @@ -166,6 +166,8 @@ class nodebank_panel : public ui_panel void index_previews(); // entries of a group as cards with their previews, in rows as many as fit the width void render_cards(std::vector> const *> const &Entries); + // the entry pointed at for a moment: a popup with its preview, big, and the model and skin it shows + void preview_popup(std::pair> const &Entry); // true if the node bank selection is a definition of a node, which the hand-made lists and the model sets can take bool node_selected() const; // members: @@ -175,6 +177,7 @@ class nodebank_panel : public ui_panel // previews of the models: image of each node definition, by its address, and the images read so far std::unordered_map m_previewpaths; std::unique_ptr m_previews; + std::unique_ptr m_largepreviews; // a few, for the popup bool m_previewfolder{false}; // the generator was run, there's the folder of the images // set manager window bool m_setsopen{false}; diff --git a/application/uilayer.cpp b/application/uilayer.cpp index 84fe0906..29bbb06c 100644 --- a/application/uilayer.cpp +++ b/application/uilayer.cpp @@ -32,6 +32,7 @@ int ui_layer::m_palette{0}; ImFont *ui_layer::font_default{nullptr}; ImFont *ui_layer::font_mono{nullptr}; ImFont *ui_layer::font_loading{nullptr}; +ImFont *ui_layer::font_bold{nullptr}; ui_panel::ui_panel(std::string Identifier, bool const Isopen) : is_open(Isopen), m_name(std::move(Identifier)) {} @@ -340,6 +341,14 @@ bool ui_layer::init(GLFWwindow *Window) } } } + for (auto const *bold : {"fonts/dejavusans-bold.ttf", "fonts/DejaVuSans-Bold.ttf"}) + { + if (font_bold == nullptr && FileExists(bold)) + { + ImFontConfig bold_config; + font_bold = m_imguiio->Fonts->AddFontFromFileTTF(bold, Global.ui_fontsize * 1.25f, &bold_config, &ranges[0]); + } + } if (FileExists("fonts/bahnschrift.ttf")) { ImFontConfig loading_config; font_loading = m_imguiio->Fonts->AddFontFromFileTTF("fonts/bahnschrift.ttf", 48, &loading_config, &ranges[0]); diff --git a/application/uilayer.h b/application/uilayer.h index dbd49f1f..7b68646f 100644 --- a/application/uilayer.h +++ b/application/uilayer.h @@ -130,6 +130,8 @@ public: static ImFont *font_default; static ImFont *font_mono; static ImFont *font_loading; + // bigger and bold, for headings like the fields of work of the editor; nullptr if the font isn't there + static ImFont *font_bold; protected: // members diff --git a/editor/editorPreviews.cpp b/editor/editorPreviews.cpp index 67588930..27f84a9f 100644 --- a/editor/editorPreviews.cpp +++ b/editor/editorPreviews.cpp @@ -76,11 +76,21 @@ std::string preview_path(std::string Model, std::string const &Skin) } std::string entry_preview_path(std::string const &Entry) +{ + std::string model, skin; + if (false == entry_model(Entry, model, skin)) + { + return {}; + } + return preview_path(model, skin); +} + +bool entry_model(std::string const &Entry, std::string &Model, std::string &Skin) { auto const nodestart{Entry.find("node")}; if (nodestart == std::string::npos) { - return {}; + return false; } auto const entry{Entry.substr(nodestart)}; // node @@ -93,18 +103,14 @@ std::string entry_preview_path(std::string const &Entry) } if (type != "model") { - return {}; + return false; } // the model and skin are the first two tokens after the location and rotation, read as the generator reads them cParser names(entry, cParser::buffer_TEXT); names.getTokens(9, false); - auto const model{names.getToken()}; - auto const skin{names.getToken(false)}; - if (model.empty()) - { - return {}; - } - return preview_path(model, skin); + Model = names.getToken(); + Skin = names.getToken(false); + return false == Model.empty(); } std::string variant(std::string const &Path, std::set const &Taken) @@ -124,17 +130,15 @@ std::string variant(std::string const &Path, std::set const &Taken) namespace { -// images kept at most; at 128 pixels that's 32 MB -std::size_t const image_limit{512}; // uploaded in a frame at most int const uploads_per_frame{8}; } // namespace -image_cache::image_cache() +image_cache::image_cache(int const Size, std::size_t const Limit) : m_limit{std::max(Limit, 2)} { - // at a bigger ui the images are drawn bigger too - m_size = std::clamp(static_cast(128.0f * std::max(1.0f, Global.ui_scale)), 128, 256); + // at a bigger ui the cards are drawn bigger too; 512 of them at 128 pixels take 32 MB + m_size = (Size > 0 ? Size : std::clamp(static_cast(128.0f * std::max(1.0f, Global.ui_scale)), 128, 256)); m_thread = std::thread(&image_cache::work, this); } @@ -182,7 +186,7 @@ void image_cache::update() { std::lock_guard lock(m_lock); // the queue keeps only what was asked for lately; the rest is asked for again when it's scrolled back into sight - while (m_queue.size() > image_limit / 2) + while (m_queue.size() > m_limit / 2) { auto const lookup{m_entries.find(m_queue.back())}; if (lookup != m_entries.end()) @@ -224,7 +228,7 @@ void image_cache::update() { textures += (entry.second.texture != 0 ? 1 : 0); } - if (textures <= image_limit) + if (textures <= m_limit) { return; } @@ -239,7 +243,7 @@ void image_cache::update() } std::sort(loaded.begin(), loaded.end()); std::vector dropped; - for (std::size_t index = 0; index < textures - image_limit; ++index) + for (std::size_t index = 0; index < textures - m_limit; ++index) { dropped.push_back(*loaded[index].second); } diff --git a/editor/editorPreviews.hpp b/editor/editorPreviews.hpp index 98c4a5a0..4e61738e 100644 --- a/editor/editorPreviews.hpp +++ b/editor/editorPreviews.hpp @@ -35,6 +35,8 @@ std::string preview_path(std::string Model, std::string const &Skin); // path of the image of a node bank entry ("node ... model ..."), without the extension; // empty if the entry isn't a model std::string entry_preview_path(std::string const &Entry); +// model and skin of a node bank entry, as above. returns: false if the entry isn't a model +bool entry_model(std::string const &Entry, std::string &Model, std::string &Skin); // name given to another entry with the same model and skin (other lights or angles): ~2, ~3... added in the order of the // node bank, after the names already Taken std::string variant(std::string const &Path, std::set const &Taken); @@ -44,7 +46,8 @@ std::string variant(std::string const &Path, std::set const &Taken) class image_cache { public: - image_cache(); + // Size: pixels of the longer side of the images, 0 for the size of the cards of the node bank; Limit: images kept at most + explicit image_cache(int const Size = 0, std::size_t const Limit = 512); ~image_cache(); image_cache(image_cache const &) = delete; image_cache &operator=(image_cache const &) = delete; @@ -81,6 +84,7 @@ class image_cache std::unordered_map m_entries; std::uint64_t m_frame{0}; int m_size{128}; // pixels of the longer side of the images + std::size_t m_limit{512}; // shared with the thread std::mutex m_lock; std::condition_variable m_wake; diff --git a/editor/editorSettings.cpp b/editor/editorSettings.cpp index 2fd580cc..4b242132 100644 --- a/editor/editorSettings.cpp +++ b/editor/editorSettings.cpp @@ -100,7 +100,7 @@ bool editorSettings::load() m_nodebank_previews = (value == "1"); else if (key == "nodebank_card") parse(value, m_nodebank_card); - else if (key == "palette") + else if (key == "colours") parse(value, m_palette); else if (key.rfind(origin_prefix, 0) == 0) { @@ -144,7 +144,7 @@ bool editorSettings::save() stream << "signal_offset " << m_signal_offset << "\n"; stream << "nodebank_previews " << (m_nodebank_previews ? 1 : 0) << "\n"; stream << "nodebank_card " << m_nodebank_card << "\n"; - stream << "palette " << m_palette << "\n"; + stream << "colours " << m_palette << "\n"; stream << std::fixed << std::setprecision(3); for (auto const &origin : m_orthophoto_origins) stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n"; diff --git a/editor/editorSettings.hpp b/editor/editorSettings.hpp index 6a6dfa88..7bfb1d36 100644 --- a/editor/editorSettings.hpp +++ b/editor/editorSettings.hpp @@ -50,7 +50,7 @@ public: int nodebank_card() const { return m_nodebank_card; } void nodebank_card(int Size) { m_nodebank_card = Size; } - // colours of the editor: 1 amber, its own; 0 green, as the rest of the simulator + // colours of the editor: 0 green, as the rest of the simulator; 1 amber int palette() const { return m_palette; } void palette(int Palette) { m_palette = Palette; } @@ -62,7 +62,7 @@ private: double m_signal_offset{2.25}; bool m_nodebank_previews{true}; int m_nodebank_card{96}; - int m_palette{1}; + int m_palette{0}; }; // global editor settings instance diff --git a/lang/editor_pl.po b/lang/editor_pl.po index b03aa410..c16a20ae 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -4683,3 +4683,42 @@ msgstr "Zielone, jak reszta symulatora" msgid "Amber, of the editor" msgstr "Bursztynowe, edytora" + +msgid "Surroundings" +msgstr "Otoczenie" + +msgid "Roads" +msgstr "Drogi" + +msgid "Sculpt" +msgstr "Rzeźbienie" + +msgid "Chunks" +msgstr "Kafle" + +msgid "Models around the line: select, insert, brush, area fill, copy to the node bank" +msgstr "Modele wokół linii: zaznaczanie, wstawianie, pędzel, wypełnianie obszaru, kopiowanie do banku" + +msgid "Tracks and switches, signals, objects along the track, checks of the line" +msgstr "Tory i rozjazdy, sygnalizatory, obiekty wzdłuż toru, kontrole linii" + +msgid "Roads and junctions, level crossings, the points where the traffic comes and goes" +msgstr "Drogi i skrzyżowania, przejazdy, punkty pojawiania się i znikania ruchu" + +msgid "Terrain patches and chunks, sculpting, streaming, orthophoto" +msgstr "Płaty i kafle terenu, rzeźbienie, strumieniowanie, ortofotomapa" + +msgid "LMB picks the models, as in the surroundings" +msgstr "LPM zaznacza modele, jak w otoczeniu" + +msgid "LMB raises the terrain under the brush, Shift+LMB lowers it" +msgstr "LPM podnosi teren pod pędzlem, Shift+LPM go obniża" + +msgid "LMB evens the terrain out under the brush" +msgstr "LPM wyrównuje teren pod pędzlem" + +msgid "LMB adds a chunk next to the clicked one, Shift+LMB deletes it" +msgstr "LPM dodaje kafel obok klikniętego, Shift+LPM go usuwa" + +msgid "A vehicle to drive, put on the selected track; it isn't written to the scenery files" +msgstr "Pojazd do jazdy, stawiany na zaznaczonym torze; nie trafia do plików scenerii"