diff --git a/resources/icons/search_.svg b/resources/icons/search_.svg new file mode 100644 index 000000000..679bb30f7 --- /dev/null +++ b/resources/icons/search_.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a02b2de69..3cd1319b5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1450,6 +1450,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); @@ -4205,6 +4206,55 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) const return action_taken; } +// Getter for the const char*[] for the search list +static bool search_string_getter(int idx, const char** out_text) +{ + return wxGetApp().plater()->search_string_getter(idx, out_text); +} + +bool GLCanvas3D::_render_search_list(float pos_x) const +{ + bool action_taken = false; + ImGuiWrapper* imgui = wxGetApp().imgui(); + + const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); + std::string title = L("Search"); + imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + size_t selected = size_t(-1); + bool edited = false; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif + + std::string& search_line = wxGetApp().sidebar().get_search_line(); + char *s = new char[255]; + strcpy(s, search_line.empty() ? _utf8(L("Type here to search")).c_str() : search_line.c_str()); + + imgui->search_list(ImVec2(22 * em, 30 * em), &search_string_getter, s, selected, edited); + + search_line = s; + delete [] s; + + if (selected != size_t(-1)) + { + wxGetApp().sidebar().jump_to_option(selected); + action_taken = true; + } + + if (edited) + { + wxGetApp().sidebar().apply_search_filter(); + action_taken = true; + } + + imgui->end(); + + return action_taken; +} + #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 #if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) @@ -4723,6 +4773,23 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "search"; + item.icon_filename = "search_.svg"; + item.tooltip = _utf8(L("Search")); + item.sprite_id = 11; + item.left.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) + { + _render_search_list(0.5f * (left + right)); + } + }; + item.enabling_callback = []()->bool { return true; }; + if (!m_main_toolbar.add_item(item)) + return false; + return true; } @@ -4829,6 +4896,18 @@ bool GLCanvas3D::_init_undoredo_toolbar() return can_redo; }; + if (!m_undoredo_toolbar.add_item(item)) + return false; + + if (!m_undoredo_toolbar.add_separator()) + return false; + + item.name = "collapse_sidebar"; + item.icon_filename = "cross.svg"; + item.tooltip = _utf8(L("Collapse right panel")); + item.sprite_id = 2; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); }; + item.enabling_callback = []()->bool { return true; }; if (!m_undoredo_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c50935b87..15249b1f2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -104,6 +104,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); @@ -725,6 +726,7 @@ private: void _render_sla_slices() const; void _render_selection_sidebar_hints() const; bool _render_undo_redo_stack(const bool is_undo, float pos_x) const; + bool _render_search_list(float pos_x) const; void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const; // render thumbnail using an off-screen framebuffer void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index a44e843b8..00b800042 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -400,6 +400,61 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( return is_hovered; } +void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, const char**), char* search_str, size_t& selected, bool& edited) +{ + // ImGui::ListBoxHeader("", size); + { + // rewrote part of function to add a TextInput instead of label Text + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return ; + + const ImGuiStyle& style = g.Style; + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = ImGui::CalcItemSize(size_, ImGui::CalcItemWidth(), ImGui::GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImRect frame_bb(window->DC.CursorPos, ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y + size.y)); + + ImRect bb(frame_bb.Min, frame_bb.Max); + window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. + g.NextItemData.ClearFlags(); + + if (!ImGui::IsRectVisible(bb.Min, bb.Max)) + { + ImGui::ItemSize(bb.GetSize(), style.FramePadding.y); + ImGui::ItemAdd(bb, 0, &frame_bb); + return ; + } + + ImGui::BeginGroup(); + + const ImGuiID id = ImGui::GetID(search_str); + ImVec2 search_size = ImVec2(size.x, ImGui::GetTextLineHeightWithSpacing() + style.ItemSpacing.y); + + ImGui::InputTextEx("", NULL, search_str, 20, search_size, 0, NULL, NULL); + edited = ImGui::IsItemEdited(); + + ImGui::BeginChildFrame(id, frame_bb.GetSize()); + } + + size_t i = 0; + const char* item_text; + while (items_getter(i, &item_text)) + { + ImGui::Selectable(item_text, false); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", item_text); + + if (ImGui::IsItemClicked()) + selected = i; + i++; + } + + ImGui::ListBoxFooter(); +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 417561881..9001f6b5a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -74,6 +74,7 @@ public: bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected); + void search_list(const ImVec2& size, bool (*items_getter)(int, const char**), char* search_str, size_t& selected, bool& edited); void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0881231c9..d16304dbc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -701,7 +701,6 @@ struct Sidebar::priv wxBoxSizer *sizer_params; FreqChangedParams *frequently_changed_parameters{ nullptr }; - SearchComboBox *search_cb{ nullptr }; ObjectList *object_list{ nullptr }; ObjectManipulation *object_manipulation{ nullptr }; ObjectSettings *object_settings{ nullptr }; @@ -715,6 +714,9 @@ struct Sidebar::priv ScalableButton *btn_remove_device; ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) + SearchOptions search_list; + std::string search_line; + priv(Plater *plater) : plater(plater) {} ~priv(); @@ -828,10 +830,6 @@ Sidebar::Sidebar(Plater *parent) p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); - // Search combobox -// p->search_cb = new SearchComboBox(p->scrolled); -// p->sizer_params->Add(p->search_cb, 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); - // Object List p->object_list = new ObjectList(p->scrolled); p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND); @@ -1085,6 +1083,17 @@ void Sidebar::msw_rescale() p->scrolled->Layout(); } +void Sidebar::apply_search_filter() +{ + p->search_list.apply_filters(p->search_line); +} + +void Sidebar::jump_to_option(size_t selected) +{ + const SearchOptions::Option& opt = p->search_list.get_option(selected); + wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key, opt.category); +} + ObjectManipulation* Sidebar::obj_manipul() { return p->object_manipulation; @@ -1357,21 +1366,14 @@ static std::vector get_search_inputs(ConfigOptionMode mode) void Sidebar::update_search_list() { - if (p->search_cb) - p->search_cb->init(get_search_inputs(m_mode)); - - std::vector search_list{}; + p->search_list.init(get_search_inputs(m_mode)); auto& tabs_list = wxGetApp().tabs_list; auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); for (auto tab : tabs_list) if (tab->supports_printer_technology(print_tech)) - search_list.emplace_back(SearchInput{ tab->get_config(), tab->type(), m_mode }); - - for (auto tab : tabs_list) - if (tab->supports_printer_technology(print_tech)) - tab->get_search_cb()->init(search_list); + tab->get_search_cb()->init(p->search_list); } void Sidebar::update_mode() @@ -1398,6 +1400,16 @@ std::vector& Sidebar::combos_filament() return p->combos_filament; } +SearchOptions& Sidebar::get_search_list() +{ + return p->search_list; +} + +std::string& Sidebar::get_search_line() +{ + return p->search_line; +} + // Plater::DropTarget class PlaterDropTarget : public wxFileDropTarget @@ -2143,6 +2155,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { /*this->redo();*/ }); view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); @@ -5249,6 +5262,18 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou out_text = ""; } +bool Plater::search_string_getter(int idx, const char** out_text) +{ + const SearchOptions& search_list = p->sidebar->get_search_list(); + + if (0 <= idx && (size_t)idx < search_list.size()) { + search_list[idx].get_label(out_text); + return true; + } + + return false; +} + void Plater::on_extruders_change(size_t num_extruders) { auto& choices = sidebar().combos_filament(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a51639cc5..f8426bf21 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -12,6 +12,7 @@ #include "libslic3r/BoundingBox.hpp" #include "wxExtensions.hpp" +#include "SearchComboBox.hpp" class wxButton; class ScalableButton; @@ -100,6 +101,8 @@ public: void update_mode_sizer() const; void update_reslice_btn_tooltip() const; void msw_rescale(); + void apply_search_filter(); + void jump_to_option(size_t selected); ObjectManipulation* obj_manipul(); ObjectList* obj_list(); @@ -125,7 +128,10 @@ public: void update_mode(); void update_search_list(); - std::vector& combos_filament(); + std::vector& combos_filament(); + SearchOptions& get_search_list(); + std::string& get_search_line(); + private: struct priv; std::unique_ptr p; @@ -220,6 +226,7 @@ public: void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); + bool search_string_getter(int idx, const char **out_text); // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. diff --git a/src/slic3r/GUI/SearchComboBox.cpp b/src/slic3r/GUI/SearchComboBox.cpp index a2efd5b02..efe46073b 100644 --- a/src/slic3r/GUI/SearchComboBox.cpp +++ b/src/slic3r/GUI/SearchComboBox.cpp @@ -48,6 +48,10 @@ bool SearchOptions::Option::is_matched_option(const wxString& search, int& outSc fts::fuzzy_match(search_pattern, opt_key_str , outScore) ); } +void SearchOptions::Filter::get_label(const char** out_text) const +{ + *out_text = label.utf8_str(); +} template void change_opt_key(std::string& opt_key, DynamicPrintConfig* config) @@ -82,19 +86,40 @@ void SearchOptions::append_options(DynamicPrintConfig* config, Preset::Type type label += _(opt.category) + " : "; label += _(opt.full_label.empty() ? opt.label : opt.full_label); - options.emplace_back(Option{ label, opt_key, opt.category, type }); + if (!label.IsEmpty()) + options.emplace_back(Option{ label, opt_key, opt.category, type }); } } -void SearchOptions::apply_filters(const wxString& search) +void SearchOptions::apply_filters(const std::string& search) { clear_filters(); - for (auto option : options) { - int score; - if (option.is_matched_option(search, score)) - filters.emplace_back(Filter{ option.label, score }); + + bool full_list = search.empty(); + for (size_t i=0; i < options.size(); i++) { + int score=0; + if (full_list || options[i].is_matched_option(search, score)) + filters.emplace_back(Filter{ options[i].label, i, score }); } - sort_filters(); + + if (!full_list) + sort_filters(); +} + +void SearchOptions::init(std::vector input_values) +{ + clear_options(); + for (auto i : input_values) + append_options(i.config, i.type, i.mode); + sort_options(); + + apply_filters(""); +} + +const SearchOptions::Option& SearchOptions::get_option(size_t pos_in_filter) const +{ + assert(pos_in_filter != size_t(-1) && filters[pos_in_filter].option_idx != size_t(-1)); + return options[filters[pos_in_filter].option_idx]; } SearchComboBox::SearchComboBox(wxWindow *parent) : @@ -177,6 +202,13 @@ void SearchComboBox::init(std::vector input_values) update_combobox(); } +void SearchComboBox::init(const SearchOptions& new_search_list) +{ + search_list = new_search_list; + + update_combobox(); +} + void SearchComboBox::update_combobox() { wxString search_str = this->GetValue(); diff --git a/src/slic3r/GUI/SearchComboBox.hpp b/src/slic3r/GUI/SearchComboBox.hpp index 482bb18eb..6b93ce9ac 100644 --- a/src/slic3r/GUI/SearchComboBox.hpp +++ b/src/slic3r/GUI/SearchComboBox.hpp @@ -42,14 +42,17 @@ public: struct Filter { wxString label; + size_t option_idx {0}; int outScore {0}; + + void get_label(const char** out_text) const; }; std::vector filters {}; void clear_options() { options.clear(); } void clear_filters() { filters.clear(); } void append_options(DynamicPrintConfig* config, Preset::Type type, ConfigOptionMode mode); - void apply_filters(const wxString& search); + void apply_filters(const std::string& search); void sort_options() { std::sort(options.begin(), options.end(), [](const Option& o1, const Option& o2) { @@ -59,6 +62,15 @@ public: std::sort(filters.begin(), filters.end(), [](const Filter& f1, const Filter& f2) { return f1.outScore > f2.outScore; }); }; + + void init(std::vector input_values); + size_t options_size() const { return options.size(); } + size_t filters_size() const { return filters.size(); } + + size_t size() const { return filters_size(); } + + const Filter& operator[](const size_t pos) const noexcept { return filters[pos]; } + const Option& get_option(size_t pos_in_filter) const; }; class SearchComboBox : public wxBitmapComboBox @@ -86,6 +98,7 @@ public: void init(DynamicPrintConfig* config, Preset::Type type, ConfigOptionMode mode); void init(std::vector input_values); + void init(const SearchOptions& new_search_list); void update_combobox(); private: