diff --git a/resources/data/hints.ini b/resources/data/hints.ini new file mode 100644 index 000000000..2ebf9091f --- /dev/null +++ b/resources/data/hints.ini @@ -0,0 +1,168 @@ + +[hint:Perspective camera] +text = Perspective camera\nDid you know that you can use the K key to quickly switch between an orthographic and perspective camera? + +[hint:Camera Views] +text = Camera Views\nDid you know that you can use the number keys 0-6 to quickly switch between predefined camera angles? + +[hint:Place on face] +text = Place on face\nDid you know that you can quickly orient a model so that one of its faces sits on the print bed? Select thePlace on facefunction or press the F key. +hypertext_type = gizmo +hypertext_gizmo_item = place + +[hint:Set number of instances] +text = Set number of instances\nDid you know that you can right-click a model and set an exact number of instances instead of copy-pasting it several times? + +[hint:Combine infill] +text = Combine infill\nDid you know that you can print the infill with a higher layer height compared to perimeters to save print time using the settingCombine infill every. +hypertext_type = settings +hypertext_settings_opt = infill_every_layers +hypertext_settings_type = 1 +hypertext_settings_category = Infill +disabled_modes = SLA; simple + +[hint:Hiding sidebar] +text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut Shift+Tab? You can also enable the icon for this from thePreferences. +hypertext_type = preferences +hypertext_preferences_page = 2 + +[hint:Variable layer height] +text = Variable layer height\nDid you know that you can print different regions of your model with a different layer height and smooth the transitions between them? Try theVariable layer height tool.(Not available for SLA printers.) +hypertext_type = plater +hypertext_plater_item = layersediting +disabled_modes = SLA + +[hint:Undo/redo history] +text = Undo/redo history\nDid you know that you can right-click theundo/redo arrowsto see the history of changes and to undo or redo several actions at once? +hypertext_type = plater +hypertext_plater_item = undo + +[hint:Auto-arrange settings] +text = Auto-arrange settings\nDid you know that you can right-click theauto-arrange iconto adjust the size of the gap between objects and to allow automatic rotations? +hypertext_type = plater +hypertext_plater_item = arrange + +[hint:Reload from disk] +text = Reload from disk\nDid you know that if you created a newer version of your model, you can simply reload it in PrusaSlicer? Right-click the model in the 3D view and choose Reload from disk. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/reload-from-disk_120427 + +[hint:Different layer height for each model] +text = Different layer height for each model\nDid you know that you can print each model on the plater with a different layer height? Right-click the model in the 3D view, choose Layers and Perimeters and adjust the values in the right panel. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/per-model-settings_1674 + +[hint:Solid infill threshold area] +text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set theSolid infill threshold area.(Expert mode only.) +hypertext_type = settings +hypertext_settings_opt = solid_infill_below_area +hypertext_settings_type = 1 +hypertext_settings_category = Infill +disabled_modes = SLA; simple; advanced + +[hint:Search functionality] +text = Search functionality\n Did you know that you use theSearchtool to quickly find a specific PrusaSlicer setting? Or use the familiar shortcut Ctrl+F. +hypertext_type = plater +hypertext_plater_item = search + +[hint:Box selection] +text = Box selection\nDid you know that you can do a box selection with Shift+Mouse drag? You can also box-deselect objects with Alt+Mouse drag. + +[hint:Zoom on selected objects or on all objects if none selected] +text =Zoom on selected objects or on all objects if none selected\nDid you know that you can zoom in on selected objects by pressing the Z key? If none are selected, the camera will zoom on all objects in the scene. + +[hint:Shapes gallery] +text = Shapes gallery\nDid you know that PrusaSlicer has a Shapes Gallery? You can use the included models as modifiers, negative volumes or as printable objects. Right-click the platter and selectAdd Shape - Gallery. +hypertext_type = gallery +disable_modes = simple + +[hint:Printable toggle] +text = Printable toggle\nDid you know that you can disable the G-code generation for the selected model without having to move or delete it? Toggle the Printable property of a model from the Right-click context menu. + +[hint:Mirror] +text = Mirror\nDid you know that you can mirror the selected model to create a reversed version of it? Right-click the model, select Mirror and pick the mirror axis. + +[hint:PageUp / PageDown quick rotation by 45 degrees] +text = PageUp / PageDown quick rotation by 45 degrees\nDid you know that you can quickly rotate selected models by 45 degrees around the Z-axis clockwise or counter-clockwise by pressing Page Up or Page Down respectively? + +[hint:Load config from G-code] +text = Load config from G-code\nDid you know that you can use File-Import Config to load print, filament and printer profiles from an existing G-code file? Similarly, you can use File-Import SL1 archive, which also lets you reconstruct 3D models from the voxel data. + +[hint:Ironing] +text = Ironing\nDid you know that you can smooth top surfaces of prints using Ironing? The nozzle will run a special second infill phase at the same layer to fill in holes and flatten any lifted plastic. Read more in thedocumentation. (Requires Advanced or Expert mode.) +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/ironing_177488 +disabled_modes = SLA; simple + +[hint:Fuzzy skin] +text = Fuzzy skin\nDid you know that you can create rough fibre-like texture on the sides of your models using theFuzzy skinfeature? You can also use modifiers to apply fuzzy-skin only to a portion of your model. +hypertext_type = settings +hypertext_settings_opt = fuzzy_skin +hypertext_settings_type = 1 +hypertext_settings_category = Layers and perimeters +disabled_modes = SLA + +[hint:Negative volume] +text = Negative volume\nDid you know that you can subtract one mesh from another using the Negative volume modifier? That way you can, for example, create easily resizable holes directly in PrusaSlicer. Read more in thedocumentation.(Requires Advanced or Expert mode.) +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/negative-volume_238503 +disabled_modes = SLA; simple + +[hint:Paint-on supports] +text = Paint-on supports\nDid you know that you can paint directly on the object and select areas, where supports should be enforced or blocked? Try thePaint-on supportsfeature. (Requires Advanced or Expert mode.) +hypertext_type = gizmo +hypertext_gizmo_item = fdm_supports +disabled_modes = SLA; simple + +[hint:Paint-on seam] +text = Paint-on seam\nDid you know that you can paint directly on the object and select where to place the start/endpoint of each perimeter loop? Try theSeam paintingfeature. (Requires Advanced or Expert mode.) +hypertext_type = gizmo +hypertext_gizmo_item = seam +disabled_modes = SLA; simple + +[hint:Insert Pause] +text = Insert Pause\nDid you know that you can schedule the print to pause at a specific layer? Right-click the layer slider in the Preview and select Add pause print (M601). This can be used to insert magnets, weights or nuts into your prints. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-g-code-at-layer_120490#insert-pause-at-layer + +[hint:Insert Custom G-code] +text = Insert Custom G-code\nDid you know that you can insert a custom G-code at a specific layer? Right-click the layer in the Preview and select Add custom G-code. With this function you can, for example, create a temperature tower. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-g-code-at-layer_120490#insert-custom-g-code-at-layer + +[hint:Configuration snapshots] +text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - Configuration snapshots menu. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/configuration-snapshots_1776 + +[hint:Minimum wall thickness] +text = Minimum wall thickness\nDid you know that instead of the number of top and bottom layers, you can define theMinimum shell thicknessin millimeters? This feature is especially useful when using the variable layer height function. +hypertext_type = settings +hypertext_settings_opt = top_solid_min_thickness +hypertext_settings_type = 1 +hypertext_settings_category = Layers and perimeters +disabled_modes = SLA + +[hint:Settings in non-modal window] +text = Settings in non-modal window\nDid you know that you can open the Settings in a new non-modal window? This means you can have settings open on one screen and the G-code Preview on the other. Go to thePreferencesand select Settings in non-modal window. +hypertext_type = preferences +hypertext_preferences_page = 2 + +[hint:Adaptive infills] +text = Adaptive infills\nDid you know that you can use the Adaptive cubic and Support cubic infills to decrease the print time and lower the filament consumption? Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/infill-patterns_177130 + +[hint:Fullscreen mode] +text = Fullscreen mode\nDid you know that you can switch PrusaSlicer to fullscreen mode? Use the F11 hotkey. + +[hint:Simplify mesh] +text = Simplify mesh\nDid you know that you can reduce the number of triangles in a mesh using the Simplify mesh feature? Right-click the model and select Simplify model. Read more in thedocumentation. +hypertext_type = link +hypertext_link = https://help.prusa3d.com/en/article/simplify-mesh_238941 + +#[hint:] +#text = +#hypertext = +#follow_text = + diff --git a/resources/icons/notification_left.svg b/resources/icons/notification_left.svg new file mode 100644 index 000000000..688f9a32b --- /dev/null +++ b/resources/icons/notification_left.svg @@ -0,0 +1,68 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_left_hover.svg b/resources/icons/notification_left_hover.svg new file mode 100644 index 000000000..66046ee50 --- /dev/null +++ b/resources/icons/notification_left_hover.svg @@ -0,0 +1,68 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_preferences.svg b/resources/icons/notification_preferences.svg new file mode 100644 index 000000000..979101e58 --- /dev/null +++ b/resources/icons/notification_preferences.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + + + + + diff --git a/resources/icons/notification_preferences_hover.svg b/resources/icons/notification_preferences_hover.svg new file mode 100644 index 000000000..09f229ba9 --- /dev/null +++ b/resources/icons/notification_preferences_hover.svg @@ -0,0 +1,70 @@ + +image/svg+xml + + + + + + + + diff --git a/resources/icons/notification_right.svg b/resources/icons/notification_right.svg new file mode 100644 index 000000000..58db9cc3c --- /dev/null +++ b/resources/icons/notification_right.svg @@ -0,0 +1,68 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_right_hover.svg b/resources/icons/notification_right_hover.svg new file mode 100644 index 000000000..ede2eb677 --- /dev/null +++ b/resources/icons/notification_right_hover.svg @@ -0,0 +1,68 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/toolbar_arrow.png b/resources/icons/toolbar_arrow.png new file mode 100644 index 000000000..65905a727 Binary files /dev/null and b/resources/icons/toolbar_arrow.png differ diff --git a/resources/icons/toolbar_arrow.svg b/resources/icons/toolbar_arrow.svg new file mode 100644 index 000000000..a1476bcd9 --- /dev/null +++ b/resources/icons/toolbar_arrow.svg @@ -0,0 +1,79 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 5dd6ec608..6416e1fc2 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -131,8 +131,6 @@ namespace ImGui const char MaterialIconMarker = 0x8; const char CloseNotifButton = 0xB; const char CloseNotifHoverButton = 0xC; -// const char TimerDotMarker = 0xE; -// const char TimerDotEmptyMarker = 0xF; const char MinimalizeButton = 0xE; const char MinimalizeHoverButton = 0xF; const char WarningMarker = 0x10; @@ -141,6 +139,12 @@ namespace ImGui const char EjectHoverButton = 0x13; const char CancelButton = 0x14; const char CancelHoverButton = 0x15; + const char LeftArrowButton = 0x16; + const char LeftArrowHoverButton = 0x17; + const char RightArrowButton = 0x18; + const char RightArrowHoverButton = 0x19; + const char PreferencesButton = 0x1A; + const char PreferencesHoverButton = 0x1B; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index dc720db48..cc5439b8d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -170,6 +170,12 @@ void AppConfig::set_defaults() if (get("show_splash_screen").empty()) set("show_splash_screen", "1"); + if (get("last_hint").empty()) + set("last_hint", "0"); + + if (get("show_hints").empty()) + set("show_hints", "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 64d09674c..d21f55d15 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -201,6 +201,8 @@ set(SLIC3R_GUI_SOURCES GUI/ProjectDirtyStateManager.cpp GUI/DesktopIntegrationDialog.cpp GUI/DesktopIntegrationDialog.hpp + GUI/HintNotification.cpp + GUI/HintNotification.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6f34f4051..e089621f2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -900,6 +900,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); +wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; @@ -2164,6 +2166,10 @@ void GLCanvas3D::bind_event_handlers() m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); + m_toolbar_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); }); + m_gizmo_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); }); m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); @@ -4399,6 +4405,29 @@ bool GLCanvas3D::_init_main_toolbar() m_main_toolbar.set_enabled(false); return true; } + // init arrow + BackgroundTexture::Metadata arrow_data; + arrow_data.filename = "toolbar_arrow.png"; +// arrow_data.filename = "toolbar_arrow.svg"; + //arrow_data.left = 16; + //arrow_data.top = 16; + //arrow_data.right = 16; + //arrow_data.bottom = 16; + + arrow_data.left = 0; + arrow_data.top = 0; + arrow_data.right = 0; + arrow_data.bottom = 0; + + if (!m_main_toolbar.init_arrow(arrow_data)) + { + BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; + } + + if (!m_gizmos.init_arrow(arrow_data)) + { + BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; + } // m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); @@ -5313,6 +5342,11 @@ void GLCanvas3D::_render_gizmos_overlay() #endif /* __WXMSW__ */ m_gizmos.render_overlay(); + + if (m_gizmo_highlighter.m_render_arrow) + { + m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type); + } } void GLCanvas3D::_render_main_toolbar() @@ -5330,6 +5364,10 @@ void GLCanvas3D::_render_main_toolbar() m_main_toolbar.set_position(top, left); m_main_toolbar.render(*this); + if (m_toolbar_highlighter.m_render_arrow) + { + m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); + } } void GLCanvas3D::_render_undoredo_toolbar() @@ -6497,6 +6535,24 @@ bool GLCanvas3D::_deactivate_collapse_toolbar_items() return false; } +void GLCanvas3D::highlight_toolbar_item(const std::string& item_name) +{ + GLToolbarItem* item = m_main_toolbar.get_item(item_name); + if (!item) + item = m_undoredo_toolbar.get_item(item_name); + if (!item || !item->is_visible()) + return; + m_toolbar_highlighter.init(item, this); +} + +void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name) +{ + GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name); + if(gizmo == GLGizmosManager::EType::Undefined) + return; + m_gizmo_highlighter.init(&m_gizmos, gizmo, this); +} + const Print* GLCanvas3D::fff_print() const { return (m_process == nullptr) ? nullptr : m_process->fff_print(); @@ -6516,10 +6572,119 @@ void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); } - -void GLCanvas3D::RenderTimer::Notify() +void GLCanvas3D::RenderTimer::Notify() { wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); } + +void GLCanvas3D::ToolbarHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::GizmoHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!toolbar_item || !canvas) + return; + + m_timer.Start(300, false); + + m_toolbar_item = toolbar_item; + m_canvas = canvas; +} + +void GLCanvas3D::ToolbarHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_toolbar_item) { + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); + } + m_toolbar_item = nullptr; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::ToolbarHighlighter::blink() +{ + if (m_toolbar_item) { + char state = m_toolbar_item->get_highlight(); + if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown) + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown); + else + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + +void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!gizmo || !canvas) + return; + + m_timer.Start(300, false); + + m_gizmo_manager = manager; + m_gizmo_type = gizmo; + m_canvas = canvas; +} + +void GLCanvas3D::GizmoHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_gizmo_manager) { + m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false); + } + m_gizmo_manager = nullptr; + m_gizmo_type = GLGizmosManager::EType::Undefined; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::GizmoHighlighter::blink() +{ + if (m_gizmo_manager) { + if (m_blink_counter % 2 == 0) + m_gizmo_manager->set_highlight(m_gizmo_type, true); + else + m_gizmo_manager->set_highlight(m_gizmo_type, false); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b5fa86235..d190c0819 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -93,6 +93,43 @@ private: wxTimer* m_timer; }; +class ToolbarHighlighterTimerEvent : public wxEvent +{ +public: + ToolbarHighlighterTimerEvent(wxEventType type, wxTimer& timer) + : wxEvent(timer.GetId(), type), + m_timer(&timer) + { + SetEventObject(timer.GetOwner()); + } + int GetInterval() const { return m_timer->GetInterval(); } + wxTimer& GetTimer() const { return *m_timer; } + + virtual wxEvent* Clone() const { return new ToolbarHighlighterTimerEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_TIMER; } +private: + wxTimer* m_timer; +}; + + +class GizmoHighlighterTimerEvent : public wxEvent +{ +public: + GizmoHighlighterTimerEvent(wxEventType type, wxTimer& timer) + : wxEvent(timer.GetId(), type), + m_timer(&timer) + { + SetEventObject(timer.GetOwner()); + } + int GetInterval() const { return m_timer->GetInterval(); } + wxTimer& GetTimer() const { return *m_timer; } + + virtual wxEvent* Clone() const { return new GizmoHighlighterTimerEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_TIMER; } +private: + wxTimer* m_timer; +}; + wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); @@ -137,6 +174,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); +wxDECLARE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); class GLCanvas3D { @@ -378,6 +417,16 @@ class GLCanvas3D virtual void Notify() override; }; + class ToolbarHighlighterTimer : public wxTimer { + private: + virtual void Notify() override; + }; + + class GizmoHighlighterTimer : public wxTimer { + private: + virtual void Notify() override; + }; + public: enum ECursorType : unsigned char { @@ -517,6 +566,38 @@ private: SequentialPrintClearance m_sequential_print_clearance; bool m_sequential_print_clearance_first_displacement{ true }; + struct ToolbarHighlighter + { + void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); + void init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas); + void blink(); + void invalidate(); + bool m_render_arrow{ false }; + GLToolbarItem* m_toolbar_item{ nullptr }; + private: + GLCanvas3D* m_canvas{ nullptr }; + int m_blink_counter{ 0 }; + ToolbarHighlighterTimer m_timer; + } + m_toolbar_highlighter; + + struct GizmoHighlighter + { + void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); + void init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas); + void blink(); + void invalidate(); + bool m_render_arrow{ false }; + GLGizmosManager::EType m_gizmo_type; + private: + GLGizmosManager* m_gizmo_manager{ nullptr }; + GLCanvas3D* m_canvas{ nullptr }; + int m_blink_counter{ 0 }; + GizmoHighlighterTimer m_timer; + + } + m_gizmo_highlighter; + public: explicit GLCanvas3D(wxGLCanvas* canvas); ~GLCanvas3D(); @@ -744,6 +825,9 @@ public: void use_slope(bool use) { m_slope.use(use); } void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } + void highlight_toolbar_item(const std::string& item_name); + void highlight_gizmo(const std::string& gizmo_name); + ArrangeSettings get_arrange_settings() const { const ArrangeSettings &settings = get_arrange_settings(this); ArrangeSettings ret = settings; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 178c17d15..0167ba3ce 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -61,6 +61,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat , m_state(Normal) , m_data(data) , m_last_action_type(Undefined) + , m_highlight_state(NotHighlighted) { } @@ -91,7 +92,8 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b assert((tex_width != 0) && (tex_height != 0)); GLTexture::Quad_UVs ret; // tiles in the texture are spaced by 1 pixel - float icon_size_px = (float)(tex_width - 1) / (float)Num_States; + float icon_size_px = (float)(tex_width - 1) / ((float)Num_States + (float)Num_Rendered_Highlight_States); + char render_state = (m_highlight_state == NotHighlighted ? m_state : Num_States + m_highlight_state); float inv_tex_width = 1.0f / (float)tex_width; float inv_tex_height = 1.0f / (float)tex_height; // tiles in the texture are spaced by 1 pixel @@ -99,7 +101,7 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b float v_offset = 1.0f * inv_tex_height; float du = icon_size_px * inv_tex_width; float dv = icon_size_px * inv_tex_height; - float left = u_offset + (float)m_state * du; + float left = u_offset + (float)render_state * du; float right = left + du - u_offset; float top = v_offset + (float)m_data.sprite_id * dv; float bottom = top + dv - v_offset; @@ -183,6 +185,24 @@ bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) return res; } +bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture) +{ + if (m_arrow_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = false; + + if (!arrow_texture.filename.empty()) + res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false); +// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100); + + if (res) + m_arrow_texture.metadata = arrow_texture; + + return res; +} + GLToolbar::Layout::EType GLToolbar::get_layout_type() const { return m_layout.type; @@ -419,6 +439,8 @@ void GLToolbar::render(const GLCanvas3D& parent) } } + + bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { if (!m_enabled) @@ -869,6 +891,21 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } } +GLToolbarItem* GLToolbar::get_item(const std::string& item_name) +{ + if (!m_enabled) + return nullptr; + + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == item_name) + { + return item; + } + } + return nullptr; +} + int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { if (!m_enabled) @@ -1105,6 +1142,63 @@ void GLToolbar::render_background(float left, float top, float right, float bott } } +void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item) +{ + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + float factor = inv_zoom * m_layout.scale; + + float scaled_icons_size = m_layout.icons_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float border = m_layout.border * factor; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top - icon_stride; + + for (const GLToolbarItem* item : m_items) { + if (!item->is_visible()) + continue; + + if (item->is_separator()) + left += separator_stride; + else { + if (item->get_name() == highlighted_item->get_name()) + break; + left += icon_stride; + } + } + + left += border; + top -= separator_stride; + float right = left + scaled_icons_size; + + unsigned int tex_id = m_arrow_texture.texture.get_id(); + float tex_width = (float)m_icons_texture.get_width(); + float tex_height = (float)m_icons_texture.get_height(); + + if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) { + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left = left + border - scaled_icons_size / 2; // add half scaled_icons_size for huge arrow + float internal_right = right - border + scaled_icons_size / 2; + float internal_top = top - border; + // bottom is not moving and should be calculated from arrow texture sides ratio + float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); + float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio; + + float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height; + + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + } +} + void GLToolbar::render_horizontal(const GLCanvas3D& parent) { unsigned int tex_id = m_icons_texture.get_id(); @@ -1217,6 +1311,8 @@ bool GLToolbar::generate_icons_texture() states.push_back({ 0, false }); // Hover states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled + states.push_back({ 0, false }); // HighlightedShown + states.push_back({ 2, false }); // HighlightedHidden } else { states.push_back({ 1, false }); // Normal @@ -1225,6 +1321,8 @@ bool GLToolbar::generate_icons_texture() states.push_back({ 0, false }); // Hover states.push_back({ 1, true }); // HoverPressed states.push_back({ 1, false }); // HoverDisabled + states.push_back({ 0, false }); // HighlightedShown + states.push_back({ 1, false }); // HighlightedHidden } unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 92df63bfb..5740db3e6 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -65,6 +65,14 @@ public: Num_States }; + enum EHighlightState : unsigned char + { + HighlightedShown, + HighlightedHidden, + Num_Rendered_Highlight_States, + NotHighlighted + }; + struct Data { struct Option @@ -104,13 +112,16 @@ private: EState m_state; Data m_data; EActionType m_last_action_type; - + EHighlightState m_highlight_state; public: GLToolbarItem(EType type, const Data& data); EState get_state() const { return m_state; } void set_state(EState state) { m_state = state; } + EHighlightState get_highlight() const { return m_highlight_state; } + void set_highlight(EHighlightState state) { m_highlight_state = state; } + const std::string& get_name() const { return m_data.name; } const std::string& get_icon_filename() const { return m_data.icon_filename; } const std::string& get_tooltip() const { return m_data.tooltip; } @@ -143,7 +154,6 @@ public: bool update_enabled_state(); void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; - private: void set_visible(bool visible) { m_data.visible = visible; } @@ -236,6 +246,7 @@ private: GLTexture m_icons_texture; bool m_icons_texture_dirty; BackgroundTexture m_background_texture; + BackgroundTexture m_arrow_texture; Layout m_layout; ItemsList m_items; @@ -262,6 +273,8 @@ public: bool init(const BackgroundTexture::Metadata& background_texture); + bool init_arrow(const BackgroundTexture::Metadata& arrow_texture); + Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); Layout::EHorizontalOrientation get_horizontal_orientation() const { return m_layout.horizontal_orientation; } @@ -310,9 +323,11 @@ public: bool update_items_state(); void render(const GLCanvas3D& parent); + void render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item); bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); - + // get item pointer for highlighter timer + GLToolbarItem* get_item(const std::string& item_name); private: void calc_layout(); float get_width_horizontal() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a73ca880a..40f375664 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -661,6 +661,10 @@ void GUI_App::post_init() this->mainframe->load_config(this->init_params->extra_config); } + // show "Did you know" notification + if (app_config->get("show_hints") == "1") + plater_->get_notification_manager()->push_hint_notification(); + // The extra CallAfter() is needed because of Mac, where this is the only way // to popup a modal dialog on start without screwing combo boxes. // This is ugly but I honestly found no better way to do it. @@ -1981,6 +1985,43 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } +void GUI_App::open_preferences(size_t open_on_tab) +{ + bool app_layout_changed = false; + { + // the dialog needs to be destroyed before the call to recreate_GUI() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + PreferencesDialog dlg(mainframe, open_on_tab); + dlg.ShowModal(); + app_layout_changed = dlg.settings_layout_changed(); +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (dlg.seq_top_layer_only_changed()) +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + this->plater_->refresh_print(); +#ifdef _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + } + if (app_layout_changed) { + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); + mainframe->update_layout(); + mainframe->select_tab(size_t(0)); + } +} + #if ENABLE_PROJECT_DIRTY_STATE bool GUI_App::has_unsaved_preset_changes() const { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 92bfd67c4..0ca73cde9 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -256,6 +256,8 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } + void open_preferences(size_t open_on_tab = 0); + virtual bool OnExceptionInMainLoop() override; #ifdef __APPLE__ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index f06acf28d..dbfeaaa80 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -123,10 +123,28 @@ bool GLGizmosManager::init() m_current = Undefined; m_hover = Undefined; + m_highlight = std::pair(Undefined, false); return true; } +bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_texture) +{ + if (m_arrow_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = false; + + if (!arrow_texture.filename.empty()) + res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false); +// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100); + if (res) + m_arrow_texture.metadata = arrow_texture; + + return res; +} + void GLGizmosManager::set_overlay_icon_size(float size) { if (m_layout.icons_size != size) @@ -973,6 +991,46 @@ void GLGizmosManager::render_background(float left, float top, float right, floa } } +void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_type) const +{ + + std::vector selectable_idxs = get_selectable_idxs(); + if (selectable_idxs.empty()) + return; + float cnv_w = (float)m_parent.get_canvas_size().get_width(); + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + float height = get_scaled_total_height(); + float zoomed_border = m_layout.scaled_border() * inv_zoom; + float zoomed_top_x = (-0.5f * cnv_w) * inv_zoom; + float zoomed_top_y = (0.5f * height) * inv_zoom; + zoomed_top_x += zoomed_border; + zoomed_top_y -= zoomed_border; + float icons_size = m_layout.scaled_icons_size(); + float zoomed_icons_size = icons_size * inv_zoom; + float zoomed_stride_y = m_layout.scaled_stride_y() * inv_zoom; + for (size_t idx : selectable_idxs) + { + if (idx == highlighted_type) { + int tex_width = m_icons_texture.get_width(); + int tex_height = m_icons_texture.get_height(); + unsigned int tex_id = m_arrow_texture.texture.get_id(); + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height; + + float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); + + GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { internal_left_uv, internal_top_uv }, { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv } }); + break; + } + zoomed_top_y -= zoomed_stride_y; + } +} + void GLGizmosManager::do_render_overlay() const { std::vector selectable_idxs = get_selectable_idxs(); @@ -1012,7 +1070,7 @@ void GLGizmosManager::do_render_overlay() const if ((icons_texture_id == 0) || (tex_width <= 1) || (tex_height <= 1)) return; - float du = (float)(tex_width - 1) / (4.0f * (float)tex_width); // 4 is the number of possible states if the icons + float du = (float)(tex_width - 1) / (6.0f * (float)tex_width); // 6 is the number of possible states if the icons float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height); // tiles in the texture are spaced by 1 pixel @@ -1022,9 +1080,9 @@ void GLGizmosManager::do_render_overlay() const for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); - unsigned int sprite_id = gizmo->get_sprite_id(); - int icon_idx = (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3)); + // higlighted state needs to be decided first so its highlighting in every other state + int icon_idx = (m_highlight.first == idx ? (m_highlight.second ? 4 : 5) : (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3))); float v_top = v_offset + sprite_id * dv; float u_left = u_offset + icon_idx * du; @@ -1055,13 +1113,26 @@ GLGizmoBase* GLGizmosManager::get_current() const return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get(); } +GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const +{ + std::vector selectable_idxs = get_selectable_idxs(); + for (size_t idx = 0; idx < selectable_idxs.size(); ++idx) + { + std::string filename = m_gizmos[selectable_idxs[idx]]->get_icon_filename(); + filename = filename.substr(0, filename.find_first_of('.')); + if (filename == gizmo_name) + return (GLGizmosManager::EType)selectable_idxs[idx]; + } + return GLGizmosManager::EType::Undefined; +} + bool GLGizmosManager::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; std::vector filenames; for (size_t idx=0; idxget_icon_filename(); if (!icon_filename.empty()) @@ -1074,6 +1145,8 @@ bool GLGizmosManager::generate_icons_texture() const states.push_back(std::make_pair(0, false)); // Hovered states.push_back(std::make_pair(0, true)); // Selected states.push_back(std::make_pair(2, false)); // Disabled + states.push_back(std::make_pair(0, false)); // HighlightedShown + states.push_back(std::make_pair(2, false)); // HighlightedHidden unsigned int sprite_size_px = (unsigned int)m_layout.scaled_icons_size(); // // force even size diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 01d7ea85c..64780a2bc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -94,9 +94,11 @@ private: mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; BackgroundTexture m_background_texture; + BackgroundTexture m_arrow_texture; Layout m_layout; EType m_current; EType m_hover; + std::pair m_highlight; // bool true = higlightedShown, false = highlightedHidden std::vector get_selectable_idxs() const; std::vector get_activable_idxs() const; @@ -128,6 +130,8 @@ public: bool init(); + bool init_arrow(const BackgroundTexture::Metadata& arrow_texture); + template void load(Archive& ar) { @@ -182,6 +186,7 @@ public: EType get_current_type() const { return m_current; } GLGizmoBase* get_current() const; + EType get_gizmo_from_name(const std::string& gizmo_name) const; bool is_running() const; bool handle_shortcut(int key); @@ -220,6 +225,8 @@ public: void render_overlay() const; + void render_arrow(const GLCanvas3D& parent, EType highlighted_type) const; + std::string get_tooltip() const; bool on_mouse(wxMouseEvent& evt); @@ -232,8 +239,13 @@ public: int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); } int get_shortcut_key(GLGizmosManager::EType) const; + // To end highlight set gizmo = undefined + void set_highlight(EType gizmo, bool highlight_shown) { m_highlight = std::pair(gizmo, highlight_shown); } + bool get_highlight_state() const { return m_highlight.second; } + private: void render_background(float left, float top, float right, float bottom, float border) const; + void do_render_overlay() const; float get_scaled_total_height() const; diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp new file mode 100644 index 000000000..5e8c3014f --- /dev/null +++ b/src/slic3r/GUI/HintNotification.cpp @@ -0,0 +1,671 @@ +#include "HintNotification.hpp" +#include "ImGuiWrapper.hpp" +#include "format.hpp" +#include "I18N.hpp" +#include "GUI_ObjectList.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +const std::string BOLD_MARKER_START = ""; +const std::string BOLD_MARKER_END = ""; +const std::string HYPERTEXT_MARKER_START = ""; +const std::string HYPERTEXT_MARKER_END = ""; + +namespace { +inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) +{ + if (fading_out) + ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); + else + ImGui::PushStyleColor(idx, col); +} +// return true if NOT in disabled mode. +inline bool disabled_modes_check(const std::string& disabled_modes) +{ + if (disabled_modes.empty()) + return true; + + // simple / advanced / expert + ConfigOptionMode config_mode = wxGetApp().get_mode(); + std::string mode_name; + if (config_mode == ConfigOptionMode::comSimple) mode_name = "simple"; + else if (config_mode == ConfigOptionMode::comAdvanced) mode_name = "advanced"; + else if (config_mode == ConfigOptionMode::comExpert) mode_name = "expert"; + + if (!mode_name.empty() && disabled_modes.find(mode_name) != std::string::npos) + return false; + + // fff / sla + const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + if (tech == ptFFF) { + if (disabled_modes.find("FFF") != std::string::npos) + return false; + } else { + if (disabled_modes.find("SLA") != std::string::npos) + return false; + } + + return true; +} +} //namespace + +void HintDatabase::init() +{ + + load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); + + const AppConfig* app_config = wxGetApp().app_config; + m_hint_id = std::atoi(app_config->get("last_hint").c_str()); + m_initialized = true; + +} +void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) +{ + namespace pt = boost::property_tree; + pt::ptree tree; + boost::nowide::ifstream ifs(path.string()); + try { + pt::read_ini(ifs, tree); + } + catch (const boost::property_tree::ini_parser::ini_parser_error& err) { + throw Slic3r::RuntimeError(format("Failed loading hints file \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + } + + for (const auto& section : tree) { + if (boost::starts_with(section.first, "hint:")) { + // create std::map with tree data + std::map dict; + for (const auto& data : section.second) { + dict.emplace(data.first, data.second.data()); + } + + //unescaping a translating all texts + //unescape text1 + std::string fulltext; + std::string text1; + std::string hypertext_text; + std::string follow_text; + std::string disabled_modes; + unescape_string_cstyle(_utf8(dict["text"]), fulltext); + // replace and for imgui markers + std::string marker_s(1, ImGui::ColorMarkerStart); + std::string marker_e(1, ImGui::ColorMarkerEnd); + // start marker + size_t marker_pos = fulltext.find(BOLD_MARKER_START); + while (marker_pos != std::string::npos) { + fulltext.replace(marker_pos, 3, marker_s); + marker_pos = fulltext.find(BOLD_MARKER_START, marker_pos); + } + // end marker + marker_pos = fulltext.find(BOLD_MARKER_END); + while (marker_pos != std::string::npos) { + fulltext.replace(marker_pos, 4, marker_e); + marker_pos = fulltext.find(BOLD_MARKER_END, marker_pos); + } + // divide fulltext + size_t hypertext_start = fulltext.find(HYPERTEXT_MARKER_START); + if (hypertext_start != std::string::npos) { + //hypertext exists + fulltext.erase(hypertext_start, HYPERTEXT_MARKER_START.size()); + if (fulltext.find(HYPERTEXT_MARKER_START) != std::string::npos) { + // This must not happen - only 1 hypertext allowed + BOOST_LOG_TRIVIAL(error) << "Hint notification with multiple hypertexts: " << _utf8(dict["text"]); + continue; + } + size_t hypertext_end = fulltext.find(HYPERTEXT_MARKER_END); + if (hypertext_end == std::string::npos) { + // hypertext was not correctly ended + BOOST_LOG_TRIVIAL(error) << "Hint notification without hypertext end marker: " << _utf8(dict["text"]); + continue; + } + fulltext.erase(hypertext_end, HYPERTEXT_MARKER_END.size()); + if (fulltext.find(HYPERTEXT_MARKER_END) != std::string::npos) { + // This must not happen - only 1 hypertext end allowed + BOOST_LOG_TRIVIAL(error) << "Hint notification with multiple hypertext end markers: " << _utf8(dict["text"]); + continue; + } + + text1 = fulltext.substr(0, hypertext_start); + hypertext_text = fulltext.substr(hypertext_start, hypertext_end - hypertext_start); + follow_text = fulltext.substr(hypertext_end); + } else { + text1 = fulltext; + } + + if (dict.find("disabled_modes") != dict.end()) { + disabled_modes = dict["disabled_modes"]; + } + + // create HintData + if (dict.find("hypertext_type") != dict.end()) { + //link to internet + if(dict["hypertext_type"] == "link") { + std::string hypertext_link = dict["hypertext_link"]; + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [hypertext_link]() { wxLaunchDefaultBrowser(hypertext_link); } }; + m_loaded_hints.emplace_back(hint_data); + // highlight settings + } else if (dict["hypertext_type"] == "settings") { + std::string opt = dict["hypertext_settings_opt"]; + Preset::Type type = static_cast(std::atoi(dict["hypertext_settings_type"].c_str())); + std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]); + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; + m_loaded_hints.emplace_back(hint_data); + // open preferences + } else if(dict["hypertext_type"] == "preferences") { + int page = static_cast(std::atoi(dict["hypertext_preferences_page"].c_str())); + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [page]() { wxGetApp().open_preferences(page); } }; + m_loaded_hints.emplace_back(hint_data); + + } else if (dict["hypertext_type"] == "plater") { + std::string item = dict["hypertext_plater_item"]; + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } }; + m_loaded_hints.emplace_back(hint_data); + } else if (dict["hypertext_type"] == "gizmo") { + std::string item = dict["hypertext_gizmo_item"]; + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } }; + m_loaded_hints.emplace_back(hint_data); + } + else if (dict["hypertext_type"] == "gallery") { + HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, []() { wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; + m_loaded_hints.emplace_back(hint_data); + } + } else { + // plain text without hypertext + HintData hint_data{ text1 }; + m_loaded_hints.emplace_back(hint_data); + } + } + } +} +HintData* HintDatabase::get_hint(bool up) +{ + if (! m_initialized) { + init(); + //return false; + } + // shift id + m_hint_id = (up ? m_hint_id + 1 : (m_hint_id == 0 ? m_loaded_hints.size() - 1 : m_hint_id - 1)); + m_hint_id %= m_loaded_hints.size(); + + AppConfig* app_config = wxGetApp().app_config; + app_config->set("last_hint", std::to_string(m_hint_id)); + + //data = &m_loaded_hints[m_hint_id]; + /* + data.text = m_loaded_hints[m_hint_id].text; + data.hypertext = m_loaded_hints[m_hint_id].hypertext; + data.follow_text = m_loaded_hints[m_hint_id].follow_text; + data.callback = m_loaded_hints[m_hint_id].callback; + */ + return &m_loaded_hints[m_hint_id]; +} + +void NotificationManager::HintNotification::count_spaces() +{ + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + + std::string text; + text = ImGui::LeftArrowButton; // TODO change to left arrow + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + + // no left button picture + //m_left_indentation = m_line_height; + + m_window_width_offset = m_left_indentation + m_line_height * 3.f;// 5.5f; // no right arrow + m_window_width = m_line_height * 25; +} + +void NotificationManager::HintNotification::count_lines() +{ + std::string text = m_text1; + size_t last_end = 0; + m_lines_count = 0; + + if (text.empty()) + return; + + m_endlines.clear(); + while (last_end < text.length() - 1) + { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + //next line is ended by '/n' + m_endlines.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { + // more than one line till end + size_t next_space = text.find_first_of(' ', last_end); + if (next_space > 0 && next_space < text.length()) { + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + } else { + next_space = text.length(); + } + // when one word longer than line. + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset || + ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3 + ) { + float width_of_a = ImGui::CalcTextSize("a").x; + int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); + while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { + letter_count++; + } + m_endlines.push_back(last_end + letter_count); + last_end += letter_count; + } else { + m_endlines.push_back(next_space); + last_end = next_space + 1; + } + } + else { + m_endlines.push_back(text.length()); + last_end = text.length(); + } + + } + m_lines_count++; + } + int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; + int size_of_last_line = ImGui::CalcTextSize(text.substr(prev_end, last_end - prev_end).c_str()).x; + // hypertext calculation + if (!m_hypertext.empty()) { + if (size_of_last_line + ImGui::CalcTextSize(m_hypertext.c_str()).x > m_window_width - m_window_width_offset) { + // hypertext on new line + size_of_last_line = ImGui::CalcTextSize((m_hypertext + " ").c_str()).x; + m_endlines.push_back(last_end); + m_lines_count++; + } else { + size_of_last_line += ImGui::CalcTextSize((m_hypertext + " ").c_str()).x; + } + } + if (!m_text2.empty()) { + text = m_text2; + last_end = 0; + m_endlines2.clear(); + // if size_of_last_line too large to fit anything + size_t first_end = std::min(text.find_first_of('\n'), text.find_first_of(' ')); + if (size_of_last_line >= m_window_width - m_window_width_offset - ImGui::CalcTextSize(text.substr(0, first_end).c_str()).x) { + m_endlines2.push_back(0); + size_of_last_line = 0; + } + while (last_end < text.length() - 1) + { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { + //next line is ended by '/n' + m_endlines2.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset - size_of_last_line) { + // more than one line till end + size_t next_space = text.find_first_of(' ', last_end); + if (next_space > 0) { + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + } + else { + next_space = text.length(); + } + // when one word longer than line. + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset - size_of_last_line || + ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 4 * 3 + ) { + float width_of_a = ImGui::CalcTextSize("a").x; + int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a); + while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { + letter_count++; + } + m_endlines2.push_back(last_end + letter_count); + last_end += letter_count; + } + else { + m_endlines2.push_back(next_space); + last_end = next_space + 1; + } + } + else { + m_endlines2.push_back(text.length()); + last_end = text.length(); + } + + } + if (size_of_last_line == 0) // if first line is continuation of previous text, do not add to line count. + m_lines_count++; + size_of_last_line = 0; // should countain value only for first line (with hypertext) + + } + } +} + +void NotificationManager::HintNotification::init() +{ + // Do not init closing notification + if (is_finished()) + return; + + count_spaces(); + count_lines(); + + m_multiline = true; + + m_notification_start = GLCanvas3D::timestamp_now(); + if (m_state == EState::Unknown) + m_state = EState::Shown; +} + +void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& imgui) +{ + /* + m_window_height = m_multiline ? + (m_lines_count + 1.f) * m_line_height : + 4.f * m_line_height; + m_window_height += 1 * m_line_height; // top and bottom + */ + + m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 4.f * m_line_height); +} + +bool NotificationManager::HintNotification::on_text_click() +{ + if (m_hypertext_callback != nullptr && (!m_runtime_disable || disabled_modes_check(m_disabled_modes))) + m_hypertext_callback(); + return false; +} + +void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (!m_has_hint_data) { + retrieve_data(); + } + + float x_offset = m_left_indentation; + int last_end = 0; + float starting_y = (m_lines_count == 2 ? win_size_y / 2 - m_line_height :(m_lines_count == 1 ? win_size_y / 2 - m_line_height / 2: m_line_height / 2)); + float shift_y = m_line_height; + std::string line; + + for (size_t i = 0; i < (m_multiline ? /*m_lines_count*/m_endlines.size() : 2); i++) { + line.clear(); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { + if (i == 1 && m_endlines.size() > 2 && !m_multiline) { + // second line with "more" hypertext + line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { + line = line.substr(0, line.length() - 1); + } + line += ".."; + } else { + // regural line + line = m_text1.substr(last_end, m_endlines[i] - last_end); + } + // first line is headline + if (i == 0) { + line = ImGui::ColorMarkerStart + line + ImGui::ColorMarkerEnd; + } + // Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line) + if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) { + line = ImGui::ColorMarkerStart + line; + } + + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + imgui.text(line.c_str()); + } + + } + //hyperlink text + if (!m_multiline && m_lines_count > 2) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); + } else if (!m_hypertext.empty()) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty()? "": " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); + } + + // text2 + if (!m_text2.empty() && m_multiline) { + starting_y += (m_endlines.size() - 1) * shift_y; + last_end = 0; + for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : 2); i++) { + if (i == 0) //first line X is shifted by hypertext + ImGui::SetCursorPosX(x_offset + ImGui::CalcTextSize((line + m_hypertext + (line.empty() ? " " : " ")).c_str()).x); + else + ImGui::SetCursorPosX(x_offset); + + ImGui::SetCursorPosY(starting_y + i * shift_y); + line.clear(); + if (m_endlines2.size() > i && m_text2.size() >= m_endlines2[i]) { + + // regural line + line = m_text2.substr(last_end, m_endlines2[i] - last_end); + + // Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line) + if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) { + line = ImGui::ColorMarkerStart + line; + } + + last_end = m_endlines2[i]; + if (m_text2.size() > m_endlines2[i]) + last_end += (m_text2[m_endlines2[i]] == '\n' || m_text2[m_endlines2[i]] == ' ' ? 1 : 0); + imgui.text(line.c_str()); + } + + } + } +} + +void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CloseNotifButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - 2 * m_line_height), + true)) + { + button_text = ImGui::CloseNotifHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + m_close_b_w = button_size.y; + if (m_lines_count <= 3) { + m_close_b_y = win_size.y / 2 - button_size.y * 1.25f; + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(m_close_b_y); + } else { + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + } + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + close(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - 2 * m_line_height)) + { + close(); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + + render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_preferences_button(imgui, win_pos_x, win_pos_y); +} + +void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) +{ + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + + std::string button_text; + button_text = ImGui::PreferencesButton; + //hover + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1), + ImVec2(win_pos_x, win_pos_y + m_window_height), + true)) + { + button_text = ImGui::PreferencesHoverButton; + } + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(m_window_width - m_line_height * 1.75f); + if (m_lines_count <= 3) { + ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f); + } else { + ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); + } + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + wxGetApp().open_preferences(2); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + // preferences button is in place of minimize button + m_minimize_b_visible = true; +} + +void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + // Used for debuging + + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::RightArrowButton; + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + + ImGui::SetCursorPosX(m_window_width - m_line_height * 3.f); + if (m_lines_count <= 3) + ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f); + else + ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); + if (imgui.button(button_text.c_str(), button_size.x * 0.8f, button_size.y * 1.f)) + { + retrieve_data(); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::ErrorMarker;//LeftArrowButton; + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + ImGui::SetCursorPosX(0); + // shouldnt it render as text? + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::HintNotification::retrieve_data(size_t recursion_counter) +{ + HintData* hint_data = HintDatabase::get_instance().get_hint(true); + if (hint_data != nullptr && !disabled_modes_check(hint_data->disabled_modes)) + { + // Content for different user - retrieve another + size_t count = HintDatabase::get_instance().get_count(); + if (count < recursion_counter) { + BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter."; + } else { + retrieve_data(recursion_counter + 1); + } + return; + } + if(hint_data != nullptr) + { + NotificationData nd { NotificationType::DidYouKnowHint, + NotificationLevel::RegularNotification, + 0, + hint_data->text, + hint_data->hypertext, nullptr, + hint_data->follow_text }; + update(nd); + m_hypertext_callback = hint_data->callback; + m_disabled_modes = hint_data->disabled_modes; + m_runtime_disable = hint_data->runtime_disable; + m_has_hint_data = true; + + } +} +} //namespace Slic3r +} //namespace GUI \ No newline at end of file diff --git a/src/slic3r/GUI/HintNotification.hpp b/src/slic3r/GUI/HintNotification.hpp new file mode 100644 index 000000000..125420fb6 --- /dev/null +++ b/src/slic3r/GUI/HintNotification.hpp @@ -0,0 +1,97 @@ +#ifndef slic3r_GUI_HintNotification_hpp_ +#define slic3r_GUI_HintNotification_hpp_ + +#include "NotificationManager.hpp" + +namespace Slic3r { +namespace GUI { + +// Database of hints updatable +struct HintData +{ + std::string text; + std::string hypertext; + std::string follow_text; + std::string disabled_modes; + bool runtime_disable; // if true - hyperlink will check before every click if not in disabled mode + std::function callback{ nullptr }; +}; + +class HintDatabase +{ +public: + static HintDatabase& get_instance() + { + static HintDatabase instance; // Guaranteed to be destroyed. + // Instantiated on first use. + return instance; + } +private: + HintDatabase() + : m_hint_id(0) + {} +public: + HintDatabase(HintDatabase const&) = delete; + void operator=(HintDatabase const&) = delete; + + // return true if HintData filled; + HintData* get_hint(bool up = true); + size_t get_count() { + if (!m_initialized) + return 0; + return m_loaded_hints.size(); + } +private: + void init(); + void load_hints_from_file(const boost::filesystem::path& path); + size_t m_hint_id; + bool m_initialized { false }; + std::vector m_loaded_hints; + +}; +// Notification class - shows current Hint ("Did you know") +class NotificationManager::HintNotification : public NotificationManager::PopNotification +{ +public: + HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) + : PopNotification(n, id_provider, evt_handler) + { + retrieve_data(); + } + virtual void init() override; +protected: + virtual void set_next_window_size(ImGuiWrapper& imgui) override; + virtual void count_spaces() override; + virtual void count_lines() override; + virtual bool on_text_click() override; + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_minimize_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y) override {} + void render_preferences_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y); + void render_right_arrow_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_logo(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + + void retrieve_data(size_t recursion_counter = 0); + + bool m_has_hint_data { false }; + std::function m_hypertext_callback; + std::string m_disabled_modes; + bool m_runtime_disable; + float m_close_b_y { 0 }; + float m_close_b_w { 0 }; +}; + +} //namespace Slic3r +} //namespace GUI + +#endif //slic3r_GUI_HintNotification_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index dbf89f025..e389d268c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -43,7 +43,11 @@ static const std::map font_icons = { {ImGui::FilamentIconMarker , "spool" }, {ImGui::MaterialIconMarker , "resin" }, {ImGui::MinimalizeButton , "notification_minimalize" }, - {ImGui::MinimalizeHoverButton , "notification_minimalize_hover" } + {ImGui::MinimalizeHoverButton , "notification_minimalize_hover" }, + {ImGui::RightArrowButton , "notification_right" }, + {ImGui::RightArrowHoverButton , "notification_right_hover" }, + {ImGui::PreferencesButton , "notification_preferences" }, + {ImGui::PreferencesHoverButton , "notification_preferences_hover"}, }; static const std::map font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, @@ -54,6 +58,8 @@ static const std::map font_icons_large = { {ImGui::ErrorMarker , "notification_error" }, {ImGui::CancelButton , "notification_cancel" }, {ImGui::CancelHoverButton , "notification_cancel_hover" }, + {ImGui::LeftArrowButton , "notification_left" }, + {ImGui::LeftArrowHoverButton , "notification_left_hover" }, }; const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b000cbd69..1142fdca8 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1,10 +1,11 @@ #include "NotificationManager.hpp" - +#include "HintNotification.hpp" #include "GUI.hpp" #include "ImGuiWrapper.hpp" #include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" +#include "libslic3r/Config.hpp" #include "../Utils/PrintHost.hpp" #include "libslic3r/Config.hpp" @@ -30,7 +31,37 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); -namespace Notifications_Internal{ +const NotificationManager::NotificationData NotificationManager::basic_notifications[] = { + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), + [](wxEvtHandler* evnthndlr) { + if (evnthndlr != nullptr) + wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); + return true; + } + }, + {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, + _u8L("You have just added a G-code for color change, but its value is empty.\n" + "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, + {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, + _u8L("This model doesn't allow to automatically add the color changes") }, + {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + _u8L("Desktop integration was successful.") }, + {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + _u8L("Desktop integration failed.") }, + {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + _u8L("Undo desktop integration was successful.") }, + {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + _u8L("Undo desktop integration failed.") }, + //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification +}; + +namespace { + /* // not used? ImFont* add_default_font(float pixel_size) { ImGuiIO& io = ImGui::GetIO(); @@ -41,8 +72,8 @@ namespace Notifications_Internal{ ImFont* font = io.Fonts->AddFontDefault(&config); return font; } - - static inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) + */ + inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) { if (fading_out) ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); @@ -129,8 +160,8 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, m_data (n) , m_id_provider (id_provider) , m_text1 (n.text1) - , m_hypertext (n.hypertext) - , m_text2 (n.text2) + , m_hypertext (n.hypertext) + , m_text2 (n.text2) , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) {} @@ -184,8 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init // color change based on fading out if (m_state == EState::FadingOut) { - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity); fading_pop = true; } @@ -219,20 +250,20 @@ bool NotificationManager::PopNotification::push_background_color() { if (m_is_gray) { ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } if (m_data.level == NotificationLevel::ErrorNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } if (m_data.level == NotificationLevel::WarningNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } return false; @@ -259,6 +290,9 @@ void NotificationManager::PopNotification::count_lines() size_t last_end = 0; m_lines_count = 0; + if (text.empty()) + return; + m_endlines.clear(); while (last_end < text.length() - 1) { @@ -283,7 +317,9 @@ void NotificationManager::PopNotification::count_lines() next_space = text.length(); } // when one word longer than line. - if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) { + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset || + ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3 + ) { float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { @@ -312,6 +348,9 @@ void NotificationManager::PopNotification::count_lines() m_lines_count++; } } + + // m_text_2 (text after hypertext) is not used for regular notifications right now. + // its caluculation is in HintNotification::count_lines() } void NotificationManager::PopNotification::init() @@ -339,105 +378,45 @@ void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& im void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ImVec2 win_size(win_size_x, win_size_y); - float x_offset = m_left_indentation; - std::string fulltext = m_text1 + m_hypertext; //+ m_text2; - ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); - // text posistions are calculated by lines count - // large texts has "more" button or are displayed whole - // smaller texts are divided as one liners and two liners - if (m_lines_count > 2) { - if (m_multiline) { - - int last_end = 0; - float starting_y = m_line_height/2; - float shift_y = m_line_height; - std::string line; + float x_offset = m_left_indentation; + int last_end = 0; + float starting_y = (m_lines_count == 2 ? win_size_y / 2 - m_line_height : (m_lines_count == 1 ? win_size_y / 2 - m_line_height / 2 : m_line_height / 2)); + float shift_y = m_line_height; + std::string line; - for (size_t i = 0; i < m_lines_count; i++) { - line.clear(); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); - if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { - line = m_text1.substr(last_end, m_endlines[i] - last_end); - last_end = m_endlines[i]; - if (m_text1.size() > m_endlines[i]) - last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - imgui.text(line.c_str()); - } - } - //hyperlink text - if (!m_hypertext.empty()) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); - } - - - } else { - // line1 - if (m_text1.size() >= m_endlines[0]) { - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); - } - // line2 - std::string line; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); - if (m_text1.size() >= m_endlines[1]) { + for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), (size_t)2)); i++) { + line.clear(); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { + if (i == 1 && m_endlines.size() > 2 && !m_multiline) { + // second line with "more" hypertext line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { - line = line.substr(0, line.length() - 6); - line += ".."; - } else - line += " "; - imgui.text(line.c_str()); + while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { + line = line.substr(0, line.length() - 1); + } + line += ".."; } - // "More" hypertext - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); - } - } else { - //text 1 - float cursor_y = win_size.y / 2 - text_size.y / 2; - float cursor_x = x_offset; - if(m_lines_count > 1) { - // line1 - if (m_text1.length() >= m_endlines[0]) { // could be equal than substr takes whole string - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + else { + // regural line + line = m_text1.substr(last_end, m_endlines[i] - last_end); } - // line2 - ImGui::SetCursorPosX(x_offset); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; - ImGui::SetCursorPosY(cursor_y); - if (m_text1.length() > m_endlines[0]) { // must be greater otherwise theres nothing to show and m_text1[m_endlines[0]] is beyond last letter - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - imgui.text(line.c_str()); - cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; - } - } else { - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_text1.c_str()); - cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + imgui.text(line.c_str()); } - //hyperlink text - if (!m_hypertext.empty()) { - render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); - } - - //notification text 2 - //text 2 is suposed to be after the hyperlink - currently it is not used - /* - if (!m_text2.empty()) - { - ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str()); - ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_text2.c_str()); - } - */ } + //hyperlink text + if (!m_multiline && m_lines_count > 2) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); + } + else if (!m_hypertext.empty()) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); + } + + // text2 (text after hypertext) is not rendered for regular notifications + // its rendering is in HintNotification::render_text } void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) @@ -470,7 +449,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, orange_color.y += 0.2f; //text - Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); imgui.text(text.c_str()); @@ -491,8 +470,8 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); @@ -542,9 +521,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); //button - if part if treggered @@ -762,8 +741,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -818,7 +797,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW } bool NotificationManager::ExportFinishedNotification::on_text_click() { - Notifications_Internal::open_folder(m_export_dir_path); + open_folder(m_export_dir_path); return false; } //------ProgressBar---------------- @@ -961,7 +940,7 @@ bool NotificationManager::PrintHostUploadNotification::push_background_color() if (m_uj_state == UploadJobState::PB_ERROR) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } return false; @@ -1031,8 +1010,8 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -1088,10 +1067,10 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : } void NotificationManager::push_notification(const NotificationType type, int timestamp) { - auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), + auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), boost::bind(&NotificationData::type, boost::placeholders::_1) == type); - assert(it != basic_notifications.end()); - if (it != basic_notifications.end()) + assert(it != std::end(basic_notifications)); + if (it != std::end(basic_notifications)) push_notification_data(*it, timestamp); } void NotificationManager::push_notification(const std::string& text, int timestamp) @@ -1331,6 +1310,15 @@ void NotificationManager::upload_job_notification_show_error(int id, const std:: } } } +void NotificationManager::push_hint_notification() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::DidYouKnowHint) + return; + } + NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 0, "" }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler), 0); +} bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) { return push_notification_data(std::make_unique(notification_data, m_id_provider, m_evt_handler), timestamp); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 17db606c0..d9a5230f3 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -89,8 +89,9 @@ enum class NotificationType UndoDesktopIntegrationSuccess, UndoDesktopIntegrationFail, // Notification that a printer has more extruders than are supported by MM Gizmo/segmentation. - MmSegmentationExceededExtrudersLimit - + MmSegmentationExceededExtrudersLimit, + // Did you know Notification appearing on startup with arrows to change hint + DidYouKnowHint }; class NotificationManager @@ -160,6 +161,8 @@ public: void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); + // Hint (did you know) notification + void push_hint_notification(); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device @@ -188,7 +191,7 @@ private: // Callback for hypertext - returns true if notification should close after triggering // Usually sends event to UI thread thru wxEvtHandler. // Examples in basic_notifications. - std::function callback { nullptr }; + std::function callback; const std::string text2; }; @@ -237,7 +240,7 @@ private: //returns top in actual frame float get_current_top() const { return m_top_y; } const NotificationType get_type() const { return m_data.type; } - const NotificationData get_data() const { return m_data; } + const NotificationData& get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } void set_gray(bool g) { m_is_gray = g; } virtual bool compare_text(const std::string& text) const; @@ -318,7 +321,10 @@ private: float m_top_y { 0.0f }; // Height of text - Used as basic scaling unit! float m_line_height; + // endlines for text1, hypertext excluded std::vector m_endlines; + // endlines for text2 + std::vector m_endlines2; // Gray are f.e. eorrors when its uknown if they are still valid bool m_is_gray { false }; //if multiline = true, notification is showing all lines(>2) @@ -337,7 +343,7 @@ private: void set_large(bool l); bool get_large() { return m_is_large; } void set_print_info(const std::string &info); - virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override + void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override { // This notification is always hidden if !large (means side bar is collapsed) if (!get_large() && !is_finished()) @@ -345,7 +351,7 @@ private: PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); } protected: - virtual void render_text(ImGuiWrapper& imgui, + void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; @@ -366,7 +372,7 @@ private: { public: PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} - virtual void close() override { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void close() override { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } void show() { m_state = EState::Unknown; } }; @@ -382,17 +388,17 @@ private: virtual void init() override; virtual void count_lines() override; - virtual void render_text(ImGuiWrapper& imgui, + virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void render_bar(ImGuiWrapper& imgui, + virtual void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) ; - virtual void render_cancel_button(ImGuiWrapper& imgui, + virtual void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) {} - virtual void render_minimize_button(ImGuiWrapper& imgui, + void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override {} float m_percentage; @@ -421,22 +427,22 @@ private: m_has_cancel_button = true; } static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } - virtual void set_percentage(float percent) override; + void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; init(); } bool compare_job_id(const int other_id) const { return m_job_id == other_id; } - virtual bool compare_text(const std::string& text) const override { return false; } + bool compare_text(const std::string& text) const override { return false; } protected: - virtual void init() override; - virtual void count_spaces() override; - virtual bool push_background_color() override; - virtual void render_bar(ImGuiWrapper& imgui, + void init() override; + void count_spaces() override; + bool push_background_color() override; + void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void render_cancel_button(ImGuiWrapper& imgui, + void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void render_left_sign(ImGuiWrapper& imgui) override; + void render_left_sign(ImGuiWrapper& imgui) override; // Identifies job in cancel callback int m_job_id; // Size of uploaded size to be displayed in MB @@ -461,24 +467,27 @@ private: std::string m_export_dir_path; protected: // Reserves space on right for more buttons - virtual void count_spaces() override; - virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) override; + void count_spaces() override; + void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; // Renders also button to open directory with exported path and eject removable media - virtual void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) override; + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; void render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override + void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override { m_minimize_b_visible = false; } - virtual bool on_text_click() override; + bool on_text_click() override; // local time of last hover for showing tooltip long m_hover_time { 0 }; }; + // in HintNotification.hpp + class HintNotification; + //pushes notification into the queue of notifications that are rendered //can be used to create custom notification bool push_notification_data(const NotificationData& notification_data, int timestamp); @@ -506,34 +515,7 @@ private: // Notification types that can be shown multiple types at once (compared by text) const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; //prepared (basic) notifications - const std::vector basic_notifications = { - {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), - [](wxEvtHandler* evnthndlr) { - if (evnthndlr != nullptr) - wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); - return true; - } - }, - {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){ - wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, - {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, - _u8L("You have just added a G-code for color change, but its value is empty.\n" - "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, - {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, - _u8L("This model doesn't allow to automatically add the color changes") }, - {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, - _u8L("Desktop integration was successful.") }, - {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, - _u8L("Desktop integration failed.") }, - {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, - _u8L("Undo desktop integration was successful.") }, - {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, - _u8L("Undo desktop integration failed.") }, - //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, - //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, - //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification - }; + static const NotificationData basic_notifications[]; }; }//namespace GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9b59026dc..0a3ae2743 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1045,6 +1045,12 @@ void Sidebar::search() p->searcher.search(); } +void Sidebar::jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category) +{ + //const Search::Option& opt = p->searcher.get_option(opt_key, type); + wxGetApp().get_tab(type)->activate_option(opt_key, category); +} + void Sidebar::jump_to_option(size_t selected) { const Search::Option& opt = p->searcher.get_option(selected); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9c37dfe2b..0909a0d2b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -83,6 +83,7 @@ public: void sys_color_changed(); void search(); void jump_to_option(size_t selected); + void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category); ObjectManipulation* obj_manipul(); ObjectList* obj_list(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index fe1f0eeb4..6b6b84949 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -11,14 +11,14 @@ namespace Slic3r { namespace GUI { -PreferencesDialog::PreferencesDialog(wxWindow* parent) : +PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) : DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { #ifdef __WXOSX__ isOSX = true; #endif - build(); + build(selected_tab); } static std::shared_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) @@ -44,7 +44,7 @@ static void activate_options_tab(std::shared_ptr optgroup) sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); } -void PreferencesDialog::build() +void PreferencesDialog::build(size_t selected_tab) { #ifdef _WIN32 wxGetApp().UpdateDarkUI(this); @@ -349,13 +349,20 @@ void PreferencesDialog::build() option = Option(def, "tabs_as_menu"); m_optgroup_gui->append_single_option_line(option); #endif + + def.label = L("Show \"Did you know\" hints after start"); + def.type = coBool; + def.tooltip = L("If enabled, useful hints are displayed at startup."); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_hints") == "1" }); + option = Option(def, "show_hints"); + m_optgroup_gui->append_single_option_line(option); def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); option = Option(def, "use_custom_toolbar_size"); - m_optgroup_gui->append_single_option_line(option); + m_optgroup_gui->append_single_option_line(option); } activate_options_tab(m_optgroup_gui); @@ -387,6 +394,9 @@ void PreferencesDialog::build() } #endif // ENABLE_ENVIRONMENT_MAP + if (selected_tab < tabs->GetPageCount()) + tabs->SetSelection(selected_tab); + auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 5c0f1a6d5..1c62f6be6 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -33,15 +33,15 @@ class PreferencesDialog : public DPIDialog bool m_recreate_GUI{false}; public: - explicit PreferencesDialog(wxWindow* parent); + explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0); ~PreferencesDialog() = default; bool settings_layout_changed() const { return m_settings_layout_changed; } bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; } bool seq_seq_top_gcode_indices_changed() const { return m_seq_top_gcode_indices_changed; } bool recreate_GUI() const { return m_recreate_GUI; } - void build(); - void accept(wxEvent&); + void build(size_t selected_tab = 0); + void accept(wxEvent&); protected: void on_dpi_changed(const wxRect &suggested_rect) override;