Make the Design tab sidebar dockable and collapsible like Prepare's

The sidebar can move to either side, float, be resized, and collapse with
the canvas button or Shift+Tab. Its layout is remembered separately from
Prepare's, starts where Prepare's sidebar is, and View > Reset Window
Layout resets both tabs.
This commit is contained in:
SoftFever
2026-10-03 19:05:53 +08:00
parent a9c8721380
commit d47fb804c3
17 changed files with 420 additions and 164 deletions
+2
View File
@@ -147,6 +147,8 @@ set(SLIC3R_GUI_SOURCES
GUI/WebPanel.hpp
GUI/Widgets/WebHosting.cpp
GUI/Widgets/WebHosting.hpp
GUI/AuiMgr.cpp
GUI/AuiMgr.hpp
GUI/AuiPaneLayout.cpp
GUI/AuiPaneLayout.hpp
GUI/DragCanvas.cpp
+80
View File
@@ -0,0 +1,80 @@
#include "AuiMgr.hpp"
#include "GUI_App.hpp"
#ifdef __WXGTK__
#include "LinuxDisplayBackend.hpp"
#endif
#include <wx/aui/dockart.h>
#include <wx/aui/floatpane.h>
namespace Slic3r { namespace GUI {
namespace {
// TODO: listen on dark ui change
class FloatFrame : public wxAuiFloatingFrame
{
public:
FloatFrame(wxWindow* parent, wxAuiManager* ownerMgr, const wxAuiPaneInfo& pane) : wxAuiFloatingFrame(parent, ownerMgr, pane)
{
wxGetApp().UpdateFrameDarkUI(this);
}
};
} // namespace
void AuiMgr::init(wxWindow* window)
{
SetManagedWindow(window);
SetDockSizeConstraint(1, 1);
#ifdef __WXGTK__
// Floating and its docking hints need global pointer positions and window moves, which Wayland does not provide.
if (is_running_on_wayland())
SetFlags(GetFlags() & ~wxAUI_MGR_ALLOW_FLOATING);
#endif
GetArtProvider()->SetMetric(wxAUI_DOCKART_CAPTION_SIZE, 18);
GetArtProvider()->SetMetric(wxAUI_DOCKART_GRADIENT_TYPE, wxAUI_GRADIENT_NONE);
apply_color_mode();
}
void AuiMgr::apply_color_mode()
{
const bool is_dark = wxGetApp().dark_mode();
const wxColour sash_color = is_dark ? wxColour(38, 46, 48) : wxColour(206, 206, 206);
GetArtProvider()->SetColour(wxAUI_DOCKART_INACTIVE_CAPTION_COLOUR, sash_color);
GetArtProvider()->SetColour(wxAUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, *wxWHITE);
GetArtProvider()->SetColour(wxAUI_DOCKART_SASH_COLOUR, sash_color);
GetArtProvider()->SetColour(wxAUI_DOCKART_BORDER_COLOUR, is_dark ? *wxBLACK : wxColour(165, 165, 165));
}
void AuiMgr::track_docked_size(wxWindow* window)
{
window->Bind(wxEVT_IDLE, [this, window](wxIdleEvent& evt) {
wxAuiPaneInfo& pane = GetPane(window);
if (pane.IsOk() && pane.IsShown() && pane.IsDocked() && pane.rect.GetWidth() > 0 && pane.rect.GetHeight() > 0) {
const bool horizontal = pane.dock_direction == wxAUI_DOCK_TOP || pane.dock_direction == wxAUI_DOCK_BOTTOM;
pane.BestSize(horizontal ? pane.best_size.GetWidth() : pane.rect.GetWidth(),
horizontal ? pane.rect.GetHeight() : pane.best_size.GetHeight());
}
evt.Skip();
});
}
wxAuiPaneInfo AuiMgr::sidebar_pane_info()
{
return wxAuiPaneInfo()
.Name("sidebar")
.Left()
.CloseButton(false)
.TopDockable(false)
.BottomDockable(false)
.BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit()));
}
wxAuiFloatingFrame* AuiMgr::CreateFloatingFrame(wxWindow* parent, const wxAuiPaneInfo& pane)
{
return new FloatFrame(parent, this, pane);
}
}} // namespace Slic3r::GUI
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <wx/aui/framemanager.h>
namespace Slic3r { namespace GUI {
// The wxAuiManager behind Orca's docked sidebars: Orca's caption and sash art in both themes,
// floating frames that follow the app theme, and no floating on Wayland.
class AuiMgr : public wxAuiManager
{
public:
// Manages `window`, whose own wxEVT_SIZE handlers must Skip().
void init(wxWindow* window);
// Captions, sashes and borders in the current theme.
void apply_color_mode();
// Keeps the docked size of `window`'s pane in its best_size, which re-docking and a saved layout
// use: wxAUI does not record a dragged sash there.
void track_docked_size(wxWindow* window);
// A tab's sidebar pane: docked left by default, movable to the right or floating, no close button.
static wxAuiPaneInfo sidebar_pane_info();
wxAuiFloatingFrame* CreateFloatingFrame(wxWindow* parent, const wxAuiPaneInfo& pane) override;
};
}} // namespace Slic3r::GUI
+15
View File
@@ -570,6 +570,21 @@ void DesignCanvas::set_show_bed(bool b)
request_repaint();
}
void DesignCanvas::set_sidebar_collapse(std::function<CollapseSide()> side, std::function<void()> toggle)
{
if (!m_canvas || !m_canvas_widget || !setup_collapse_toolbar(m_collapse_toolbar, toggle))
return;
m_canvas->set_collapse_toolbar(&m_collapse_toolbar, std::move(side));
m_canvas->enable_collapse_toolbar(true);
// Shift+Tab: the canvas posts this for its sidebar, as Prepare's canvases do.
m_canvas_widget->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [toggle](SimpleEvent&) { toggle(); });
}
void DesignCanvas::set_sidebar_collapse_tooltip(const std::string& tooltip)
{
m_collapse_toolbar.set_tooltip(m_collapse_toolbar.get_item_id("collapse_sidebar"), tooltip);
}
bool DesignCanvas::is_sketching() const { return m_sketch_tool.is_active(); }
void DesignCanvas::cancel_sketch()
+6
View File
@@ -17,6 +17,7 @@
#include "slic3r/GUI/3DBed.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/GLToolbar.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/CAD/SketchEngine.hpp"
#include "slic3r/GUI/CAD/DesignSketchTool.hpp"
@@ -87,6 +88,10 @@ public:
void unbind_canvas_event_handlers(); // app close / language switch, from the plater's teardown
void reset_canvas_volumes();
void set_show_bed(bool b); // view option: draw the printer bed + plate grid, or not
// The sidebar's collapse button, as Prepare's: drawn on the edge `side` reports, it runs
// `toggle` on a click and on Shift+Tab. Set before the first paint.
void set_sidebar_collapse(std::function<CollapseSide()> side, std::function<void()> toggle);
void set_sidebar_collapse_tooltip(const std::string& tooltip);
// N: look straight down the sketch plane's normal, keeping the current zoom. A sketch drawn
// at an angle is a sketch drawn wrong, and no amount of orbiting by hand lands exactly square.
bool view_normal_to_sketch();
@@ -368,6 +373,7 @@ private:
wxGLCanvas* m_canvas_widget{nullptr};
GLCanvas3D* m_canvas{nullptr};
int m_sw_gl{-1}; // -1 unknown, 0 hardware GL, 1 software GL
GLToolbar m_collapse_toolbar{GLToolbar::Normal, "Collapse"};
std::function<void(const wxPoint&)> m_on_context_menu;
bool m_ctx_bound{false}; // bind the RIGHT_UP handler once, however often the cb is set
+109 -21
View File
@@ -104,6 +104,7 @@
#include "libslic3r/BuildVolume.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
// English-only pin for the Design tab (see design-ux-contract): one lever
// de-translates this whole TU so our strings never half-translate against the host's
@@ -357,10 +358,12 @@ DesignPanel::DesignPanel(wxWindow* parent)
// Left column: a slim feature-tree + docked tool-dialog column. All form
// controls are parented to m_form so it can scroll independently of the
// live GL viewport. The tool buttons live in the top toolbar (built below).
m_form = new wxScrolledWindow(this, wxID_ANY);
auto* body_panel = new wxPanel(this, wxID_ANY); // below the toolbar, managed by m_aui
m_form = new wxScrolledWindow(body_panel, wxID_ANY);
// Explicit token background in both themes, so a theme switch can move it (an inherited
// colour stays whatever the theme was when the panel was built).
SetBackgroundColour(dp_panel_bg());
body_panel->SetBackgroundColour(dp_panel_bg());
m_form->SetBackgroundColour(dp_panel_bg());
auto* root = new wxBoxSizer(wxVERTICAL);
@@ -3521,7 +3524,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
build_phase("sidebar and tool cards");
// Right column: a small view toolbar over the live 3D viewport that mirrors
// the CadDocument body.
m_viewport = new DesignCanvas(this);
auto* view_col = new wxPanel(body_panel, wxID_ANY); // the centre pane: sketch banner over the viewport
view_col->SetBackgroundColour(dp_panel_bg());
m_viewport = new DesignCanvas(view_col);
build_phase("3D canvas");
m_viewport->set_on_sketch_commit([this](const SketchProfile& prof, const SketchPlane& plane) {
@@ -4339,7 +4344,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
// The sketch banner sits ABOVE the viewport rather than floating inside it: a child window
// over a wxGLCanvas is a platform argument (it is a native window on GTK and does not
// reliably stack over GL), and the banner's job is to be unmissable, not to be clever.
m_sketch_banner = new wxPanel(this, wxID_ANY);
m_sketch_banner = new wxPanel(view_col, wxID_ANY);
m_sketch_banner->SetBackgroundColour(wxColour(0, 122, 116)); // Orca teal: not a plate colour
m_sketch_banner_txt = new wxStaticText(m_sketch_banner, wxID_ANY, wxString());
m_sketch_banner_txt->SetForegroundColour(*wxWHITE);
@@ -4356,16 +4361,33 @@ DesignPanel::DesignPanel(wxWindow* parent)
// The bottom 3D-navigator orb handles all view orientation, so no separate view buttons.
// Fit view is a double-click on the viewport (the tool intercepts it -> zoom_to_volumes).
vcol->Add(m_viewport, 1, wxEXPAND);
view_col->SetSizer(vcol);
// Onshape layout: top toolbar over [ slim left column | center viewport ].
auto* body = new wxBoxSizer(wxHORIZONTAL);
body->Add(m_form, 0, wxEXPAND);
body->Add(vcol, 1, wxEXPAND);
// Onshape layout: top toolbar over [ slim left column | center viewport ], with the column
// docked like Prepare's sidebar: left by default, movable to the right or floating, resizable
// and collapsible.
m_aui.init(body_panel);
m_aui.AddPane(m_form, AuiMgr::sidebar_pane_info().MinSize(m_form->GetMinSize()));
m_aui.AddPane(view_col, wxAuiPaneInfo().Name("main").CenterPane().PaneBorder(false));
m_default_layout = m_aui.SavePerspective();
load_window_layout();
m_aui.track_docked_size(m_form);
// The collapse button sits on the canvas edge the sidebar is docked on, and is gone while it
// floats. A click arrives inside the canvas's mouse handler, so the relayout waits for it.
m_viewport->set_sidebar_collapse(
[this] {
const wxAuiPaneInfo& sidebar = m_aui.GetPane(m_form);
if (!sidebar.IsOk() || sidebar.IsFloating())
return CollapseSide::None;
return sidebar.dock_direction == wxAUI_DOCK_RIGHT ? CollapseSide::Right : CollapseSide::Left;
},
[this] { CallAfter([this] { collapse_sidebar(!m_sidebar_collapsed); }); });
collapse_sidebar(m_sidebar_collapsed); // the button's tooltip
auto* outer = new wxBoxSizer(wxVERTICAL);
outer->Add(m_toolbar, 0, wxEXPAND);
outer->Add(new wxStaticLine(this, wxID_ANY), 0, wxEXPAND);
outer->Add(body, 1, wxEXPAND);
outer->Add(body_panel, 1, wxEXPAND);
SetSizer(outer);
// The atlas says a verb is wired; the registrations above say what it runs. Nothing checks
@@ -7215,6 +7237,7 @@ void DesignPanel::on_tab_hidden()
{
if (m_viewport)
m_viewport->leave_viewport(); // hand the shared camera back to the editor tabs
update_sidebar_pane();
}
void DesignPanel::on_tab_shown()
@@ -7232,7 +7255,7 @@ void DesignPanel::on_tab_shown()
update_reference_planes(); // entering the Design tab: show the XY/XZ/YZ planes if no object yet
if (show_clock.Time() > 100) // a slow first show is what users report; the usual one is not news
BOOST_LOG_TRIVIAL(info) << "Design tab shown: bed, project recipe and planes in " << show_clock.Time() << " ms";
sync_sidebar_width(); // keep the panel as wide as Prepare's so the canvas edge doesn't jump
update_sidebar_pane();
if (m_viewport) m_viewport->force_repaint(); // the page was just re-shown: paint it for real
}
@@ -7267,6 +7290,7 @@ void DesignPanel::msw_rescale()
void DesignPanel::on_sys_color_changed()
{
m_aui.apply_color_mode();
// Every chrome colour here was set from a DpToken in the theme that was current at the time.
// Move each one, background and text, onto the same token in the new theme; any other colour
// (the teal accents, the status colours) is the same in both and stays.
@@ -7325,19 +7349,83 @@ void DesignPanel::reset_canvas_volumes()
if (m_viewport) m_viewport->reset_canvas_volumes();
}
// Match Prepare's sidebar width instead of hardcoding one. Design used a fixed 264 px against
// Prepare's ~467, so the canvas edge jumped sideways on every tab switch; reading the live width
// also means the two stay aligned if Orca ever changes its sidebar.
void DesignPanel::sync_sidebar_width()
void DesignPanel::shutdown()
{
if (m_form == nullptr) return;
Plater* pl = wxGetApp().plater();
if (pl == nullptr) return;
const int w = pl->sidebar().GetSize().GetWidth();
if (w < 200) return; // sidebar not laid out yet — keep what we have
if (m_form->GetMinSize().GetWidth() == w) return;
m_form->SetMinSize(wxSize(w, -1));
Layout();
wxAuiPaneInfo& sidebar = m_aui.GetPane(m_form);
if (!sidebar.IsOk()) // shut down already, floating: a layout saved now would lack the sidebar
return;
// Saved hidden only when collapsed, whichever tab is shown.
sidebar.Show(!m_sidebar_collapsed);
wxGetApp().app_config->set("design_window_layout", m_aui.SavePerspective().utf8_string());
// A floating frame is a child of the managed panel and dereferences its manager when it is
// deleted, so it must not outlive m_aui, a member. Detaching queues the frame for deletion, which
// runs before the main frame destroys its children (~wxTopLevelWindowBase deletes pending child
// frames).
if (sidebar.IsFloating())
m_aui.DetachPane(m_form);
}
void DesignPanel::reset_window_layout()
{
load_default_layout();
collapse_sidebar(false);
update_sidebar_pane(true);
}
// The sidebar where the user left it, collapsed if it was.
void DesignPanel::load_window_layout()
{
const wxString saved = wxString::FromUTF8(wxGetApp().app_config->get("design_window_layout"));
if (saved.empty() || !m_aui.LoadPerspective(saved, false)) {
if (!saved.empty())
BOOST_LOG_TRIVIAL(warning) << "Design tab: failed to restore the saved window layout";
load_default_layout();
}
wxAuiPaneInfo& sidebar = m_aui.GetPane(m_form);
m_sidebar_collapsed = !sidebar.IsShown();
if ((m_aui.GetFlags() & wxAUI_MGR_ALLOW_FLOATING) == 0) // saved where windows can float
sidebar.Dock().Floatable(false);
update_sidebar_pane(true);
}
// Docked where Prepare's sidebar is, and as wide, so the canvas edge does not move between the tabs.
void DesignPanel::load_default_layout()
{
m_aui.LoadPerspective(m_default_layout, false);
Plater* plater = wxGetApp().plater();
const Sidebar::DockingState prepare = plater != nullptr ? plater->get_sidebar_docking_state() : Sidebar::None;
if (prepare == Sidebar::None)
return;
wxAuiPaneInfo& sidebar = m_aui.GetPane(m_form);
if (prepare == Sidebar::Right)
sidebar.Right();
const int width = plater->sidebar().GetSize().GetWidth();
if (width >= sidebar.min_size.GetWidth()) // Prepare's sidebar has been laid out
sidebar.BestSize(width, sidebar.best_size.GetHeight());
}
// The collapse button on the canvas and Shift+Tab, as in Prepare.
void DesignPanel::collapse_sidebar(bool collapse)
{
m_sidebar_collapsed = collapse;
if (m_viewport)
m_viewport->set_sidebar_collapse_tooltip(wxGetApp().shortcuts().with_key(
(collapse ? _L("Expand sidebar") : _L("Collapse sidebar")).utf8_string(), Shortcut::CollapseSidebar));
update_sidebar_pane();
}
// The sidebar shows unless collapsed; a floating one also only while the tab is shown, since it is
// a top-level window and does not hide with the page.
void DesignPanel::update_sidebar_pane(bool force_update)
{
wxAuiPaneInfo& sidebar = m_aui.GetPane(m_form);
const bool show = !m_sidebar_collapsed && (sidebar.IsDocked() || IsShownOnScreen());
if (sidebar.IsOk() && sidebar.IsShown() != show) {
sidebar.Show(show);
force_update = true;
}
if (force_update)
m_aui.Update();
}
void DesignPanel::load_recipe(const std::string& blob)
+14 -1
View File
@@ -21,6 +21,7 @@
#include "libslic3r/CAD/CadDocument.hpp"
#include "slic3r/GUI/CAD/DesignInteraction.hpp" // CadLevel: what one Esc press means
#include "slic3r/GUI/AuiMgr.hpp"
#include "slic3r/GUI/Lazy.hpp"
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
@@ -70,6 +71,8 @@ public:
void on_sys_color_changed();
void unbind_canvas_event_handlers(); // app close / language switch, from the plater's teardown
void reset_canvas_volumes();
void shutdown(); // app close / language switch, from MainFrame: save the window layout
void reset_window_layout(); // View > Reset Window Layout, with Prepare's
void clear_document(); // New Project / Open Project: drop the document with the project
// Rebuild off the UI thread (progress dialog only if it turns out to be slow), so a feature
// op on a heavy imported solid does not freeze the window. Returns m_doc.recompute()'s result.
@@ -497,6 +500,17 @@ private:
wxScrolledWindow* m_form{nullptr};
DesignCanvas* m_viewport{nullptr};
// Below the toolbar, m_form docks beside the viewport column like Prepare's sidebar: it can
// move to the other side, float, be resized and be collapsed, and the layout is kept between
// sessions.
AuiMgr m_aui;
wxString m_default_layout;
bool m_sidebar_collapsed{false};
void load_window_layout();
void load_default_layout();
void collapse_sidebar(bool collapse);
void update_sidebar_pane(bool force_update = false);
// Top contextual toolbar (parented to the panel, above the form/viewport row).
UiMode m_ui_mode{UiMode::Feature};
// Sketch environment banner: a strip across the top of the viewport saying, in words, that
@@ -938,7 +952,6 @@ private:
int tree_selection() const; // selected feature row, or wxNOT_FOUND
int tree_body_selection() const; // selected Parts-list body index, or -1
void refresh_parts(); // rebuild the Bodies list under the feature tree
void sync_sidebar_width(); // keep the panel as wide as Prepare's sidebar
void set_tree_selection(int row);
static int tree_icon_for(CadFeatureType t);
+62 -28
View File
@@ -1440,7 +1440,7 @@ void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) {
// set dirty to re-generate icon texture
m_separator_toolbar.set_icon_dirty();
m_main_toolbar.set_icon_dirty();
wxGetApp().plater()->get_collapse_toolbar().set_icon_dirty();
collapse_toolbar().set_icon_dirty();
m_assemble_view_toolbar.set_icon_dirty();
m_gizmos.set_icon_dirty();
}
@@ -1972,6 +1972,12 @@ void GLCanvas3D::enable_collapse_toolbar(bool enable)
m_collapse_toolbar_enabled = enable;
}
void GLCanvas3D::set_collapse_toolbar(GLToolbar* toolbar, std::function<CollapseSide()> side)
{
m_collapse_toolbar = toolbar;
m_collapse_side = std::move(side);
}
void GLCanvas3D::enable_plate_chrome(bool enable)
{
m_plate_chrome_enabled = enable;
@@ -2052,24 +2058,33 @@ void GLCanvas3D::update_volumes_colors_by_extruder()
bool GLCanvas3D::is_collapse_toolbar_on_left() const
{
auto state = wxGetApp().plater()->get_sidebar_docking_state();
return state == Sidebar::Left;
return collapse_side() == CollapseSide::Left;
}
float GLCanvas3D::get_collapse_toolbar_width() const
{
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
const auto state = wxGetApp().plater()->get_sidebar_docking_state();
return state != Sidebar::None ? collapse_toolbar.get_width() : 0;
return collapse_side() != CollapseSide::None ? collapse_toolbar().get_width() : 0;
}
float GLCanvas3D::get_collapse_toolbar_height() const
{
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
const auto state = wxGetApp().plater()->get_sidebar_docking_state();
return collapse_side() != CollapseSide::None ? collapse_toolbar().get_height() : 0;
}
return state != Sidebar::None ? collapse_toolbar.get_height() : 0;
GLToolbar& GLCanvas3D::collapse_toolbar() const
{
return m_collapse_toolbar != nullptr ? *m_collapse_toolbar : wxGetApp().plater()->get_collapse_toolbar();
}
CollapseSide GLCanvas3D::collapse_side() const
{
if (m_collapse_side)
return m_collapse_side();
switch (wxGetApp().plater()->get_sidebar_docking_state()) {
case Sidebar::Left: return CollapseSide::Left;
case Sidebar::Right: return CollapseSide::Right;
default: return CollapseSide::None;
}
}
bool GLCanvas3D::make_current_for_postinit() {
@@ -2274,7 +2289,7 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
tooltip = m_assemble_view_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip();
tooltip = collapse_toolbar().get_tooltip();
// BBS
#if 0
@@ -3475,7 +3490,7 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_overlay_dirty |= m_assemble_view_toolbar.update_items_state();
// BBS
//m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
m_overlay_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
m_overlay_dirty |= collapse_toolbar().update_items_state();
// apply() DRAINS the 3D-mouse queue, so only the canvas actually on screen may call it: a
// hidden canvas renders nothing, so the motion it swallowed moves the shared camera without
// ever being drawn and the next visible frame jumps several states at once.
@@ -4267,6 +4282,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// (drag/orbit/wheel) so the camera keeps working over the display-only plate.
#ifdef SLIC3R_CAD
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display()) {
// Except over the sidebar collapse button, which is drawn over the sketch.
if (collapse_toolbar().on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
return;
}
if (evt.LeftDown() && m_canvas != nullptr)
m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas
if (m_design_sketch_tool->on_mouse(evt, *this)) {
@@ -4315,7 +4337,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return;
}
if (!mouse_in_layer_editing && wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) {
if (!mouse_in_layer_editing && collapse_toolbar().on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
@@ -6892,7 +6914,7 @@ void GLCanvas3D::_switch_toolbars_icon_filename()
m_main_toolbar.init(background_data);
m_assemble_view_toolbar.init(background_data);
m_separator_toolbar.init(background_data);
wxGetApp().plater()->get_collapse_toolbar().init(background_data);
collapse_toolbar().init(background_data);
// main toolbar
{
@@ -7320,7 +7342,17 @@ bool GLCanvas3D::_init_view_toolbar()
bool GLCanvas3D::_init_collapse_toolbar()
{
return wxGetApp().plater()->init_collapse_toolbar();
if (m_collapse_toolbar == nullptr)
return wxGetApp().plater()->init_collapse_toolbar();
// Its owner laid it out; the background needs the GL context.
BackgroundTexture::Metadata background_data;
background_data.filename = m_is_dark ? "toolbar_background_dark.png" : "toolbar_background.png";
background_data.left = 16;
background_data.top = 16;
background_data.right = 16;
background_data.bottom = 16;
return m_collapse_toolbar->init(background_data);
}
bool GLCanvas3D::_set_current()
@@ -9085,11 +9117,12 @@ void GLCanvas3D::_render_sequential_clearance()
void GLCanvas3D::_check_and_update_toolbar_icon_scale()
{
// Update collapse toolbar
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
collapse_toolbar.set_enabled(wxGetApp().plater()->get_sidebar_docking_state() != Sidebar::None);
GLToolbar& collapse_toolbar = this->collapse_toolbar();
collapse_toolbar.set_enabled(collapse_side() != CollapseSide::None);
// Don't update a toolbar scale, when we are on a Preview
if (wxGetApp().plater()->is_preview_shown()) {
// Don't update a toolbar scale, when we are on a Preview. Asked of this canvas, not of the
// plater: the Design tab's canvas is on screen while the plater's current view may be Preview.
if (m_canvas_type == CanvasPreview) {
IMSlider *m_layers_slider = get_gcode_viewer().get_layers_slider();
IMSlider *m_moves_slider = get_gcode_viewer().get_moves_slider();
float sc = get_scale();
@@ -9161,7 +9194,9 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
// set minimum scale as a auto scale for the toolbars
float new_scale = std::min(new_h_scale, new_v_scale);
new_scale /= get_scale();
if (fabs(new_scale - scale) > 0.05) // scale is changed by 5% and more
// Only a canvas with toolbars to fit sets the shared size: a collapse button alone (the Design
// tab's canvas) always fits at the largest.
if (items_cnt > collapse_toolbar.get_visible_items_cnt() && fabs(new_scale - scale) > 0.05) // scale is changed by 5% and more
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
}
@@ -9243,7 +9278,7 @@ size_t GLCanvas3D::_overlay_signature(const ImDrawData* draw_data) const
size_t hash = ImGuiWrapper::draw_data_signature(draw_data);
for (size_t state_hash : { m_main_toolbar.get_state_hash(), m_separator_toolbar.get_state_hash(),
m_assemble_view_toolbar.get_state_hash(),
wxGetApp().plater()->get_collapse_toolbar().get_state_hash(),
collapse_toolbar().get_state_hash(),
m_gizmos.get_overlay_state_hash() })
boost::hash_combine(hash, state_hash);
return hash;
@@ -10298,18 +10333,17 @@ void GLCanvas3D::_render_collapse_toolbar() const
if (!m_collapse_toolbar_enabled)
return;
auto& plater = *wxGetApp().plater();
const auto sidebar_docking_dir = plater.get_sidebar_docking_state();
if (sidebar_docking_dir == Sidebar::None) {
const CollapseSide side = collapse_side();
if (side == CollapseSide::None) {
return;
}
GLToolbar& collapse_toolbar = plater.get_collapse_toolbar();
GLToolbar& collapse_toolbar = this->collapse_toolbar();
const Size cnv_size = get_canvas_size();
const float top = 0.5f * (float)cnv_size.get_height();
const float left = sidebar_docking_dir == Sidebar::Right ? 0.5f * (float) cnv_size.get_width() - (float) collapse_toolbar.get_width() :
-0.5f * (float) cnv_size.get_width();
const float left = side == CollapseSide::Right ? 0.5f * (float) cnv_size.get_width() - (float) collapse_toolbar.get_width() :
-0.5f * (float) cnv_size.get_width();
collapse_toolbar.set_position(top, left);
collapse_toolbar.render(*this);
@@ -11705,7 +11739,7 @@ bool GLCanvas3D::_deactivate_layersediting_menu()
bool GLCanvas3D::_deactivate_collapse_toolbar_items()
{
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
GLToolbar& collapse_toolbar = this->collapse_toolbar();
if (collapse_toolbar.is_item_pressed("print")) {
collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this);
return true;
+10
View File
@@ -20,6 +20,7 @@
#include "libslic3r/Line.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
#include <stddef.h>
#include <functional>
#include <memory>
#include <chrono>
#include <cstdint>
@@ -594,6 +595,9 @@ private:
mutable float m_sc{1};
mutable float m_paint_toolbar_width;
bool m_collapse_toolbar_enabled{true};
// The collapse button of a sidebar other than Prepare's, from set_collapse_toolbar().
GLToolbar* m_collapse_toolbar{nullptr};
std::function<CollapseSide()> m_collapse_side;
bool m_plate_chrome_enabled{true};
// Design tab: render the world-axis triad at the bed centre (= modeling origin) instead of
// the bed corner. Default false preserves the main editor's corner triad.
@@ -1008,6 +1012,10 @@ public:
void enable_return_toolbar(bool enable);
void enable_separator_toolbar(bool enable);
void enable_collapse_toolbar(bool enable);
// A canvas beside a sidebar other than Prepare's shows that sidebar's collapse button: `toolbar`,
// set up with setup_collapse_toolbar(), on the edge `side` reports. Call before the canvas is
// initialized, which loads the toolbar's background.
void set_collapse_toolbar(GLToolbar* toolbar, std::function<CollapseSide()> side);
void enable_plate_chrome(bool enable);
void set_axes_at_bed_center(bool b) { m_axes_at_bed_center = b; }
void set_show_bed(bool b) { m_show_bed = b; }
@@ -1374,6 +1382,8 @@ private:
// BBS
//bool _init_view_toolbar();
bool _init_collapse_toolbar();
GLToolbar& collapse_toolbar() const;
CollapseSide collapse_side() const;
bool _set_current();
bool _set_shown_canvas_current();
+22
View File
@@ -1672,5 +1672,27 @@ float GLToolbar::get_scaled_icon_size()
return m_layout.icons_size * m_layout.scale;
}
bool setup_collapse_toolbar(GLToolbar& toolbar, std::function<void()> toggle)
{
toolbar.set_layout_type(GLToolbar::Layout::Vertical);
toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
toolbar.set_border(4.0f);
toolbar.set_separator_size(4);
toolbar.set_gap_size(2);
toolbar.del_all_item();
GLToolbarItem::Data item;
item.name = "collapse_sidebar";
// set collapse svg name
item.icon_filename = "collapse.svg";
item.sprite_id = 0;
item.left.action_callback = std::move(toggle);
return toolbar.add_item(item);
}
} // namespace GUI
} // namespace Slic3r
+8
View File
@@ -457,6 +457,14 @@ private:
bool update_items_enabled_state();
};
// The canvas edge a sidebar collapse button sits on: the side its sidebar is docked on, or none
// while that sidebar floats.
enum class CollapseSide { None, Left, Right };
// Lays `toolbar` out as a sidebar collapse button that runs `toggle`: Prepare's, and those of
// other sidebars (GLCanvas3D::set_collapse_toolbar()).
bool setup_collapse_toolbar(GLToolbar& toolbar, std::function<void()> toggle);
} // namespace GUI
} // namespace Slic3r
+8
View File
@@ -1249,6 +1249,10 @@ void MainFrame::shutdown()
m_plugin_pages.shutdown();
if (m_plater != nullptr)
m_plater->remove_dock_panes();
#ifdef SLIC3R_CAD
if (DesignPanel* design = DesignPanel::if_built())
design->shutdown();
#endif
#ifdef __WXGTK__
// Edge panels are child windows — wxWidgets destroys them automatically.
m_edge_bottom = nullptr;
@@ -3377,6 +3381,10 @@ void MainFrame::init_menubar_as_editor()
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
[this]() {
#ifdef SLIC3R_CAD
if (shown_design_panel() != nullptr)
return true;
#endif
return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled();
},
this);
+20 -101
View File
@@ -191,6 +191,7 @@
#ifdef __WXGTK__
#include "LinuxDisplayBackend.hpp"
#endif
#include "AuiMgr.hpp"
#include "AuiPaneLayout.hpp"
#include "GUI_Utils.hpp"
#include "GUI_Factories.hpp"
@@ -7111,28 +7112,6 @@ enum ExportingStatus{
EXPORTING_TO_LOCAL
};
// TODO: listen on dark ui change
class FloatFrame : public wxAuiFloatingFrame
{
public:
FloatFrame(wxWindow* parent, wxAuiManager* ownerMgr, const wxAuiPaneInfo& pane) : wxAuiFloatingFrame(parent, ownerMgr, pane)
{
wxGetApp().UpdateFrameDarkUI(this);
}
};
class AuiMgr : public wxAuiManager
{
public:
AuiMgr() : wxAuiManager(){}
virtual wxAuiFloatingFrame* CreateFloatingFrame(wxWindow* parent, const wxAuiPaneInfo& p) override
{
return new FloatFrame(parent, this, p);
}
};
// Plater / private
struct Plater::priv
{
@@ -7616,7 +7595,6 @@ struct Plater::priv
//BBS: change dark/light mode
void on_change_color_mode(SimpleEvent& evt);
void on_apple_change_color_mode(wxSysColourChangedEvent& evt);
void apply_color_mode();
void on_update_geometry(Vec3dsEvent<2>&);
void on_3dcanvas_mouse_dragging_started(SimpleEvent&);
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
@@ -7829,20 +7807,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
{
m_is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";
#ifdef __WXGTK__
const bool disable_wayland_floating = Slic3r::GUI::is_running_on_wayland();
#endif
m_aui_mgr.SetManagedWindow(q);
m_aui_mgr.SetDockSizeConstraint(1, 1);
#ifdef __WXGTK__
if (disable_wayland_floating)
m_aui_mgr.SetFlags(m_aui_mgr.GetFlags() & ~wxAUI_MGR_ALLOW_FLOATING);
#endif
//m_aui_mgr.GetArtProvider()->SetMetric(wxAUI_DOCKART_PANE_BORDER_SIZE, 0);
//m_aui_mgr.GetArtProvider()->SetMetric(wxAUI_DOCKART_SASH_SIZE, 2);
m_aui_mgr.GetArtProvider()->SetMetric(wxAUI_DOCKART_CAPTION_SIZE, 18);
m_aui_mgr.GetArtProvider()->SetMetric(wxAUI_DOCKART_GRADIENT_TYPE, wxAUI_GRADIENT_NONE);
m_aui_mgr.init(q);
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
@@ -7925,13 +7890,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
update();
// Orca: Make sidebar dockable
m_aui_mgr.AddPane(sidebar, wxAuiPaneInfo()
.Name("sidebar")
.Left()
.CloseButton(false)
.TopDockable(false)
.BottomDockable(false)
.BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit())));
m_aui_mgr.AddPane(sidebar, AuiMgr::sidebar_pane_info());
// UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview - a resizable/dockable
// sibling of "sidebar"/"main" like everything else registered on this same AUI manager, not a
@@ -7983,8 +7942,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
if (!layout.empty()) {
bool removed_floating_state = false;
#ifdef __WXGTK__
if (disable_wayland_floating)
layout = sanitize_window_layout_for_wayland(layout, &removed_floating_state);
layout = sanitize_window_layout_for_wayland(layout, &removed_floating_state);
#endif
if (!m_aui_mgr.LoadPerspective(layout, false)) {
@@ -8006,12 +7964,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// Keep tracking the current sidebar size, by storing it using `best_size`, which will be stored
// in the config and re-applied when the app is opened again.
this->sidebar->Bind(wxEVT_IDLE, [&sidebar](wxIdleEvent& e) {
if (sidebar.IsShown() && sidebar.IsDocked() && sidebar.rect.GetWidth() > 0) {
sidebar.BestSize(sidebar.rect.GetWidth(), sidebar.best_size.GetHeight());
}
e.Skip();
});
m_aui_mgr.track_docked_size(this->sidebar);
// Hide sidebar initially, will re-show it after initialization when we got proper window size
sidebar.Hide();
@@ -8236,8 +8189,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
q->SetDropTarget(new PlaterDropTarget(*main_frame, *q)); // if my understanding is right, wxWindow takes the owenership
q->Layout();
apply_color_mode();
set_current_panel(wxGetApp().is_editor() ? static_cast<wxPanel*>(view3D) : static_cast<wxPanel*>(preview));
// updates camera type from .ini file
@@ -8716,18 +8667,7 @@ void Plater::priv::add_dock_pane(wxWindow* window, const std::string& name, cons
info.Show(dock_pane_visible(dock_pane, info));
m_aui_mgr.AddPane(window, info);
// wxAUI does not record a dragged sash in best_size, so track the docked size like the sidebar
// does, for the saved layout.
window->Bind(wxEVT_IDLE, [this, window](wxIdleEvent& evt) {
wxAuiPaneInfo& pane = m_aui_mgr.GetPane(window);
if (pane.IsOk() && pane.IsShown() && pane.IsDocked() && pane.rect.GetWidth() > 0 && pane.rect.GetHeight() > 0) {
const bool horizontal = pane.dock_direction == wxAUI_DOCK_TOP || pane.dock_direction == wxAUI_DOCK_BOTTOM;
pane.BestSize(horizontal ? pane.best_size.GetWidth() : pane.rect.GetWidth(),
horizontal ? pane.rect.GetHeight() : pane.best_size.GetHeight());
}
evt.Skip();
});
m_aui_mgr.track_docked_size(window);
m_aui_mgr.Update();
}
@@ -13908,7 +13848,7 @@ void Plater::priv::on_apple_change_color_mode(wxSysColourChangedEvent& evt) {
assemble_view->get_canvas3d()->on_change_color_mode(m_is_dark);
}
apply_color_mode();
m_aui_mgr.apply_color_mode();
}
void Plater::priv::on_change_color_mode(SimpleEvent& evt) {
@@ -13919,19 +13859,7 @@ void Plater::priv::on_change_color_mode(SimpleEvent& evt) {
assemble_view->get_canvas3d()->on_change_color_mode(m_is_dark);
if (m_send_to_sdcard_dlg) m_send_to_sdcard_dlg->on_change_color_mode();
apply_color_mode();
}
void Plater::priv::apply_color_mode()
{
const bool is_dark = wxGetApp().dark_mode();
wxColour orca_color = wxColour(59, 68, 70);//wxColour(ColorRGBA::ORCA().r_uchar(), ColorRGBA::ORCA().g_uchar(), ColorRGBA::ORCA().b_uchar());
orca_color = is_dark ? StateColor::darkModeColorFor(orca_color) : StateColor::lightModeColorFor(orca_color);
wxColour sash_color = is_dark ? wxColour(38, 46, 48) : wxColour(206, 206, 206);
m_aui_mgr.GetArtProvider()->SetColour(wxAUI_DOCKART_INACTIVE_CAPTION_COLOUR, sash_color);
m_aui_mgr.GetArtProvider()->SetColour(wxAUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, *wxWHITE);
m_aui_mgr.GetArtProvider()->SetColour(wxAUI_DOCKART_SASH_COLOUR, sash_color);
m_aui_mgr.GetArtProvider()->SetColour(wxAUI_DOCKART_BORDER_COLOUR, is_dark ? *wxBLACK : wxColour(165, 165, 165));
m_aui_mgr.apply_color_mode();
}
static void get_position(wxWindowBase* child, wxWindowBase* until_parent, int& x, int& y) {
@@ -14592,26 +14520,9 @@ bool Plater::priv::init_collapse_toolbar()
if (!collapse_toolbar.init(background_data))
return false;
collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
collapse_toolbar.set_border(4.0f);
collapse_toolbar.set_separator_size(4);
collapse_toolbar.set_gap_size(2);
collapse_toolbar.del_all_item();
GLToolbarItem::Data item;
item.name = "collapse_sidebar";
// set collapse svg name
item.icon_filename = "collapse.svg";
item.sprite_id = 0;
item.left.action_callback = []() {
wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed());
};
if (!collapse_toolbar.add_item(item))
if (!setup_collapse_toolbar(collapse_toolbar, []() {
wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed());
}))
return false;
// Now "collapse" sidebar to current state. This is done so the tooltip
@@ -18481,7 +18392,15 @@ bool Plater::is_sidebar_collapsed() const { return p->sidebar_layout.is_collapse
void Plater::collapse_sidebar(bool collapse) { p->collapse_sidebar(collapse); }
Sidebar::DockingState Plater::get_sidebar_docking_state() const { return p->get_sidebar_docking_state(); }
void Plater::reset_window_layout() { p->reset_window_layout(); }
void Plater::reset_window_layout()
{
p->reset_window_layout();
#ifdef SLIC3R_CAD
// The Design tab docks its own sidebar.
if (DesignPanel* design = DesignPanel::if_built())
design->reset_window_layout();
#endif
}
void Plater::add_dock_pane(wxWindow* window, const std::string& name, const wxString& caption, const std::string& dock,
const wxSize& size, std::function<void()> on_close)