18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-13 12:20:51 +01:00

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

This commit is contained in:
2026-10-09 00:42:17 +02:00
parent f184ce7a6a
commit 791d5d052c
15 changed files with 407 additions and 143 deletions

View File

@@ -488,6 +488,7 @@ editor_mode::editor_mode() {
ui()->set_fill_options([this]() { render_area_fill(); }); ui()->set_fill_options([this]() { render_area_fill(); });
ui()->set_gizmo_options([this]() { render_gizmo_options(); }); ui()->set_gizmo_options([this]() { render_gizmo_options(); });
ui()->set_toolbar_options([this]() { render_toolbar(); }); 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_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::roads, [this]() { render_road_tool_options(); }, [this]() { render_road_selection(); });
ui()->set_workspace_options(editor_ui::workspace::terrain, [this]() { render_terrain_tool_options(); }); 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); 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<int>(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() void editor_mode::render_toolbar()
{ {
// edit modes of the node bank, each of them takes the mouse over from the track window auto const area{current_work_area()};
std::pair<char const *, nodebank_panel::edit_mode> const modes[] = { switch (area)
{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 &mode{modes[index]}; case work_area::surroundings:
if (ImGui::MenuItem(mode.first, nullptr, false == workspace && ui()->mode() == mode.second)) {
choose_edit_mode(mode.second); // edit modes of the node bank
if (ImGui::IsItemHovered()) std::pair<char const *, nodebank_panel::edit_mode> const modes[] = {
ImGui::SetTooltip("%s (%d)", mode.first, static_cast<int>(index + 1)); {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<int>(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(); ImGui::Separator();
bool const tracks{m_track_window_open && false == m_terrain_open}; // the gizmo moves the models and the tracks; on the terrain the mouse is the brush's
bool const vehicle{tracks && m_track_tab == track_tab::lineside && m_vehicle.open}; if (m_gizmo_enabled && area != work_area::terrain)
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)
{ {
std::pair<char const *, gizmo_operation> const operations[] = { std::pair<char const *, gizmo_operation> const operations[] = {
{STR_C("Translate (Q)"), gizmo_operation::translate}, {STR_C("Rotate (W)"), gizmo_operation::rotate}, {STR_C("Scale (E)"), gizmo_operation::scale}}; {STR_C("Translate (Q)"), gizmo_operation::translate}, {STR_C("Rotate (W)"), gizmo_operation::rotate}, {STR_C("Scale (E)"), gizmo_operation::scale}};

View File

@@ -233,6 +233,8 @@ class editor_mode : public application_mode, private editor_track::observer
void render_terrain_ui(); 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 // 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(); 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 // 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); void terrain_workspace(bool const Open);
// creates a large terrain as a grid of adjacent chunks (each its own editable patch) // 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(); void render_toolbar();
// an edit mode of the node bank as the field of work, from the toolbar or its key (1-5) // 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); 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_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_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 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 // the track tools in the tool options window, and the selected path in the inspector
void render_track_tool_options(); void render_track_tool_options();
void render_track_selection(); 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); 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_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); 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_menu();
void render_road_tool_options(); 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(); void render_road_selection();
bool render_road_layout(road_node::state &State); 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); bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value);

View File

@@ -2261,6 +2261,57 @@ bool editor_mode::render_road_layout(road_node::state &State)
return changed; 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<int>(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<int>(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() // 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() void editor_mode::render_road_tool_options()
{ {
@@ -2269,28 +2320,7 @@ void editor_mode::render_road_tool_options()
if (false == editor_road::available(&reason)) if (false == editor_road::available(&reason))
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", reason.c_str()); ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", reason.c_str());
auto const previoustool{tool.tool}; // the tools and what the place tool puts are chosen in the toolbar
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);
if (tool.tool == 1) if (tool.tool == 1)
{ {
@@ -2338,12 +2368,6 @@ void editor_mode::render_road_tool_options()
} }
else if (tool.tool == 2) 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); tool.placekind = std::clamp(tool.placekind, 0, 3);
switch (tool.placekind) switch (tool.placekind)
{ {

View File

@@ -772,7 +772,7 @@ void editor_mode::render_track_inspector()
void editor_mode::render_track_tool_options() void editor_mode::render_track_tool_options()
{ {
auto *track{selected_track()}; auto *track{selected_track()};
render_track_modes(); // the tools themselves are in the toolbar
render_track_search(); render_track_search();
ImGui::Spacing(); ImGui::Spacing();
render_track_guide(); render_track_guide();
@@ -827,33 +827,41 @@ void editor_mode::render_track_selection()
render_path_ui(); render_path_ui();
} }
void editor_mode::render_track_modes() void editor_mode::render_track_toolbar()
{ {
auto const &modes{track_modes()}; auto const &modes{track_modes()};
// the tools in groups: building the track, what goes by it, the checks auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.open};
auto const group = [&](char const *Label, std::initializer_list<track_tab> const Tabs) { // a tool chosen again gives way to the select one
ImGui::SeparatorText(STR_C(Label)); auto const tool = [&](track_tab const Tab) {
if (false == ImGui::BeginTable(Label, 3, ImGuiTableFlags_SizingStretchSame)) auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == Tab; })};
if (found == modes.end())
return; return;
for (auto const tab : Tabs) 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<int>(found - modes.begin()))};
auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == tab; })}; if (ImGui::MenuItem(label.c_str(), nullptr, chosen))
if (found == modes.end()) show_track_tab(chosen ? track_tab::path : Tab);
continue; if (ImGui::IsItemHovered())
ImGui::TableNextColumn(); ImGui::SetTooltip("%s [%s]", STR_C(found->tooltip), found->key);
auto const index{static_cast<int>(found - modes.begin())};
auto const label{std::string{STR_C(found->label)} + "###trackmode" + std::to_string(index)};
if (ImGui::Selectable(label.c_str(), m_track_tab == tab))
show_track_tab(tab);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s [%s]", STR_C(found->tooltip), found->key);
}
ImGui::EndTable();
}; };
group("Build", {track_tab::path, track_tab::lay, track_tab::turnout, track_tab::straights, track_tab::route, track_tab::turntable}); // building the track, what goes by it, the checks
group("By the track", {track_tab::signals, track_tab::lineside}); for (auto const tab : {track_tab::path, track_tab::lay, track_tab::turnout, track_tab::straights, track_tab::route, track_tab::turntable})
group("Checks", {track_tab::profile, track_tab::speed, track_tab::joints, track_tab::infra}); tool(tab);
ImGui::Checkbox(STR_C("Structure gauge"), &m_gauge.open); 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()) 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)")); ImGui::SetTooltip("%s", STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)"));
} }

View File

@@ -586,7 +586,19 @@ void editor_ui::render_()
{ {
auto *viewport{ImGui::GetMainViewport()}; auto *viewport{ImGui::GetMainViewport()};
auto const flags{ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar}; 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::BeginViewportSideBar("##editortoolbar", viewport, ImGuiDir_Up, ImGui::GetFrameHeight(), flags))
{ {
if (ImGui::BeginMenuBar()) if (ImGui::BeginMenuBar())

View File

@@ -69,6 +69,11 @@ class editor_ui : public ui_layer
{ {
m_toolbaroptions = std::move(Renderer); 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<void()> Renderer)
{
m_workspacebar = std::move(Renderer);
}
// draws the gizmo settings in the toolset window (the gizmo state lives in the editor mode) // draws the gizmo settings in the toolset window (the gizmo state lives in the editor mode)
void set_gizmo_options(std::function<void()> Renderer) void set_gizmo_options(std::function<void()> Renderer)
{ {
@@ -182,6 +187,7 @@ class editor_ui : public ui_layer
std::function<void()> m_filloptions; std::function<void()> m_filloptions;
std::function<void()> m_menuoptions; std::function<void()> m_menuoptions;
std::function<void()> m_toolbaroptions; std::function<void()> m_toolbaroptions;
std::function<void()> m_workspacebar;
bool m_layoutreset{false}; // the default layout is to be built again on the next frame bool m_layoutreset{false}; // the default layout is to be built again on the next frame
bool m_tooloptionsopen{true}; bool m_tooloptionsopen{true};
bool m_inspectoropen{true}; bool m_inspectoropen{true};

View File

@@ -568,6 +568,10 @@ void nodebank_panel::nodebank_reload()
{ {
m_previews->clear(); m_previews->clear();
} }
if (m_largepreviews)
{
m_largepreviews->clear();
}
std::ifstream file; std::ifstream file;
file.open("nodebank.txt", std::ios_base::in | std::ios_base::binary); file.open("nodebank.txt", std::ios_base::in | std::ios_base::binary);
std::string line; std::string line;
@@ -619,6 +623,52 @@ void nodebank_panel::index_previews()
} }
} }
void nodebank_panel::preview_popup(std::pair<std::string, std::shared_ptr<std::string>> 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<int>(512.0f * Global.ui_scale), 256, 1024)};
if (m_largepreviews == nullptr)
{
m_largepreviews = std::make_unique<editor_previews::image_cache>(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<float>(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<ImTextureID>(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<std::pair<std::string, std::shared_ptr<std::string>> const *> const &Entries) 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 &style{ImGui::GetStyle()};
@@ -690,7 +740,7 @@ void nodebank_panel::render_cards(std::vector<std::pair<std::string, std::shared
ImGui::RenderTextEllipsis(list, labelmin, labelmax, labelmax.x, labelmax.x, label, nullptr, nullptr); ImGui::RenderTextEllipsis(list, labelmin, labelmax, labelmax.x, labelmax.x, label, nullptr, nullptr);
if (hovered) if (hovered)
{ {
ImGui::SetTooltip("%s", label); preview_popup(entry);
} }
ImGui::PopID(); ImGui::PopID();
} }
@@ -724,6 +774,10 @@ void nodebank_panel::render()
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags)) if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
{ {
if (m_largepreviews)
{
m_largepreviews->update();
}
if (ImGui::Button(STR_C("Reload node bank"))) if (ImGui::Button(STR_C("Reload node bank")))
{ {
nodebank_reload(); nodebank_reload();
@@ -833,6 +887,7 @@ void nodebank_panel::render()
auto const label{" " + entry.first + "##" + std::to_string(idx)}; auto const label{" " + entry.first + "##" + std::to_string(idx)};
if (list_item(label.c_str(), entry.second == m_selectedtemplate)) if (list_item(label.c_str(), entry.second == m_selectedtemplate))
m_selectedtemplate = entry.second; m_selectedtemplate = entry.second;
preview_popup(entry);
++idx; ++idx;
} }
} }

View File

@@ -166,6 +166,8 @@ class nodebank_panel : public ui_panel
void index_previews(); void index_previews();
// entries of a group as cards with their previews, in rows as many as fit the width // 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); void render_cards(std::vector<std::pair<std::string, std::shared_ptr<std::string>> 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<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 // 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; bool node_selected() const;
// members: // 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 // previews of the models: image of each node definition, by its address, and the images read so far
std::unordered_map<std::string const *, std::string> m_previewpaths; std::unordered_map<std::string const *, std::string> m_previewpaths;
std::unique_ptr<editor_previews::image_cache> m_previews; 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 bool m_previewfolder{false}; // the generator was run, there's the folder of the images
// set manager window // set manager window
bool m_setsopen{false}; bool m_setsopen{false};

View File

@@ -32,6 +32,7 @@ int ui_layer::m_palette{0};
ImFont *ui_layer::font_default{nullptr}; ImFont *ui_layer::font_default{nullptr};
ImFont *ui_layer::font_mono{nullptr}; ImFont *ui_layer::font_mono{nullptr};
ImFont *ui_layer::font_loading{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)) {} 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")) { if (FileExists("fonts/bahnschrift.ttf")) {
ImFontConfig loading_config; ImFontConfig loading_config;
font_loading = m_imguiio->Fonts->AddFontFromFileTTF("fonts/bahnschrift.ttf", 48, &loading_config, &ranges[0]); font_loading = m_imguiio->Fonts->AddFontFromFileTTF("fonts/bahnschrift.ttf", 48, &loading_config, &ranges[0]);

View File

@@ -130,6 +130,8 @@ public:
static ImFont *font_default; static ImFont *font_default;
static ImFont *font_mono; static ImFont *font_mono;
static ImFont *font_loading; 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: protected:
// members // members

View File

@@ -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 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")}; auto const nodestart{Entry.find("node")};
if (nodestart == std::string::npos) if (nodestart == std::string::npos)
{ {
return {}; return false;
} }
auto const entry{Entry.substr(nodestart)}; auto const entry{Entry.substr(nodestart)};
// node <range max> <range min> <name> <type> // node <range max> <range min> <name> <type>
@@ -93,18 +103,14 @@ std::string entry_preview_path(std::string const &Entry)
} }
if (type != "model") 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 // 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); cParser names(entry, cParser::buffer_TEXT);
names.getTokens(9, false); names.getTokens(9, false);
auto const model{names.getToken<std::string>()}; Model = names.getToken<std::string>();
auto const skin{names.getToken<std::string>(false)}; Skin = names.getToken<std::string>(false);
if (model.empty()) return false == Model.empty();
{
return {};
}
return preview_path(model, skin);
} }
std::string variant(std::string const &Path, std::set<std::string> const &Taken) std::string variant(std::string const &Path, std::set<std::string> const &Taken)
@@ -124,17 +130,15 @@ std::string variant(std::string const &Path, std::set<std::string> const &Taken)
namespace 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 // uploaded in a frame at most
int const uploads_per_frame{8}; int const uploads_per_frame{8};
} // namespace } // namespace
image_cache::image_cache() image_cache::image_cache(int const Size, std::size_t const Limit) : m_limit{std::max<std::size_t>(Limit, 2)}
{ {
// at a bigger ui the images are drawn bigger too // at a bigger ui the cards are drawn bigger too; 512 of them at 128 pixels take 32 MB
m_size = std::clamp(static_cast<int>(128.0f * std::max(1.0f, Global.ui_scale)), 128, 256); m_size = (Size > 0 ? Size : std::clamp(static_cast<int>(128.0f * std::max(1.0f, Global.ui_scale)), 128, 256));
m_thread = std::thread(&image_cache::work, this); m_thread = std::thread(&image_cache::work, this);
} }
@@ -182,7 +186,7 @@ void image_cache::update()
{ {
std::lock_guard<std::mutex> lock(m_lock); std::lock_guard<std::mutex> 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 // 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())}; auto const lookup{m_entries.find(m_queue.back())};
if (lookup != m_entries.end()) if (lookup != m_entries.end())
@@ -224,7 +228,7 @@ void image_cache::update()
{ {
textures += (entry.second.texture != 0 ? 1 : 0); textures += (entry.second.texture != 0 ? 1 : 0);
} }
if (textures <= image_limit) if (textures <= m_limit)
{ {
return; return;
} }
@@ -239,7 +243,7 @@ void image_cache::update()
} }
std::sort(loaded.begin(), loaded.end()); std::sort(loaded.begin(), loaded.end());
std::vector<std::string> dropped; std::vector<std::string> 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); dropped.push_back(*loaded[index].second);
} }

View File

@@ -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 <x> <y> <z> <angle> <model> <skin> ..."), without the extension; // path of the image of a node bank entry ("node ... model <x> <y> <z> <angle> <model> <skin> ..."), without the extension;
// empty if the entry isn't a model // empty if the entry isn't a model
std::string entry_preview_path(std::string const &Entry); 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 // 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 // node bank, after the names already Taken
std::string variant(std::string const &Path, std::set<std::string> const &Taken); std::string variant(std::string const &Path, std::set<std::string> const &Taken);
@@ -44,7 +46,8 @@ std::string variant(std::string const &Path, std::set<std::string> const &Taken)
class image_cache class image_cache
{ {
public: 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(image_cache const &) = delete; image_cache(image_cache const &) = delete;
image_cache &operator=(image_cache const &) = delete; image_cache &operator=(image_cache const &) = delete;
@@ -81,6 +84,7 @@ class image_cache
std::unordered_map<std::string, entry> m_entries; std::unordered_map<std::string, entry> m_entries;
std::uint64_t m_frame{0}; std::uint64_t m_frame{0};
int m_size{128}; // pixels of the longer side of the images int m_size{128}; // pixels of the longer side of the images
std::size_t m_limit{512};
// shared with the thread // shared with the thread
std::mutex m_lock; std::mutex m_lock;
std::condition_variable m_wake; std::condition_variable m_wake;

View File

@@ -100,7 +100,7 @@ bool editorSettings::load()
m_nodebank_previews = (value == "1"); m_nodebank_previews = (value == "1");
else if (key == "nodebank_card") else if (key == "nodebank_card")
parse(value, m_nodebank_card); parse(value, m_nodebank_card);
else if (key == "palette") else if (key == "colours")
parse(value, m_palette); parse(value, m_palette);
else if (key.rfind(origin_prefix, 0) == 0) else if (key.rfind(origin_prefix, 0) == 0)
{ {
@@ -144,7 +144,7 @@ bool editorSettings::save()
stream << "signal_offset " << m_signal_offset << "\n"; stream << "signal_offset " << m_signal_offset << "\n";
stream << "nodebank_previews " << (m_nodebank_previews ? 1 : 0) << "\n"; stream << "nodebank_previews " << (m_nodebank_previews ? 1 : 0) << "\n";
stream << "nodebank_card " << m_nodebank_card << "\n"; stream << "nodebank_card " << m_nodebank_card << "\n";
stream << "palette " << m_palette << "\n"; stream << "colours " << m_palette << "\n";
stream << std::fixed << std::setprecision(3); stream << std::fixed << std::setprecision(3);
for (auto const &origin : m_orthophoto_origins) for (auto const &origin : m_orthophoto_origins)
stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n"; stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n";

View File

@@ -50,7 +50,7 @@ public:
int nodebank_card() const { return m_nodebank_card; } int nodebank_card() const { return m_nodebank_card; }
void nodebank_card(int Size) { m_nodebank_card = Size; } 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; } int palette() const { return m_palette; }
void palette(int Palette) { m_palette = Palette; } void palette(int Palette) { m_palette = Palette; }
@@ -62,7 +62,7 @@ private:
double m_signal_offset{2.25}; double m_signal_offset{2.25};
bool m_nodebank_previews{true}; bool m_nodebank_previews{true};
int m_nodebank_card{96}; int m_nodebank_card{96};
int m_palette{1}; int m_palette{0};
}; };
// global editor settings instance // global editor settings instance

View File

@@ -4683,3 +4683,42 @@ msgstr "Zielone, jak reszta symulatora"
msgid "Amber, of the editor" msgid "Amber, of the editor"
msgstr "Bursztynowe, edytora" 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"