17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 04:29:18 +02:00

feat(editor): the node bank in two panes, as the assets of unity: its groups on the left with the scenery templates, the entries of the chosen one on the right, as cards or a list, a search going through all of them; the models dragged from the node bank onto a model set, the templates of a set or the list of the brush or the area fill; the templates of the model sets shown with their previews; the model sets docked by the node bank by default; Polish translation

This commit is contained in:
2026-10-09 12:01:00 +02:00
parent 645a54a739
commit 1bf687b4c9
4 changed files with 379 additions and 130 deletions

View File

@@ -98,8 +98,12 @@ void editor_ui::build_default_layout(unsigned int const Dockspace)
ImGui::DockBuilderDockWindow(window, leftbottom);
for (auto const *window : {"###inspector", "###editorsettings", "###Include database", "###orthophoto", "###environment"})
ImGui::DockBuilderDockWindow(window, right);
for (auto const *window : {"###Node bank", "###profilestrip", "###trackanalysis", "###structuregauge", "###editorhistory", "###modelsets"})
ImGui::DockBuilderDockWindow(window, bottom);
// bottom: the node bank, and the model sets by its side, the models are dragged from the one to the other
ImGuiID bottomleft{bottom};
auto const bottomright{ImGui::DockBuilderSplitNode(bottomleft, ImGuiDir_Right, 0.45f, nullptr, &bottomleft)};
for (auto const *window : {"###Node bank", "###profilestrip", "###trackanalysis", "###structuregauge", "###editorhistory"})
ImGui::DockBuilderDockWindow(window, bottomleft);
ImGui::DockBuilderDockWindow("###modelsets", bottomright);
ImGui::DockBuilderFinish(Dockspace);
}

View File

@@ -59,6 +59,71 @@ bool list_item(char const *Label, bool const Selected)
ImGui::PopStyleColor();
return clicked;
}
// name of the payload of a node definition dragged out of the node bank
char const *const template_payload{"EU07_NODEBANK_ENTRY"};
// a node definition of the node bank dropped on the item drawn last. returns: true if one was, with its text
bool dropped_template(std::string &Template)
{
auto dropped{false};
if (ImGui::BeginDragDropTarget())
{
if (auto const *payload{ImGui::AcceptDragDropPayload(template_payload)}; payload != nullptr && payload->DataSize > 0)
{
Template.assign(static_cast<char const *>(payload->Data), static_cast<std::size_t>(payload->DataSize));
dropped = true;
}
ImGui::EndDragDropTarget();
}
return dropped;
}
// a card: the image fills its square, or what's missing is said in its place, and the label is under it, cut short with an
// ellipsis. returns: true if it was clicked; the card is the item drawn last
bool card(ImVec2 const &Cell, float const Side, std::uint64_t const Texture, char const *Placeholder, char const *Label, bool const Selected)
{
auto const &style{ImGui::GetStyle()};
auto const position{ImGui::GetCursorScreenPos()};
auto const clicked{ImGui::InvisibleButton("##card", Cell)};
auto const hovered{ImGui::IsItemHovered()};
auto *list{ImGui::GetWindowDrawList()};
ImVec2 const end{position.x + Cell.x, position.y + Cell.y};
list->AddRectFilled(position, end, ImGui::GetColorU32(Selected ? ImGuiCol_Header : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_FrameBg), style.FrameRounding);
ImVec2 const imagemin{position.x + 2.0f, position.y + 2.0f};
ImVec2 const imagemax{position.x + Side - 2.0f, position.y + Side - 2.0f};
if (Texture != 0)
{
list->AddImage(static_cast<ImTextureID>(Texture), imagemin, imagemax);
}
else
{
list->AddRect(imagemin, imagemax, ImGui::GetColorU32(ImGuiCol_Border), style.FrameRounding);
auto const size{ImGui::CalcTextSize(Placeholder)};
list->PushClipRect(imagemin, imagemax, true);
list->AddText(ImVec2(imagemin.x + std::max(0.0f, (imagemax.x - imagemin.x - size.x) * 0.5f), imagemin.y + (imagemax.y - imagemin.y - size.y) * 0.5f), ImGui::GetColorU32(ImGuiCol_TextDisabled), Placeholder);
list->PopClipRect();
}
while (*Label == ' ')
{
++Label;
}
ImVec2 const labelmin{position.x + style.FramePadding.x, position.y + Side + style.FramePadding.y};
ImVec2 const labelmax{end.x - style.FramePadding.x, end.y};
ImGui::RenderTextEllipsis(list, labelmin, labelmax, labelmax.x, labelmax.x, Label, nullptr, nullptr);
return clicked;
}
// cells of the cards of the side, and how many of them fit the width in a row
ImVec2 card_cell(float const Side)
{
return ImVec2(Side, Side + ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 2.0f);
}
int card_columns(ImVec2 const &Cell)
{
auto const &style{ImGui::GetStyle()};
return std::max(1, static_cast<int>((ImGui::GetContentRegionAvail().x + style.ItemSpacing.x) / (Cell.x + style.ItemSpacing.x)));
}
} // namespace
void itemproperties_panel::update(scene::basic_node *Node)
@@ -602,6 +667,7 @@ void nodebank_panel::nodebank_reload()
void nodebank_panel::index_previews()
{
m_previewpaths.clear();
m_templatepreviews.clear();
std::error_code error;
m_previewfolder = std::filesystem::is_directory(editor_previews::folder, error);
// entries with the same model and skin are numbered in the order of the file, the way the generator numbers them
@@ -620,12 +686,14 @@ void nodebank_panel::index_previews()
auto const name{editor_previews::variant(path, taken)};
taken.insert(name);
m_previewpaths[entry.second.get()] = name + ".png";
m_templatepreviews.try_emplace(*entry.second, name + ".png");
}
}
void nodebank_panel::preview_popup(std::pair<std::string, std::shared_ptr<std::string>> const &Entry)
{
if (false == ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay))
// not while something's dragged, the tooltip of the drag is there then
if (false == ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay) || ImGui::GetDragDropPayload() != nullptr)
{
return;
}
@@ -669,13 +737,62 @@ void nodebank_panel::preview_popup(std::pair<std::string, std::shared_ptr<std::s
ImGui::EndTooltip();
}
std::uint64_t nodebank_panel::preview_texture(std::string const &Path, char const *&Placeholder)
{
if (Path.empty() || m_previews == nullptr)
{
Placeholder = STR_C("not a model");
return 0;
}
auto const texture{m_previews->image(Path)};
Placeholder = (m_previews->missing(Path) ? STR_C("no preview") : "...");
return texture;
}
std::string const &nodebank_panel::template_preview(std::string const &Template) const
{
static std::string const none;
auto const lookup{m_templatepreviews.find(Template)};
return lookup != m_templatepreviews.end() ? lookup->second : none;
}
void nodebank_panel::drag_entry(std::pair<std::string, std::shared_ptr<std::string>> const &Entry)
{
// the scenery templates are placed with include directives, the lists of the models have no use for them
if (Entry.second->empty() || Entry.second->starts_with(editor_includes::directive_mark))
{
return;
}
if (false == ImGui::BeginDragDropSource())
{
return;
}
ImGui::SetDragDropPayload(template_payload, Entry.second->data(), Entry.second->size());
auto const lookup{m_previewpaths.find(Entry.second.get())};
if (lookup != m_previewpaths.end() && m_previews != nullptr)
{
if (auto const texture{m_previews->image(lookup->second)}; texture != 0)
{
auto const side{64.0f * std::max(1.0f, Global.ui_scale)};
ImGui::Image(static_cast<ImTextureID>(texture), ImVec2(side, side));
}
}
auto const *label{Entry.first.c_str()};
while (*label == ' ')
{
++label;
}
ImGui::TextUnformatted(label);
ImGui::TextDisabled("%s", STR_C("Drop it on a model set, or on the list of the brush or the area fill"));
ImGui::EndDragDropSource();
}
void nodebank_panel::render_cards(std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> const &Entries)
{
auto const &style{ImGui::GetStyle()};
auto const card{std::max(16.0f, EditorSettings.nodebank_card() * Global.ui_scale)};
auto const labelheight{ImGui::GetTextLineHeight()};
ImVec2 const cell{card, card + labelheight + style.FramePadding.y * 2.0f};
auto const columns{std::max(1, static_cast<int>((ImGui::GetContentRegionAvail().x + style.ItemSpacing.x) / (cell.x + style.ItemSpacing.x)))};
auto const side{std::max(16.0f, EditorSettings.nodebank_card() * Global.ui_scale)};
auto const cell{card_cell(side)};
auto const columns{card_columns(cell)};
auto const rows{(static_cast<int>(Entries.size()) + columns - 1) / columns};
ImGuiListClipper clipper;
clipper.Begin(rows, cell.y + style.ItemSpacing.y);
@@ -696,49 +813,15 @@ void nodebank_panel::render_cards(std::vector<std::pair<std::string, std::shared
ImGui::SameLine();
}
ImGui::PushID(entry.second.get());
auto const position{ImGui::GetCursorScreenPos()};
if (ImGui::InvisibleButton("##card", cell))
auto const lookup{m_previewpaths.find(entry.second.get())};
char const *placeholder{nullptr};
auto const texture{preview_texture(lookup != m_previewpaths.end() ? lookup->second : std::string{}, placeholder)};
if (card(cell, side, texture, placeholder, entry.first.c_str(), entry.second == m_selectedtemplate))
{
m_selectedtemplate = entry.second;
}
auto const hovered{ImGui::IsItemHovered()};
auto const selected{entry.second == m_selectedtemplate};
auto *list{ImGui::GetWindowDrawList()};
ImVec2 const end{position.x + cell.x, position.y + cell.y};
list->AddRectFilled(position, end, ImGui::GetColorU32(selected ? ImGuiCol_Header : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_FrameBg), style.FrameRounding);
// the image fills the square of the card; without one, what's missing is said in its place
ImVec2 const imagemin{position.x + 2.0f, position.y + 2.0f};
ImVec2 const imagemax{position.x + card - 2.0f, position.y + card - 2.0f};
auto const lookup{m_previewpaths.find(entry.second.get())};
std::uint64_t texture{0};
char const *placeholder{STR_C("not a model")};
if (lookup != m_previewpaths.end())
{
texture = m_previews->image(lookup->second);
placeholder = (m_previews->missing(lookup->second) ? STR_C("no preview") : "...");
}
if (texture != 0)
{
list->AddImage(static_cast<ImTextureID>(texture), imagemin, imagemax);
}
else
{
list->AddRect(imagemin, imagemax, ImGui::GetColorU32(ImGuiCol_Border), style.FrameRounding);
auto const size{ImGui::CalcTextSize(placeholder)};
list->PushClipRect(imagemin, imagemax, true);
list->AddText(ImVec2(imagemin.x + std::max(0.0f, (imagemax.x - imagemin.x - size.x) * 0.5f), imagemin.y + (imagemax.y - imagemin.y - size.y) * 0.5f), ImGui::GetColorU32(ImGuiCol_TextDisabled), placeholder);
list->PopClipRect();
}
// the label under the image, cut short with an ellipsis; the whole of it in the tooltip
auto const *label{entry.first.c_str()};
while (*label == ' ')
{
++label;
}
ImVec2 const labelmin{position.x + style.FramePadding.x, position.y + card + style.FramePadding.y};
ImVec2 const labelmax{end.x - style.FramePadding.x, end.y};
ImGui::RenderTextEllipsis(list, labelmin, labelmax, labelmax.x, labelmax.x, label, nullptr, nullptr);
if (hovered)
drag_entry(entry);
if (ImGui::IsItemHovered())
{
preview_popup(entry);
}
@@ -749,6 +832,87 @@ void nodebank_panel::render_cards(std::vector<std::pair<std::string, std::shared
clipper.End();
}
void nodebank_panel::render_list(std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> const &Entries)
{
for (auto const *entry : Entries)
{
ImGui::PushID(entry->second.get());
auto const label{" " + entry->first};
if (list_item(label.c_str(), entry->second == m_selectedtemplate))
{
m_selectedtemplate = entry->second;
}
drag_entry(*entry);
preview_popup(*entry);
ImGui::PopID();
}
}
void nodebank_panel::render_templates_list(std::string const &Filter)
{
if (false == EditorIncludes.scanned())
{
EditorIncludes.scan();
}
auto const &templates{EditorIncludes.entries()};
std::string const *category{nullptr};
auto listed{0};
for (auto const entryidx : EditorIncludes.ready())
{
auto const &entry{templates[entryidx]};
if (false == Filter.empty() && false == contains(entry.name, Filter) && false == contains(entry.file, Filter) && false == contains(entry.category, Filter))
{
continue;
}
if (false == entry.category.empty() && (category == nullptr || *category != entry.category))
{
ImGui::TextDisabled(" %s", entry.category.c_str());
}
category = &entry.category;
auto const label{(entry.category.empty() ? " " : " ") + entry.name + " (" + Bezogonkow(entry.file) + ")##inc" + std::to_string(entryidx)};
if (list_item(label.c_str(), entry.statement == m_selectedtemplate))
m_selectedtemplate = entry.statement;
++listed;
}
if (listed == 0)
{
ImGui::TextDisabled(EditorIncludes.ready().empty() ? " (none yet, describe the templates in the Include database window)" : " (no match)");
}
}
void nodebank_panel::render_set_cards(int const Setid)
{
auto const *set{EditorModelSets.find(Setid)};
if (set == nullptr)
{
return;
}
// a bit smaller than in the node bank, the window of the sets is the smaller one
auto const side{std::max(48.0f, EditorSettings.nodebank_card() * Global.ui_scale * 0.75f)};
auto const cell{card_cell(side)};
auto const columns{card_columns(cell)};
for (int index = 0; index < static_cast<int>(set->templates.size()); ++index)
{
if (index % columns != 0)
{
ImGui::SameLine();
}
ImGui::PushID(index);
char const *placeholder{nullptr};
auto const texture{preview_texture(template_preview(set->templates[index]), placeholder)};
auto const &label{index < static_cast<int>(set->labels.size()) ? set->labels[index] : set->templates[index]};
if (card(cell, side, texture, placeholder, label.c_str(), index == m_setsentry))
{
m_setsentry = index;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", label.c_str());
}
ImGui::PopID();
}
}
void nodebank_panel::render()
{
if (false == is_open)
@@ -829,101 +993,132 @@ void nodebank_panel::render()
ImGui::TextDisabled("%s", STR_C("No previews yet: they're made by eu07 -generate-nodebank-previews"));
}
}
if (previews && ImGui::BeginChild("##nodebankcards", ImVec2(-1, listheight), ImGuiChildFlags_Borders))
// two panes: the groups of the node bank on the left, like folders, and the entries of the chosen one on the right.
// a search goes through all of them
struct category
{
// the groups as in the list, the entries of an open one as cards
auto isvisible{false};
auto const searchfilter{std::string(m_nodesearch)};
std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> cards;
for (auto const &entry : m_nodebank)
std::string const *name;
std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> entries;
int matches{0};
};
static std::string const ungrouped{"(ungrouped)"};
auto const searchfilter{std::string(m_nodesearch)};
std::vector<category> categories;
for (auto const &entry : m_nodebank)
{
if (entry.second->empty())
{
if (entry.second->empty())
{
if (false == cards.empty())
{
render_cards(cards);
cards.clear();
}
isvisible = ImGui::CollapsingHeader(entry.first.c_str());
}
else if (isvisible && (searchfilter.empty() || contains(entry.first, searchfilter)))
{
cards.push_back(&entry);
}
categories.push_back({&entry.first, {}});
continue;
}
if (false == cards.empty())
if (categories.empty())
{
render_cards(cards);
// templates listed before the first header, as group_names() has them
categories.push_back({&ungrouped, {}});
}
auto &group{categories.back()};
group.entries.push_back(&entry);
if (false == searchfilter.empty() && contains(entry.first, searchfilter))
{
++group.matches;
}
ImGui::Separator();
ImGui::TextDisabled("%s", STR_C("Scenery templates (.inc) are listed with the previews off"));
}
if (previews)
auto const templates{Global.editor_session};
if (m_category >= static_cast<int>(categories.size()) || (m_category == scenery_templates && false == templates) || m_category < scenery_templates)
{
m_category = 0;
}
// the panes of the height of the table, inside the padding of its cells
auto const paneheight{listheight - 2.0f * ImGui::GetStyle().CellPadding.y - 1.0f};
if (ImGui::BeginTable("##nodebankpanes", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV, ImVec2(-1, listheight)))
{
ImGui::TableSetupColumn("##groups", ImGuiTableColumnFlags_WidthFixed, 170.0f * Global.ui_scale);
ImGui::TableSetupColumn("##entries", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
if (ImGui::BeginChild("##groups", ImVec2(0, paneheight)))
{
auto const searching{false == searchfilter.empty()};
for (int index = 0; index < static_cast<int>(categories.size()); ++index)
{
auto const &group{categories[index]};
auto const count{searching ? group.matches : static_cast<int>(group.entries.size())};
auto const label{*group.name + " (" + std::to_string(count) + ")##group" + std::to_string(index)};
if (searching && count == 0)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
}
// a group chosen while searching is shown whole, the search is done
if (list_item(label.c_str(), false == searching && index == m_category))
{
m_category = index;
m_nodesearch[0] = '\0';
}
if (searching && count == 0)
{
ImGui::PopStyleColor();
}
}
if (templates)
{
ImGui::Separator();
if (list_item(STR_C("Scenery templates (.inc)"), m_category == scenery_templates))
{
m_category = scenery_templates;
}
}
}
ImGui::EndChild();
}
else if (ImGui::BeginListBox("##nodebank", ImVec2(-1, listheight)))
{
auto idx{0};
auto isvisible{false};
auto const searchfilter{std::string(m_nodesearch)};
for (auto const &entry : m_nodebank)
ImGui::TableSetColumnIndex(1);
if (ImGui::BeginChild("##entries", ImVec2(0, paneheight)))
{
if (entry.second->empty())
if (m_category == scenery_templates)
{
// special case, header indicator
isvisible = ImGui::CollapsingHeader(entry.first.c_str());
render_templates_list(searchfilter);
}
else
else if (false == searchfilter.empty())
{
if (false == isvisible)
// what was found, under the names of the groups
std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> found;
for (auto const &group : categories)
{
continue;
if (group.matches == 0)
{
continue;
}
found.clear();
for (auto const *entry : group.entries)
{
if (contains(entry->first, searchfilter))
{
found.push_back(entry);
}
}
ImGui::SeparatorText(group.name->c_str());
if (previews)
{
render_cards(found);
}
else
{
render_list(found);
}
}
if (false == searchfilter.empty() && false == contains(entry.first, searchfilter))
}
else if (false == categories.empty())
{
if (previews)
{
continue;
render_cards(categories[m_category].entries);
}
else
{
render_list(categories[m_category].entries);
}
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;
}
}
// scenery templates. placing one adds an include directive to a scenery file, so they take scenery opened for editing
if (Global.editor_session && ImGui::CollapsingHeader(STR_C("Scenery templates (.inc)")))
{
if (false == EditorIncludes.scanned())
{
EditorIncludes.scan();
}
auto const &templates{EditorIncludes.entries()};
std::string const *category{nullptr};
auto listed{0};
for (auto const entryidx : EditorIncludes.ready())
{
auto const &entry{templates[entryidx]};
if (false == searchfilter.empty() && false == contains(entry.name, searchfilter) && false == contains(entry.file, searchfilter) && false == contains(entry.category, searchfilter))
{
continue;
}
if (false == entry.category.empty() && (category == nullptr || *category != entry.category))
{
ImGui::TextDisabled(" %s", entry.category.c_str());
}
category = &entry.category;
auto const label{(entry.category.empty() ? " " : " ") + entry.name + " (" + Bezogonkow(entry.file) + ")##inc" + std::to_string(entryidx)};
if (list_item(label.c_str(), entry.statement == m_selectedtemplate))
m_selectedtemplate = entry.statement;
++listed;
}
if (listed == 0)
{
ImGui::TextDisabled(EditorIncludes.ready().empty() ? " (none yet, describe the templates in the Include database window)" : " (no match)");
}
}
ImGui::EndListBox();
ImGui::EndChild();
ImGui::EndTable();
}
ImGui::PopItemWidth();
}
@@ -999,6 +1194,11 @@ void nodebank_panel::manual_list(char const *Id, std::vector<std::string> &List,
}
ImGui::EndListBox();
}
// a model dragged from the node bank onto the list goes to its end
if (std::string dropped; dropped_template(dropped))
{
List.push_back(dropped);
}
if (ImGui::Button(STR_C("Add selected")))
{
if (node_selected())
@@ -1121,7 +1321,8 @@ void nodebank_panel::render_sets_window()
// left side: the sets
auto const buttonsheight{ImGui::GetFrameHeightWithSpacing()};
ImGui::BeginGroup();
ImGui::BeginChild("##setlist", ImVec2(190 * Global.ui_scale, -buttonsheight), true);
// the list of the sets takes up to a part of a narrow window, the templates of the set need the room
ImGui::BeginChild("##setlist", ImVec2(std::min(190.0f * Global.ui_scale, ImGui::GetContentRegionAvail().x * 0.4f), -buttonsheight), true);
for (auto const &set : sets)
{
auto const label{set.name + " (" + std::to_string(set.templates.size()) + ")##set" + std::to_string(set.id)};
@@ -1130,6 +1331,13 @@ void nodebank_panel::render_sets_window()
m_setsselected = set.id;
m_setsentry = -1;
}
// a model dragged from the node bank onto a set goes to its end
if (std::string dropped; dropped_template(dropped))
{
EditorModelSets.add(set.id, {dropped});
m_setsselected = set.id;
m_setsentry = -1;
}
}
if (sets.empty())
{
@@ -1201,17 +1409,30 @@ void nodebank_panel::render_sets_window()
}
ImGui::BeginChild("##setentries", ImVec2(0, -buttonsheight * 2), true);
for (int idx = 0; idx < static_cast<int>(edited->labels.size()); ++idx)
// with the previews of the models, as in the node bank
if (EditorSettings.nodebank_previews() && m_previews != nullptr)
{
auto const label{edited->labels[idx] + "##entry" + std::to_string(idx)};
if (list_item(label.c_str(), idx == m_setsentry))
m_setsentry = idx;
render_set_cards(edited->id);
}
else
{
for (int idx = 0; idx < static_cast<int>(edited->labels.size()); ++idx)
{
auto const label{edited->labels[idx] + "##entry" + std::to_string(idx)};
if (list_item(label.c_str(), idx == m_setsentry))
m_setsentry = idx;
}
}
if (edited->templates.empty())
{
ImGui::TextDisabled(STR_C("(empty) select a template in the node bank and press \"Add selected\""));
ImGui::TextDisabled("%s", STR_C("or drag models here from the node bank"));
}
ImGui::EndChild();
if (std::string dropped; dropped_template(dropped))
{
EditorModelSets.add(edited->id, {dropped});
}
auto const setid{edited->id};
if (ImGui::Button(STR_C("Add selected")))

View File

@@ -166,6 +166,18 @@ 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<std::pair<std::string, std::shared_ptr<std::string>> const *> const &Entries);
// the same entries as a list, for when the previews are off
void render_list(std::vector<std::pair<std::string, std::shared_ptr<std::string>> const *> const &Entries);
// the scenery templates (.inc), as a list; they're placed with include directives, so they take a scenery opened for editing
void render_templates_list(std::string const &Filter);
// the entry dragged from the item drawn last, to drop it on a list of the model sets or of a tool
void drag_entry(std::pair<std::string, std::shared_ptr<std::string>> const &Entry);
// image of a node definition as the node bank has it, or an empty string
std::string const &template_preview(std::string const &Template) const;
// texture of the small preview of the image, 0 if there's none (yet)
std::uint64_t preview_texture(std::string const &Path, char const *&Placeholder);
// the templates of the edited model set as cards with their previews
void render_set_cards(int const Setid);
// 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<std::string, std::shared_ptr<std::string>> 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
@@ -179,6 +191,10 @@ class nodebank_panel : public ui_panel
std::unique_ptr<editor_previews::image_cache> m_previews;
std::unique_ptr<editor_previews::image_cache> m_largepreviews; // a few, for the popup
bool m_previewfolder{false}; // the generator was run, there's the folder of the images
std::unordered_map<std::string, std::string> m_templatepreviews; // images of the node definitions by their text, for the model sets
// the group of the node bank shown, by its place among the groups; scenery_templates for the scenery templates
static int constexpr scenery_templates{-1};
int m_category{0};
// set manager window
bool m_setsopen{false};
int m_setsselected{0}; // id of the user set being edited

View File

@@ -3860,7 +3860,7 @@ msgid "Not for the node bank yet: %s."
msgstr "Jeszcze nie do banku węzłów: %s."
msgid "A template placed from the node bank gets the cursor location in the pos.* parameters, the rotation set in the\nInsert tab in rot.y, and a unique name (the default value followed by a random suffix) in the name parameter.\nThe other parameters receive their default values."
msgstr "Szablon wstawiony z banku węzłów dostaje położenie kursora w parametrach pos.*, obrót ustawiony w zakładce\nWstaw w rot.y, a w parametrze name niepowtarzalną nazwę (wartość domyślna z losowym przyrostkiem).\nPozostałe parametry dostają wartości domyślne."
msgstr "Szablon wstawiony z banku obiektów dostaje położenie kursora w parametrach pos.*, obrót ustawiony w zakładce\nWstaw w rot.y, a w parametrze name niepowtarzalną nazwę (wartość domyślna z losowym przyrostkiem).\nPozostałe parametry dostają wartości domyślne."
msgid "Suggest roles"
msgstr "Podpowiedz role"
@@ -5053,3 +5053,11 @@ msgstr "LPM: początek drogi, potem każdy kolejny punkt; na wolnym końcu, boku
msgid "Level crossings, and the points where vehicles appear on the roads or are taken off them"
msgstr "Przejazdy kolejowe oraz punkty, w których pojazdy pojawiają się na drogach albo są z nich zdejmowane"
# scenery editor: node bank in two panes, models dragged to the sets
msgid "Drop it on a model set, or on the list of the brush or the area fill"
msgstr "Upuść go na zestaw modeli albo na listę pędzla lub wypełniania obszaru"
msgid "or drag models here from the node bank"
msgstr "albo przeciągnij tu modele z banku obiektów"