diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 69269ae0ce..a3d161aa5d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -218,6 +218,15 @@ void AppConfig::set_defaults() if (get("auth_login_dialog_confirmed").empty()) set("auth_login_dialog_confirmed", "0"); + if (get("show_estimated_times_in_dbl_slider").empty()) + set("show_estimated_times_in_dbl_slider", "1"); + + if (get("show_ruler_in_dbl_slider").empty()) + set("show_ruler_in_dbl_slider", "0"); + + if (get("show_ruler_bg_in_dbl_slider").empty()) + set("show_ruler_bg_in_dbl_slider", "1"); + #ifdef _WIN32 if (get("use_legacy_3DConnexion").empty()) set("use_legacy_3DConnexion", "0"); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 0cf5b4aa5d..e05de4013f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -262,6 +262,8 @@ set(SLIC3R_GUI_SOURCES GUI/DoubleSliderForLayers.hpp GUI/DoubleSliderForGcode.cpp GUI/DoubleSliderForGcode.hpp + GUI/RulerForDoubleSlider.cpp + GUI/RulerForDoubleSlider.hpp GUI/Notebook.cpp GUI/Notebook.hpp GUI/TopBar.cpp diff --git a/src/slic3r/GUI/DoubleSliderForLayers.cpp b/src/slic3r/GUI/DoubleSliderForLayers.cpp index a2c5f7917b..869687d0c3 100644 --- a/src/slic3r/GUI/DoubleSliderForLayers.cpp +++ b/src/slic3r/GUI/DoubleSliderForLayers.cpp @@ -27,7 +27,7 @@ using Slic3r::format; namespace DoubleSlider { -static const float VERTICAL_SLIDER_WIDTH = 105.0f; +//static const float VERTICAL_SLIDER_WIDTH = 105.0f; DSForLayers::DSForLayers( int lowerValue, int higherValue, @@ -42,7 +42,10 @@ DSForLayers::DSForLayers( int lowerValue, Init(lowerValue, higherValue, minValue, maxValue, "layers_slider", false); m_ctrl.ShowLabelOnMouseMove(true); - m_ctrl.set_get_label_on_move_cb([this](int pos) { return m_show_estimated_times ? get_label(pos, ltEstimatedTime) : ""; }); + m_ctrl.set_get_label_on_move_cb([this](int pos) { + m_pos_on_move = pos; + return m_show_estimated_times ? get_label(pos, ltEstimatedTime) : ""; + }); m_ctrl.set_extra_draw_cb([this](const ImRect& draw_rc) {return draw_ticks(draw_rc); }); m_ticks.set_values(&m_values); @@ -170,22 +173,25 @@ using namespace ImGui; void DSForLayers::draw_ticks(const ImRect& slideable_region) { - //if(m_draw_mode != dmRegular) - // return; - //if (m_ticks.empty() || m_mode == MultiExtruder) - // return; + if (m_show_ruler) + draw_ruler(slideable_region); + if (m_ticks.empty() || m_draw_mode == dmSlaPrint) return; + // distance form center begin end const ImVec2 tick_border = ImVec2(23.0f, 2.0f) * m_scale; - // distance form center begin end - const ImVec2 tick_size = ImVec2(19.0f, 11.0f) * m_scale; - const float tick_width = 1.0f * m_scale; - const float icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; - const float icon_offset = 0.5f * icon_side;; - const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_DARK); - const ImU32 tick_hovered_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); + const float inner_x = 11.f * m_scale; + const float outer_x = 19.f * m_scale; + const float x_center = slideable_region.GetCenter().x; + + const float tick_width = float(int(1.0f * m_scale + 0.5f)); + const float icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; + const float icon_offset = 0.5f * icon_side;; + + const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(m_show_ruler ? ImGuiPureWrap::COL_ORANGE_LIGHT : ImGuiPureWrap::COL_ORANGE_DARK); + const ImU32 tick_hovered_clr = ImGui::ColorConvertFloat4ToU32(m_show_ruler ? ImGuiPureWrap::COL_ORANGE_DARK : ImGuiPureWrap::COL_WINDOW_BACKGROUND); auto get_tick_pos = [this, slideable_region](int tick) { return m_ctrl.GetPositionInRect(tick, slideable_region); @@ -197,8 +203,8 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) 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_border.x, tick_pos - tick_border.y, - slideable_region.GetCenter().x + tick_border.x, tick_pos + tick_border.y - tick_width); + ImRect tick_hover_box = ImRect(x_center - tick_border.x, tick_pos - tick_border.y, + x_center + tick_border.x, tick_pos + tick_border.y - tick_width); if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) { ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hovered_clr, false); @@ -219,12 +225,12 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) float tick_pos = get_tick_pos(tick_it->tick); //draw ticks - ImRect tick_left = ImRect(slideable_region.GetCenter().x - tick_size.x, tick_pos - tick_width, slideable_region.GetCenter().x - tick_size.y, tick_pos); - ImRect tick_right = ImRect(slideable_region.GetCenter().x + tick_size.y, tick_pos - tick_width, slideable_region.GetCenter().x + tick_size.x, tick_pos); + ImRect tick_left = ImRect(x_center - outer_x, tick_pos - tick_width, x_center - inner_x, tick_pos); + ImRect tick_right = ImRect(x_center + inner_x, tick_pos - tick_width, x_center + outer_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); - ImVec2 icon_pos = ImVec2(tick_right.Max.x + 0.5f * icon_offset, tick_pos - icon_offset); + ImVec2 icon_pos = ImVec2(m_ctrl.GetCtrlPos().x + GetWidth(), tick_pos - icon_offset); std::string btn_label = "tick " + std::to_string(tick_it->tick); //draw tick icon-buttons @@ -257,6 +263,174 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region) } } +void DSForLayers::draw_ruler(const ImRect& slideable_region) +{ + if (m_values.empty()) + return; + + const double step = double(slideable_region.GetHeight()) / (m_ctrl.GetMaxPos() - m_ctrl.GetMinPos()); + + if (!m_ruler.valid()) + m_ruler.init(m_values, step); + + const float inner_x = 11.f * m_scale; + const float long_outer_x = 17.f * m_scale; + const float short_outer_x = 14.f * m_scale; + const float tick_width = float(int(1.0f * m_scale +0.5f)); + const float label_height = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; + + const ImU32 tick_clr = IM_COL32(255, 255, 255, 255); + + const float x_center = slideable_region.GetCenter().x; + + double max_val = 0.; + for (const auto& val : m_ruler.max_values) + if (max_val < val) + max_val = val; + + if (m_show_ruler_bg) { + // draw ruler BG + ImRect bg_rect = slideable_region; + bg_rect.Expand(ImVec2(0.f, long_outer_x)); + bg_rect.Min.x -= tick_width; + bg_rect.Max.x = m_ctrl.GetCtrlPos().x + GetWidth(); + bg_rect.Min.y = m_ctrl.GetCtrlPos().y + label_height; + bg_rect.Max.y = m_ctrl.GetCtrlPos().y + GetHeight() - label_height; + const ImU32 bg_color = ImGui::ColorConvertFloat4ToU32(ImVec4(0.13f, 0.13f, 0.13f, 0.5f)); + + ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, bg_color, false, 2.f * m_ctrl.rounding()); + } + + auto get_tick_pos = [this, slideable_region](int tick) -> float { + return m_ctrl.GetPositionInRect(tick, slideable_region); + }; + + auto draw_text = [max_val, x_center, label_height, long_outer_x, this](const int tick, const float tick_pos) + { + ImVec2 start = ImVec2(x_center + long_outer_x + 1, tick_pos - (0.5f * label_height)); + std::string label = get_label(tick, ltHeight, max_val > 100.0 ? "%1$.1f" : "%1$.2f"); + ImGui::RenderText(start, label.c_str()); + }; + + auto draw_tick = [tick_clr, x_center, tick_width, inner_x](const float tick_pos, const float outer_x) + { + ImRect tick_right = ImRect(x_center + inner_x, tick_pos - tick_width, x_center + outer_x, tick_pos); + ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false); + }; + + auto draw_short_ticks = [this, short_outer_x, draw_tick, get_tick_pos](double& current_tick, int max_tick) + { + if (m_ruler.short_step <= 0.0) + return; + while (current_tick < max_tick) { + float pos = get_tick_pos(lround(current_tick)); + draw_tick(pos, short_outer_x); + current_tick += m_ruler.short_step; + if (current_tick > m_ctrl.GetMaxPos()) + break; + } + }; + + double short_tick = NaNd; + int tick = 0; + double value = 0.0; + size_t sequence = 0; + int prev_y_pos = -1; + int values_size = (int)m_values.size(); + + if (m_ruler.long_step < 0) { + // sequential print when long_step wasn't detected because of a lot of printed objects + if (m_ruler.max_values.size() > 1) { + while (tick <= m_ctrl.GetMaxPos() && sequence < m_ruler.count()) { + // draw just ticks with max value + value = m_ruler.max_values[sequence]; + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_ctrl.GetMaxPos()) + break; + + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { + draw_text(tick, pos); + prev_y_pos = pos; + } + draw_short_ticks(short_tick, tick); + + sequence++; + tick++; + } + } + // very short object or some non-trivial ruler with non-regular step (see https://github.com/prusa3d/PrusaSlicer/issues/7263) + else { + if (step < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode) + return; + for (size_t tick = 1; tick < m_values.size(); tick++) { + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + draw_text(tick, pos); + } + } + } + else { + while (tick <= m_ctrl.GetMaxPos()) { + value += m_ruler.long_step; + + if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) + value = m_ruler.max_values[sequence]; + + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_ctrl.GetMaxPos()) + break; + + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { + draw_text(tick, pos); + prev_y_pos = pos; + } + + draw_short_ticks(short_tick, tick); + + if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { + value = 0.0; + sequence++; + tick++; + } + } + // short ticks from the last tick to the end + draw_short_ticks(short_tick, m_ctrl.GetMaxPos()); + } + + // draw mose move line + if (m_pos_on_move > 0) { + float line_pos = get_tick_pos(m_pos_on_move); + + ImRect move_line = ImRect(x_center + 0.75f * inner_x, line_pos - tick_width, x_center + 1.5f * long_outer_x, line_pos); + ImGui::RenderFrame(move_line.Min, move_line.Max, ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_LIGHT), false); + m_pos_on_move = -1; + } +} + static std::array decode_color_to_float_array(const std::string color) { auto hex_digit_to_int = [](const char c) { @@ -504,7 +678,7 @@ bool DSForLayers::render_multi_extruders_menu(bool switch_current_code/* = false void DSForLayers::render_color_picker() { ImGuiContext& context = *GImGui; - const std::string title = _u8L("Select color for Color Change"); + const std::string title = ("Select color for Color Change"); if (m_show_color_picker) { ImGuiPureWrap::set_next_window_pos(1200, 200, ImGuiCond_Always, 0.5f, 0.0f); @@ -534,12 +708,31 @@ void DSForLayers::render_cog_menu() } if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show estimated print time on hover").c_str(), "", icon_sz, 0, m_show_estimated_times)) { m_show_estimated_times = !m_show_estimated_times; + if (m_cb_change_app_config) + m_cb_change_app_config("show_estimated_times_in_dbl_slider", m_show_estimated_times ? "1" : "0"); } if (m_mode == MultiAsSingle && m_draw_mode == dmRegular && ImGuiPureWrap::menu_item_with_icon(_u8L("Set extruder sequence for the entire print").c_str(), "")) { if (m_ticks.edit_extruder_sequence(m_ctrl.GetMaxPos(), m_mode)) process_ticks_changed(); } + if (ImGuiPureWrap::begin_menu(_u8L("Ruler").c_str())) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show").c_str(), "", icon_sz, 0, m_show_ruler)) { + m_show_ruler = !m_show_ruler; + if (m_show_ruler) + m_imgui->set_requires_extra_frame(); + if (m_cb_change_app_config) + m_cb_change_app_config("show_ruler_in_dbl_slider", m_show_ruler ? "1" : "0"); + } + + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show backgroung").c_str(), "", icon_sz, 0, m_show_ruler_bg)) { + m_show_ruler_bg = !m_show_ruler_bg; + if (m_cb_change_app_config) + m_cb_change_app_config("show_ruler_bg_in_dbl_slider", m_show_ruler_bg ? "1" : "0"); + } + + ImGuiPureWrap::end_menu(); + } if (can_edit()) { if (ImGuiPureWrap::menu_item_with_icon(_u8L("Use default colors").c_str(), "", icon_sz, 0, m_ticks.used_default_colors())) { UseDefaultColors(!m_ticks.used_default_colors()); @@ -707,11 +900,15 @@ void DSForLayers::Render(const int canvas_width, const int canvas_height, float return; m_scale = extra_scale * 0.1f * m_em; + m_ruler.set_scale(m_scale); + const float action_btn_sz = m_imgui->GetTextureCustomRect(ImGui::DSRevert)->Height; const float tick_icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; ImVec2 pos; + const float VERTICAL_SLIDER_WIDTH = m_show_ruler ? 125.f : 105.0f; + pos.x = canvas_width - VERTICAL_SLIDER_WIDTH * m_scale - tick_icon_side; pos.y = 1.5f * action_btn_sz + offset; if (m_allow_editing) @@ -719,7 +916,7 @@ void DSForLayers::Render(const int canvas_width, const int canvas_height, float ImVec2 size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz - offset); - m_ctrl.Init(pos, size, m_scale); + m_ctrl.Init(pos, size, m_scale, m_show_ruler); if (m_ctrl.render()) { // request one more frame if value was changes with mouse wheel if (GImGui->IO.MouseWheel != 0.0f) @@ -770,6 +967,12 @@ void DSForLayers::Render(const int canvas_width, const int canvas_height, float render_color_picker(); } +void DSForLayers::force_ruler_update() +{ + if (m_show_ruler) + m_ruler.invalidate(); +} + bool DSForLayers::is_wipe_tower_layer(int tick) const { if (!m_ticks.is_wipe_tower || tick >= (int)m_values.size()) @@ -824,7 +1027,7 @@ static std::string short_and_splitted_time(const std::string& time) return get_s(); } -std::string DSForLayers::get_label(int pos, LabelType label_type) const +std::string DSForLayers::get_label(int pos, LabelType label_type, const std::string& fmt/* = "%1$.2f"*/) const { const size_t value = pos; @@ -861,7 +1064,7 @@ std::string DSForLayers::get_label(int pos, LabelType label_type) const } return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; } - std::string str = format("%1$.2f", m_values[value]); + std::string str = format(fmt, m_values[value]); if (label_type == ltHeight) return str; if (label_type == ltHeightWithLayer) { diff --git a/src/slic3r/GUI/DoubleSliderForLayers.hpp b/src/slic3r/GUI/DoubleSliderForLayers.hpp index a985994f42..e25543a33e 100644 --- a/src/slic3r/GUI/DoubleSliderForLayers.hpp +++ b/src/slic3r/GUI/DoubleSliderForLayers.hpp @@ -6,6 +6,7 @@ #define slic3r_GUI_DoubleSliderForLayers_hpp_ #include "ImGuiDoubleSlider.hpp" +#include "RulerForDoubleSlider.hpp" #include "TickCodesManager.hpp" #include @@ -72,6 +73,7 @@ public: void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); void Render(const int canvas_width, const int canvas_height, float extra_scale = 1.f, float offset = 0.f) override; + void force_ruler_update(); // jump to selected layer void jump_to_value(); @@ -82,6 +84,8 @@ public: void UseDefaultColors(bool def_colors_on); bool is_new_print(const std::string& print_obj_idxs); void set_imgui_wrapper(Slic3r::GUI::ImGuiWrapper* imgui) { m_imgui = imgui; } + void show_estimated_times(bool show) { m_show_estimated_times = show; } + void show_ruler(bool show, bool show_bg) { m_show_ruler = show; m_show_ruler_bg = show_bg; } // manipulation with slider from keyboard @@ -104,6 +108,9 @@ public: void set_callback_on_get_print (std::function cb) { m_cb_get_print = cb; } + void set_callback_on_change_app_config (std::function cb) + { m_cb_change_app_config = cb; } + void set_callback_on_empty_auto_color_change(std::function cb) { m_ticks.set_callback_on_empty_auto_color_change(cb); } @@ -134,14 +141,18 @@ private: bool is_osx { false }; bool m_allow_editing { true }; - bool m_show_estimated_times { false }; + bool m_show_estimated_times { true }; + bool m_show_ruler { false }; + bool m_show_ruler_bg { true }; bool m_show_cog_menu { false }; bool m_show_edit_menu { false }; + int m_pos_on_move { -1 }; DrawMode m_draw_mode { dmRegular }; Mode m_mode { SingleExtruder }; FocusedItem m_focus { fiNone }; + Ruler m_ruler; TickCodeManager m_ticks; Slic3r::GUI::ImGuiWrapper* m_imgui { nullptr }; @@ -150,7 +161,7 @@ private: bool is_wipe_tower_layer(int tick) const; - std::string get_label(int tick, LabelType label_type) const; + std::string get_label(int tick, LabelType label_type, const std::string& fmt = "%1$.2f") const; std::string get_tooltip(int tick = -1); @@ -160,6 +171,7 @@ private: void draw_colored_band(const ImRect& groove, const ImRect& slideable_region); void draw_ticks(const ImRect& slideable_region); + void draw_ruler(const ImRect& slideable_region); void render_menu(); void render_cog_menu(); void render_edit_menu(); @@ -195,6 +207,7 @@ private: std::function m_cb_ticks_changed { nullptr }; std::function()> m_cb_get_extruder_colors { nullptr }; std::function m_cb_get_print { nullptr }; + std::function m_cb_change_app_config { nullptr }; }; } // DoubleSlider; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 549c8d05ae..a92903e08e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6008,7 +6008,7 @@ bool GLCanvas3D::check_toolbar_icon_size(float init_scale, float& new_scale_to_s const float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); float items_cnt = float(m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt()); const float noitems_width = top_tb_width - float(size) * items_cnt; // width of separators and borders in top toolbars - items_cnt += 1.6; // +1.6 means a place for some minimal margin between toolbars + items_cnt += 1.6f; // +1.6 means a place for some minimal margin between toolbars // calculate scale needed for items in all top toolbars // the std::max() is there because on some Linux dialects/virtual machines this code is called when the canvas has not been properly initialized yet, diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index a9968afe69..842596af7d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -360,6 +360,7 @@ void Preview::hide_layers_slider() void Preview::on_size(wxSizeEvent& evt) { evt.Skip(); + m_layers_slider->force_ruler_update(); Refresh(); } @@ -393,12 +394,18 @@ void Preview::create_sliders() m_layers_slider = std::make_unique(0, 0, 0, 100, wxGetApp().is_editor()); m_layers_slider->SetEmUnit(wxGetApp().em_unit()); m_layers_slider->set_imgui_wrapper(wxGetApp().imgui()); + m_layers_slider->show_estimated_times(wxGetApp().app_config->get_bool("show_estimated_times_in_dbl_slider")); + m_layers_slider->show_ruler(wxGetApp().app_config->get_bool("show_ruler_in_dbl_slider"), wxGetApp().app_config->get_bool("show_ruler_bg_in_dbl_slider")); m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); m_layers_slider->set_callback_on_thumb_move( [this]() -> void { Preview::on_layers_slider_scroll_changed(); } ); + m_layers_slider->set_callback_on_change_app_config([this](const std::string& key, const std::string& val) -> void { + wxGetApp().app_config->set(key, val); + }); + if (wxGetApp().is_editor()) { m_layers_slider->set_callback_on_ticks_changed([this]() -> void { Model& model = wxGetApp().plater()->model(); @@ -605,6 +612,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee //first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM m_layers_slider->SetExtruderColors(plater->get_extruder_color_strings_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result)); m_layers_slider->SetSliderValues(layers_z); + m_layers_slider->force_ruler_update(); assert(m_layers_slider->GetMinPos() == 0); m_layers_slider->Freeze(); diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.cpp b/src/slic3r/GUI/ImGuiDoubleSlider.cpp index 172d3616e1..fcc976261e 100644 --- a/src/slic3r/GUI/ImGuiDoubleSlider.cpp +++ b/src/slic3r/GUI/ImGuiDoubleSlider.cpp @@ -80,6 +80,43 @@ static bool behavior(ImGuiID id, const ImRect& region, return value_changed; } +static bool lclicked_on_thumb(ImGuiID id, const ImRect& region, + const ImS32 v_min, const ImS32 v_max, + const ImRect& thumb, ImGuiSliderFlags flags = 0) +{ + ImGuiContext& context = *GImGui; + + if (context.ActiveId == id && context.ActiveIdSource == ImGuiInputSource_Mouse && + context.IO.MouseReleased[0]) + { + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + + 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]; + + const float mouse_abs_pos = context.IO.MousePos[axis]; + float 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; + + ImS32 v_new = v_min + (ImS32)(v_range * mouse_pos_ratio + 0.5f); + + // Output thumb position so it can be displayed by the caller + const ImS32 v_clamped = (v_min < v_max) ? ImClamp(v_new, v_min, v_max) : ImClamp(v_new, v_max, v_min); + float thumb_pos_ratio = v_range != 0 ? ((float)(v_clamped - v_min) / (float)v_range) : 0.0f; + thumb_pos_ratio = axis == ImGuiAxis_Y ? 1.0f - thumb_pos_ratio : thumb_pos_ratio; + const float thumb_pos = region_usable_pos_min + (region_usable_pos_max - region_usable_pos_min) * thumb_pos_ratio; + + ImVec2 new_thumb_center = axis == ImGuiAxis_Y ? ImVec2(thumb.GetCenter().x, thumb_pos) : ImVec2(thumb_pos, thumb.GetCenter().y); + if (thumb.Contains(new_thumb_center)) + return true; + } + + return false; +} + ImRect ImGuiControl::DrawOptions::groove(const ImVec2& pos, const ImVec2& size, bool is_horizontal) const { ImVec2 groove_start = is_horizontal ? @@ -262,6 +299,7 @@ bool ImGuiControl::IsLClickOnThumb() if (m_lclick_on_selected_thumb) { // discard left mouse click at list its value is checked to avoud reuse it on next frame m_lclick_on_selected_thumb = false; + m_suppress_process_behavior = false; return true; } return false; @@ -493,29 +531,55 @@ bool ImGuiControl::draw_slider( int* higher_pos, int* lower_pos, ImGui::ItemHoverable(m_regions.lower_thumb, id) && context.IO.MouseClicked[0]) m_selection = ssLower; + // detect left click on selected thumb + { + const ImRect& active_thumb = m_selection == ssHigher ? m_regions.higher_thumb : m_regions.lower_thumb; + if (ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseClicked[0]) { + m_active_thumb = active_thumb; + m_suppress_process_behavior = true; + } + else if (ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseReleased[0]) { + const ImRect& slideable_region = m_selection == ssHigher ? m_regions.higher_slideable_region : m_regions.lower_slideable_region; + if (lclicked_on_thumb(id, slideable_region, m_min_pos, m_max_pos, m_active_thumb, m_flags)) { + m_suppress_process_behavior = true; + m_lclick_on_selected_thumb = true; + } + } + } + // update thumb position bool pos_changed = false; - if (m_selection == ssHigher) { - pos_changed = behavior(id, m_regions.higher_slideable_region, m_min_pos, m_max_pos, - higher_pos, &m_regions.higher_thumb, m_flags); - } - else if (m_draw_lower_thumb && !m_combine_thumbs) { - pos_changed = behavior(id, m_regions.lower_slideable_region, m_min_pos, m_max_pos, - lower_pos, &m_regions.lower_thumb, m_flags); - } - - // check thumbs poss and correct them if needed - check_and_correct_thumbs(higher_pos, lower_pos); + if (!m_suppress_process_behavior) { + if (m_selection == ssHigher) { + pos_changed = behavior(id, m_regions.higher_slideable_region, m_min_pos, m_max_pos, + higher_pos, &m_regions.higher_thumb, m_flags); + } + else if (m_draw_lower_thumb && !m_combine_thumbs) { + pos_changed = behavior(id, m_regions.lower_slideable_region, m_min_pos, m_max_pos, + lower_pos, &m_regions.lower_thumb, m_flags); + } + // check thumbs poss and correct them if needed + check_and_correct_thumbs(higher_pos, lower_pos); + } const ImRect& slideable_region = m_selection == ssHigher ? m_regions.higher_slideable_region : m_regions.lower_slideable_region; const ImRect& active_thumb = m_selection == ssHigher ? m_regions.higher_thumb : m_regions.lower_thumb; bool show_move_label = false; ImRect mouse_pos_rc = active_thumb; - if (!pos_changed && ImGui::ItemHoverable(item_size, id) && !ImGui::IsMouseDragging(0)) { - behavior(id, slideable_region, m_min_pos, m_max_pos, + + std::string move_label = ""; + + auto move_size = item_size; + move_size.Min.x += left_dummy_sz().x; + if (!pos_changed && ImGui::ItemHoverable(move_size, id) && !ImGui::IsMouseDragging(0)) { + auto sl_region = slideable_region; + if (!is_horizontal() && m_draw_opts.has_ruler) + sl_region.Max.x += m_draw_opts.dummy_sz().x; + behavior(id, sl_region, m_min_pos, m_max_pos, &m_mouse_pos, &mouse_pos_rc, m_flags, true); show_move_label = true; + move_label = get_label_on_move(m_mouse_pos); } // detect right click on selected thumb @@ -525,16 +589,8 @@ bool ImGuiControl::draw_slider( int* higher_pos, int* lower_pos, context.IO.MouseClicked[0]) m_rclick_on_selected_thumb = false; - // detect left click on selected thumb - if (ImGui::ItemHoverable(active_thumb, id) && !pos_changed) { - ImVec2 active_thumb_center = active_thumb.GetCenter(); - if (context.IO.MouseClicked[0]) - m_active_thumb_center_on_lcklick = active_thumb_center; - if (context.IO.MouseReleased[0] && - (m_active_thumb_center_on_lcklick.y == active_thumb_center.y) && - (m_active_thumb_center_on_lcklick.x == active_thumb_center.x) ) - m_lclick_on_selected_thumb = true; - } + if (m_suppress_process_behavior && ImGui::ItemHoverable(item_size, id) && ImGui::IsMouseDragging(0)) + m_suppress_process_behavior = false; // render slider @@ -565,8 +621,7 @@ bool ImGuiControl::draw_slider( int* higher_pos, int* lower_pos, } // draw label on mouse move - if (show_move_label) - draw_label(get_label_on_move(m_mouse_pos), mouse_pos_rc, false, true); + draw_label(move_label, mouse_pos_rc, false, true); return pos_changed; } diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.hpp b/src/slic3r/GUI/ImGuiDoubleSlider.hpp index 0185d8aec9..a7ad879d39 100644 --- a/src/slic3r/GUI/ImGuiDoubleSlider.hpp +++ b/src/slic3r/GUI/ImGuiDoubleSlider.hpp @@ -62,10 +62,11 @@ public: void SetCtrlPos(ImVec2 pos) { m_pos = pos; } void SetCtrlSize(ImVec2 size) { m_size = size; } void SetCtrlScale(float scale) { m_draw_opts.scale = scale; } - void Init(const ImVec2& pos, const ImVec2& size, float scale) { + void Init(const ImVec2& pos, const ImVec2& size, float scale, bool has_ruler = false) { m_pos = pos; m_size = size; m_draw_opts.scale = scale; + m_draw_opts.has_ruler = has_ruler; } ImVec2 GetCtrlSize() { return m_size; } ImVec2 GetCtrlPos() { return m_pos; } @@ -90,6 +91,8 @@ public: bool render(); std::string get_label(int pos) const; + float rounding() const { return m_draw_opts.rounding(); } + ImVec2 left_dummy_sz() const { return m_draw_opts.text_dummy_sz() + m_draw_opts.text_padding(); } void set_get_label_on_move_cb(std::function cb) { m_cb_get_label_on_move = cb; } void set_get_label_cb(std::function cb) { m_cb_get_label = cb; } @@ -100,12 +103,13 @@ private: struct DrawOptions { float scale { 1.f }; // used for Retina on osx + bool has_ruler { false }; - ImVec2 dummy_sz() const { return ImVec2(24.0f, 16.0f) * scale; } + ImVec2 dummy_sz() const { return ImVec2(has_ruler ? 48.0f : 24.0f, 16.0f) * scale; } ImVec2 thumb_dummy_sz() const { return ImVec2(17.0f, 17.0f) * scale; } ImVec2 groove_sz() const { return ImVec2(4.0f, 4.0f) * scale; } ImVec2 draggable_region_sz()const { return ImVec2(20.0f, 19.0f) * scale; } - ImVec2 text_dummy_sz() const { return ImVec2(50.0f, 34.0f) * scale; } + ImVec2 text_dummy_sz() const { return ImVec2(60.0f, 34.0f) * scale; } ImVec2 text_padding() const { return ImVec2( 5.0f, 2.0f) * scale; } float thumb_radius() const { return 10.0f * scale; } @@ -140,7 +144,8 @@ private: bool m_rclick_on_selected_thumb{ false }; bool m_lclick_on_selected_thumb{ false }; - ImVec2 m_active_thumb_center_on_lcklick; + bool m_suppress_process_behavior{ false }; + ImRect m_active_thumb; bool m_draw_lower_thumb{ true }; bool m_combine_thumbs { false }; diff --git a/src/slic3r/GUI/RulerForDoubleSlider.cpp b/src/slic3r/GUI/RulerForDoubleSlider.cpp new file mode 100644 index 0000000000..d1d6f2f776 --- /dev/null +++ b/src/slic3r/GUI/RulerForDoubleSlider.cpp @@ -0,0 +1,96 @@ +///|/ Copyright (c) Prusa Research 2020 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "RulerForDoubleSlider.hpp" +#include "libslic3r/CustomGCode.hpp" + +using namespace Slic3r; +using namespace CustomGCode; + +namespace DoubleSlider { + +static const double PIXELS_PER_SM_DEFAULT = 96./*DEFAULT_DPI*/ * 5. / 25.4; + +void Ruler::init(const std::vector& values, double scroll_step) +{ + if (m_is_valid) + return; + max_values.clear(); + max_values.reserve(std::count(values.begin(), values.end(), values.front())); + + auto it = std::find(values.begin() + 1, values.end(), values.front()); + while (it != values.end()) { + max_values.push_back(*(it - 1)); + it = std::find(it + 1, values.end(), values.front()); + } + max_values.push_back(*(it - 1)); + + m_is_valid = true; + update(values, scroll_step); +} + +void Ruler::update(const std::vector& values, double scroll_step) +{ + if (!m_is_valid || values.empty() || + // check if need to update ruler in respect to input values + (values.front() == m_min_val && values.back() == m_max_val && m_scroll_step == scroll_step && max_values.size() == m_max_values_cnt)) + return; + + m_min_val = values.front(); + m_max_val = values.back(); + m_scroll_step = scroll_step; + m_max_values_cnt = max_values.size(); + + int pixels_per_sm = lround(m_scale * PIXELS_PER_SM_DEFAULT); + + if (lround(scroll_step) > pixels_per_sm) { + long_step = -1.0; + return; + } + + int pow = -2; + int step = 0; + auto end_it = std::find(values.begin() + 1, values.end(), values.front()); + + while (pow < 3) { + for (int istep : {1, 2, 5}) { + double val = (double)istep * std::pow(10, pow); + auto val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); + + if (val_it == values.end()) + break; + int tick = val_it - values.begin(); + + // find next tick with istep + val *= 2; + val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); + // count of short ticks between ticks + int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick; + + if (lround(short_ticks_cnt * scroll_step) > pixels_per_sm) { + step = istep; + // there couldn't be more then 10 short ticks between ticks + short_step = 0.1 * short_ticks_cnt; + break; + } + } + if (step > 0) + break; + pow++; + } + + long_step = step == 0 ? -1.0 : (double)step * std::pow(10, pow); + if (long_step < 0) + short_step = long_step; +} + +void Ruler::set_scale(double scale) +{ + if (!is_approx(m_scale, scale)) + m_scale = scale; +} + +} // DoubleSlider + + diff --git a/src/slic3r/GUI/RulerForDoubleSlider.hpp b/src/slic3r/GUI/RulerForDoubleSlider.hpp new file mode 100644 index 0000000000..90a7bf72b5 --- /dev/null +++ b/src/slic3r/GUI/RulerForDoubleSlider.hpp @@ -0,0 +1,42 @@ +///|/ Copyright (c) Prusa Research 2020 - 2022 Vojtěch Bubník @bubnikv, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_GUI_RulerForDoubleSlider_hpp_ +#define slic3r_GUI_RulerForDoubleSlider_hpp_ + +#include +#include + +namespace DoubleSlider { + +class Ruler +{ + bool m_is_valid { false }; + double m_scale { 1. }; + double m_min_val; + double m_max_val; + double m_scroll_step; + size_t m_max_values_cnt; + +public: + + double long_step; + double short_step; + std::vector max_values;// max value for each object/instance in sequence print + // > 1 for sequential print + + void init(const std::vector& values, double scroll_step); + void update(const std::vector& values, double scroll_step); + void set_scale(double scale); + void invalidate() { m_is_valid = false; } + bool is_ok() { return long_step > 0 && short_step > 0; } + size_t count() { return max_values.size(); } + bool valid() { return m_is_valid; } +}; + +} // DoubleSlider; + + + +#endif // slic3r_GUI_RulerForDoubleSlider_hpp_