From c908a4674ab1667de51e55675619a378483dc664 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 6 Dec 2018 10:38:19 +0100 Subject: [PATCH] Removal of tabs from plater -> added buttons to scene to switch between views --- resources/icons/view_toolbar.png | Bin 0 -> 30880 bytes src/slic3r/GUI/GLCanvas3D.cpp | 83 +++++++- src/slic3r/GUI/GLCanvas3D.hpp | 17 ++ src/slic3r/GUI/GLToolbar.cpp | 326 ++++++++++++++++++++++++++++++- src/slic3r/GUI/GLToolbar.hpp | 123 ++++++++++-- src/slic3r/GUI/GUI_Preview.cpp | 14 ++ src/slic3r/GUI/GUI_Preview.hpp | 9 + src/slic3r/GUI/Plater.cpp | 41 ++++ 8 files changed, 595 insertions(+), 18 deletions(-) create mode 100644 resources/icons/view_toolbar.png diff --git a/resources/icons/view_toolbar.png b/resources/icons/view_toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..349d0bd094caf4e9012bd14e86f3a448deecd866 GIT binary patch literal 30880 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%0Dr&L>YkWb=KubWq7@Z!ivQ>E>9?S++ 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 literal 0 HcmV?d00001 diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c958cd18f0..2b9022bf97 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -26,7 +26,11 @@ #include #include #include +#if ENABLE_REMOVE_TABS_FROM_PLATER +#include +#else #include +#endif // ENABLE_REMOVE_TABS_FROM_PLATER #include // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. @@ -3360,6 +3364,9 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const } } +#if ENABLE_REMOVE_TABS_FROM_PLATER +wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); @@ -3379,6 +3386,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_context(nullptr) , m_in_render(false) , m_toolbar(*this) +#if ENABLE_REMOVE_TABS_FROM_PLATER + , m_view_toolbar(nullptr) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER , m_use_clipping_planes(false) , m_config(nullptr) , m_process(nullptr) @@ -3522,6 +3532,10 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (!_init_toolbar()) return false; +#if ENABLE_REMOVE_TABS_FROM_PLATER + post_event(SimpleEvent(EVT_GLCANVAS_INIT)); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + m_initialized = true; return true; @@ -3845,7 +3859,9 @@ void GLCanvas3D::render() float theta = m_camera.get_theta(); bool is_custom_bed = m_bed.is_custom(); +#if !ENABLE_REMOVE_TABS_FROM_PLATER set_tooltip(""); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER #if ENABLE_IMGUI wxGetApp().imgui()->new_frame(); @@ -3889,7 +3905,13 @@ void GLCanvas3D::render() _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + _resize_toolbars(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER _render_toolbar(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + _render_view_toolbar(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER _render_layer_editing_overlay(); #if ENABLE_IMGUI @@ -4575,6 +4597,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position); +#if ENABLE_REMOVE_TABS_FROM_PLATER + int view_toolbar_contains_mouse = (m_view_toolbar != nullptr) ? m_view_toolbar->contains_mouse(m_mouse.position, *this) : -1; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER if (evt.Entering()) { @@ -4660,6 +4685,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_gizmos.get_current_type() == Gizmos::SlaSupports) m_gizmos.delete_current_grabber(); } +#if ENABLE_REMOVE_TABS_FROM_PLATER + else if (view_toolbar_contains_mouse != -1) + { + if (m_view_toolbar != nullptr) + m_view_toolbar->do_action((unsigned int)view_toolbar_contains_mouse, *this); + } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER else if (toolbar_contains_mouse != -1) { m_toolbar_action_running = true; @@ -4936,7 +4968,31 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Moving()) { - m_mouse.position = Vec2d((double)pos(0), (double)pos(1)); + m_mouse.position = pos.cast(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + std::string tooltip = ""; + + // updates gizmos overlay + if (!m_selection.is_empty()) + tooltip = m_gizmos.update_hover_state(*this, m_mouse.position, m_selection); + else + m_gizmos.reset_all_states(); + + // updates toolbar overlay + if (tooltip.empty()) + tooltip = m_toolbar.update_hover_state(m_mouse.position); + + // updates view toolbar overlay + if (tooltip.empty() && (m_view_toolbar != nullptr)) + { + tooltip = m_view_toolbar->update_hover_state(m_mouse.position, *this); + if (!tooltip.empty()) + m_dirty = true; + } + + set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. if (m_picking_enabled) m_dirty = true; @@ -5660,6 +5716,7 @@ void GLCanvas3D::_picking_pass() const _update_volumes_hover_state(); +#if !ENABLE_REMOVE_TABS_FROM_PLATER // updates gizmos overlay if (!m_selection.is_empty()) { @@ -5671,6 +5728,7 @@ void GLCanvas3D::_picking_pass() const m_gizmos.reset_all_states(); m_toolbar.update_hover_state(pos); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER } } @@ -5899,10 +5957,20 @@ void GLCanvas3D::_render_gizmos_overlay() const void GLCanvas3D::_render_toolbar() const { +#if !ENABLE_REMOVE_TABS_FROM_PLATER _resize_toolbar(); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER m_toolbar.render(); } +#if ENABLE_REMOVE_TABS_FROM_PLATER +void GLCanvas3D::_render_view_toolbar() const +{ + if (m_view_toolbar != nullptr) + m_view_toolbar->render(*this); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + #if ENABLE_SHOW_CAMERA_TARGET void GLCanvas3D::_render_camera_target() const { @@ -7495,7 +7563,11 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +void GLCanvas3D::_resize_toolbars() const +#else void GLCanvas3D::_resize_toolbar() const +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { Size cnv_size = get_canvas_size(); float zoom = get_camera_zoom(); @@ -7524,6 +7596,15 @@ void GLCanvas3D::_resize_toolbar() const break; } } + +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (m_view_toolbar != nullptr) + { + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + m_view_toolbar->set_position(top, left); + } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } const Print* GLCanvas3D::fff_print() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7270d87579..8b19da8664 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -94,6 +94,9 @@ template using Vec2dsEvent = ArrayEvent; using Vec3dEvent = Event; template using Vec3dsEvent = ArrayEvent; +#if ENABLE_REMOVE_TABS_FROM_PLATER +wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); @@ -737,6 +740,9 @@ private: Mouse m_mouse; mutable Gizmos m_gizmos; mutable GLToolbar m_toolbar; +#if ENABLE_REMOVE_TABS_FROM_PLATER + GLRadioToolbar* m_view_toolbar; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER ClippingPlane m_clipping_planes[2]; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; @@ -787,6 +793,10 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } +#if ENABLE_REMOVE_TABS_FROM_PLATER + void set_view_toolbar(GLRadioToolbar* toolbar) { m_view_toolbar = toolbar; } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + bool init(bool useVBOs, bool use_legacy_opengl); void post_event(wxEvent &&event); @@ -958,6 +968,9 @@ private: void _render_current_gizmo() const; void _render_gizmos_overlay() const; void _render_toolbar() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + void _render_view_toolbar() const; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER #if ENABLE_SHOW_CAMERA_TARGET void _render_camera_target() const; #endif // ENABLE_SHOW_CAMERA_TARGET @@ -1021,7 +1034,11 @@ private: bool _is_any_volume_outside() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + void _resize_toolbars() const; +#else void _resize_toolbar() const; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER static std::vector _parse_colors(const std::vector& colors); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 0e99f88ff8..c06c0fa7b5 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -26,6 +26,8 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); +wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent); +wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); GLToolbarItem::Data::Data() : name("") @@ -121,7 +123,7 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned i return uvs; } -GLToolbar::ItemsIconsTexture::ItemsIconsTexture() +ItemsIconsTexture::ItemsIconsTexture() : items_icon_size(0) , items_icon_border_size(0) , items_icon_gap_size(0) @@ -289,14 +291,27 @@ bool GLToolbar::is_item_pressed(const std::string& name) const return false; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#else void GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (!m_enabled) + return ""; +#else if (!m_enabled) return; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER switch (m_layout.type) { default: +#if ENABLE_REMOVE_TABS_FROM_PLATER + case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos); } + case Layout::Vertical: { return update_hover_state_vertical(mouse_pos); } +#else case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos); @@ -307,6 +322,7 @@ void GLToolbar::update_hover_state(const Vec2d& mouse_pos) update_hover_state_vertical(mouse_pos); break; } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } } @@ -428,7 +444,11 @@ float GLToolbar::get_main_size() const return size; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#else void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { float zoom = m_parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; @@ -505,11 +525,19 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) } } +#if ENABLE_REMOVE_TABS_FROM_PLATER + return tooltip; +#else if (!tooltip.empty()) m_parent.set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } +#if ENABLE_REMOVE_TABS_FROM_PLATER +std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#else void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { float zoom = m_parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; @@ -586,7 +614,11 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) } } +#if ENABLE_REMOVE_TABS_FROM_PLATER + return tooltip; +#else m_parent.set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const @@ -739,5 +771,297 @@ void GLToolbar::render_vertical() const } } +GLRadioToolbarItem::Data::Data() + : name("") + , tooltip("") + , sprite_id(-1) +{ +} + +GLRadioToolbarItem::GLRadioToolbarItem(const GLRadioToolbarItem::Data& data) + : m_state(Normal) + , m_data(data) +{ +} + +GLRadioToolbarItem::EState GLRadioToolbarItem::get_state() const +{ + return m_state; +} + +void GLRadioToolbarItem::set_state(GLRadioToolbarItem::EState state) +{ + m_state = state; +} + +const std::string& GLRadioToolbarItem::get_name() const +{ + return m_data.name; +} + +const std::string& GLRadioToolbarItem::get_tooltip() const +{ + return m_data.tooltip; +} + +bool GLRadioToolbarItem::is_hovered() const +{ + return (m_state == Hover) || (m_state == HoverPressed); +} + +bool GLRadioToolbarItem::is_pressed() const +{ + return (m_state == Pressed) || (m_state == HoverPressed); +} + +void GLRadioToolbarItem::do_action(wxEvtHandler *target) +{ + wxPostEvent(target, SimpleEvent(m_data.action_event)); +} + +void GLRadioToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size)); +} + +GLTexture::Quad_UVs GLRadioToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::Quad_UVs uvs; + + float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; + + float scaled_icon_size = (float)icon_size * inv_texture_size; + float scaled_border_size = (float)border_size * inv_texture_size; + float scaled_gap_size = (float)gap_size * inv_texture_size; + float stride = scaled_icon_size + scaled_gap_size; + + float left = scaled_border_size + (float)m_state * stride; + float right = left + scaled_icon_size; + float top = scaled_border_size + (float)m_data.sprite_id * stride; + float bottom = top + scaled_icon_size; + + uvs.left_top = { left, top }; + uvs.left_bottom = { left, bottom }; + uvs.right_bottom = { right, bottom }; + uvs.right_top = { right, top }; + + return uvs; +} + +GLRadioToolbar::GLRadioToolbar() + : m_top(0.0f) + , m_left(0.0f) +{ +} + +GLRadioToolbar::~GLRadioToolbar() +{ + for (GLRadioToolbarItem* item : m_items) + { + delete item; + } +} + +bool GLRadioToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size) +{ + if (m_icons_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false); + if (res) + { + m_icons_texture.items_icon_size = items_icon_size; + m_icons_texture.items_icon_border_size = items_icon_border_size; + m_icons_texture.items_icon_gap_size = items_icon_gap_size; + } + + return res; +} + +bool GLRadioToolbar::add_item(const GLRadioToolbarItem::Data& data) +{ + GLRadioToolbarItem* item = new GLRadioToolbarItem(data); + if (item == nullptr) + return false; + + m_items.push_back(item); + return true; +} + +float GLRadioToolbar::get_height() const +{ + return m_icons_texture.items_icon_size; +} + +void GLRadioToolbar::set_position(float top, float left) +{ + m_top = top; + m_left = left; +} + +void GLRadioToolbar::set_selection(const std::string& name) +{ + for (GLRadioToolbarItem* item : m_items) + { + item->set_state((item->get_name() == name) ? GLRadioToolbarItem::Pressed : GLRadioToolbarItem::Normal); + } +} + +int GLRadioToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +{ + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = parent.get_canvas_size(); + Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + int id = -1; + + for (GLRadioToolbarItem* item : m_items) + { + ++id; + + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return id; + + left += scaled_icons_size; + } + + return -1; +} + +std::string GLRadioToolbar::update_hover_state(const Vec2d& mouse_pos, const GLCanvas3D& parent) +{ + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = parent.get_canvas_size(); + Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + std::string tooltip = ""; + + for (GLRadioToolbarItem* item : m_items) + { + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + GLRadioToolbarItem::EState state = item->get_state(); + bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); + + switch (state) + { + case GLRadioToolbarItem::Normal: + { + if (inside) + item->set_state(GLRadioToolbarItem::Hover); + + break; + } + case GLRadioToolbarItem::Hover: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLRadioToolbarItem::Normal); + + break; + } + case GLRadioToolbarItem::Pressed: + { + if (inside) + item->set_state(GLRadioToolbarItem::HoverPressed); + + break; + } + case GLRadioToolbarItem::HoverPressed: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLRadioToolbarItem::Pressed); + + break; + } + default: + { + break; + } + } + + left += scaled_icons_size; + } + + return tooltip; +} + +void GLRadioToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +{ + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (i != item_id) + m_items[i]->set_state(GLRadioToolbarItem::Normal); + } + + if (item_id < (unsigned int)m_items.size()) + { + GLRadioToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && item->is_hovered() && !item->is_pressed()) + { + item->set_state(GLRadioToolbarItem::HoverPressed); + item->do_action(parent.get_wxglcanvas()); + } + } + + parent.set_as_dirty(); +} + +void GLRadioToolbar::render(const GLCanvas3D& parent) const +{ + if (m_items.empty()) + return; + + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + ::glLoadIdentity(); + + unsigned int tex_id = m_icons_texture.texture.get_id(); + int tex_size = m_icons_texture.texture.get_width(); + + if ((tex_id == 0) || (tex_size <= 0)) + return; + + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + // renders icons + for (const GLRadioToolbarItem* item : m_items) + { + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); + left += scaled_icons_size; + } + + ::glPopMatrix(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index dde62c6452..85e606583e 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -26,6 +26,9 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); +wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent); +wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); + class GLToolbarItem { public: @@ -86,25 +89,25 @@ private: GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; }; +// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done +// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState +// from left to right +struct ItemsIconsTexture +{ + GLTexture texture; + // size of the square icons, in pixels + unsigned int items_icon_size; + // distance from the border, in pixels + unsigned int items_icon_border_size; + // distance between two adjacent icons (to avoid filtering artifacts), in pixels + unsigned int items_icon_gap_size; + + ItemsIconsTexture(); +}; + class GLToolbar { public: - // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done - // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState - // from left to right - struct ItemsIconsTexture - { - GLTexture texture; - // size of the square icons, in pixels - unsigned int items_icon_size; - // distance from the border, in pixels - unsigned int items_icon_border_size; - // distance between two adjacent icons (to avoid filtering artifacts), in pixels - unsigned int items_icon_gap_size; - - ItemsIconsTexture(); - }; - struct Layout { enum Type : unsigned char @@ -160,7 +163,11 @@ public: bool is_item_pressed(const std::string& name) const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + std::string update_hover_state(const Vec2d& mouse_pos); +#else void update_hover_state(const Vec2d& mouse_pos); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER // returns the id of the item under the given mouse position or -1 if none int contains_mouse(const Vec2d& mouse_pos) const; @@ -175,8 +182,13 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + std::string update_hover_state_horizontal(const Vec2d& mouse_pos); + std::string update_hover_state_vertical(const Vec2d& mouse_pos); +#else void update_hover_state_horizontal(const Vec2d& mouse_pos); void update_hover_state_vertical(const Vec2d& mouse_pos); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER int contains_mouse_horizontal(const Vec2d& mouse_pos) const; int contains_mouse_vertical(const Vec2d& mouse_pos) const; @@ -184,6 +196,85 @@ private: void render_vertical() const; }; +class GLRadioToolbarItem +{ +public: + struct Data + { + std::string name; + std::string tooltip; + unsigned int sprite_id; + wxEventType action_event; + + Data(); + }; + + enum EState : unsigned char + { + Normal, + Pressed, + Hover, + HoverPressed, + Num_States + }; + +private: + EState m_state; + Data m_data; + +public: + GLRadioToolbarItem(const Data& data); + + EState get_state() const; + void set_state(EState state); + + const std::string& get_name() const; + const std::string& get_tooltip() const; + + bool is_hovered() const; + bool is_pressed() const; + + void do_action(wxEvtHandler *target); + + void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; + +private: + GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; +}; + +class GLRadioToolbar +{ + typedef std::vector ItemsList; + + ItemsIconsTexture m_icons_texture; + + ItemsList m_items; + float m_top; + float m_left; + +public: + GLRadioToolbar(); + ~GLRadioToolbar(); + + bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size); + + bool add_item(const GLRadioToolbarItem::Data& data); + + float get_height() const; + + void set_position(float top, float left); + void set_selection(const std::string& name); + + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + std::string update_hover_state(const Vec2d& mouse_pos, const GLCanvas3D& parent); + + void do_action(unsigned int item_id, GLCanvas3D& parent); + + void render(const GLCanvas3D& parent) const; +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index a8ba5ba525..724815076e 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -92,6 +92,12 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba return true; } +void View3D::set_view_toolbar(GLRadioToolbar* toolbar) +{ + if (m_canvas != nullptr) + m_canvas->set_view_toolbar(toolbar); +} + void View3D::set_as_dirty() { if (m_canvas != nullptr) @@ -358,6 +364,14 @@ Preview::~Preview() } } +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Preview::set_view_toolbar(GLRadioToolbar* toolbar) +{ + if (m_canvas != nullptr) + m_canvas->set_view_toolbar(toolbar); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index dd2587d52a..bfccf9a98d 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -28,6 +28,9 @@ class Model; namespace GUI { class GLCanvas3D; +#if ENABLE_REMOVE_TABS_FROM_PLATER +class GLRadioToolbar; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER #if ENABLE_REMOVE_TABS_FROM_PLATER class View3D : public wxPanel @@ -50,6 +53,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_view_toolbar(GLRadioToolbar* toolbar); + void set_as_dirty(); void set_bed_shape(const Pointfs& shape); @@ -116,6 +121,10 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } +#if ENABLE_REMOVE_TABS_FROM_PLATER + void set_view_toolbar(GLRadioToolbar* toolbar); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + void set_number_extruders(unsigned int number_extruders); void reset_gcode_preview_data(); void set_canvas_as_dirty(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e0e9393855..20a9859283 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -931,6 +931,7 @@ struct Plater::priv Sidebar *sidebar; #if ENABLE_REMOVE_TABS_FROM_PLATER View3D* view3D; + GLRadioToolbar view_toolbar; #else #if !ENABLE_IMGUI wxPanel *panel3d; @@ -1031,6 +1032,9 @@ struct Plater::priv private: bool init_object_menu(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + void init_view_toolbar(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER bool can_delete_object() const; bool can_increase_instances() const; @@ -1201,6 +1205,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); + view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); #else // 3DScene events: canvas3Dwidget->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); @@ -1230,9 +1235,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); +#if ENABLE_REMOVE_TABS_FROM_PLATER + q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); + q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER // Drop target: q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership @@ -2462,6 +2474,35 @@ bool Plater::priv::init_object_menu() return true; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Plater::priv::init_view_toolbar() +{ + if (!view_toolbar.init("view_toolbar.png", 36, 1, 1)) + return; + + GLRadioToolbarItem::Data item; + + item.name = "3d"; + item.tooltip = GUI::L_str("3D editor view"); + item.sprite_id = 0; + item.action_event = EVT_GLVIEWTOOLBAR_3D; + if (!view_toolbar.add_item(item)) + return; + + item.name = "preview"; + item.tooltip = GUI::L_str("Preview"); + item.sprite_id = 1; + item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; + if (!view_toolbar.add_item(item)) + return; + + view3D->set_view_toolbar(&view_toolbar); + preview->set_view_toolbar(&view_toolbar); + + view_toolbar.set_selection("3d"); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + bool Plater::priv::can_delete_object() const { int obj_idx = get_selected_object_idx();