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"