#include "IMSlider.hpp" #include "libslic3r/GCode.hpp" #include "GUI_App.hpp" #include "NotificationManager.hpp" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include namespace Slic3r { namespace GUI { 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 HORIZONTAL_SLIDER_WINDOW_HEIGHT = 64.0f; static const float VERTICAL_SLIDER_WINDOW_WIDTH = 124.0f; static const float GROOVE_WIDTH = 12.0f; static const ImVec2 ONE_LAYER_MARGIN = ImVec2(20.0f, 20.0f); static const ImVec2 ONE_LAYER_BUTTON_SIZE = ImVec2(28.0f, 28.0f); static const ImU32 BACKGROUND_COLOR_DARK = IM_COL32(65, 65, 71, 255); static const ImU32 BACKGROUND_COLOR_LIGHT = IM_COL32(255, 255, 255, 255); static const ImU32 GROOVE_COLOR_DARK = IM_COL32(45, 45, 49, 255); static const ImU32 GROOVE_COLOR_LIGHT = IM_COL32(206, 206, 206, 255); //static const ImU32 BRAND_COLOR = IM_COL32(0, 174, 66, 255); static const ImU32 BRAND_COLOR = IM_COL32(33, 95, 154, 255); static int m_tick_value = -1; static ImVec4 m_tick_rect; bool equivalent_areas(const double& bottom_area, const double& top_area) { return fabs(bottom_area - top_area) <= miscalculation; } bool check_color_change(PrintObject *object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, std::function break_condition) { double prev_area = area(object->get_layer(frst_layer_id)->lslices); bool detected = false; for (size_t i = frst_layer_id + 1; i < layers_cnt; i++) { Layer *layer = object->get_layer(i); double cur_area = area(layer->lslices); // check for overhangs if (check_overhangs && cur_area > prev_area && !equivalent_areas(prev_area, cur_area)) break; // Check percent of the area decrease. // This value have to be more than min_delta_area and more then 10% if ((prev_area - cur_area > min_delta_area) && (cur_area / prev_area < 0.9)) { detected = true; if (break_condition(layer)) break; } prev_area = cur_area; } return detected; } static std::string gcode(Type type) { Slic3r::DynamicPrintConfig config = wxGetApp().preset_bundle->full_config(); switch (type) { //BBS case Template: return config.opt_string("template_custom_gcode"); case PausePrint: return config.opt_string("machine_pause_gcode"); default: return ""; } //const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config(); //switch (type) { ////BBS ////case ColorChange: return config.color_change_gcode; //case PausePrint: return config.machine_pause_gcode; //case Template: return config.template_custom_gcode; //default: return ""; //} } static std::string short_and_splitted_time(const std::string &time) { // Parse the dhms time format. int days = 0; int hours = 0; int minutes = 0; int seconds = 0; if (time.find('d') != std::string::npos) ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); else if (time.find('h') != std::string::npos) ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); else if (time.find('m') != std::string::npos) ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); else if (time.find('s') != std::string::npos) ::sscanf(time.c_str(), "%ds", &seconds); // Format the dhm time. char buffer[64]; if (days > 0) ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); else if (hours > 0) { if (hours < 10 && minutes < 10 && seconds < 10) ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds); else if (hours > 10 && minutes > 10 && seconds > 10) ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds); else if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds); else ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds); } else if (minutes > 0) { if (minutes > 10 && seconds > 10) ::sprintf(buffer, "%dm%ds", minutes, seconds); else ::sprintf(buffer, "%dm%ds", minutes, seconds); } else ::sprintf(buffer, "%ds", seconds); return std::string(buffer); } IMSlider::IMSlider(int lowerValue, int higherValue, int minValue, int maxValue, long style) { m_lower_value = lowerValue; m_higher_value = higherValue; m_min_value = minValue; m_max_value = maxValue; m_style = style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style : wxSL_HORIZONTAL; // BBS set to none style by default m_extra_style = style == wxSL_VERTICAL ? 0 : 0; m_selection = ssHigher; m_is_need_post_tick_changed_event = false; m_tick_change_event_type = Type::Unknown; m_ticks.set_extruder_colors(&m_extruder_colors); } bool IMSlider::init_texture() { bool result = true; if (!is_horizontal()) { // BBS init image texture id result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_on.svg", 24, 24, m_one_layer_on_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_on_hover.svg", 28, 28, m_one_layer_on_hover_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_off.svg", 28, 28, m_one_layer_off_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_off_hover.svg", 28, 28, m_one_layer_off_hover_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_on_dark.svg", 24, 24, m_one_layer_on_dark_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_on_hover_dark.svg", 28, 28, m_one_layer_on_hover_dark_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_off_dark.svg", 28, 28, m_one_layer_off_dark_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/one_layer_off_hover_dark.svg", 28, 28, m_one_layer_off_hover_dark_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/im_gcode_pause.svg", 14, 14, m_pause_icon_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/im_gcode_custom.svg", 14, 14, m_custom_icon_id); result &= IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/im_slider_delete.svg", 14, 14, m_delete_icon_id); } return result; } int IMSlider::GetActiveValue() const { return m_selection == ssLower ? m_lower_value : m_selection == ssHigher ? m_higher_value : -1; } void IMSlider::SetLowerValue(const int lower_val) { m_selection = ssLower; m_lower_value = lower_val; correct_lower_value(); set_as_dirty(); } void IMSlider::SetHigherValue(const int higher_val) { m_selection = ssHigher; m_higher_value = higher_val; correct_higher_value(); set_as_dirty(); } void IMSlider::SetSelectionSpan(const int lower_val, const int higher_val) { m_lower_value = std::max(lower_val, m_min_value); m_higher_value = std::max(std::min(higher_val, m_max_value), m_lower_value); if (m_lower_value < m_higher_value) m_is_one_layer = false; set_as_dirty(); } void IMSlider::SetMaxValue(const int max_value) { m_max_value = max_value; set_as_dirty(); } void IMSlider::SetSliderValues(const std::vector &values) { m_values = values; } Info IMSlider::GetTicksValues() const { Info custom_gcode_per_print_z; std::vector &values = custom_gcode_per_print_z.gcodes; const int val_size = m_values.size(); if (!m_values.empty()) for (const TickCode &tick : m_ticks.ticks) { if (tick.tick > val_size) break; values.emplace_back(CustomGCode::Item{m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra}); } if (m_force_mode_apply) custom_gcode_per_print_z.mode = m_mode; return custom_gcode_per_print_z; } void IMSlider::SetTicksValues(const Info &custom_gcode_per_print_z) { if (m_values.empty()) { m_ticks.mode = m_mode; return; } static bool last_spiral_vase_status = false; const bool was_empty = m_ticks.empty(); m_ticks.ticks.clear(); const std::vector &heights = custom_gcode_per_print_z.gcodes; for (auto h : heights) { int tick = get_tick_from_value(h.print_z, true); if (tick >= 0) m_ticks.ticks.emplace(TickCode{tick, h.type, h.extruder, h.color, h.extra}); } if (!was_empty && m_ticks.empty()) // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one ;// post_ticks_changed_event(); if (m_ticks.has_tick_with_code(ToolChange) && !m_can_change_color) { if (!wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->using_exported_file()) { m_ticks.erase_all_ticks_with_code(ToolChange); post_ticks_changed_event(); } } if (last_spiral_vase_status != m_is_spiral_vase) { last_spiral_vase_status = m_is_spiral_vase; if (!m_ticks.empty()) { m_ticks.ticks.clear(); post_ticks_changed_event(); } } //auto has_tick_execpt = [this](CustomGCode::Type type) { // for (const TickCode& tick : m_ticks.ticks) // if (tick.type != type) return true; // return false; //}; if ((!m_ticks.empty() /*&& has_tick_execpt(PausePrint)*/) && m_draw_mode == dmSequentialFffPrint) { for (auto it{ m_ticks.ticks.begin() }, end{ m_ticks.ticks.end() }; it != end;) { if (true/*it->type != PausePrint*/) it = m_ticks.ticks.erase(it); else ++it; } post_ticks_changed_event(); } if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty()) m_ticks.mode = custom_gcode_per_print_z.mode; set_as_dirty(); } void IMSlider::SetLayersTimes(const std::vector &layers_times, float total_time) { m_layers_times.clear(); if (layers_times.empty()) return; m_layers_times.resize(layers_times.size(), 0.0); m_layers_times[0] = layers_times[0]; for (size_t i = 1; i < layers_times.size(); i++) m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; // Erase duplicates values from m_values and save it to the m_layers_values // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled if (m_is_wipe_tower && m_values.size() != m_layers_times.size()) { m_layers_values = m_values; sort(m_layers_values.begin(), m_layers_values.end()); m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times // So, add this value from the total print time value if (m_layers_values.size() != m_layers_times.size()) for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++) m_layers_times.push_back(total_time); set_as_dirty(); set_as_dirty(); } } void IMSlider::SetLayersTimes(const std::vector &layers_times) { m_is_wipe_tower = false; m_layers_times = layers_times; for (size_t i = 1; i < m_layers_times.size(); i++) m_layers_times[i] += m_layers_times[i - 1]; } void IMSlider::SetDrawMode(bool is_sequential_print) { m_draw_mode = is_sequential_print ? dmSequentialFffPrint : dmRegular; m_can_change_color = m_can_change_color && !(m_draw_mode == dmSequentialFffPrint); } void IMSlider::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder, bool can_change_color) { m_mode = !is_one_extruder_printed_model ? MultiExtruder : only_extruder < 0 ? SingleExtruder : MultiAsSingle; if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode)) m_ticks.mode = m_mode; m_only_extruder = only_extruder; UseDefaultColors(m_mode == SingleExtruder); m_is_wipe_tower = m_mode != SingleExtruder; auto config = wxGetApp().preset_bundle->full_config(); m_is_spiral_vase = config.option("spiral_mode")->value; m_can_change_color = can_change_color && !m_is_spiral_vase; // close opened menu window after reslice m_show_menu = false; m_show_custom_gcode_window = false; m_show_go_to_layer_dialog = false; } void IMSlider::SetExtruderColors( const std::vector& extruder_colors) { m_extruder_colors = extruder_colors; } bool IMSlider::IsNewPrint() { const Print &print = GUI::wxGetApp().plater()->fff_print(); std::string idxs; for (auto object : print.objects()) idxs += std::to_string(object->id().id) + "_"; if (idxs == m_print_obj_idxs) return false; m_print_obj_idxs = idxs; return true; } void IMSlider::post_ticks_changed_event(Type type) { m_tick_change_event_type = type; m_is_need_post_tick_changed_event = true; } void IMSlider::add_custom_gcode(std::string custom_gcode) { if (m_selection == ssUndef) return; const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; const auto it = m_ticks.ticks.find(TickCode{ tick }); if (it != m_ticks.ticks.end()) { m_ticks.ticks.erase(it); } m_ticks.ticks.emplace(TickCode{ tick, Custom, std::max(1, m_only_extruder), "", custom_gcode }); post_ticks_changed_event(Custom); } void IMSlider::add_code_as_tick(Type type, int selected_extruder) { if (m_selection == ssUndef) return; const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; if (!check_ticks_changed_event(type)) { BOOST_LOG_TRIVIAL(trace) << "check ticks change event false"; return; } if (type == ColorChange && gcode(ColorChange).empty()) GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode); const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_only_extruder); const auto it = m_ticks.ticks.find(TickCode{tick}); if (it == m_ticks.ticks.end()) { // try to add tick if (!m_ticks.add_tick(tick, type, extruder, m_values[tick])) return; } else if (type == ToolChange || type == ColorChange) { // try to switch tick code to ToolChange or ColorChange accordingly if (!m_ticks.switch_code_for_tick(it, type, extruder)) return; } else return; post_ticks_changed_event(type); } void IMSlider::delete_tick(const TickCode& tick) { m_ticks.ticks.erase(tick); post_ticks_changed_event(tick.type); } bool IMSlider::check_ticks_changed_event(Type type) { //BBL only support MultiExtruder if (m_ticks.mode == m_mode || (type != ColorChange && type != ToolChange) || (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle)) // Just mark ColorChanges for all unused extruders return true; if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder) || (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder)) { if (!m_ticks.has_tick_with_code(ColorChange)) return true; /* wxString message = (m_ticks.mode == SingleExtruder ? _L("The last color change data was saved for a single extruder printing.") : _L("The last color change data was saved for a multi extruder printing.")) + "\n" + _L("Your current changes will delete all saved color changes.") + "\n\n\t" + _L("Are you sure you want to continue?"); GUI::MessageDialog msg(this, message, _L("Notice"), wxYES_NO); if (msg.ShowModal() == wxID_YES) { m_ticks.erase_all_ticks_with_code(ColorChange); post_ticks_changed_event(ColorChange); } */ return false; } return true; } // switch on/off one layer mode bool IMSlider::switch_one_layer_mode() { if (m_show_custom_gcode_window) return false; m_is_one_layer = !m_is_one_layer; if (!m_is_one_layer) { SetLowerValue(m_min_value); SetHigherValue(m_max_value); } m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (m_selection == ssUndef) m_selection = ssHigher; set_as_dirty(); return true; } void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& groove) { const ImU32 bg_rect_col = m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT; const ImU32 groove_col = m_is_dark ? GROOVE_COLOR_DARK : GROOVE_COLOR_LIGHT; // draw bg of slider ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, bg_rect_col, false, 0.5 * bg_rect.GetWidth()); // draw groove ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth()); } bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, 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 ImVec2 pos = window->DC.CursorPos; const ImRect draw_region(pos, pos + size); ImGui::ItemSize(draw_region); const float handle_dummy_width = 10.0f * m_scale; const float text_right_dummy = 50.0f * scale * m_scale; const float handle_radius = 12.0f * m_scale; const float handle_border = 2.0f * m_scale; const float text_frame_rounding = 2.0f * scale * m_scale; const float text_start_offset = 8.0f * m_scale; const ImVec2 text_padding = ImVec2(5.0f, 2.0f) * m_scale; const float triangle_offsets[3] = {-3.5f * m_scale, 3.5f * m_scale, -6.06f * m_scale}; const ImU32 white_bg = m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT; const ImU32 handle_clr = BRAND_COLOR; const ImU32 handle_border_clr = m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT; // calculate groove size const ImVec2 groove_start = ImVec2(pos.x + handle_dummy_width, pos.y + size.y - ONE_LAYER_MARGIN.y * m_scale - ONE_LAYER_BUTTON_SIZE.y * m_scale * 0.5f - GROOVE_WIDTH * m_scale * 0.5f); const ImVec2 groove_size = ImVec2(size.x - 2 * handle_dummy_width - text_right_dummy, GROOVE_WIDTH * m_scale); const ImRect groove = ImRect(groove_start, groove_start + groove_size); const ImRect bg_rect = ImRect(groove.Min - ImVec2(6.0f, 6.0f) * m_scale, groove.Max + ImVec2(6.0f, 6.0f) * m_scale); const float mid_y = groove.GetCenter().y; // set mouse active region. active region. bool hovered = ImGui::ItemHoverable(draw_region, id); if (hovered && context.IO.MouseDown[0]) { ImGui::SetActiveID(id, window); ImGui::SetFocusID(id, window); ImGui::FocusWindow(window); } // draw background draw_background_and_groove(bg_rect, groove); // set scrollable region const ImRect slideable_region = ImRect(bg_rect.Min + ImVec2(handle_radius, 0.0f), bg_rect.Max - ImVec2(handle_radius, 0.0f)); // 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 = 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(groove.Min, ImVec2(handle_center.x, groove.Max.y)); window->DrawList->AddRectFilled(scroll_line.Min, scroll_line.Max, handle_clr, 0.5f * GROOVE_WIDTH * m_scale); // draw handle window->DrawList->AddCircleFilled(handle_center, handle_radius, handle_border_clr); window->DrawList->AddCircleFilled(handle_center, handle_radius - handle_border, handle_clr); // draw label auto text_utf8 = into_u8(std::to_string(*value)); ImVec2 text_content_size = ImGui::CalcTextSize(text_utf8.c_str()); ImVec2 text_size = text_content_size + text_padding * 2; ImVec2 text_start = ImVec2(handle_center.x + handle_radius + text_start_offset, handle_center.y - 0.5 * text_size.y); ImRect text_rect(text_start, text_start + text_size); ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding); ImVec2 pos_1 = ImVec2(text_rect.Min.x, text_rect.GetCenter().y + triangle_offsets[0]); ImVec2 pos_2 = ImVec2(text_rect.Min.x, text_rect.GetCenter().y + triangle_offsets[1]); ImVec2 pos_3 = ImVec2(text_rect.Min.x + triangle_offsets[2], text_rect.GetCenter().y); window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg); ImGui::RenderText(text_start + text_padding, std::to_string(*value).c_str()); return value_changed; } void IMSlider::draw_colored_band(const ImRect& groove, const ImRect& slideable_region) { if (!m_ticks.has_tick_with_code(ToolChange)) return; ImRect main_band = groove; auto draw_band = [this](const ImU32& clr, const ImRect& band_rc) { if (clr == (m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT)) { ImRect rc = band_rc; rc.Min += ImVec2(1, 1) * m_scale; rc.Max -= ImVec2(1, 1) * m_scale; ImGui::RenderFrame(band_rc.Min, band_rc.Max, m_is_dark ? GROOVE_COLOR_DARK : GROOVE_COLOR_LIGHT, 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, m_is_dark ? GROOVE_COLOR_DARK : GROOVE_COLOR_LIGHT, false); ImGui::RenderFrame(rc.Min, rc.Max, clr, false, rc.GetWidth() * 0.5); //cover round corner ImGui::RenderFrame(ImVec2(rc.Min.x, rc.Max.y - rc.GetWidth() * 0.5), rc.Max, clr, false); } else { 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) { if (clr == (m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT)) { ImRect rc = main_band; rc.Min += ImVec2(1, 1) * m_scale; rc.Max -= ImVec2(1, 1) * m_scale; ImGui::RenderFrame(main_band.Min, main_band.Max, m_is_dark ? GROOVE_COLOR_DARK : GROOVE_COLOR_LIGHT, false, main_band.GetWidth() * 0.5); ImGui::RenderFrame(rc.Min, rc.Max, clr, false, rc.GetWidth() * 0.5); } else { 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); //draw colored band if (tick_it->type == ToolChange) { if ((m_mode == SingleExtruder) || (m_mode == MultiAsSingle)) { ImRect band_rect = ImRect(main_band.Min, ImVec2(main_band.Max.x, tick_pos)); 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 IMSlider::draw_custom_label_block(const ImVec2 anchor, Type type) { wxString label; switch (type) { case ColorChange: label = _L("Color"); break; case PausePrint: label = _L("Pause"); break; case ToolChange: label = _L("Color"); break; case Template: label = _L("Template"); break; case Custom: label = _L("Custom"); break; case Unknown: break; default: break; } const ImVec2 text_size = ImGui::CalcTextSize(into_u8(label).c_str()); const ImVec2 padding = ImVec2(4, 2) * m_scale; const ImU32 clr = IM_COL32(255, 111, 0, 255); const float rounding = 2.0f * m_scale; ImVec2 block_pos = { anchor.x - text_size.x - padding.x * 2, anchor.y - text_size.y / 2 - padding.y }; ImVec2 block_size = { text_size.x + padding.x * 2, text_size.y + padding.y * 2 }; ImGui::RenderFrame(block_pos, block_pos + block_size, clr, false, rounding); ImGui::PushStyleColor(ImGuiCol_Text, { 1,1,1,1 }); ImGui::RenderText(block_pos + padding, into_u8(label).c_str()); ImGui::PopStyleColor(); } void IMSlider::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(52.0f, 16.0f) * m_scale; ImVec2 tick_offset = ImVec2(22.0f, 14.0f) * m_scale; float tick_width = 1.0f * m_scale; ImVec2 icon_offset = ImVec2(16.0f, 7.0f) * m_scale; ImVec2 icon_size = ImVec2(14.0f, 14.0f) * m_scale; const ImU32 tick_clr = IM_COL32(144, 144, 144, 255); const ImU32 tick_hover_box_clr = m_is_dark ? IM_COL32(65, 65, 71, 255) : IM_COL32(219, 253, 231, 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)) { // render left tick box ImRect left_hover_box = ImRect(tick_hover_box.Min, { slideable_region.Min.x, tick_hover_box.Max.y }); ImGui::RenderFrame(left_hover_box.Min, left_hover_box.Max, tick_hover_box_clr, false); // render right tick box ImRect right_hover_box = ImRect({ slideable_region.Max.x, tick_hover_box.Min.y }, tick_hover_box.Max); ImGui::RenderFrame(right_hover_box.Min, right_hover_box.Max, tick_hover_box_clr, false); show_tooltip(*tick_it); 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 (tick_it->type == Custom || tick_it->type == Template) { ImTextureID custom_icon_id = m_custom_icon_id; ImVec2 icon_pos = ImVec2(slideable_region.GetCenter().x + icon_offset.x, tick_pos - icon_offset.y); button_with_pos(custom_icon_id, icon_size, icon_pos); } //draw label block ImVec2 label_block_anchor = ImVec2(slideable_region.GetCenter().x - tick_offset.y, tick_pos); draw_custom_label_block(label_block_anchor, tick_it->type); ++tick_it; } tick_it = GetSelection() == ssHigher ? m_ticks.ticks.find(TickCode{this->GetHigherValue()}) : GetSelection() == ssLower ? m_ticks.ticks.find(TickCode{this->GetLowerValue()}) : m_ticks.ticks.end(); if (tick_it != m_ticks.ticks.end()) { //draw label block again, to keep it in front ImVec2 label_block_anchor = ImVec2(slideable_region.GetCenter().x - tick_offset.y, get_tick_pos(tick_it->tick)); draw_custom_label_block(label_block_anchor, tick_it->type); // draw delete icon 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 delete_tick(*tick_it); } } } } void IMSlider::draw_tick_on_mouse_position(const ImRect& slideable_region) { int v_min = GetMinValue(); int v_max = GetMaxValue(); ImGuiContext& context = *GImGui; int tick = get_tick_near_point(v_min, v_max, context.IO.MousePos, slideable_region); //draw tick ImVec2 tick_offset = ImVec2(22.0f, 14.0f) * m_scale; float tick_width = 1.0f * m_scale; const ImU32 tick_clr = IM_COL32(144, 144, 144, 255); float tick_pos = get_pos_from_value(v_min, v_max, tick, slideable_region); 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 layer time std::string label = get_label(tick, ltEstimatedTime); show_tooltip(label); } void IMSlider::show_tooltip(const std::string tooltip) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 6 * m_scale, 3 * m_scale }); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, { 3 * m_scale }); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 }); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); ImGui::BeginTooltip(); ImGui::TextUnformatted(tooltip.c_str()); ImGui::EndTooltip(); ImGui::PopStyleColor(3); ImGui::PopStyleVar(2); } void IMSlider::show_tooltip(const TickCode& tick){ // Use previous layer's complete time as current layer's tick time, // since ticks are added at the beginning of current layer std::string time_str = ""; if (tick.tick > 0) { time_str = get_label(tick.tick - 1, ltEstimatedTime); } if (!time_str.empty()) { time_str.insert(0, "\n"); } switch (tick.type) { case CustomGCode::ColorChange: break; case CustomGCode::PausePrint: show_tooltip(_u8L("Pause:") + " \"" + gcode(PausePrint) + "\"" + time_str); break; case CustomGCode::ToolChange: show_tooltip(_u8L("Change Filament") + time_str); break; case CustomGCode::Template: show_tooltip(_u8L("Custom Template:") + " \"" + gcode(Template) + "\"" + time_str); break; case CustomGCode::Custom: show_tooltip(_u8L("Custom G-code:") + " \"" + tick.extra + "\"" + time_str); break; default: break; } } bool IMSlider::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& 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 ImVec2 pos = window->DC.CursorPos; const ImRect draw_region(pos, pos + size); ImGui::ItemSize(draw_region); const float text_dummy_height = 30.0f * scale * m_scale; const float handle_radius = 12.0f * m_scale; const float handle_border = 2.0f * m_scale; const float line_width = 1.0f * m_scale; const float line_length = 12.0f * m_scale; const float one_handle_offset = 26.0f * m_scale; const float bar_width = 28.0f * m_scale; const float text_frame_rounding = 2.0f * scale * m_scale; const ImVec2 text_padding = ImVec2(5.0f, 2.0f) * m_scale; const 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; ImVec2 text_size; const ImU32 white_bg = m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT; const ImU32 handle_clr = BRAND_COLOR; const ImU32 handle_border_clr = m_is_dark ? BACKGROUND_COLOR_DARK : BACKGROUND_COLOR_LIGHT; // calculate slider groove size const ImVec2 groove_start = ImVec2(pos.x + size.x - ONE_LAYER_MARGIN.x * m_scale - ONE_LAYER_BUTTON_SIZE.x * m_scale * 0.5f - GROOVE_WIDTH * m_scale * 0.5f, pos.y + text_dummy_height); const ImVec2 groove_size = ImVec2(GROOVE_WIDTH * m_scale, size.y - 2 * text_dummy_height); const ImRect groove = ImRect(groove_start, groove_start + groove_size); const ImRect bg_rect = ImRect(groove.Min - ImVec2(6.0f, 6.0f) * m_scale, groove.Max + ImVec2(6.0f, 6.0f) * m_scale); const float mid_x = groove.GetCenter().x; // set mouse active region. const ImRect active_region = ImRect(ImVec2(draw_region.Min.x + 35.0f * m_scale, draw_region.Min.y), draw_region.Max); bool hovered = ImGui::ItemHoverable(active_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); } // draw background draw_background_and_groove(bg_rect, groove); // Processing interacting // set scrollable region const ImRect region = ImRect(bg_rect.Min + ImVec2(0.0f, handle_radius), bg_rect.Max - ImVec2(0.0f, handle_radius)); const ImRect higher_slideable_region = ImRect(region.Min, region.Max - ImVec2(0, handle_radius)); const ImRect lower_slideable_region = ImRect(region.Min + ImVec2(0, handle_radius), region.Max); const ImRect one_slideable_region = 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)); bool value_changed = false; if (!one_layer_flag) { // select higher handle by default static bool h_selected = (selection == ssHigher); 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; // draw ticks draw_ticks(h_selected ? higher_slideable_region : lower_slideable_region); // draw colored band draw_colored_band(groove, h_selected ? higher_slideable_region : lower_slideable_region); if (!m_ticks.has_tick_with_code(ToolChange)) { // draw scroll line ImRect scroll_line = ImRect(ImVec2(groove.Min.x, higher_handle_center.y), ImVec2(groove.Max.x, lower_handle_center.y)); window->DrawList->AddRectFilled(scroll_line.Min, scroll_line.Max, handle_clr); } // draw handles window->DrawList->AddCircleFilled(higher_handle_center, handle_radius, handle_border_clr); window->DrawList->AddCircleFilled(higher_handle_center, handle_radius - handle_border, handle_clr); window->DrawList->AddCircleFilled(lower_handle_center, handle_radius, handle_border_clr); window->DrawList->AddCircleFilled(lower_handle_center, handle_radius - handle_border, handle_clr); if (h_selected) { window->DrawList->AddCircleFilled(higher_handle_center, handle_radius, handle_border_clr); window->DrawList->AddCircleFilled(higher_handle_center, handle_radius - handle_border, handle_clr); window->DrawList->AddLine(higher_handle_center + ImVec2(-0.5f * line_length, 0.0f), higher_handle_center + ImVec2(0.5f * line_length, 0.0f), white_bg, line_width); window->DrawList->AddLine(higher_handle_center + ImVec2(0.0f, -0.5f * line_length), higher_handle_center + ImVec2(0.0f, 0.5f * line_length), white_bg, line_width); } if (!h_selected) { window->DrawList->AddLine(lower_handle_center + ImVec2(-0.5f * line_length, 0.0f), lower_handle_center + ImVec2(0.5f * line_length, 0.0f), white_bg, line_width); window->DrawList->AddLine(lower_handle_center + ImVec2(0.0f, -0.5f * line_length), lower_handle_center + ImVec2(0.0f, 0.5f * line_length), white_bg, line_width); } // draw higher label auto text_utf8 = into_u8(higher_label); text_content_size = ImGui::CalcTextSize(text_utf8.c_str()); text_size = text_content_size + text_padding * 2; ImVec2 text_start = ImVec2(higher_handle.Min.x - text_size.x - triangle_offsets[2].x, higher_handle_center.y - text_size.y); ImRect text_rect(text_start, text_start + text_size); ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding); ImVec2 pos_1 = text_rect.Max - triangle_offsets[0]; ImVec2 pos_2 = pos_1 - triangle_offsets[1]; ImVec2 pos_3 = pos_1 + triangle_offsets[2]; window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg); ImGui::RenderText(text_start + text_padding, higher_label.c_str()); // draw lower label text_utf8 = into_u8(lower_label); text_content_size = ImGui::CalcTextSize(text_utf8.c_str()); text_size = text_content_size + text_padding * 2; text_start = ImVec2(lower_handle.Min.x - text_size.x - triangle_offsets[2].x, lower_handle_center.y); text_rect = ImRect(text_start, text_start + text_size); ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding); pos_1 = ImVec2(text_rect.Max.x, text_rect.Min.y) - triangle_offsets[0]; pos_2 = pos_1 + triangle_offsets[1]; pos_3 = pos_1 + triangle_offsets[2]; window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg); ImGui::RenderText(text_start + text_padding, lower_label.c_str()); if (hovered) { draw_tick_on_mouse_position(h_selected ? higher_slideable_region : lower_slideable_region); } } if (one_layer_flag) { // update handle position value_changed = 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(); // 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 - 0.5 * bar_width, handle_center.y), ImVec2(mid_x + 0.5 * bar_width, handle_center.y), handle_clr, 2 * line_width); window->DrawList->AddCircleFilled(handle_center, handle_radius, handle_border_clr); window->DrawList->AddCircleFilled(handle_center, handle_radius - handle_border, handle_clr); window->DrawList->AddLine(handle_center + ImVec2(-0.5f * line_length, 0.0f), handle_center + ImVec2(0.5f * line_length, 0.0f), white_bg, line_width); window->DrawList->AddLine(handle_center + ImVec2(0.0f, -0.5f * line_length), handle_center + ImVec2(0.0f, 0.5f * line_length), white_bg, line_width); // draw label auto text_utf8 = into_u8(higher_label); text_content_size = ImGui::CalcTextSize(text_utf8.c_str()); text_size = text_content_size + text_padding * 2; ImVec2 text_start = ImVec2(one_handle.Min.x - text_size.x, handle_center.y - 0.5 * text_size.y); ImRect text_rect = ImRect(text_start, text_start + text_size); ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding); ImGui::RenderText(text_start + text_padding, higher_label.c_str()); if (hovered) { draw_tick_on_mouse_position(one_slideable_region); } } return value_changed; } bool IMSlider::render(int canvas_width, int canvas_height) { bool result = false; ImGuiWrapper &imgui = *wxGetApp().imgui(); /* 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_::ImGuiCol_Text, ImVec4(0, 0.682f, 0.259f, 1.0f)); int windows_flag = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; float scale = (float) wxGetApp().em_unit() / 10.0f; if (is_horizontal()) { ImVec2 size = ImVec2(canvas_width - 2 * std::max(LEFT_MARGIN * m_scale, 0.2f * canvas_width), HORIZONTAL_SLIDER_WINDOW_HEIGHT * m_scale); imgui.set_next_window_pos(0.5f * static_cast(canvas_width), canvas_height, ImGuiCond_Always, 0.5f, 1.0f); imgui.begin(std::string("moves_slider"), windows_flag); int value = GetHigherValue(); if (horizontal_slider("moves_slider", &value, GetMinValue(), GetMaxValue(), size, scale)) { result = true; SetHigherValue(value); } imgui.end(); } else { ImVec2 size = ImVec2(VERTICAL_SLIDER_WINDOW_WIDTH * m_scale, 0.8f * canvas_height); imgui.set_next_window_pos(canvas_width, 0.5f * static_cast(canvas_height), ImGuiCond_Always, 1.0f, 0.5f); imgui.begin(std::string("laysers_slider"), windows_flag); render_menu(); int higher_value = GetHigherValue(); int lower_value = GetLowerValue(); std::string higher_label = get_label(m_higher_value); std::string lower_label = 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(), 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.end(); imgui.set_next_window_pos(canvas_width, canvas_height, ImGuiCond_Always, 1.0f, 1.0f); ImGui::SetNextWindowSize((ONE_LAYER_BUTTON_SIZE + ONE_LAYER_MARGIN) * m_scale, 0); imgui.begin(std::string("one_layer_button"), windows_flag); ImTextureID normal_id = m_is_dark ? is_one_layer() ? m_one_layer_on_dark_id : m_one_layer_off_dark_id : is_one_layer() ? m_one_layer_on_id : m_one_layer_off_id; ImTextureID hover_id = m_is_dark ? is_one_layer() ? m_one_layer_on_hover_dark_id : m_one_layer_off_hover_dark_id : is_one_layer() ? m_one_layer_on_hover_id : m_one_layer_off_hover_id; if (ImGui::ImageButton3(normal_id, hover_id, ONE_LAYER_BUTTON_SIZE * m_scale)) { switch_one_layer_mode(); } imgui.end(); } ImGui::PopStyleVar(3); ImGui::PopStyleColor(2); return result; } void IMSlider::render_input_custom_gcode(std::string custom_gcode) { if (m_show_custom_gcode_window) ImGui::OpenPopup((_u8L("Custom G-code")).c_str()); ImGuiWrapper& imgui = *wxGetApp().imgui(); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); static bool set_focus = true; ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); imgui.push_menu_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 10) * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.f * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 3) * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 7) * m_scale); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, m_is_dark ? ImVec4(54 / 255.0f, 54 / 255.0f, 60 / 255.0f, 1.00f) : ImVec4(245 / 255.0f, 245 / 255.0f, 245 / 255.0f, 1.00f)); ImGui::GetCurrentContext()->DimBgRatio = 1.0f; int windows_flag = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::BeginPopupModal((_u8L("Custom G-code")).c_str(), NULL, windows_flag)) { imgui.text(_u8L("Enter Custom G-code used on current layer:")); if (ImGui::IsMouseClicked(0)) { set_focus = false; } if (set_focus && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)) { wxGetApp().plater()->get_current_canvas3D()->force_set_focus(); ImGui::SetKeyboardFocusHere(0); strcpy(m_custom_gcode, custom_gcode.c_str()); } const int text_height = 6; ImGui::InputTextMultiline("##text", m_custom_gcode, sizeof(m_custom_gcode), ImVec2(-1, ImGui::GetTextLineHeight() * text_height)); ImGui::NewLine(); ImGui::SameLine(ImGui::GetStyle().WindowPadding.x * 14); imgui.push_confirm_button_style(); bool disable_button = false; if (strlen(m_custom_gcode) == 0) disable_button = true; if (disable_button) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); imgui.push_button_disable_style(); } if (imgui.bbl_button(_L("OK"))) { add_custom_gcode(m_custom_gcode); m_show_custom_gcode_window = false; ImGui::CloseCurrentPopup(); set_focus = true; } if (disable_button) { ImGui::PopItemFlag(); imgui.pop_button_disable_style(); } imgui.pop_confirm_button_style(); ImGui::SameLine(); imgui.push_cancel_button_style(); if (imgui.bbl_button(_L("Cancel")) || ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) { m_show_custom_gcode_window = false; ImGui::CloseCurrentPopup(); set_focus = true; } imgui.pop_cancel_button_style(); ImGui::EndPopup(); } ImGui::PopStyleVar(4); ImGui::PopStyleColor(); imgui.pop_menu_style(); } void IMSlider::do_go_to_layer(size_t layer_number) { clamp((int)layer_number, m_min_value, m_max_value); GetSelection() == ssLower ? SetLowerValue(layer_number) : SetHigherValue(layer_number); } void IMSlider::render_go_to_layer_dialog() { if (m_show_go_to_layer_dialog) ImGui::OpenPopup((_u8L("Jump to Layer")).c_str()); ImGuiWrapper& imgui = *wxGetApp().imgui(); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); static bool set_focus = true; imgui.push_menu_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 10) * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.f * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 3) * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 7) * m_scale); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, m_is_dark ? ImVec4(54 / 255.0f, 54 / 255.0f, 60 / 255.0f, 1.00f) : ImVec4(245 / 255.0f, 245 / 255.0f, 245 / 255.0f, 1.00f)); ImGui::GetCurrentContext()->DimBgRatio = 1.0f; int windows_flag = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::BeginPopupModal((_u8L("Jump to layer")).c_str(), NULL, windows_flag)) { imgui.text(_u8L("Please enter the layer number") + " (" + std::to_string(m_min_value + 1) + " - " + std::to_string(m_max_value + 1) + "):"); if (ImGui::IsMouseClicked(0)) { set_focus = false; } if (set_focus && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)) { wxGetApp().plater()->get_current_canvas3D()->force_set_focus(); ImGui::SetKeyboardFocusHere(0); } ImGui::InputText("##input_layer_number", m_layer_number, sizeof(m_layer_number)); ImGui::NewLine(); ImGui::SameLine(GImGui->Style.WindowPadding.x * 8); imgui.push_confirm_button_style(); bool disable_button = false; if (strlen(m_layer_number) == 0) disable_button = true; else { for (size_t i = 0; i < strlen(m_layer_number); i++) if (!isdigit(m_layer_number[i])) disable_button = true; if (!disable_button && (m_min_value > atoi(m_layer_number) - 1 || atoi(m_layer_number) - 1 > m_max_value)) disable_button = true; } if (disable_button) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); imgui.push_button_disable_style(); } if (imgui.bbl_button(_L("OK")) || (!disable_button && ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Enter)))) { do_go_to_layer(atoi(m_layer_number) - 1); m_show_go_to_layer_dialog = false; ImGui::CloseCurrentPopup(); set_focus = true; } if (disable_button) { ImGui::PopItemFlag(); imgui.pop_button_disable_style(); } imgui.pop_confirm_button_style(); ImGui::SameLine(); imgui.push_cancel_button_style(); if (imgui.bbl_button(_L("Cancel")) || ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) { m_show_go_to_layer_dialog = false; ImGui::CloseCurrentPopup(); set_focus = true; } imgui.pop_cancel_button_style(); ImGui::EndPopup(); } ImGui::PopStyleVar(4); ImGui::PopStyleColor(); imgui.pop_menu_style(); } void IMSlider::render_menu() { if (!m_menu_enable) return; ImGuiWrapper::push_menu_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); auto tick_it = GetSelection() == ssHigher ? m_ticks.ticks.find(TickCode{ GetHigherValue() }) : GetSelection() == ssLower ? m_ticks.ticks.find(TickCode{ GetLowerValue() }) : m_ticks.ticks.end(); std::string custom_code; if (tick_it != m_ticks.ticks.end()) { if (tick_it->type == CustomGCode::Custom) custom_code = tick_it->extra; render_edit_menu(*tick_it); } else { render_add_menu(); } ImGui::PopStyleVar(1); ImGuiWrapper::pop_menu_style(); render_input_custom_gcode(custom_code); render_go_to_layer_dialog(); } void IMSlider::render_add_menu() { int extruder_num = m_extruder_colors.size(); if (m_show_menu) ImGui::OpenPopup("slider_add_menu_popup"); if (ImGui::BeginPopup("slider_add_menu_popup")) { bool menu_item_enable = m_draw_mode != dmSequentialFffPrint; bool hovered = false; { if (menu_item_with_icon(_u8L("Add Pause").c_str(), "", ImVec2(0, 0), 0, false, menu_item_enable, &hovered)) { add_code_as_tick(PausePrint); } if (hovered) { show_tooltip(_u8L("Insert a pause command at the beginning of this layer.")); } if (menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "", ImVec2(0, 0), 0, false, menu_item_enable, &hovered)) { m_show_custom_gcode_window = true; } if (hovered) { show_tooltip(_u8L("Insert custom G-code at the beginning of this layer.")); } if (!gcode(Template).empty()) { if (menu_item_with_icon(_u8L("Add Custom Template").c_str(), "", ImVec2(0, 0), 0, false, menu_item_enable, &hovered)) { add_code_as_tick(Template); } if (hovered) { show_tooltip(_u8L("Insert template custom G-code at the beginning of this layer.")); } } if (menu_item_with_icon(_u8L("Jump to Layer").c_str(), "")) { m_show_go_to_layer_dialog = true; } } //BBS render this menu item only when extruder_num > 1 if (extruder_num > 1) { if (!m_can_change_color) { 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(m_extruder_colors[i]); ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); if (rgba[3] == 0) icon_clr = 0; if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, true, &hovered)) add_code_as_tick(ToolChange, i + 1); if (hovered) { show_tooltip(_u8L("Change filament at the beginning of this layer.")); } } end_menu(); } } ImGui::EndPopup(); } } void IMSlider::render_edit_menu(const TickCode& tick) { if (m_show_menu) ImGui::OpenPopup("slider_edit_menu_popup"); if (ImGui::BeginPopup("slider_edit_menu_popup")) { switch (tick.type) { case CustomGCode::PausePrint: if (menu_item_with_icon(_u8L("Delete Pause").c_str(), "")) { delete_tick(tick); } break; case CustomGCode::Template: if (!gcode(Template).empty()) { if (menu_item_with_icon(_u8L("Delete Custom Template").c_str(), "")) { delete_tick(tick); } } break; case CustomGCode::Custom: if (menu_item_with_icon(_u8L("Edit Custom G-code").c_str(), "")) { m_show_custom_gcode_window = true; } if (menu_item_with_icon(_u8L("Delete Custom G-code").c_str(), "")) { delete_tick(tick); } break; case CustomGCode::ToolChange: { int extruder_num = m_extruder_colors.size(); if (extruder_num > 1) { if (!m_can_change_color) { 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(m_extruder_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(); } if (menu_item_with_icon(_u8L("Delete Filament Change").c_str(), "")) { delete_tick(tick); } } break; } case CustomGCode::ColorChange: case CustomGCode::Unknown: default: break; } ImGui::EndPopup(); } } void IMSlider::on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } void IMSlider::set_scale(float scale) { if(m_scale != scale) m_scale = scale; } void IMSlider::on_mouse_wheel(wxMouseEvent& evt) { auto moves_slider_window = ImGui::FindWindowByName("moves_slider"); auto layers_slider_window = ImGui::FindWindowByName("laysers_slider"); if (!moves_slider_window || !layers_slider_window) { BOOST_LOG_TRIVIAL(info) << "Couldn't find slider window"; return; } float wheel = 0.0f; wheel = evt.GetWheelRotation() > 0 ? 1.0f : -1.0f; if (wheel == 0.0f) return; #ifdef __WXOSX__ if (wxGetKeyState(WXK_SHIFT)) { wheel *= -5; } else if (wxGetKeyState(WXK_RAW_CONTROL)) { wheel *= 5; } #else if (wxGetKeyState(WXK_COMMAND) || wxGetKeyState(WXK_SHIFT)) wheel *= 5; #endif if (is_horizontal()) { if( evt.GetPosition().x > moves_slider_window->Pos.x && evt.GetPosition().x < moves_slider_window->Pos.x + moves_slider_window->Size.x && evt.GetPosition().y > moves_slider_window->Pos.y && evt.GetPosition().y < moves_slider_window->Pos.y + moves_slider_window->Size.y){ const int new_pos = GetHigherValue() + wheel; SetHigherValue(new_pos); set_as_dirty(); } } else { if (evt.GetPosition().x > layers_slider_window->Pos.x && evt.GetPosition().x < layers_slider_window->Pos.x + layers_slider_window->Size.x && evt.GetPosition().y > layers_slider_window->Pos.y && evt.GetPosition().y < layers_slider_window->Pos.y + layers_slider_window->Size.y) { if (is_one_layer()) { const int new_pos = GetHigherValue() + wheel; SetHigherValue(new_pos); } else { const int new_pos = m_selection == ssLower ? GetLowerValue() + wheel : GetHigherValue() + wheel; m_selection == ssLower ? SetLowerValue(new_pos) : SetHigherValue(new_pos); } set_as_dirty(); } } } void IMSlider::correct_lower_value() { if (m_lower_value < m_min_value) m_lower_value = m_min_value; else if (m_lower_value > m_max_value) m_lower_value = m_max_value; if ((m_lower_value >= m_higher_value && m_lower_value <= m_max_value) || m_is_one_layer) m_higher_value = m_lower_value; } void IMSlider::correct_higher_value() { if (m_higher_value > m_max_value) m_higher_value = m_max_value; else if (m_higher_value < m_min_value) m_higher_value = m_min_value; if ((m_higher_value <= m_lower_value && m_higher_value >= m_min_value) || m_is_one_layer) m_lower_value = m_higher_value; } bool IMSlider::is_wipe_tower_layer(int tick) const { if (!m_is_wipe_tower || tick >= (int) m_values.size()) return false; if (tick == 0 || (tick == (int) m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) return false; if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || (tick > 0 && m_values[tick] < m_values[tick - 1])) // if there is just one wiping on the layer return true; return false; } std::string IMSlider::get_label(int tick, LabelType label_type) { const size_t value = tick; if (m_label_koef == 1.0 && m_values.empty()) { std::to_string(value); } if (value >= m_values.size()) return "error"; auto get_layer_number = [this](int value, LabelType label_type) { if (label_type == ltEstimatedTime && m_layers_times.empty()) return size_t(-1); double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); if (it == m_layers_values.end()) { it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon()); if (it == m_values.end()) return size_t(-1); return size_t(value); } return size_t(it - m_layers_values.begin()); }; if (m_draw_mode == dmSequentialGCodeView) { return std::to_string(tick); } else { if (label_type == ltEstimatedTime) { if (m_is_wipe_tower) { size_t layer_number = get_layer_number(value, label_type); return (layer_number == size_t(-1) || layer_number == m_layers_times.size()) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); } return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; } char layer_height[64]; ::sprintf(layer_height, "%.2f", m_values.empty() ? m_label_koef * value : m_values[value]); if (label_type == ltHeight) return std::string(layer_height); if (label_type == ltHeightWithLayer) { char buffer[64]; size_t layer_number; layer_number = m_draw_mode == dmSequentialFffPrint ? (m_values.empty() ? value : value + 1) : m_is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1); ::sprintf(buffer, "%5s\n%5s", std::to_string(layer_number).c_str(), layer_height); return std::string(buffer); } } return ""; } double IMSlider::get_double_value(const SelectedSlider &selection) { if (m_values.empty() || m_lower_value < 0) return 0.0; if (m_values.size() <= size_t(m_higher_value)) { correct_higher_value(); return m_values.back(); } return m_values[selection == ssLower ? m_lower_value : m_higher_value]; } int IMSlider::get_tick_from_value(double value, bool force_lower_bound /* = false*/) { std::vector::iterator it; if (m_is_wipe_tower && !force_lower_bound) it = std::find_if(m_values.begin(), m_values.end(), [value](const double &val) { return fabs(value - val) <= epsilon(); }); else it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); if (it == m_values.end()) return -1; return int(it - m_values.begin()); } float IMSlider::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; } else { pos_ratio = 1.0f - pos_ratio; handle_pos = rect.Min.y + (rect.Max.y - rect.Min.y) * pos_ratio; } return handle_pos; } int IMSlider::get_tick_near_point(int v_min, int v_max, const ImVec2& pt, const ImRect& rect) { ImS32 v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); const ImGuiAxis axis = is_horizontal() ? ImGuiAxis_X : ImGuiAxis_Y; const float region_usable_sz = (rect.Max[axis] - rect.Min[axis]); const float region_usable_pos_min = rect.Min[axis]; const float abs_pos = pt[axis]; float pos_ratio = (region_usable_sz > 0.0f) ? ImClamp((abs_pos - region_usable_pos_min) / region_usable_sz, 0.0f, 1.0f) : 0.0f; if (axis == ImGuiAxis_Y) pos_ratio = 1.0f - pos_ratio; return v_min + (ImS32)(v_range * pos_ratio + 0.5f); } std::string IMSlider::get_color_for_tool_change_tick(std::set::const_iterator it) const { const int current_extruder = it->extruder == 0 ? std::max(m_only_extruder, 1) : it->extruder; auto it_n = it; while (it_n != m_ticks.ticks.begin()) { --it_n; if (it_n->type == ColorChange && it_n->extruder == current_extruder) return it_n->color; } if ((current_extruder > 0 && (current_extruder - 1) < m_extruder_colors.size())) { return m_extruder_colors[current_extruder - 1]; // return a color for a specific extruder from the colors list } return ""; } // Get active extruders for tick. // Means one current extruder for not existing tick OR // 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick) // Use those values to disable selection of active extruders std::array IMSlider::get_active_extruders_for_tick(int tick) const { int default_initial_extruder = m_mode == MultiAsSingle ? std::max(1, m_only_extruder) : 1; std::array extruders = {default_initial_extruder, -1}; if (m_ticks.empty()) return extruders; auto it = m_ticks.ticks.lower_bound(TickCode{tick}); if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists extruders[1] = it->extruder; while (it != m_ticks.ticks.begin()) { --it; if (it->type == ToolChange) { extruders[0] = it->extruder; break; } } return extruders; } } } // Slic3r