diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 8045936c4c..ba3cd1c368 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -153,6 +153,8 @@ namespace ImGui const wchar_t PlugMarker = 0x1C; const wchar_t DowelMarker = 0x1D; const wchar_t SnapMarker = 0x1E; + const wchar_t Lock = 0x1F; + const wchar_t Unlock = 0x17; const wchar_t HorizontalHide = 0xB1; const wchar_t HorizontalShow = 0xB2; // Do not forget use following letters only in wstring diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 623601fd12..db4d840327 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -37,17 +37,39 @@ #include "ImGuiPureWrap.hpp" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include + namespace Slic3r { using GUI::from_u8; using GUI::into_u8; using GUI::format_wxstr; +using GUI::format; + +using namespace GUI; namespace DoubleSlider { constexpr double min_delta_area = scale_(scale_(25)); // equal to 25 mm2 constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2 +static const float LEFT_MARGIN = 13.0f + 100.0f; // avoid thumbnail toolbar +static const float SLIDER_LENGTH = 680.0f; +static const float TEXT_WIDTH_DUMMY = 63.0f; +static const ImVec2 ONE_LAYER_OFFSET = ImVec2(41.0f, /*44*/33.0f); +static const ImVec2 HORIZONTAL_SLIDER_SIZE = ImVec2(764.0f, 90.0f);//764 = 680 + handle_dummy_width * 2 + text_right_dummy +static const ImVec2 VERTICAL_SLIDER_SIZE = ImVec2(105.0f, 748.0f);//748 = 680 + text_dummy_height * 2 + +const ImU32 tooltip_bg = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_GREY_LIGHT); +const ImU32 handle_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_LIGHT); +const ImU32 handle_border_clr = IM_COL32(255, 255, 255, 255);//white_bg; + +int m_tick_value = -1; +ImVec4 m_tick_rect; + bool equivalent_areas(const double& bottom_area, const double& top_area) { return fabs(bottom_area - top_area) <= miscalculation; @@ -563,32 +585,690 @@ void Control::render() } } -void Control::imgui_render(GUI::GLCanvas3D& canvas) -{ - GUI::Size cnv_size = canvas.get_canvas_size(); - ImVec2 mouse_pos = ImGui::GetMousePos(); - const float width = get_min_size().x*5; - const float height = float(cnv_size.get_height()); - - ImVec2 win_pos(1.0f * (float)cnv_size.get_width(), 1.0f); - ImGuiPureWrap::set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); - ImGuiPureWrap::set_next_window_size(width, height - 2.f, ImGuiCond_Always); - - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); - - // name of window indentifies window - has to be unique string - std::string name = "DblSlider"; - - int flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoFocusOnAppearing; - - if (ImGuiPureWrap::begin(name, flags)) { +// ImGuiDS +float Control::get_pos_from_value(int v_min, int v_max, int value, const ImRect& rect) { + float pos_ratio = (v_max - v_min) != 0 ? ((float)(value - v_min) / (float)(v_max - v_min)) : 0.0f; + float handle_pos; + if (is_horizontal()) { + handle_pos = rect.Min.x + (rect.Max.x - rect.Min.x) * pos_ratio; } - ImGuiPureWrap::end(); + else { + pos_ratio = 1.0f - pos_ratio; + handle_pos = rect.Min.y + (rect.Max.y - rect.Min.y) * pos_ratio; + } + return handle_pos; +} + +void Control::draw_background(const ImRect& groove) +{ + const ImU32 bg_rect_col = IM_COL32(255, 255, 255, 255); + const ImU32 groove_col = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); + + if (is_horizontal() || m_ticks.empty()) { + ImVec2 groove_padding = ImVec2(2.0f, 2.0f) * m_scale; + + ImRect bg_rect = groove; + bg_rect.Expand(groove_padding); + + // draw bg of slider + ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, bg_rect_col, false, 0.5 * bg_rect.GetWidth()); + // draw bg of scroll + ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth()); + } + else { + ImVec2 groove_padding = ImVec2(3.0f, 4.0f) * m_scale; + + ImRect bg_rect = groove; + bg_rect.Expand(groove_padding); + + // draw bg of slider + ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, bg_rect_col, false, bg_rect.GetWidth() * 0.5); + // draw bg of scroll + ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth()); + } +} + +void Control::draw_ticks(const ImRect& slideable_region) { + //if(m_draw_mode != dmRegular) + // return; + //if (m_ticks.empty() || m_mode == MultiExtruder) + // return; + if (m_ticks.empty()) + return; + + ImGuiContext& context = *GImGui; + + ImVec2 tick_box = ImVec2(46.0f, 16.0f) * m_scale; + ImVec2 tick_offset = ImVec2(19.0f, 11.0f) * m_scale; + float tick_width = 1.0f * m_scale; + ImVec2 icon_offset = ImVec2(13.0f, 7.0f) * m_scale; + ImVec2 icon_size = ImVec2(14.0f, 14.0f) * m_scale; + + const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_DARK); //IM_COL32(144, 144, 144, 255); + const ImU32 tick_hover_box_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); //IM_COL32(219, 253, 231, 255); + const ImU32 delete_btn_clr = IM_COL32(144, 144, 144, 255); + + auto get_tick_pos = [this, slideable_region](int tick) + { + int v_min = GetMinValue(); + int v_max = GetMaxValue(); + return get_pos_from_value(v_min, v_max, tick, slideable_region); + }; + + std::set::const_iterator tick_it = m_ticks.ticks.begin(); + while (tick_it != m_ticks.ticks.end()) + { + float tick_pos = get_tick_pos(tick_it->tick); + + //draw tick hover box when hovered + ImRect tick_hover_box = ImRect(slideable_region.GetCenter().x - tick_box.x / 2, tick_pos - tick_box.y / 2, slideable_region.GetCenter().x + tick_box.x / 2, + tick_pos + tick_box.y / 2); + + if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) + { + ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hover_box_clr, false); + if (context.IO.MouseClicked[0]) { + m_tick_value = tick_it->tick; + m_tick_rect = ImVec4(tick_hover_box.Min.x, tick_hover_box.Min.y, tick_hover_box.Max.x, tick_hover_box.Max.y); + } + } + ++tick_it; + } + + tick_it = m_ticks.ticks.begin(); + while (tick_it != m_ticks.ticks.end()) + { + float tick_pos = get_tick_pos(tick_it->tick); + + //draw ticks + ImRect tick_left = ImRect(slideable_region.GetCenter().x - tick_offset.x, tick_pos - tick_width, slideable_region.GetCenter().x - tick_offset.y, tick_pos); + ImRect tick_right = ImRect(slideable_region.GetCenter().x + tick_offset.y, tick_pos - tick_width, slideable_region.GetCenter().x + tick_offset.x, tick_pos); + ImGui::RenderFrame(tick_left.Min, tick_left.Max, tick_clr, false); + ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false); + + //draw pause icon + if (tick_it->type == PausePrint) { + //ImTextureID pause_icon_id = m_pause_icon_id; + ImVec2 icon_pos = ImVec2(slideable_region.GetCenter().x + icon_offset.x, tick_pos - icon_offset.y); + //button_with_pos(pause_icon_id, icon_size, icon_pos); + if (ImGui::IsMouseHoveringRect(icon_pos, icon_pos + icon_size)) { + if (context.IO.MouseClicked[0]) + int a = 0; + } + } + ++tick_it; + } + + tick_it = m_selection == ssHigher ? m_ticks.ticks.find(TickCode{ this->GetHigherValue() }) : + m_selection == ssLower ? m_ticks.ticks.find(TickCode{ this->GetLowerValue() }) : + m_ticks.ticks.end(); + if (tick_it != m_ticks.ticks.end()) { + // draw delete icon + //ImTextureID delete_icon_id = m_delete_icon_id; + ImVec2 icon_pos = ImVec2(slideable_region.GetCenter().x + icon_offset.x, get_tick_pos(tick_it->tick) - icon_offset.y); + //! button_with_pos(m_delete_icon_id, icon_size, icon_pos); + if (ImGui::IsMouseHoveringRect(icon_pos, icon_pos + icon_size)) { + if (context.IO.MouseClicked[0]) { + // delete tick + Type type = tick_it->type; + m_ticks.ticks.erase(tick_it); + post_ticks_changed_event(type); + } + } + } + +} + +inline int hex_to_int(const char c) +{ + return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} + +static std::array decode_color_to_float_array(const std::string color) +{ + // set alpha to 1.0f by default + std::array ret = { 0, 0, 0, 1.0f }; + const char* c = color.data() + 1; + if (color.size() == 7 && color.front() == '#') { + for (size_t j = 0; j < 3; ++j) { + int digit1 = hex_to_int(*c++); + int digit2 = hex_to_int(*c++); + if (digit1 == -1 || digit2 == -1) break; + ret[j] = float(digit1 * 16 + digit2) / 255.0f; + } + } + return ret; +} + +void Control::draw_colored_band(const ImRect& groove, const ImRect& slideable_region) { + if (m_ticks.empty()) + return; + + const ImU32 blank_col = IM_COL32(255, 255, 255, 255); + + ImVec2 blank_padding = ImVec2(4.0f, 2.0f) * m_scale; + float blank_width = 1.0f * m_scale; + + ImRect blank_rect = ImRect(groove.GetCenter().x - blank_width, groove.Min.y, groove.GetCenter().x + blank_width, groove.Max.y); + + ImRect main_band = ImRect(blank_rect); + main_band.Expand(blank_padding); + + auto draw_band = [](const ImU32& clr, const ImRect& band_rc) + { + ImGui::RenderFrame(band_rc.Min, band_rc.Max, clr, false, band_rc.GetWidth() * 0.5); + //cover round corner + ImGui::RenderFrame(ImVec2(band_rc.Min.x, band_rc.Max.y - band_rc.GetWidth() * 0.5), band_rc.Max, clr, false); + }; + auto draw_main_band = [&main_band, this](const ImU32& clr) { + ImGui::RenderFrame(main_band.Min, main_band.Max, clr, false, main_band.GetWidth() * 0.5); + }; + //draw main colored band + const int default_color_idx = m_mode == MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; + std::arrayrgba = decode_color_to_float_array(m_extruder_colors[default_color_idx]); + ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + draw_main_band(band_clr); + + static float tick_pos; + std::set::const_iterator tick_it = m_ticks.ticks.begin(); + while (tick_it != m_ticks.ticks.end()) + { + //get position from tick + tick_pos = get_pos_from_value(GetMinValue(), GetMaxValue(), tick_it->tick, slideable_region); + + ImRect band_rect = ImRect(ImVec2(main_band.Min.x, std::min(tick_pos, main_band.Min.y)), + ImVec2(main_band.Max.x, std::min(tick_pos, main_band.Max.y))); + + if (main_band.Contains(band_rect)) { + //draw colored band + if (tick_it->type == ToolChange) { + if ((m_mode == SingleExtruder) || (m_mode == MultiAsSingle)) { + const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : get_color_for_tool_change_tick(tick_it); + if (!clr_str.empty()) { + std::arrayrgba = decode_color_to_float_array(clr_str); + ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + if (tick_it->tick == 0) + draw_main_band(band_clr); + else + draw_band(band_clr, band_rect); + } + } + } + else if (tick_it->type == ColorChange/* && m_mode == SingleExtruder*/) { + const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : get_color_for_tool_change_tick(tick_it); + if (!clr_str.empty()) { + std::arrayrgba = decode_color_to_float_array(clr_str); + ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + if (tick_it->tick == 0) + draw_main_band(band_clr); + else + draw_band(band_clr, band_rect); + } + } + } + tick_it++; + } +} + +void Control::draw_label(std::string label, const ImRect& handle, const ImVec2& handle_center, bool is_horizontal /*= false*/) +{ + ImVec2 text_padding = ImVec2(5.0f, 2.0f) * m_scale; + float rounding = 2.0f * m_scale; + ImVec2 triangle_offsets[3] = { ImVec2(2.0f, 0.0f) * m_scale, ImVec2(0.0f, 8.0f) * m_scale, ImVec2(9.0f, 0.0f) * m_scale }; + + ImVec2 text_content_size = ImGui::CalcTextSize(label.c_str()); + ImVec2 text_size = text_content_size + text_padding * 2; + ImVec2 text_start = is_horizontal ? + ImVec2(handle.Max.x + triangle_offsets[2].x, handle_center.y - text_size.y) : + ImVec2(handle.Min.x - text_size.x - triangle_offsets[2].x, handle_center.y - text_size.y) ; + ImRect text_rect(text_start, text_start + text_size); + + ImVec2 pos_1 = is_horizontal ? + ImVec2(text_rect.Min.x + triangle_offsets[0].x, text_rect.Max.y - triangle_offsets[0].y) : + text_rect.Max - triangle_offsets[0]; + ImVec2 pos_2 = is_horizontal ? pos_1 - triangle_offsets[2] : pos_1 - triangle_offsets[1]; + ImVec2 pos_3 = is_horizontal ? pos_1 - triangle_offsets[1] : pos_1 + triangle_offsets[2]; + + ImGui::RenderFrame(text_rect.Min, text_rect.Max, tooltip_bg, true, rounding); + ImGui::GetCurrentWindow()->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, tooltip_bg); + ImGui::RenderText(text_start + text_padding, label.c_str()); +}; + +bool Control::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& pos, const ImVec2& size, float scale) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& context = *GImGui; + const ImGuiID id = window->GetID(str_id); + + const ImRect draw_region(pos, pos + size); + ImGui::ItemSize(draw_region); + + float bottom_dummy = 44.0f * m_scale; + float handle_dummy_width = 17.0f * m_scale; + float text_right_dummy = 50.0f * scale * m_scale; + float groove_y = 8.0f * m_scale; + float draggable_region_y = 19.0f * m_scale; + float handle_radius = 14.0f * m_scale; + float handle_border = 2.0f * m_scale; + float rounding = 2.0f * m_scale; + float text_start_offset = 8.0f * m_scale; + ImVec2 text_padding = ImVec2(5.0f, 2.0f) * m_scale; + float triangle_offsets[3] = { -3.5f * m_scale, 3.5f * m_scale, -6.06f * m_scale }; + + // calc groove size + ImVec2 groove_start = ImVec2(pos.x + handle_dummy_width, pos.y + size.y - groove_y - bottom_dummy); + ImVec2 groove_size = ImVec2(size.x - 2 * handle_dummy_width - text_right_dummy, groove_y); + ImRect groove = ImRect(groove_start, groove_start + groove_size); + + // set active(draggable) region. + ImRect draggable_region = ImRect(groove.Min.x, groove.GetCenter().y, groove.Max.x, groove.GetCenter().y); + draggable_region.Expand(ImVec2(handle_radius, draggable_region_y)); + float mid_y = draggable_region.GetCenter().y; + bool hovered = ImGui::ItemHoverable(draggable_region, id); + if (hovered && context.IO.MouseDown[0]) { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + } + + // draw background + draw_background(groove); + + // set slideable region + ImRect slideable_region = draggable_region; + slideable_region.Expand(ImVec2(-handle_radius, 0)); + + // initialize the handle + float handle_pos = get_pos_from_value(v_min, v_max, *value, groove); + ImRect handle = ImRect(handle_pos - handle_radius, mid_y - handle_radius, handle_pos + handle_radius, mid_y + handle_radius); + + // update handle position and value + bool value_changed = GUI::slider_behavior(id, slideable_region, (const ImS32)v_min, (const ImS32)v_max, (ImS32*)value, &handle); + ImVec2 handle_center = handle.GetCenter(); + + // draw scroll line + ImRect scroll_line = ImRect(ImVec2(groove.Min.x, mid_y - groove_y / 2), ImVec2(handle_center.x, mid_y + groove_y / 2)); + window->DrawList->AddRectFilled(scroll_line.Min, scroll_line.Max, handle_clr, rounding); + + // draw handle + ImGuiPureWrap::draw_hexagon(handle_center, handle_radius, handle_border_clr); + ImGuiPureWrap::draw_hexagon(handle_center, handle_radius - handle_border, handle_clr); + + draw_label(std::to_string(*value), handle, handle_center, true); + + return value_changed; +} + +bool Control::vertical_slider(const char* str_id, int* higher_value, int* lower_value, std::string& higher_label, std::string& lower_label, int v_min, int v_max, const ImVec2& pos, const ImVec2& size, SelectedSlider& selection, bool one_layer_flag, float scale) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& context = *GImGui; + const ImGuiID id = window->GetID(str_id); + + const ImRect draw_region(pos, pos + size); + ImGui::ItemSize(draw_region); + + float right_dummy = 24.0f * m_scale; + float text_dummy_height = 34.0f * scale * m_scale; + float groove_x = 10.0f * m_scale; + float draggable_region_x = 40.0f * m_scale; + float handle_radius = 14.0f * m_scale; + float handle_border = 2.0f * m_scale; + float line_width = 2.0f * m_scale; + float line_offset = 9.0f * m_scale; + float one_handle_offset = 26.0f * m_scale; + float bar_width = 12.0f * m_scale; + ImVec2 text_content_size; + ImVec2 text_size; + + const ImU32 delete_btn_clr = IM_COL32(144, 144, 144, 255); + + // calc slider groove size + ImVec2 groove_start = ImVec2(pos.x + size.x - groove_x - right_dummy, pos.y + text_dummy_height); + ImVec2 groove_size = ImVec2(groove_x, size.y - /*2 * */text_dummy_height); + ImRect groove = ImRect(groove_start, groove_start + groove_size); + + // set active(draggable) region. + ImRect draggable_region = ImRect(groove.GetCenter().x, groove.Min.y, groove.GetCenter().x, groove.Max.y); + draggable_region.Expand(ImVec2(draggable_region_x, 0)); + float mid_x = draggable_region.GetCenter().x; + bool hovered = ImGui::ItemHoverable(draggable_region, id) && !ImGui::ItemHoverable(m_tick_rect, id); + if (hovered && context.IO.MouseDown[0]) { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + } + + // Processing interacting + // set slideable region + ImRect higher_slideable_region = ImRect(draggable_region.Min, draggable_region.Max - ImVec2(0, handle_radius)); + ImRect lower_slideable_region = ImRect(draggable_region.Min + ImVec2(0, handle_radius), draggable_region.Max); + ImRect one_slideable_region = draggable_region; + + // initialize the handles. + float higher_handle_pos = get_pos_from_value(v_min, v_max, *higher_value, higher_slideable_region); + ImRect higher_handle = ImRect(mid_x - handle_radius, higher_handle_pos - handle_radius, mid_x + handle_radius, higher_handle_pos + handle_radius); + + float lower_handle_pos = get_pos_from_value(v_min, v_max, *lower_value, lower_slideable_region); + ImRect lower_handle = ImRect(mid_x - handle_radius, lower_handle_pos - handle_radius, mid_x + handle_radius, lower_handle_pos + handle_radius); + + ImRect one_handle = ImRect(higher_handle.Min - ImVec2(one_handle_offset, 0), higher_handle.Max - ImVec2(one_handle_offset, 0)); + + //static bool become_del_handle = false; + bool value_changed = false; + const float hexagon_angle = IM_PI * 0.5; + if (!one_layer_flag) + { + // select higher handle by default + static bool h_selected = true; + if (ImGui::ItemHoverable(higher_handle, id) && context.IO.MouseClicked[0]) { + selection = ssHigher; + h_selected = true; + } + if (ImGui::ItemHoverable(lower_handle, id) && context.IO.MouseClicked[0]) { + selection = ssLower; + h_selected = false; + } + + // update handle position and value + if (h_selected) + { + value_changed = slider_behavior(id, higher_slideable_region, v_min, v_max, + higher_value, &higher_handle, ImGuiSliderFlags_Vertical, + m_tick_value, m_tick_rect); + } + if (!h_selected) { + value_changed = slider_behavior(id, lower_slideable_region, v_min, v_max, + lower_value, &lower_handle, ImGuiSliderFlags_Vertical, + m_tick_value, m_tick_rect); + } + + ImVec2 higher_handle_center = higher_handle.GetCenter(); + ImVec2 lower_handle_center = lower_handle.GetCenter(); + if (higher_handle_center.y + handle_radius > lower_handle_center.y && h_selected) + { + lower_handle = higher_handle; + lower_handle.TranslateY(handle_radius); + lower_handle_center.y = higher_handle_center.y + handle_radius; + *lower_value = *higher_value; + } + if (higher_handle_center.y + handle_radius > lower_handle_center.y && !h_selected) + { + higher_handle = lower_handle; + higher_handle.TranslateY(-handle_radius); + higher_handle_center.y = lower_handle_center.y - handle_radius; + *higher_value = *lower_value; + } + + // judge whether to open menu + if (ImGui::ItemHoverable(h_selected ? higher_handle : lower_handle, id) && context.IO.MouseClicked[1]) + m_show_menu = true; + if ((!ImGui::ItemHoverable(h_selected ? higher_handle : lower_handle, id) && context.IO.MouseClicked[1]) || + context.IO.MouseClicked[0]) + m_show_menu = false; + + ImRect scroll_line = ImRect(ImVec2(mid_x - groove_x / 2, higher_handle_center.y), ImVec2(mid_x + groove_x / 2, lower_handle_center.y)); + if (!m_ticks.empty()) { + // draw ticks + draw_ticks(h_selected ? higher_slideable_region : lower_slideable_region); + // draw background + draw_background(groove); + // draw colored band + draw_colored_band(scroll_line, h_selected ? higher_slideable_region : lower_slideable_region); + } + else { + // draw background + draw_background(groove); + // draw scroll line + window->DrawList->AddRectFilled(scroll_line.Min, scroll_line.Max, handle_clr, 2.0f * m_scale); + } + + // draw handles + ImGuiPureWrap::draw_hexagon(higher_handle_center, handle_radius, handle_border_clr, hexagon_angle); + ImGuiPureWrap::draw_hexagon(higher_handle_center, handle_radius - handle_border, handle_clr, hexagon_angle); + ImGuiPureWrap::draw_hexagon(lower_handle_center, handle_radius, handle_border_clr, hexagon_angle); + ImGuiPureWrap::draw_hexagon(lower_handle_center, handle_radius - handle_border, handle_clr, hexagon_angle); + if (h_selected) { + ImGuiPureWrap::draw_hexagon(higher_handle_center, handle_radius, handle_border_clr, hexagon_angle); + ImGuiPureWrap::draw_hexagon(higher_handle_center, handle_radius - handle_border, handle_clr, hexagon_angle); + window->DrawList->AddLine(higher_handle_center + ImVec2(-line_offset, 0.0f), higher_handle_center + ImVec2(line_offset, 0.0f), handle_border_clr, line_width); + window->DrawList->AddLine(higher_handle_center + ImVec2(0.0f, -line_offset), higher_handle_center + ImVec2(0.0f, line_offset), handle_border_clr, line_width); + } + if (!h_selected) { + window->DrawList->AddLine(lower_handle_center + ImVec2(-line_offset, 0.0f), lower_handle_center + ImVec2(line_offset, 0.0f), handle_border_clr, line_width); + window->DrawList->AddLine(lower_handle_center + ImVec2(0.0f, -line_offset), lower_handle_center + ImVec2(0.0f, line_offset), handle_border_clr, line_width); + } + + // draw higher label + draw_label(higher_label, higher_handle, higher_handle_center); + // draw lower label + draw_label(lower_label, lower_handle, lower_handle_center); + } + + if (one_layer_flag) + { + // update handle position + value_changed = GUI::slider_behavior(id, one_slideable_region, v_min, v_max, + higher_value, &one_handle, ImGuiSliderFlags_Vertical, + m_tick_value, m_tick_rect); + + ImVec2 handle_center = one_handle.GetCenter(); + + // judge whether to open menu + if (ImGui::ItemHoverable(one_handle, id) && context.IO.MouseClicked[1]) + m_show_menu = true; + if ((!ImGui::ItemHoverable(one_handle, id) && context.IO.MouseClicked[1]) || + context.IO.MouseClicked[0]) + m_show_menu = false; + + ImVec2 bar_center = higher_handle.GetCenter(); + + if (!m_ticks.empty()) { + // draw ticks + draw_ticks(one_slideable_region); + // draw colored band + draw_colored_band(groove, one_slideable_region); + } + + // draw handle + window->DrawList->AddLine(ImVec2(mid_x - bar_width, handle_center.y), ImVec2(mid_x + bar_width, handle_center.y), handle_clr, line_width); + ImGuiPureWrap::draw_hexagon(handle_center, handle_radius, handle_border_clr, hexagon_angle); + ImGuiPureWrap::draw_hexagon(handle_center, handle_radius - handle_border, handle_clr, hexagon_angle); + window->DrawList->AddLine(handle_center + ImVec2(-line_offset, 0.0f), handle_center + ImVec2(line_offset, 0.0f), handle_border_clr, line_width); + window->DrawList->AddLine(handle_center + ImVec2(0.0f, -line_offset), handle_center + ImVec2(0.0f, line_offset), handle_border_clr, line_width); + + // draw label + draw_label(higher_label, one_handle, handle_center); + } + + return value_changed; +} + +void Control::render_menu() +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f) * m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f * m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, ImGui::GetStyle().ItemSpacing.y }); + + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + + std::vector colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + int extruder_num = colors.size(); + + if (m_show_menu) { + ImGui::OpenPopup("slider_menu_popup"); + } + + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); + if (ImGui::BeginPopup("slider_menu_popup")) { + if ((m_selection == ssLower && GetLowerValueD() == 0.0) || (m_selection == ssHigher && GetHigherValueD() == 0.0)) + { + menu_item_with_icon(_u8L("Add Pause").c_str(), "", ImVec2(0, 0), 0, false, false); + } + else + { + if (menu_item_with_icon(_u8L("Add Color Change").c_str(), "")) { + add_code_as_tick(ColorChange); + } + if (menu_item_with_icon(_u8L("Add Pause").c_str(), "")) { + add_code_as_tick(PausePrint); + } + if (menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "")) { + + } + if (!gcode(Template).empty()) { + if (menu_item_with_icon(_u8L("Add Custom Template").c_str(), "")) { + add_code_as_tick(Template); + } + } + } + + //BBS render this menu item only when extruder_num > 1 + if (extruder_num > 1) { + if (!m_can_change_color || m_draw_mode == dmSequentialFffPrint) { + begin_menu(_u8L("Change Filament").c_str(), false); + } + else 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(), "", ImVec2(14, 14) * m_scale, icon_clr)) add_code_as_tick(ToolChange, i + 1); + } + end_menu(); + } + } + ImGui::EndPopup(); + } + ImGui::PopStyleVar(1); + + ImGui::PopStyleColor(1); + ImGui::PopStyleVar(3); +} + +bool Control::render_button(const wchar_t btn_icon, const std::string& label_id, FocusedItem focus) +{ + float scale = (float)wxGetApp().em_unit() / 10.0f; + ImGui::SameLine(66.f * scale * m_scale); + + const ImGuiStyle& style = ImGui::GetStyle(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); + + const ImVec4 col = { 0.25f, 0.25f, 0.25f, 0.0f }; + ImGui::PushStyleColor(ImGuiCol_Button, col); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, col); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, col); + + std::string btn_label; + btn_label += btn_icon; + const bool ret = ImGui::Button((btn_label + "##" + label_id).c_str(), ImVec2(16*scale, 0)); + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) { + m_focus = focus; + std::string tooltip = into_u8(get_tooltip()); + ImGuiPureWrap::tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); + } + + ImGui::PopStyleVar(); + + return ret; +} + + +bool Control::imgui_render(GUI::GLCanvas3D& canvas) +{ + bool result = false; + GUI::Size cnv_size = canvas.get_canvas_size(); + + int canvas_width = cnv_size.get_width(); + int canvas_height = cnv_size.get_height(); + + /* style and colors */ + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + int windows_flag = ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse; + +// render_input_custom_gcode(); + + float scale = (float)wxGetApp().em_unit() / 10.0f; + + if (is_horizontal()) { + float pos_x = std::max(LEFT_MARGIN, 0.2f * canvas_width); + float pos_y = (canvas_height - HORIZONTAL_SLIDER_SIZE.y * m_scale); + ImVec2 size = ImVec2(canvas_width - 2 * pos_x, HORIZONTAL_SLIDER_SIZE.y * m_scale); + ImGuiPureWrap::set_next_window_pos(pos_x, pos_y, ImGuiCond_Always); + ImGuiPureWrap::begin(std::string("moves_slider"), windows_flag); + int value = GetHigherValue(); + if (horizontal_slider("moves_slider", &value, GetMinValue(), GetMaxValue(), ImVec2(pos_x, pos_y), size, scale)) { + result = true; + SetHigherValue(value); + } + ImGuiPureWrap::end(); + } + else { + float pos_x = canvas_width - (VERTICAL_SLIDER_SIZE.x + TEXT_WIDTH_DUMMY * scale - TEXT_WIDTH_DUMMY) * m_scale; + float pos_y = ONE_LAYER_OFFSET.y; + ImVec2 size = ImVec2((VERTICAL_SLIDER_SIZE.x + TEXT_WIDTH_DUMMY * scale - TEXT_WIDTH_DUMMY) * m_scale, canvas_height - 4 * pos_y); + + ImGuiPureWrap::set_next_window_pos(pos_x, pos_y, ImGuiCond_Always); + ImGuiPureWrap::begin(std::string("laysers_slider"), windows_flag); + + if (!m_ticks.empty() && /*m_draw_mode == dmRegular && */render_button(ImGui::RevertButton, "revert", fiRevertIcon)) + discard_all_thicks(); + else + ImGui::NewLine(); + + render_menu(); + + int higher_value = GetHigherValue(); + int lower_value = GetLowerValue(); + std::string higher_label = into_u8(get_label(m_higher_value)); + std::string lower_label = into_u8(get_label(m_lower_value)); + int temp_higher_value = higher_value; + int temp_lower_value = lower_value; + + if (vertical_slider("laysers_slider", &higher_value, &lower_value, higher_label, lower_label, GetMinValue(), GetMaxValue(), + ImVec2(pos_x, pos_y), size, m_selection, is_one_layer(), scale)) { + if (temp_higher_value != higher_value) + SetHigherValue(higher_value); + if (temp_lower_value != lower_value) + SetLowerValue(lower_value); + result = true; + } + + ImGui::NewLine(); + if (render_button(is_one_layer() ? ImGui::Lock : ImGui::Unlock, "one_layer", fiOneLayerIcon)) + switch_one_layer_mode(); + + ImGui::NewLine(); + if (m_draw_mode != dmSequentialGCodeView && render_button(ImGui::PrintIconMarker, "settings", fiCogIcon)) + show_cog_icon_context_menu(); + + ImGuiPureWrap::end(); + } + + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(2); + + return result; } bool Control::is_wipe_tower_layer(int tick) const @@ -743,10 +1423,10 @@ static wxString short_and_splitted_time(const std::string& time) ::sscanf(time.c_str(), "%ds", &seconds); // Format the dhm time. - auto get_d = [days]() { return format(_u8L("%1%d"), days); }; - auto get_h = [hours]() { return format(_u8L("%1%h"), hours); }; - auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); }; - auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); }; + auto get_d = [days]() { return GUI::format(_u8L("%1%d"), days); }; + auto get_h = [hours]() { return GUI::format(_u8L("%1%h"), hours); }; + auto get_m = [minutes](){ return GUI::format(_u8L("%1%m"), minutes); }; + auto get_s = [seconds](){ return GUI::format(_u8L("%1%s"), seconds); }; if (days > 0) return format_wxstr("%1%%2%\n%3%", get_d(), get_h(), get_m()); diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 1e20a4b6e8..80158c4ef1 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -303,7 +303,7 @@ public: void show_cog_icon_context_menu(); void auto_color_change(); - void imgui_render(GUI::GLCanvas3D& canvas); + bool imgui_render(GUI::GLCanvas3D& canvas); protected: @@ -476,6 +476,25 @@ private: size_t count() { return max_values.size(); } bool can_draw() { return m_parent != nullptr; } } m_ruler; + + // ImGuiDS + float m_scale{ 1.0 }; + bool m_can_change_color{ true }; + bool m_show_menu{ false }; + float get_pos_from_value(int v_min, int v_max, int value, const ImRect& rect); + + void draw_background(const ImRect& groove); + void draw_colored_band(const ImRect& groove, const ImRect& slideable_region); + void draw_label(std::string label, const ImRect& handle, const ImVec2& handle_center, bool is_horizontal = false); + void draw_ticks(const ImRect& slideable_region); + void render_menu(); + bool render_button(const wchar_t btn_icon, const std::string& label_id, FocusedItem focus); + bool horizontal_slider(const char* str_id, int* v, int v_min, int v_max, const ImVec2& pos, const ImVec2& size, float scale = 1.0); + bool vertical_slider(const char* str_id, int* higher_value, int* lower_value, + std::string& higher_label, std::string& lower_label, + int v_min, int v_max, const ImVec2& pos, const ImVec2& size, + SelectedSlider& selection, bool one_layer_flag = false, float scale = 1.0f); + }; } // DoubleSlider; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ed20c4cb64..b8f6a0965d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -352,6 +352,8 @@ void Preview::render_imgui_double_slider(GLCanvas3D& canvas) { if (m_layers_slider && m_layers_slider->IsShown()) m_layers_slider->imgui_render(canvas); + if (m_moves_slider && m_moves_slider->IsShown()) + m_moves_slider->imgui_render(canvas); } void Preview::jump_layers_slider(wxKeyEvent& evt) diff --git a/src/slic3r/GUI/ImGuiPureWrap.cpp b/src/slic3r/GUI/ImGuiPureWrap.cpp index f7179f685f..8128602fd3 100644 --- a/src/slic3r/GUI/ImGuiPureWrap.cpp +++ b/src/slic3r/GUI/ImGuiPureWrap.cpp @@ -296,6 +296,20 @@ bool combo(const std::string& label, const std::vector& options, in return res; } +void draw_hexagon(const ImVec2& center, float radius, ImU32 col, float start_angle) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + float a_min = start_angle; + float a_max = start_angle + 2.f * IM_PI; + + window->DrawList->PathArcTo(center, radius, a_min, a_max, 6); + window->DrawList->PathFillConvex(col); +} + // Scroll up for one item void scroll_up() { diff --git a/src/slic3r/GUI/ImGuiPureWrap.hpp b/src/slic3r/GUI/ImGuiPureWrap.hpp index a039ad6243..5af36ace83 100644 --- a/src/slic3r/GUI/ImGuiPureWrap.hpp +++ b/src/slic3r/GUI/ImGuiPureWrap.hpp @@ -53,6 +53,8 @@ namespace ImGuiPureWrap // Use selection = -1 to not mark any option as selected bool combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); + void draw_hexagon(const ImVec2& center, float radius, ImU32 col, float start_angle = 0.f); + void text(const char* label); void text(const std::string& label); void text(const std::wstring& label); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 9fceabbf63..863bd90c01 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1523,6 +1523,308 @@ void ImGuiWrapper::clipboard_set(void* /* user_data */, const char* text) } } +static float accer = 1.f; + +bool slider_behavior(ImGuiID id, const ImRect& region, const ImS32 v_min, const ImS32 v_max, ImS32* out_value, ImRect* out_handle, ImGuiSliderFlags flags/* = 0*/, const int fixed_value/* = -1*/, const ImVec4& fixed_rect/* = ImRect()*/) +{ + ImGuiContext& context = *GImGui; + ImGuiIO& io = ImGui::GetIO(); + + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + + const ImVec2 handle_sz = out_handle->GetSize(); + ImS32 v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + const float region_usable_sz = (region.Max[axis] - region.Min[axis]); + const float region_usable_pos_min = region.Min[axis]; + const float region_usable_pos_max = region.Max[axis]; + + // Process interacting with the slider + ImS32 v_new = *out_value; + bool value_changed = false; + // wheel behavior + ImRect mouse_wheel_responsive_region; + if (axis == ImGuiAxis_X) + mouse_wheel_responsive_region = ImRect(region.Min - ImVec2(handle_sz.x / 2, 0), region.Max + ImVec2(handle_sz.x / 2, 0)); + if (axis == ImGuiAxis_Y) + mouse_wheel_responsive_region = ImRect(region.Min - ImVec2(0, handle_sz.y), region.Max + ImVec2(0, handle_sz.y)); + if (ImGui::ItemHoverable(mouse_wheel_responsive_region, id)) { + v_new = ImClamp(*out_value + (ImS32)(context.IO.MouseWheel * accer), v_min, v_max); + } + // drag behavior + if (context.ActiveId == id) + { + float mouse_pos_ratio = 0.0f; + if (context.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (context.IO.MouseReleased[0]) + { + ImGui::ClearActiveID(); + } + if (context.IO.MouseDown[0]) + { + const float mouse_abs_pos = context.IO.MousePos[axis]; + mouse_pos_ratio = (region_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - region_usable_pos_min) / region_usable_sz, 0.0f, 1.0f) : 0.0f; + if (axis == ImGuiAxis_Y) + mouse_pos_ratio = 1.0f - mouse_pos_ratio; + v_new = v_min + (ImS32)(v_range * mouse_pos_ratio + 0.5f); + } + } + } + // click in fixed_rect behavior + if (ImGui::ItemHoverable(fixed_rect, id) && context.IO.MouseReleased[0]) + { + v_new = fixed_value; + } + + // apply result, output value + if (*out_value != v_new) + { + *out_value = v_new; + value_changed = true; + } + + // Output handle position so it can be displayed by the caller + const ImS32 v_clamped = (v_min < v_max) ? ImClamp(*out_value, v_min, v_max) : ImClamp(*out_value, v_max, v_min); + float handle_pos_ratio = v_range != 0 ? ((float)(v_clamped - v_min) / (float)v_range) : 0.0f; + handle_pos_ratio = axis == ImGuiAxis_Y ? 1.0f - handle_pos_ratio : handle_pos_ratio; + const float handle_pos = region_usable_pos_min + (region_usable_pos_max - region_usable_pos_min) * handle_pos_ratio; + + ImVec2 new_handle_center = axis == ImGuiAxis_Y ? ImVec2(out_handle->GetCenter().x, handle_pos) : ImVec2(handle_pos, out_handle->GetCenter().y); + *out_handle = ImRect(new_handle_center - handle_sz * 0.5f, new_handle_center + handle_sz * 0.5f); + + return value_changed; +} + + +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; +} + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index a5d1e411d9..0984d06d82 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -22,6 +22,7 @@ struct OptionViewParameters; class wxString; class wxMouseEvent; class wxKeyEvent; +struct ImRect; struct IMGUI_API ImGuiWindow; @@ -153,6 +154,11 @@ namespace ImGuiPSWrap ColorRGBA from_ImU32(const ImU32& color); ColorRGBA from_ImVec4(const ImVec4& color); } +bool slider_behavior(ImGuiID id, const ImRect& region, const ImS32 v_min, const ImS32 v_max, ImS32* out_value, ImRect* out_handle, ImGuiSliderFlags flags = 0, const int fixed_value = -1, const ImVec4& fixed_rect = ImVec4()); + +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); } // namespace GUI } // namespace Slic3r