17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 16:52:31 +01:00

feat(editor): pins of the docks: an unpinned dock hides its windows in a strip at the edge of the main window, a tab of the strip shows its window over the 3d view when pointed at for a moment or clicked, and it goes back when the cursor leaves it or on a click elsewhere, but not while it's in use (button held, popup open, typing, drag); pin in the tab bar of each dock, or Unpin in its window menu where the tabs leave no room; pin in the title of the shown window, or docking it by hand, puts the dock back next to the docks at that edge; Ctrl+Space (also in Mode windows) unpins all docks for the most of the view, or pins them back the way they were; unpinned docks kept in imgui.ini, cleared by Reset window layout; Polish translation

This commit is contained in:
2026-10-08 23:27:17 +02:00
parent 4e69d8f86c
commit 4b451622de
5 changed files with 566 additions and 0 deletions

View File

@@ -4283,6 +4283,14 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
return; return;
} }
// all docks unpinned and hidden at the edges, for the most of the view, or pinned back
if (Global.ctrlState && false == Global.shiftState && Key == GLFW_KEY_SPACE)
{
if (is_press(Action))
ui()->toggle_docks();
return;
}
// save; checked ahead of the camera keys, which have S bound to moving back // save; checked ahead of the camera keys, which have S bound to moving back
if (Global.ctrlState && false == Global.shiftState && Key == GLFW_KEY_S) if (Global.ctrlState && false == Global.shiftState && Key == GLFW_KEY_S)
{ {

View File

@@ -19,6 +19,9 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/translation.h" #include "utilities/translation.h"
#include "imgui/imgui_internal.h" #include "imgui/imgui_internal.h"
#include <cstdio>
#include <limits>
glm::vec2 editor_ui::m_viewmin{0.0f}; glm::vec2 editor_ui::m_viewmin{0.0f};
glm::vec2 editor_ui::m_viewmax{0.0f}; glm::vec2 editor_ui::m_viewmax{0.0f};
@@ -34,6 +37,8 @@ editor_ui::editor_ui()
// the menu is a part of the layout of the editor, not a pop-up as in the driver mode // the menu is a part of the layout of the editor, not a pop-up as in the driver mode
m_menu_always = true; m_menu_always = true;
// the window menu of the docks can unpin them
GImGui->DockNodeWindowMenuHandler = &editor_ui::autohide_menu;
} }
void editor_ui::render_dockspace() void editor_ui::render_dockspace()
@@ -41,11 +46,17 @@ void editor_ui::render_dockspace()
// windows of the editor are docked around the 3d view, which is left free in the middle // windows of the editor are docked around the 3d view, which is left free in the middle
auto *viewport{ImGui::GetMainViewport()}; auto *viewport{ImGui::GetMainViewport()};
auto const dockspace{ImGui::GetID("###editordockspace")}; auto const dockspace{ImGui::GetID("###editordockspace")};
m_dockspace = dockspace;
if (m_layoutreset || ImGui::DockBuilderGetNode(dockspace) == nullptr) if (m_layoutreset || ImGui::DockBuilderGetNode(dockspace) == nullptr)
{ {
m_layoutreset = false; m_layoutreset = false;
// the windows go to their default docks, none is left unpinned
m_autohide.clear();
m_autohideshown = 0;
ImGui::MarkIniSettingsDirty();
build_default_layout(dockspace); build_default_layout(dockspace);
} }
autohide_begin();
ImGui::DockSpaceOverViewport(dockspace, viewport, ImGuiDockNodeFlags_PassthruCentralNode); ImGui::DockSpaceOverViewport(dockspace, viewport, ImGuiDockNodeFlags_PassthruCentralNode);
if (auto const *central{ImGui::DockBuilderGetCentralNode(dockspace)}; central != nullptr) if (auto const *central{ImGui::DockBuilderGetCentralNode(dockspace)}; central != nullptr)
{ {
@@ -57,6 +68,7 @@ void editor_ui::render_dockspace()
m_viewmin = {viewport->WorkPos.x, viewport->WorkPos.y}; m_viewmin = {viewport->WorkPos.x, viewport->WorkPos.y};
m_viewmax = {viewport->WorkPos.x + viewport->WorkSize.x, viewport->WorkPos.y + viewport->WorkSize.y}; m_viewmax = {viewport->WorkPos.x + viewport->WorkSize.x, viewport->WorkPos.y + viewport->WorkSize.y};
} }
autohide_place();
} }
void editor_ui::build_default_layout(unsigned int const Dockspace) void editor_ui::build_default_layout(unsigned int const Dockspace)
@@ -90,6 +102,485 @@ void editor_ui::build_default_layout(unsigned int const Dockspace)
ImGui::DockBuilderFinish(Dockspace); ImGui::DockBuilderFinish(Dockspace);
} }
// unpinned docks. imgui has no auto-hide of its own: the windows of an unpinned dock are undocked, kept hidden while they aren't shown,
// and the one shown is placed over the edge of the 3d view its dock was at. a window docked again by hand is pinned again
std::vector<editor_ui::autohide_group> editor_ui::m_autohide;
unsigned int editor_ui::m_dockspace{0};
namespace
{
// imgui math operators aren't defined here, imgui.h is included before imgui_internal.h asks for them
ImVec2 vec_add(ImVec2 const A, ImVec2 const B)
{
return {A.x + B.x, A.y + B.y};
}
ImVec2 vec_sub(ImVec2 const A, ImVec2 const B)
{
return {A.x - B.x, A.y - B.y};
}
ImVec2 vec_mul(ImVec2 const A, float const B)
{
return {A.x * B, A.y * B};
}
bool autohide_vertical(int const Edge)
{
return Edge == ImGuiDir_Left || Edge == ImGuiDir_Right;
}
// leaf nodes of the dockspace which hold windows, the central node aside
void autohide_leaves(ImGuiDockNode *Node, std::vector<ImGuiDockNode *> &Leaves)
{
if (Node == nullptr)
return;
if (Node->IsLeafNode())
{
if (false == Node->IsCentralNode() && Node->Windows.Size > 0)
Leaves.push_back(Node);
return;
}
autohide_leaves(Node->ChildNodes[0], Leaves);
autohide_leaves(Node->ChildNodes[1], Leaves);
}
// edge of the central node the node lies at
int autohide_edge(ImGuiDockNode const *Node, ImGuiDockNode const *Central)
{
if (Central == nullptr)
return ImGuiDir_Left;
auto const center{vec_add(Node->Pos, vec_mul(Node->Size, 0.5f))};
if (center.x < Central->Pos.x)
return ImGuiDir_Left;
if (center.x > Central->Pos.x + Central->Size.x)
return ImGuiDir_Right;
if (center.y > Central->Pos.y + Central->Size.y)
return ImGuiDir_Down;
return ImGuiDir_Up;
}
// pin drawn with primitives, there's no icon font; a pinned one stands, an unpinned one lies
void autohide_draw_pin(ImDrawList *List, ImRect const &Rect, bool const Pinned, bool const Hovered)
{
auto const color{ImGui::GetColorU32(ImGuiCol_Text)};
if (Hovered)
List->AddRectFilled(Rect.Min, Rect.Max, ImGui::GetColorU32(ImGuiCol_ButtonHovered), 2.0f);
auto const center{Rect.GetCenter()};
auto const s{Rect.GetWidth()};
// along the pin: from the head to the point, and across it
ImVec2 const along{Pinned ? ImVec2(0.0f, 1.0f) : ImVec2(-1.0f, 0.0f)};
ImVec2 const across{along.y, -along.x};
auto const head{vec_sub(center, vec_mul(along, s * 0.30f))};
auto const collar{vec_add(center, vec_mul(along, s * 0.05f))};
auto const point{vec_add(center, vec_mul(along, s * 0.40f))};
auto const half{vec_mul(across, s * 0.16f)};
auto const guard{vec_mul(across, s * 0.30f)};
// the head and the collar the needle comes out of
List->AddQuadFilled(vec_sub(head, half), vec_add(head, half), vec_add(collar, half), vec_sub(collar, half), color);
List->AddLine(vec_sub(collar, guard), vec_add(collar, guard), color, 1.5f);
List->AddLine(collar, point, color, 1.0f);
}
// text turned to read from the top down, for the strips at the sides
void autohide_vertical_text(ImDrawList *List, ImVec2 const Position, ImU32 const Color, char const *Begin, char const *End)
{
auto const height{ImGui::GetFontSize()};
auto const first{List->VtxBuffer.Size};
// the glyphs are laid out across first, from the left edge of the main window: out of the clip rectangle or the screen
// they'd be left out, as they would at the right edge
ImVec2 const origin{ImGui::GetMainViewport()->Pos.x, Position.y};
List->PushClipRectFullScreen();
List->AddText(origin, Color, Begin, End);
List->PopClipRect();
for (auto index = first; index < List->VtxBuffer.Size; ++index)
{
auto &position{List->VtxBuffer[index].pos};
auto const offset{vec_sub(position, origin)};
position = ImVec2(Position.x + height - offset.y, Position.y + offset.x);
}
}
} // namespace
std::size_t editor_ui::autohide_group_of(unsigned int const Window)
{
for (std::size_t index = 0; index < m_autohide.size(); ++index)
if (std::find(m_autohide[index].windows.begin(), m_autohide[index].windows.end(), Window) != m_autohide[index].windows.end())
return index;
return static_cast<std::size_t>(-1);
}
void editor_ui::autohide_node(ImGuiDockNode *Node)
{
if (Node == nullptr || Node->Windows.Size == 0 || Node->IsCentralNode())
return;
autohide_group group;
group.edge = autohide_edge(Node, ImGui::DockNodeGetRootNode(Node)->CentralNode);
group.size = autohide_vertical(group.edge) ? Node->Size.x : Node->Size.y;
// place in the column of docks at that edge; a dock alone there goes back before the one which comes back to it
auto const along{autohide_vertical(group.edge) ? ImGuiAxis_Y : ImGuiAxis_X};
group.first = true;
if (auto const *parent{Node->ParentNode}; parent != nullptr && parent->SplitAxis == along)
{
group.first = parent->ChildNodes[0] == Node;
group.share = along == ImGuiAxis_Y ? Node->Size.y / std::max(parent->Size.y, 1.0f) : Node->Size.x / std::max(parent->Size.x, 1.0f);
}
// docks split along the edge make one column; the docks put back all at once are put back from the outer columns in,
// so each spans as far as it did
auto const *column{Node};
while (column->ParentNode != nullptr && column->ParentNode->SplitAxis == along)
column = column->ParentNode;
for (auto const *parent{column->ParentNode}; parent != nullptr; parent = parent->ParentNode)
++group.depth;
for (auto *window : Node->Windows)
{
group.windows.push_back(window->ID);
// carried out at the start of the next frame
ImGui::DockContextQueueUndockWindow(GImGui, window);
}
m_autohide.push_back(std::move(group));
ImGui::MarkIniSettingsDirty();
}
void editor_ui::autohide_pin(std::size_t const Group)
{
if (Group >= m_autohide.size())
return;
auto &group{m_autohide[Group]};
auto *central{ImGui::DockBuilderGetCentralNode(m_dockspace)};
ImGuiID target{0};
if (central != nullptr)
{
// the node went with its last window, and its id may have been given to another one since: another dock at that edge
// gets split, so that docks taken there one by one come back the way they were, or the room of the view does
std::vector<ImGuiDockNode *> leaves;
autohide_leaves(ImGui::DockBuilderGetNode(m_dockspace), leaves);
ImGuiDockNode *neighbour{nullptr};
for (auto *leaf : leaves)
if (autohide_edge(leaf, central) == group.edge)
neighbour = leaf;
ImGuiID rest{0};
if (neighbour != nullptr)
{
auto const vertical{autohide_vertical(group.edge)};
auto const direction{vertical ? (group.first ? ImGuiDir_Up : ImGuiDir_Down) : (group.first ? ImGuiDir_Left : ImGuiDir_Right)};
target = ImGui::DockBuilderSplitNode(neighbour->ID, direction, std::clamp(group.share, 0.15f, 0.85f), nullptr, &rest);
}
else
{
auto const room{std::max(autohide_vertical(group.edge) ? central->Size.x : central->Size.y, 1.0f)};
target = ImGui::DockBuilderSplitNode(central->ID, static_cast<ImGuiDir>(group.edge), std::clamp(group.size / room, 0.1f, 0.45f), nullptr, &rest);
}
}
if (target == 0)
return;
for (auto const id : group.windows)
if (auto const *window{ImGui::FindWindowByID(id)}; window != nullptr)
ImGui::DockBuilderDockWindow(window->Name, target);
ImGui::DockBuilderFinish(m_dockspace);
ImGui::MarkIniSettingsDirty();
}
void editor_ui::autohide_show(unsigned int const Window, bool const Focus)
{
m_autohideshown = Window;
m_autohideappear = true;
m_autohidefocus = Focus;
m_autohideleft = -1.0;
}
void editor_ui::autohide_begin()
{
auto &g{*GImGui};
// all docks unpinned, or all pinned back when none is left. the unpinned ones are undocked at the start of the next frame
auto unpinall{false};
if (m_dockstoggle)
{
m_dockstoggle = false;
std::vector<ImGuiDockNode *> leaves;
autohide_leaves(ImGui::DockBuilderGetNode(m_dockspace), leaves);
unpinall = false == leaves.empty();
if (false == unpinall)
{
for (std::size_t index = 0; index < m_autohide.size(); ++index)
m_autohidepin.push_back(index);
std::stable_sort(m_autohidepin.begin(), m_autohidepin.end(), [](std::size_t const Left, std::size_t const Right) { return m_autohide[Left].depth < m_autohide[Right].depth; });
}
}
for (auto const index : m_autohidepin)
autohide_pin(index);
m_autohidepin.clear();
// windows docked again, by the pin or by hand, are pinned
auto changed{false};
for (auto &group : m_autohide)
{
auto const count{group.windows.size()};
std::erase_if(group.windows, [](unsigned int const Id) {
auto const *window{ImGui::FindWindowByID(Id)};
return window != nullptr && window->DockId != 0;
});
changed |= group.windows.size() != count;
}
std::erase_if(m_autohide, [](autohide_group const &Group) { return Group.windows.empty(); });
if (changed)
ImGui::MarkIniSettingsDirty();
if (autohide_group_of(m_autohideshown) == static_cast<std::size_t>(-1))
m_autohideshown = 0;
// the shown window goes back when the cursor has been away from it for a while, or at once on a click elsewhere;
// it stays while it's in use: a button held, a field being typed in, a popup open, a drag
if (auto *shown{m_autohideshown != 0 ? ImGui::FindWindowByID(m_autohideshown) : nullptr}; shown != nullptr && false == m_autohideappear)
{
auto const &io{ImGui::GetIO()};
auto area{shown->Rect()};
area.Expand(8.0f);
auto const over{area.Contains(io.MousePos) || m_autohidestrip};
auto const popup{ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel)};
auto const typing{io.WantTextInput && g.ActiveIdWindow != nullptr && g.ActiveIdWindow->RootWindow == shown};
auto const busy{ImGui::IsAnyMouseDown() || g.MovingWindow != nullptr || popup || typing || ImGui::IsDragDropActive()};
if (false == over && false == popup && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Right)))
m_autohideshown = 0;
else if (over || busy)
m_autohideleft = -1.0;
else if (m_autohideleft < 0.0)
m_autohideleft = ImGui::GetTime();
else if (ImGui::GetTime() - m_autohideleft > 0.5)
m_autohideshown = 0;
}
// the rest stay hidden. the count goes down in Begin before it's checked, so it's set to 2 to hide the window in this frame
for (auto const &group : m_autohide)
for (auto const id : group.windows)
if (id != m_autohideshown)
if (auto *window{ImGui::FindWindowByID(id)}; window != nullptr)
window->HiddenFramesCanSkipItems = static_cast<ImS8>(std::max<int>(window->HiddenFramesCanSkipItems, 2));
// after the check above, the windows are still docked until the next frame
if (unpinall)
{
std::vector<ImGuiDockNode *> leaves;
autohide_leaves(ImGui::DockBuilderGetNode(m_dockspace), leaves);
for (auto *leaf : leaves)
autohide_node(leaf);
}
}
void editor_ui::autohide_place()
{
auto *shown{m_autohideshown != 0 ? ImGui::FindWindowByID(m_autohideshown) : nullptr};
if (shown == nullptr)
return;
auto &group{m_autohide[autohide_group_of(m_autohideshown)]};
auto const vertical{autohide_vertical(group.edge)};
// the size the user gave it by dragging its edge is kept for the next time
if (false == m_autohideappear)
group.size = vertical ? shown->SizeFull.x : shown->SizeFull.y;
auto const room{vertical ? m_viewmax.x - m_viewmin.x : m_viewmax.y - m_viewmin.y};
auto const size{std::clamp(group.size, std::min(120.0f, room), std::max(room * 0.85f, 1.0f))};
ImVec2 position{m_viewmin.x, m_viewmin.y};
ImVec2 extent{vertical ? size : m_viewmax.x - m_viewmin.x, vertical ? m_viewmax.y - m_viewmin.y : size};
if (group.edge == ImGuiDir_Right)
position.x = m_viewmax.x - size;
else if (group.edge == ImGuiDir_Down)
position.y = m_viewmax.y - size;
ImGui::SetWindowCollapsed(shown, false, ImGuiCond_Always);
ImGui::SetWindowSize(shown, extent, ImGuiCond_Always);
// dragged by its title it may be docked somewhere, it's left to go
if (GImGui->MovingWindow != shown)
ImGui::SetWindowPos(shown, position, ImGuiCond_Always);
if (m_autohideappear)
{
m_autohideappear = false;
ImGui::BringWindowToDisplayFront(shown);
if (m_autohidefocus)
ImGui::FocusWindow(shown);
}
}
void editor_ui::autohide_strips()
{
auto *viewport{ImGui::GetMainViewport()};
auto const &style{ImGui::GetStyle()};
auto const thickness{ImGui::GetFrameHeight()};
auto const padding{style.FramePadding.x};
auto hovered{false};
m_autohidestrip = false;
for (auto const edge : {ImGuiDir_Left, ImGuiDir_Right, ImGuiDir_Up, ImGuiDir_Down})
{
std::vector<ImGuiWindow *> windows;
for (auto const &group : m_autohide)
{
if (group.edge != edge)
continue;
for (auto const id : group.windows)
if (auto *window{ImGui::FindWindowByID(id)}; window != nullptr && window->Active)
windows.push_back(window);
}
if (windows.empty())
continue;
auto const vertical{autohide_vertical(edge)};
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
auto const name{std::string{"##autohidestrip"} + std::to_string(static_cast<int>(edge))};
if (ImGui::BeginViewportSideBar(name.c_str(), viewport, edge, thickness, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing))
{
auto *list{ImGui::GetWindowDrawList()};
auto position{vec_add(ImGui::GetCursorScreenPos(), vertical ? ImVec2(0.0f, 4.0f) : ImVec2(4.0f, 0.0f))};
for (auto *window : windows)
{
auto const *end{ImGui::FindRenderedTextEnd(window->Name)};
auto const text{ImGui::CalcTextSize(window->Name, end)};
ImVec2 const size{vertical ? ImVec2(thickness, text.x + padding * 2.0f) : ImVec2(text.x + padding * 2.0f, thickness)};
ImGui::SetCursorScreenPos(position);
ImGui::PushID(static_cast<int>(window->ID));
ImGui::InvisibleButton("##tab", size);
ImGui::PopID();
auto const over{ImGui::IsItemHovered()};
auto const id{window->ID};
auto const color{m_autohideshown == id ? ImGuiCol_TabSelected : over ? ImGuiCol_TabHovered : ImGuiCol_Tab};
list->AddRectFilled(position, vec_add(position, size), ImGui::GetColorU32(color), style.TabRounding);
if (vertical)
autohide_vertical_text(list, vec_add(position, ImVec2((thickness - ImGui::GetFontSize()) * 0.5f, padding)), ImGui::GetColorU32(ImGuiCol_Text), window->Name, end);
else
list->AddText(vec_add(position, ImVec2(padding, style.FramePadding.y)), ImGui::GetColorU32(ImGuiCol_Text), window->Name, end);
// a click shows the window and gives it the focus, or puts it away; pointing at the tab for a while shows it too
if (ImGui::IsItemClicked(ImGuiMouseButton_Left))
{
// shown by pointing at it, the click keeps it out and gives it the focus
if (m_autohideshown == id && m_autohidefocus)
m_autohideshown = 0;
else
autohide_show(id, true);
m_autohidehovered = id;
m_autohidehovertime = std::numeric_limits<double>::max();
}
else if (over && false == ImGui::IsAnyMouseDown())
{
if (m_autohidehovered != id)
{
m_autohidehovered = id;
m_autohidehovertime = ImGui::GetTime();
}
else if (m_autohideshown != id && ImGui::GetTime() - m_autohidehovertime >= 0.25)
autohide_show(id, false);
}
if (over)
{
hovered = true;
m_autohidestrip = true;
}
position = vec_add(position, vertical ? ImVec2(0.0f, size.y + 4.0f) : ImVec2(size.x + 4.0f, 0.0f));
}
}
ImGui::End();
ImGui::PopStyleVar(2);
}
if (false == hovered)
m_autohidehovered = 0;
}
void editor_ui::autohide_pins()
{
auto &g{*GImGui};
auto const &style{g.Style};
auto const size{g.FontSize};
auto const popup{ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel)};
// pins of the docks, in their tab bars by the close button, where the tabs leave room for them; the window menu of a dock
// (the arrow at the left of its tabs) unpins it too
std::vector<ImGuiDockNode *> leaves;
autohide_leaves(ImGui::DockBuilderGetNode(m_dockspace), leaves);
for (auto *node : leaves)
{
if (node->HostWindow == nullptr || false == node->IsVisible || node->IsHiddenTabBar() || node->IsNoTabBar() || node->TabBar == nullptr)
continue;
auto const x{node->Pos.x + node->Size.x - style.WindowBorderSize - style.FramePadding.x - (node->HasCloseButton ? size + style.ItemInnerSpacing.x : 0.0f) - size};
if (node->TabBar->BarRect.Min.x + node->TabBar->WidthAllTabsIdeal + style.ItemInnerSpacing.x > x)
continue;
ImRect const rect{ImVec2(x, node->Pos.y + style.FramePadding.y), ImVec2(x + size, node->Pos.y + style.FramePadding.y + size)};
auto const over{false == popup && g.HoveredWindow != nullptr && g.HoveredWindow->RootWindowDockTree == node->HostWindow->RootWindowDockTree && ImGui::IsMouseHoveringRect(rect.Min, rect.Max, false)};
auto *list{node->HostWindow->DrawList};
list->PushClipRect(node->Pos, vec_add(node->Pos, node->Size), false);
autohide_draw_pin(list, rect, true, over);
list->PopClipRect();
if (over)
{
ImGui::SetTooltip("%s", STR_C("Unpin: the dock hides at the edge of the window, its tabs show it over the view (Ctrl+Space: all docks)"));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
autohide_node(node);
}
}
// the pin of the shown window puts its dock back
auto *shown{m_autohideshown != 0 ? ImGui::FindWindowByID(m_autohideshown) : nullptr};
if (shown == nullptr || false == shown->Active || shown->Hidden || shown->DockIsActive || (shown->Flags & ImGuiWindowFlags_NoTitleBar) != 0)
return;
auto const title{shown->TitleBarRect()};
auto const x{title.Max.x - style.FramePadding.x - (shown->HasCloseButton ? size + style.ItemInnerSpacing.x : 0.0f) - size};
ImRect const rect{ImVec2(x, title.Min.y + style.FramePadding.y), ImVec2(x + size, title.Min.y + style.FramePadding.y + size)};
auto const over{false == popup && g.HoveredWindow == shown && ImGui::IsMouseHoveringRect(rect.Min, rect.Max, false)};
shown->DrawList->PushClipRect(title.Min, title.Max, false);
autohide_draw_pin(shown->DrawList, rect, false, over);
shown->DrawList->PopClipRect();
if (over)
{
ImGui::SetTooltip("%s", STR_C("Pin: the dock goes back to its place"));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
m_autohidepin.push_back(autohide_group_of(m_autohideshown));
}
}
void editor_ui::autohide_menu(ImGuiContext *Context, ImGuiDockNode *Node, ImGuiTabBar *TabBar)
{
ImGui::DockNodeWindowMenuHandler_Default(Context, Node, TabBar);
if (m_dockspace == 0 || Node == nullptr || Node->IsCentralNode() || ImGui::DockNodeGetRootNode(Node)->ID != m_dockspace)
return;
ImGui::Separator();
if (ImGui::MenuItem(STR_C("Unpin (hide at the edge)")))
autohide_node(Node);
}
void editor_ui::register_settings()
{
ImGuiSettingsHandler handler;
handler.TypeName = "MaSzynaAutoHide";
handler.TypeHash = ImHashStr(handler.TypeName);
handler.ClearAllFn = [](ImGuiContext *, ImGuiSettingsHandler *) { m_autohide.clear(); };
handler.ReadOpenFn = [](ImGuiContext *, ImGuiSettingsHandler *, char const *) -> void * {
m_autohide.emplace_back();
return &m_autohide.back();
};
handler.ReadLineFn = [](ImGuiContext *, ImGuiSettingsHandler *, void *Entry, char const *Line) {
auto &group{*static_cast<autohide_group *>(Entry)};
int edge{0};
unsigned int id{0};
// whole numbers only, the decimal point of the locale doesn't matter then
if (std::sscanf(Line, "Edge=%d", &edge) == 1)
group.edge = std::clamp(edge, static_cast<int>(ImGuiDir_Left), static_cast<int>(ImGuiDir_Down));
else if (std::sscanf(Line, "Size=%d", &edge) == 1)
group.size = static_cast<float>(edge);
else if (std::sscanf(Line, "First=%d", &edge) == 1)
group.first = edge != 0;
else if (std::sscanf(Line, "Share=%d", &edge) == 1)
group.share = edge / 1000.0f;
else if (std::sscanf(Line, "Depth=%d", &edge) == 1)
group.depth = edge;
else if (std::sscanf(Line, "Window=0x%X", &id) == 1)
group.windows.push_back(id);
};
handler.ApplyAllFn = [](ImGuiContext *, ImGuiSettingsHandler *) {
std::erase_if(m_autohide, [](autohide_group const &Group) { return Group.windows.empty(); });
};
handler.WriteAllFn = [](ImGuiContext *, ImGuiSettingsHandler *Handler, ImGuiTextBuffer *Buffer) {
for (auto const &group : m_autohide)
{
if (group.windows.empty())
continue;
Buffer->appendf("[%s][Dock]\nEdge=%d\nSize=%d\nFirst=%d\nShare=%d\nDepth=%d\n", Handler->TypeName, group.edge, static_cast<int>(group.size), group.first ? 1 : 0, static_cast<int>(group.share * 1000.0f), group.depth);
for (auto const id : group.windows)
Buffer->appendf("Window=0x%08X\n", id);
Buffer->append("\n");
}
};
ImGui::AddSettingsHandler(&handler);
}
void editor_ui::render_() void editor_ui::render_()
{ {
auto *viewport{ImGui::GetMainViewport()}; auto *viewport{ImGui::GetMainViewport()};
@@ -131,6 +622,9 @@ void editor_ui::render_()
} }
} }
ImGui::End(); ImGui::End();
// strips of the unpinned docks, inside the toolbar and the status bar; the pins over the tab bars drawn by now
autohide_strips();
autohide_pins();
} }
// updates state of UI elements // updates state of UI elements
@@ -472,6 +966,10 @@ void editor_ui::render_menu_contents()
{ {
if (ImGui::MenuItem(STR_C("Reset window layout"))) if (ImGui::MenuItem(STR_C("Reset window layout")))
m_layoutreset = true; m_layoutreset = true;
std::vector<ImGuiDockNode *> pinned;
autohide_leaves(ImGui::DockBuilderGetNode(m_dockspace), pinned);
if (ImGui::MenuItem(pinned.empty() ? STR_C("Pin the docks back") : STR_C("Unpin all docks"), "Ctrl+Space", false, false == pinned.empty() || false == m_autohide.empty()))
toggle_docks();
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem(STR_C("Tool options"), nullptr, &m_tooloptionsopen); ImGui::MenuItem(STR_C("Tool options"), nullptr, &m_tooloptionsopen);
ImGui::MenuItem(STR_C("Inspector"), nullptr, &m_inspectoropen); ImGui::MenuItem(STR_C("Inspector"), nullptr, &m_inspectoropen);

View File

@@ -19,6 +19,9 @@ class basic_node;
} }
struct ImGuiDockNode;
struct ImGuiTabBar;
class editor_ui : public ui_layer class editor_ui : public ui_layer
{ {
@@ -126,6 +129,14 @@ class editor_ui : public ui_layer
{ {
m_sceneselect = std::move(Select); m_sceneselect = std::move(Select);
} }
// pins of the docks: an unpinned dock hides its windows in a strip at the edge of the main window, and they come out over the 3d view
// when their tab in the strip is pointed at or clicked. the shortcut unpins all docks, or pins them back
void toggle_docks()
{
m_dockstoggle = true;
}
// the unpinned docks are kept in imgui.ini along with the layout; the handler has to be there before the file is read
static void register_settings();
// shows outcome of an operation in the layers window // shows outcome of an operation in the layers window
void set_status(std::string const &Status, bool const Error = false); void set_status(std::string const &Status, bool const Error = false);
@@ -143,6 +154,16 @@ class editor_ui : public ui_layer
void render_() override; void render_() override;
// docks the windows of the editor in their default places // docks the windows of the editor in their default places
void build_default_layout(unsigned int const Dockspace); void build_default_layout(unsigned int const Dockspace);
// unpinned docks: at the start of the frame the windows are hidden, docked back or placed over the view, at its end the strips and pins are drawn
void autohide_begin();
void autohide_place();
void autohide_strips();
void autohide_pins();
void autohide_show(unsigned int const Window, bool const Focus);
void autohide_pin(std::size_t const Group);
static void autohide_node(ImGuiDockNode *Node);
static std::size_t autohide_group_of(unsigned int const Window);
static void autohide_menu(ImGuiContext *Context, ImGuiDockNode *Node, ImGuiTabBar *TabBar);
// members // members
itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window
functions_panel m_functionspanel{"Functions", true}; // not a window of its own, its settings are drawn in the toolset tabs functions_panel m_functionspanel{"Functions", true}; // not a window of its own, its settings are drawn in the toolset tabs
@@ -191,4 +212,25 @@ class editor_ui : public ui_layer
bool m_bendexpand{false}; bool m_bendexpand{false};
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
bool m_track{false}; bool m_track{false};
// windows of an unpinned dock, and the edge they're hidden at
struct autohide_group
{
int edge{0}; // ImGuiDir
float size{300.0f}; // width of the side docks, height of the top and bottom ones, in pixels
bool first{false}; // the node was above or left of the other one in its split, and took this share of it
float share{0.5f};
int depth{0}; // of the column of docks the node was in, in the tree of the dockspace; outer docks are put back first
std::vector<unsigned int> windows;
};
static std::vector<autohide_group> m_autohide;
static unsigned int m_dockspace;
unsigned int m_autohideshown{0}; // window of an unpinned dock shown over the view
bool m_autohideappear{false}; // the shown window is to be brought forward
bool m_autohidefocus{false}; // and given the focus, it was clicked in the strip
unsigned int m_autohidehovered{0}; // tab in the strips under the cursor, and since when
double m_autohidehovertime{0.0};
double m_autohideleft{-1.0}; // when the cursor left the shown window, -1 while it's over it
bool m_autohidestrip{false}; // the cursor was over a tab of the strips in the last frame
std::vector<std::size_t> m_autohidepin; // groups to be docked back at the start of the next frame
bool m_dockstoggle{false};
}; };

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/translation.h" #include "utilities/translation.h"
#include "application/application.h" #include "application/application.h"
#include "application/editormode.h" #include "application/editormode.h"
#include "application/editoruilayer.h"
#include "imgui/imgui_impl_glfw.h" #include "imgui/imgui_impl_glfw.h"
@@ -269,6 +270,8 @@ bool ui_layer::init(GLFWwindow *Window)
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
m_imguiio = &ImGui::GetIO(); m_imguiio = &ImGui::GetIO();
// imgui.ini is read with the first frame, which may be of another mode than the editor
editor_ui::register_settings();
m_imguiio->ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; m_imguiio->ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
// m_imguiio->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // m_imguiio->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;

View File

@@ -4641,3 +4641,18 @@ msgstr "Kontrola prędkości"
msgid "What the check finds is listed in its window" msgid "What the check finds is listed in its window"
msgstr "Wyniki kontroli są w jej oknie" msgstr "Wyniki kontroli są w jej oknie"
msgid "Unpin: the dock hides at the edge of the window, its tabs show it over the view (Ctrl+Space: all docks)"
msgstr "Odepnij: dok chowa się przy krawędzi okna, jego zakładki wysuwają go nad widok (Ctrl+Spacja: wszystkie doki)"
msgid "Pin: the dock goes back to its place"
msgstr "Przypnij: dok wraca na swoje miejsce"
msgid "Unpin (hide at the edge)"
msgstr "Odepnij (schowaj przy krawędzi)"
msgid "Unpin all docks"
msgstr "Odepnij wszystkie doki"
msgid "Pin the docks back"
msgstr "Przypnij doki z powrotem"