mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
feat: support tab icons
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
#include <utility>
|
||||
@@ -204,6 +206,11 @@ void PluginPages::initialize(Notebook* parent)
|
||||
if (m_parent == nullptr)
|
||||
return;
|
||||
|
||||
// Keep image-list indices stable for the lifetime of this notebook. Removing an image
|
||||
// would shift every later index, so deregistration only removes the page.
|
||||
m_image_list = std::make_unique<wxImageList>(20, 20, true, 0);
|
||||
m_parent->SetImageList(m_image_list.get());
|
||||
|
||||
for (const auto& capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::Pages)) {
|
||||
if (capability)
|
||||
on_cap_register(capability->identity());
|
||||
@@ -214,6 +221,9 @@ void PluginPages::shutdown()
|
||||
{
|
||||
while (!m_pages.empty())
|
||||
remove_page(m_pages.begin()->first);
|
||||
if (m_parent != nullptr)
|
||||
m_parent->SetImageList(nullptr);
|
||||
m_image_list.reset();
|
||||
m_parent = nullptr;
|
||||
}
|
||||
|
||||
@@ -235,6 +245,15 @@ void PluginPages::on_cap_register(const PluginCapabilityId& id)
|
||||
if (!capability)
|
||||
return;
|
||||
|
||||
std::string icon;
|
||||
try {
|
||||
icon = capability->get_icon();
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key << ": " << error.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
||||
}
|
||||
|
||||
auto* page = new PluginPage(m_parent, std::move(capability));
|
||||
if (!page->is_valid()) {
|
||||
page->Destroy();
|
||||
@@ -242,8 +261,29 @@ void PluginPages::on_cap_register(const PluginCapabilityId& id)
|
||||
}
|
||||
|
||||
const wxString title = wxString::FromUTF8(id.name);
|
||||
const wxString page_id = wxString::FromUTF8("plugin." + id.plugin_key + "." + id.name);
|
||||
if (!m_parent->AddPage(page_id, page, title, "tab_auxiliary_active", "tab_auxiliary_active", false)) {
|
||||
|
||||
int image_id = wxBookCtrlBase::NO_IMAGE;
|
||||
if (!icon.empty() && m_image_list) {
|
||||
try {
|
||||
boost::filesystem::path icon_path(icon);
|
||||
const std::string extension = icon_path.extension().string();
|
||||
if (extension == ".svg" || extension == ".png")
|
||||
icon_path.replace_extension();
|
||||
|
||||
const wxBitmap bitmap = create_scaled_bitmap(icon_path.string(), m_parent, 20);
|
||||
if (bitmap.IsOk())
|
||||
image_id = m_image_list->Add(bitmap);
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key;
|
||||
}
|
||||
}
|
||||
|
||||
page->set_icon_image_id(image_id);
|
||||
if (!m_parent->AddPage(page, title, false, image_id)) {
|
||||
if (image_id != wxBookCtrlBase::NO_IMAGE && m_image_list && image_id == m_image_list->GetImageCount() - 1)
|
||||
m_image_list->Remove(image_id);
|
||||
page->Destroy();
|
||||
return;
|
||||
}
|
||||
@@ -285,12 +325,39 @@ void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||
return;
|
||||
|
||||
PluginPage* page = it->second;
|
||||
const int removed_image_id = page->get_icon_image_id();
|
||||
page->detach_capability();
|
||||
if (m_parent != nullptr) {
|
||||
const int index = m_parent->FindPage(page);
|
||||
if (index != wxNOT_FOUND)
|
||||
m_parent->RemovePage(static_cast<size_t>(index));
|
||||
}
|
||||
|
||||
if (m_image_list && removed_image_id != wxBookCtrlBase::NO_IMAGE &&
|
||||
removed_image_id >= 0 && removed_image_id < m_image_list->GetImageCount()) {
|
||||
m_image_list->Remove(removed_image_id);
|
||||
|
||||
// wxImageList IDs are positional. Removing one shifts all later images down by
|
||||
// one, so update both the page state and the notebook button for those pages.
|
||||
for (const auto& [other_id, other_page] : m_pages) {
|
||||
if (other_id == id)
|
||||
continue;
|
||||
|
||||
const int other_image_id = other_page->get_icon_image_id();
|
||||
if (other_image_id <= removed_image_id)
|
||||
continue;
|
||||
|
||||
const int updated_image_id = other_image_id - 1;
|
||||
other_page->set_icon_image_id(updated_image_id);
|
||||
|
||||
if (m_parent != nullptr) {
|
||||
const int other_index = m_parent->FindPage(other_page);
|
||||
if (other_index != wxNOT_FOUND)
|
||||
m_parent->SetPageImage(static_cast<size_t>(other_index), updated_image_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
page->Destroy();
|
||||
m_pages.erase(it);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/imaglist.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/webview.h>
|
||||
|
||||
@@ -29,16 +31,20 @@ public:
|
||||
void on_new_window(wxWebViewEvent& event);
|
||||
void on_script_message(wxWebViewEvent& event);
|
||||
void push_message(const std::string& message);
|
||||
void set_icon_image_id(int id) { m_icon_image_id = id; }
|
||||
int get_icon_image_id() const { return m_icon_image_id; }
|
||||
|
||||
private:
|
||||
void load_plugin_content();
|
||||
wxString bootstrap_url() const;
|
||||
wxString web_base_url() const;
|
||||
|
||||
wxWebView* m_browser{nullptr};
|
||||
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||
bool m_content_loaded{false};
|
||||
wxWebView* m_browser{nullptr};
|
||||
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||
bool m_content_loaded{false};
|
||||
|
||||
int m_icon_image_id = wxBookCtrlBase::NO_IMAGE;
|
||||
};
|
||||
|
||||
class PluginPages
|
||||
@@ -47,7 +53,7 @@ public:
|
||||
PluginPages() = default;
|
||||
~PluginPages();
|
||||
|
||||
PluginPages(const PluginPages&) = delete;
|
||||
PluginPages(const PluginPages&) = delete;
|
||||
PluginPages& operator=(const PluginPages&) = delete;
|
||||
|
||||
void initialize(Notebook* parent);
|
||||
@@ -63,7 +69,9 @@ private:
|
||||
void remove_page(const PluginCapabilityId& id);
|
||||
|
||||
std::map<PluginCapabilityId, PluginPage*> m_pages;
|
||||
Notebook* m_parent{nullptr};
|
||||
Notebook* m_parent{nullptr};
|
||||
|
||||
std::unique_ptr<wxImageList> m_image_list;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -23,6 +23,7 @@ void PagesPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
.def(py::init<>())
|
||||
.def("get_type", &PagesPluginCapability::get_type)
|
||||
.def("get_ui", &PagesPluginCapability::get_ui)
|
||||
.def("get_icon", &PagesPluginCapability::get_icon)
|
||||
.def("on_message", &PagesPluginCapability::on_message)
|
||||
.def(
|
||||
"post_message",
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
|
||||
virtual std::string get_ui() = 0;
|
||||
virtual void on_message(std::string message) { (void) message; }
|
||||
virtual std::string get_icon() { return {}; }
|
||||
|
||||
void post_message(std::string message);
|
||||
void set_message_sender(std::function<void(const std::string&)> sender);
|
||||
|
||||
@@ -13,6 +13,17 @@ class PyPagesPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PagesP
|
||||
public:
|
||||
using PyPluginCommonTrampoline<PagesPluginCapability>::PyPluginCommonTrampoline;
|
||||
|
||||
std::string get_icon() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE,
|
||||
std::string,
|
||||
PagesPluginCapability,
|
||||
get_icon);
|
||||
}
|
||||
|
||||
std::string get_ui() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
|
||||
Reference in New Issue
Block a user