diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bd3ea10d16..56b920349f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -740,6 +740,15 @@ void GLCanvas3D::set_as_dirty() m_dirty = true; } +const float GLCanvas3D::get_scale() const +{ +#if ENABLE_RETINA_GL + return m_retina_helper->get_scale_factor(); +#else + return 1.0f; +#endif +} + unsigned int GLCanvas3D::get_volumes_count() const { return (unsigned int)m_volumes.volumes.size(); @@ -4404,7 +4413,7 @@ bool GLCanvas3D::_render_orient_menu(float left, float right, float bottom, floa //now change to left_up as {0,0}, and top is 0, bottom is canvas_h #if BBS_TOOLBAR_ON_TOP const float x = left * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(get_scale()); imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); #else const float x = canvas_w - m_main_toolbar.get_width(); @@ -4500,7 +4509,7 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo #endif //BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(get_scale()); imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); @@ -6582,7 +6591,7 @@ void GLCanvas3D::_render_explosion_control() const ImGuiWrapper* imgui = wxGetApp().imgui(); - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(get_scale()); auto canvas_w = float(get_canvas_size().get_width()); auto canvas_h = float(get_canvas_size().get_height()); @@ -6652,7 +6661,7 @@ void GLCanvas3D::_render_assemble_info() const ImGui::PushFont(font); ImGui::PopFont(); imgui->set_next_window_pos(canvas_w - window_width, 0.0f, ImGuiCond_Always, 0, 0); - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(get_scale()); imgui->begin(_L("Assembly Info"), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); font->Scale = origScale; ImGui::PushFont(font); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index e07b31ce65..2d26513d63 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -600,6 +600,7 @@ public: const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); ModelInstanceEPrintVolumeState check_volumes_outside_state() const; + const float get_scale() const; //BBS GCodeViewer& get_gcode_viewer() { return m_gcode_viewer; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index 6b05afed35..f6cd0ed02e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -414,7 +414,7 @@ void GLGizmoAdvancedCut::on_render_input_window(float x, float y, float bottom_l GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); GizmoImguiBegin(on_get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1aea3c1706..5398a99c6e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -210,7 +210,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); //BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c796f5171b..080e9743cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -332,7 +332,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott wchar_t old_tool = m_current_tool; // BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 16.0f)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index e7915a9c73..35fab68b1b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -178,7 +178,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) wchar_t old_tool = m_current_tool; //BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index b8653f5d67..e625f0e3d0 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -550,7 +550,7 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, #endif // BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_glcanvas.get_scale()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0)); std::string name = this->m_new_title_string + "##" + window_name; @@ -664,7 +664,7 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe #endif // BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_glcanvas.get_scale()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0)); std::string name = this->m_new_title_string + "##" + window_name; @@ -783,7 +783,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w #endif //BBS - ImGuiWrapper::push_toolbar_style(); + ImGuiWrapper::push_toolbar_style(m_glcanvas.get_scale()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0)); std::string name = this->m_new_title_string + "##" + window_name; diff --git a/src/slic3r/GUI/IMSlider.cpp b/src/slic3r/GUI/IMSlider.cpp index 44376d35f4..ac3c0da2b1 100644 --- a/src/slic3r/GUI/IMSlider.cpp +++ b/src/slic3r/GUI/IMSlider.cpp @@ -1238,7 +1238,7 @@ bool IMSlider::render(int canvas_width, int canvas_height) void IMSlider::render_menu() { - ImGuiWrapper::push_menu_style(); + ImGuiWrapper::push_menu_style(m_scale); std::vector colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); int extruder_num = colors.size(); @@ -1246,24 +1246,25 @@ void IMSlider::render_menu() ImGui::OpenPopup("slider_menu_popup"); } + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); if (ImGui::BeginPopup("slider_menu_popup")) { - bool selected = false; - ImGui::MenuItem(_u8L("Add Pause").c_str(), "", &selected); - if (selected) { add_code_as_tick(PausePrint); } + if(menu_item_with_icon(_u8L("Add Pause").c_str(), "")) { add_code_as_tick(PausePrint); } //BBS render this menu item only when extruder_num > 1 if (extruder_num > 1) { - if (ImGui::BeginMenu(_u8L("Change Filament").c_str())) { + if (begin_menu(_u8L("Change Filament").c_str())) { for (int i = 0; i < extruder_num; i++) { std::array rgba = decode_color_to_float_array(colors[i]); ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); - if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", icon_clr, false, true)) add_code_as_tick(ToolChange, i + 1); + if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", ImVec2(14, 14) * m_scale, icon_clr)) add_code_as_tick(ToolChange, i + 1); } - ImGui::EndMenu(); + end_menu(); } } ImGui::EndPopup(); } + ImGui::PopStyleVar(1); + ImGuiWrapper::pop_menu_style(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c34551534e..b5e88cd155 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -291,56 +291,6 @@ bool button_with_pos(ImTextureID user_texture_id, const ImVec2 &size, const ImVe return pressed; } -bool menu_item_with_icon(const char* label, const char* shortcut, ImU32 icon_color, bool selected, bool enabled/* = true*/) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - ImGuiStyle& style = g.Style; - ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), - // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. - ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); - bool pressed; - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - { - // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful - // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. - float w = label_size.x; - window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); - pressed = ImGui::Selectable(label, selected, flags, ImVec2(w, 0.0f)); - ImGui::PopStyleVar(); - window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). - } - else - { - // Menu item inside a vertical menu - // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. - // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. - float shortcut_w = shortcut ? ImGui::CalcTextSize(shortcut, NULL).x : 0.0f; - float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame - float extra_w = std::max(0.0f, ImGui::GetContentRegionAvail().x - min_w); - pressed = ImGui::Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); - ImVec2 pos_min = pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f); - ImGui::RenderFrame(pos_min, pos_min + ImVec2(14, 14), icon_color); - if (shortcut_w > 0.0f) - { - ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - ImGui::RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); - ImGui::PopStyleColor(); - } - if (selected) - ImGui::RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); - } - - IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); - return pressed; -} - ImGuiWrapper::ImGuiWrapper() { ImGui::CreateContext(); @@ -1170,14 +1120,17 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl // mark a label with a ColorMarkerHovered, if item is hovered char marked_label[512]; //255 symbols is not enough for translated string (e.t. to Russian) - if (hovered) + if (hovered || selected) { sprintf(marked_label, "%c%s", ImGui::ColorMarkerHovered, label); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + } else strcpy(marked_label, label); if (flags & ImGuiSelectableFlags_Disabled) ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); ImGui::RenderTextClipped(text_min, text_max, marked_label, NULL, &label_size, style.SelectableTextAlign, &bb); if (flags & ImGuiSelectableFlags_Disabled) ImGui::PopStyleColor(); + if (hovered || selected) ImGui::PopStyleColor(); // Automatically close popups if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.CurrentItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) @@ -1187,6 +1140,228 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl return pressed; } +bool begin_menu(const char *label, bool enabled) +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext & g = *GImGui; + const ImGuiStyle &style = g.Style; + const ImGuiID id = window->GetID(label); + bool menu_is_open = ImGui::IsPopupOpen(id, ImGuiPopupFlags_None); + + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) flags |= ImGuiWindowFlags_ChildWindow; + + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). + // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. + // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. + if (g.MenusIdSubmittedThisFrame.contains(id)) { + if (menu_is_open) + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values + return menu_is_open; + } + + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu + g.MenusIdSubmittedThisFrame.push_back(id); + + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + bool pressed; + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && + (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow *backed_nav_window = g.NavWindow; + if (menuset_is_open) g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, + // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). + // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + float w = label_size.x; + pressed = selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } else { + // Menu inside a menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(min_w, 0.0f)); + ImU32 text_col = ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); + ImGui::RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); + } + + const bool hovered = enabled && ImGui::ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) g.NavWindow = backed_nav_window; + + bool want_open = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_other_child_menu = false; + + ImGuiWindow *child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? + g.OpenPopupStack[g.BeginPopupStack.Size].Window : + NULL; + if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { + // FIXME-DPI: Values should be derived from a master "scale" factor. + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + // GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + } + if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) + want_close = true; + + if (!menu_is_open && hovered && pressed) // Click to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open + want_open = true; + + if (g.NavActivateId == id) { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } else { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && ImGui::IsPopupOpen(id, ImGuiPopupFlags_None)) ImGui::ClosePopupToLevel(g.BeginPopupStack.Size, true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + ImGui::OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) ImGui::OpenPopup(label); + + if (menu_is_open) { + ImGui::SetNextWindowPos(popup_pos, + ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } else { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + } + + return menu_is_open; +} + +void end_menu() +{ + ImGui::EndMenu(); +} + +bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_size /* = ImVec2(0, 0)*/, ImU32 icon_color /* = 0*/, bool selected /* = false*/, bool enabled /* = true*/) +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext &g = *GImGui; + ImGuiStyle & style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = ImGui::Selectable(label, selected, flags, ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } else { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float shortcut_w = shortcut ? ImGui::CalcTextSize(shortcut, NULL).x : 0.0f; + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = std::max(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + + if (icon_size.x != 0 && icon_size.y != 0) { + float selectable_pos_y = pos.y + -0.5f * style.ItemSpacing.y; + float icon_pos_y = selectable_pos_y + (label_size.y + style.ItemSpacing.y - icon_size.y) / 2; + float icon_pos_x = pos.x + window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f; + ImVec2 icon_pos = ImVec2(icon_pos_x, icon_pos_y); + ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color); + } + + if (shortcut_w > 0.0f) { + ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + ImGui::RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + ImGui::PopStyleColor(); + } + if (selected) { + //ImGui::RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), + // ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + } + + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + // Scroll so that the hovered item is at the top of the window static void scroll_y(int hover_id) { @@ -1445,13 +1620,13 @@ std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name //BBS -void ImGuiWrapper::push_toolbar_style() +void ImGuiWrapper::push_toolbar_style(const float scale) { - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 10.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * scale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 10.0f) * scale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f * scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f) * scale); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(50/255.0f, 58/255.0f, 61/255.0f, 1.00f)); // 1 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGuiWrapper::COL_WINDOW_BG); // 2 ImGui::PushStyleColor(ImGuiCol_TitleBg, ImGuiWrapper::COL_TITLE_BG); // 3 @@ -1476,11 +1651,11 @@ void ImGuiWrapper::pop_toolbar_style() ImGui::PopStyleVar(5); } -void ImGuiWrapper::push_menu_style() +void ImGuiWrapper::push_menu_style(const float scale) { - ImGuiWrapper::push_toolbar_style(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f); + ImGuiWrapper::push_toolbar_style(scale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f) * scale); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f * scale); ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BG); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index db57b95cb7..8cb579f504 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -37,7 +37,9 @@ bool button_with_pos(ImTextureID user_texture_id, const ImVec4 &bg_col = ImVec4(0, 0, 0, 0), const ImVec4 &tint_col = ImVec4(1, 1, 1, 1), const ImVec2 &margin = ImVec2(0, 0)); -bool menu_item_with_icon(const char* label, const char* shortcut, ImU32 icon_color, bool selected, bool enabled = true); +bool begin_menu(const char *label, bool enabled = true); +void end_menu(); +bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_size = ImVec2(0, 0), ImU32 icon_color = 0, bool selected = false, bool enabled = true); class ImGuiWrapper @@ -181,9 +183,9 @@ public: static const ImVec4 COL_SEPARATOR; //BBS - static void push_toolbar_style(); + static void push_toolbar_style(const float scale); static void pop_toolbar_style(); - static void push_menu_style(); + static void push_menu_style(const float scale); static void pop_menu_style(); //BBS