#include "DailyTips.hpp" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include namespace Slic3r { namespace GUI { struct DailyTipsData { std::string main_text; std::string wiki_url; std::string img_url; std::string additional_text; // currently not used std::string hyper_text; // currently not used std::function hypertext_callback; // currently not used }; class DailyTipsDataRenderer { public: DailyTipsDataRenderer() = default; ~DailyTipsDataRenderer(); void update_data(const DailyTipsData& data); void render(const ImVec2& pos, const ImVec2& size) const; bool has_image() const; protected: void load_texture_from_img_url(const std::string url); void open_wiki() const; // relative to the window's upper-left position void render_img(const ImVec2& start_pos, const ImVec2& size) const; void render_text(const ImVec2& start_pos, const ImVec2& size) const; private: DailyTipsData m_data; GLTexture* m_texture{ nullptr }; GLTexture* m_placeholder_texture{ nullptr }; }; DailyTipsDataRenderer::~DailyTipsDataRenderer() { if (m_texture) delete m_texture; if (m_placeholder_texture) delete m_placeholder_texture; } void DailyTipsDataRenderer::update_data(const DailyTipsData& data) { m_data = data; load_texture_from_img_url(m_data.img_url); } void DailyTipsDataRenderer::load_texture_from_img_url(const std::string url) { if (m_texture) { delete m_texture; m_texture = nullptr; } if (!url.empty()) { m_texture = new GLTexture(); m_texture->load_from_file(Slic3r::resources_dir() + "/" + url, true, GLTexture::None, false); } else { if (!m_placeholder_texture) { m_placeholder_texture = new GLTexture(); m_placeholder_texture->load_from_file(Slic3r::resources_dir() + "/images/dailytips_placeholder.png", true, GLTexture::None, false); } } } void DailyTipsDataRenderer::open_wiki() const { if (!m_data.wiki_url.empty()) { wxGetApp().open_browser_with_warning_dialog(m_data.wiki_url); } } void DailyTipsDataRenderer::render(const ImVec2& pos, const ImVec2& size) const { ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); int window_flags = parent_window->Flags; std::string name = "##DailyTipsDataRenderer" + std::to_string(parent_window->ID); ImGui::SetNextWindowPos(pos); if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) { ImVec2 img_size = ImVec2(size.x, 9.0f / 16.0f * size.x); render_img({0, 0}, img_size); float img_text_gap = ImGui::CalcTextSize("A").y; render_text({0, img_size.y + img_text_gap }, size); } ImGui::EndChild(); } bool DailyTipsDataRenderer::has_image() const { return !m_data.img_url.empty(); } void DailyTipsDataRenderer::render_img(const ImVec2& start_pos, const ImVec2& size) const { if (has_image()) ImGui::Image((ImTextureID)(intptr_t)m_texture->get_id(), size); else { ImGui::Image((ImTextureID)(intptr_t)m_placeholder_texture->get_id(), size); } } void DailyTipsDataRenderer::render_text(const ImVec2& start_pos, const ImVec2& size) const { ImGuiWrapper& imgui = *wxGetApp().imgui(); // main text // first line is headline (for hint notification it must be divided by \n) std::string title_line; std::string content_lines; size_t end_pos = m_data.main_text.find_first_of('\n'); if (end_pos != std::string::npos) { title_line = m_data.main_text.substr(0, end_pos); title_line = ImGui::ColorMarkerStart + title_line + ImGui::ColorMarkerEnd; content_lines = m_data.main_text.substr(end_pos + 1); } ImGui::SetCursorPosY(start_pos.y); imgui.text(title_line); bool is_zh = false; for (int i = 0; i < content_lines.size() - 1; i += 2) { if ((content_lines[i] & 0x80) && (content_lines[i + 1] & 0x80)) is_zh = true; } if (!is_zh) { // problem in Chinese with spaces imgui.text_wrapped(content_lines, size.x); } else { Label* wrapped_text = new Label(wxGetApp().GetTopWindow()); wrapped_text->Hide(); wrapped_text->SetLabelText(wxString::FromUTF8(content_lines)); wrapped_text->Wrap(size.x + ImGui::CalcTextSize("A").x * 5.0f); std::string wrapped_content_lines = wrapped_text->GetLabel().ToUTF8().data(); wrapped_text->Destroy(); imgui.text(wrapped_content_lines); } // wiki if (!m_data.wiki_url.empty()) { std::string tips_line = _u8L("For more information, please check out Wiki"); std::string wiki_part_text = _u8L("Wiki"); std::string first_part_text = tips_line.substr(0, tips_line.find(wiki_part_text)); ImVec2 wiki_part_size = ImGui::CalcTextSize(wiki_part_text.c_str()); ImVec2 first_part_size = ImGui::CalcTextSize(first_part_text.c_str()); ImVec2 link_start_pos = ImGui::GetCursorScreenPos(); //text imgui.text(first_part_text); ImColor HyperColor = ImColor(31, 142, 234).Value; ImVec2 wiki_part_rect_min = ImVec2(link_start_pos.x + first_part_size.x, link_start_pos.y); ImVec2 wiki_part_rect_max = wiki_part_rect_min + wiki_part_size; ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value); ImGui::SetCursorScreenPos(wiki_part_rect_min); imgui.text(wiki_part_text.c_str()); ImGui::PopStyleColor(); //click behavior if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) { //underline ImVec2 lineEnd = ImGui::GetItemRectMax(); lineEnd.y -= 2.0f; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x; ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) open_wiki(); } } } int DailyTipsPanel::uid = 0; DailyTipsPanel::DailyTipsPanel(bool can_expand) : DailyTipsPanel(ImVec2(0, 0), ImVec2(0, 0), can_expand) { } void DailyTipsPanel::set_position(const ImVec2& pos) { m_pos = pos; } void DailyTipsPanel::set_size(const ImVec2& size) { m_width = size.x; m_height = size.y; m_content_height = m_height - m_header_height - m_footer_height; } ImVec2 DailyTipsPanel::get_size() { return ImVec2(m_width, m_height); } DailyTipsPanel::DailyTipsPanel(const ImVec2& pos, const ImVec2& size, bool can_expand) : m_pos(pos), m_width(size.x), m_height(size.y), m_can_expand(can_expand) { m_dailytips_renderer = std::make_unique(); m_is_expanded = wxGetApp().app_config->get("show_hints") == "true"; m_uid = (DailyTipsPanel::uid++); } void DailyTipsPanel::render() { m_header_height = m_can_expand ? 38.0f * m_scale : 0; m_footer_height = 38.0f * m_scale; m_content_height = m_height - m_header_height - m_footer_height; if (!m_first_enter) { retrieve_data_from_hint_database(HintDataNavigation::Curr); m_first_enter = true; } push_styles(); if (m_can_expand) { if (m_is_expanded) { m_height = m_header_height + m_content_height + m_footer_height; } else { m_height = m_footer_height; } } ImGui::SetNextWindowPos(m_pos); ImGui::SetNextWindowSizeConstraints(ImVec2(m_width, m_height), ImVec2(m_width, m_height)); int window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::BeginChild((std::string("##DailyTipsPanel") + std::to_string(m_uid)).c_str(), ImVec2(m_width, m_height), false, window_flags)) { if (m_can_expand) { if (m_is_expanded) { render_header(m_pos, { m_width, m_header_height }); m_dailytips_renderer->render({ m_pos.x, m_pos.y + m_header_height }, { m_width, m_content_height }); render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height }); } else { render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height }); } } else { m_dailytips_renderer->render({ m_pos.x, m_pos.y }, { m_width, m_content_height }); render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height }); } //{// for debug // ImVec2 vMin = ImGui::GetWindowContentRegionMin(); // ImVec2 vMax = ImGui::GetWindowContentRegionMax(); // vMin += ImGui::GetWindowPos(); // vMax += ImGui::GetWindowPos(); // ImGui::GetForegroundDrawList()->AddRect(vMin, vMax, IM_COL32(180, 180, 255, 255)); //} } ImGui::EndChild(); pop_styles(); } void DailyTipsPanel::retrieve_data_from_hint_database(HintDataNavigation nav) { HintData* hint_data = HintDatabase::get_instance().get_hint(nav); if (hint_data != nullptr) { DailyTipsData data{ hint_data->text, hint_data->documentation_link, hint_data->image_url, hint_data->follow_text, hint_data->hypertext, hint_data->callback }; m_dailytips_renderer->update_data(data); } } void DailyTipsPanel::expand(bool expand) { if (!m_can_expand) return; m_is_expanded = expand; wxGetApp().app_config->set_bool("show_hints", expand); } void DailyTipsPanel::collapse() { if (!m_can_expand) return; m_is_expanded = false; wxGetApp().app_config->set_bool("show_hints", false); } bool DailyTipsPanel::is_expanded() { return m_is_expanded; } void DailyTipsPanel::set_scale(float scale) { m_scale = scale; } void DailyTipsPanel::render_header(const ImVec2& pos, const ImVec2& size) { ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); int window_flags = parent_window->Flags; std::string name = "##DailyTipsPanelHeader" + std::to_string(parent_window->ID); ImGui::SetNextWindowPos(pos); if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) { ImVec2 text_pos = pos + ImVec2(0, (size.y - ImGui::CalcTextSize("A").y) / 2); ImGui::SetCursorScreenPos(text_pos); imgui.push_bold_font(); imgui.text(_u8L("Daily Tips")); imgui.pop_bold_font(); } ImGui::EndChild(); } void DailyTipsPanel::render_controller_buttons(const ImVec2& pos, const ImVec2& size) { ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); int window_flags = parent_window->Flags; std::string name = "##DailyTipsPanelControllers" + std::to_string(parent_window->ID); ImGui::SetNextWindowPos(pos); if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) { ImVec2 button_size = ImVec2(size.y, size.y); float button_margin_x = 8.0f; std::wstring button_text; // collapse / expand ImVec2 btn_pos = pos + ImVec2(0, (size.y - ImGui::CalcTextSize("A").y) / 2); ImGui::SetCursorScreenPos(btn_pos); if (m_can_expand) { if (m_is_expanded) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_Text, ImColor(144, 144, 144).Value); button_text = ImGui::CollapseArrowIcon; imgui.button((_u8L("Collapse") + button_text).c_str()); ImVec2 collapse_btn_size = ImGui::CalcTextSize((_u8L("Collapse")).c_str()); collapse_btn_size.x += button_size.x / 2.0f; if (ImGui::IsMouseHoveringRect(btn_pos, btn_pos + collapse_btn_size, true)) { //underline ImVec2 lineEnd = ImGui::GetItemRectMax(); lineEnd.x -= ImGui::CalcTextSize("A").x / 2; lineEnd.y -= 2; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x; ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, ImColor(144, 144, 144)); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) collapse(); } ImGui::PopStyleColor(4); } else { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); // for bold font text, split text and icon-font button imgui.push_bold_font(); imgui.button((_u8L("Daily Tips")).c_str()); imgui.pop_bold_font(); ImVec2 expand_btn_size = ImGui::CalcTextSize((_u8L("Daily Tips")).c_str()); ImGui::SetCursorScreenPos(ImVec2(btn_pos.x + expand_btn_size.x + ImGui::CalcTextSize(" ").x, btn_pos.y)); std::wstring button_text; button_text = ImGui::ExpandArrowIcon; imgui.button(button_text.c_str()); expand_btn_size.x += 19.0f * m_scale; if (ImGui::IsMouseHoveringRect(btn_pos, btn_pos + expand_btn_size, true)) { //underline ImVec2 lineEnd = ImGui::GetItemRectMax(); lineEnd.x -= ImGui::CalcTextSize("A").x / 2; lineEnd.y -= 2; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x - expand_btn_size.x; ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, ImColor(38, 46, 48)); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) expand(); } ImGui::PopStyleColor(3); ImGui::EndChild(); return; } } // page index m_page_index = HintDatabase::get_instance().get_index() + 1; m_pages_count = HintDatabase::get_instance().get_count(); std::string text_str = std::to_string(m_page_index) + "/" + std::to_string(m_pages_count); float text_item_width = ImGui::CalcTextSize(text_str.c_str()).x; ImGui::PushItemWidth(text_item_width); float text_pos_x = (pos + size).x - button_margin_x * 2 - button_size.x * 2 - text_item_width; float text_pos_y = pos.y + (size.y - ImGui::CalcTextSize("A").y) / 2; ImGui::SetCursorScreenPos(ImVec2(text_pos_x, text_pos_y)); imgui.text(text_str); ImGui::PopItemWidth(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 255, 255).Value);// for icon-font button // prev button ImVec2 prev_button_pos = pos + size + ImVec2(-button_margin_x - button_size.x * 2, -size.y); ImGui::SetCursorScreenPos(prev_button_pos); button_text = ImGui::PrevArrowBtnIcon; if (ImGui::IsMouseHoveringRect(prev_button_pos, prev_button_pos + button_size, true)) { button_text = ImGui::PrevArrowHoverBtnIcon; if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) retrieve_data_from_hint_database(HintDataNavigation::Prev); } imgui.button(button_text.c_str()); // next button ImVec2 next_button_pos = pos + size + ImVec2(-button_size.x, -size.y); ImGui::SetCursorScreenPos(next_button_pos); button_text = ImGui::NextArrowBtnIcon; if (ImGui::IsMouseHoveringRect(next_button_pos, next_button_pos + button_size, true)) { button_text = ImGui::NextArrowHoverBtnIcon; if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) retrieve_data_from_hint_database(HintDataNavigation::Next); } imgui.button(button_text.c_str()); ImGui::PopStyleColor(5); } ImGui::EndChild(); } void DailyTipsPanel::push_styles() { ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.push_common_window_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); // framePadding cannot be zero. Otherwise, there is a problem with icon font button display ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0.0f, 0.0f)); } void DailyTipsPanel::pop_styles() { ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.pop_common_window_style(); ImGui::PopStyleVar(5); } DailyTipsWindow::DailyTipsWindow() { m_panel = new DailyTipsPanel(false); } void DailyTipsWindow::open() { m_show = true; m_panel->retrieve_data_from_hint_database(HintDataNavigation::Curr); } void DailyTipsWindow::close() { m_show = false; } void DailyTipsWindow::render() { if (!m_show) return; //if (m_show) // ImGui::OpenPopup((_u8L("Daily Tips")).c_str()); ImGuiWrapper& imgui = *wxGetApp().imgui(); const Size& cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); ImVec2 center = ImVec2(cnv_size.get_width() * 0.5f, cnv_size.get_height() * 0.5f); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImVec2 padding = ImVec2(25, 25) * m_scale; imgui.push_menu_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding); 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; imgui.push_bold_font(); //if (ImGui::BeginPopupModal((_u8L("Daily Tips")).c_str(), NULL, windows_flag)) if (ImGui::Begin((_u8L("Daily Tips")).c_str(), &m_show, windows_flag)) { imgui.pop_bold_font(); ImVec2 panel_pos = ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin(); ImVec2 panel_size = ImVec2(400.0f, 435.0f) * m_scale; m_panel->set_position(panel_pos); m_panel->set_size(panel_size); m_panel->render(); if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) { m_show = false; ImGui::CloseCurrentPopup(); } ImGui::End(); //ImGui::EndPopup(); } else { imgui.pop_bold_font(); } ImGui::PopStyleVar(4); ImGui::PopStyleColor(); imgui.pop_menu_style(); } void DailyTipsWindow::set_scale(float scale) { m_scale = scale; m_panel->set_scale(scale); } void DailyTipsWindow::on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } }}