From 47467b626c0d6db3763f82bf02985d5b4c0de6df Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 3 Jun 2026 03:01:58 +0800 Subject: [PATCH] feat(automation): guarded ImGui item/window recording hooks --- src/slic3r/GUI/GUI_App.hpp | 7 ++++ src/slic3r/GUI/ImGuiWrapper.cpp | 60 +++++++++++++++++++++++++++++++-- src/slic3r/GUI/ImGuiWrapper.hpp | 5 +++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index dc0f1e52de..ca2e93a8d7 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -238,6 +238,7 @@ private: bool m_initialized { false }; bool m_post_initialized { false }; bool m_app_conf_exists{ false }; + int m_automation_port { 0 }; // UI automation: 0 = off; set by Task 17 from CLI EAppMode m_app_mode{ EAppMode::Editor }; bool m_is_recreating_gui{ false }; #ifdef __linux__ @@ -364,6 +365,12 @@ public: FilamentColorCodeQuery* get_filament_color_code_query(); bool is_editor() const { return m_app_mode == EAppMode::Editor; } bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } + + // UI automation: true once a TCP port has been assigned (via CLI). When false, + // every automation recording hook short-circuits to a single bool check. + // NOTE: Task 17 extends the automation lifecycle (server/backend) around this + // accessor; here we only add the minimal flag/accessor the hooks depend on. + bool is_automation_enabled() const { return m_automation_port > 0; } bool is_recreating_gui() const { return m_is_recreating_gui; } std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7743ff8e00..3c7a4bec31 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,6 +37,7 @@ #include "Search.hpp" #include "BitmapCache.hpp" #include "GUI_App.hpp" +#include "slic3r/GUI/Automation/ImGuiItemTable.hpp" #include "../Utils/MacDarkMode.hpp" #include @@ -570,11 +571,44 @@ void ImGuiWrapper::new_frame() // BBL: end copy & paste } +void ImGuiWrapper::automation_record_last_item(const char* type, const std::string& label, + bool has_value, const std::string& value) { + if (!wxGetApp().is_automation_enabled()) + return; + using namespace Slic3r::GUI::Automation; + const ImVec2 mn = ImGui::GetItemRectMin(); + const ImVec2 mx = ImGui::GetItemRectMax(); + ImGuiItemRecord rec; + ImGuiContext* ctx = ImGui::GetCurrentContext(); + rec.window_name = (ctx && ctx->CurrentWindow) ? ctx->CurrentWindow->Name : ""; + rec.label = label; + rec.type = type; + rec.x = mn.x; rec.y = mn.y; rec.w = mx.x - mn.x; rec.h = mx.y - mn.y; + rec.enabled = true; // v1: precise per-item disabled-state read is non-trivial across ImGui versions + rec.has_value = has_value; + rec.value = value; + ImGuiItemTable::instance().record_item(std::move(rec)); +} + void ImGuiWrapper::render() { ImGui::Render(); render_draw_data(ImGui::GetDrawData()); m_new_frame_open = false; + + if (wxGetApp().is_automation_enabled()) { + using namespace Slic3r::GUI::Automation; + ImGuiContext& g = *ImGui::GetCurrentContext(); + for (ImGuiWindow* w : g.Windows) { + if (w == nullptr) continue; + ImGuiWindowRecord wr; + wr.name = w->Name ? w->Name : ""; + wr.x = w->Pos.x; wr.y = w->Pos.y; wr.w = w->Size.x; wr.h = w->Size.y; + wr.visible = w->Active && !w->Hidden; + ImGuiItemTable::instance().record_window(std::move(wr)); + } + ImGuiItemTable::instance().swap_frame(); + } } ImVec2 ImGuiWrapper::calc_text_size(std::string_view text, @@ -870,6 +904,7 @@ bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip) { auto label_utf8 = into_u8(label); const bool ret = ImGui::Button(label_utf8.c_str()); + automation_record_last_item("button", label_utf8, false, {}); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -883,6 +918,7 @@ bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip) { auto label_utf8 = into_u8(label); const bool ret = ImGui::BBLButton(label_utf8.c_str()); + automation_record_last_item("button", label_utf8, false, {}); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -960,7 +996,9 @@ bool ImGuiWrapper::glyph_button(wchar_t icon_char, ImVec2 icon_size) bool ImGuiWrapper::radio_button(const wxString &label, bool active) { auto label_utf8 = into_u8(label); - return ImGui::RadioButton(label_utf8.c_str(), active); + const bool ret = ImGui::RadioButton(label_utf8.c_str(), active); + automation_record_last_item("radio", label_utf8, true, active ? "true" : "false"); + return ret; } ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGB &color) { @@ -969,7 +1007,11 @@ ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGB &color) { bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { - return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal); + const bool ret = ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal); + char value_buf[64]; + snprintf(value_buf, sizeof(value_buf), format.c_str(), value); + automation_record_last_item("input", label, true, value_buf); + return ret; } bool ImGuiWrapper::input_double(const wxString &label, const double &value, const std::string &format) @@ -1000,7 +1042,9 @@ bool ImGuiWrapper::input_vec3(const std::string &label, const Vec3d &value, floa bool ImGuiWrapper::checkbox(const wxString &label, bool &value) { auto label_utf8 = into_u8(label); - return ImGui::Checkbox(label_utf8.c_str(), &value); + const bool ret = ImGui::Checkbox(label_utf8.c_str(), &value); + automation_record_last_item("checkbox", label_utf8, true, value ? "true" : "false"); + return ret; } bool ImGuiWrapper::bbl_checkbox(const wxString &label, bool &value) @@ -1014,6 +1058,7 @@ bool ImGuiWrapper::bbl_checkbox(const wxString &label, bool &value) } auto label_utf8 = into_u8(label); result = ImGui::BBLCheckbox(label_utf8.c_str(), &value); + automation_record_last_item("checkbox", label_utf8, true, value ? "true" : "false"); if (b_value) { ImGui::PopStyleColor(3);} return result; @@ -1147,6 +1192,11 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float str_label = str_label.substr(0, pos) + str_label.substr(pos + 2); bool ret = ImGui::SliderFloat(str_label.c_str(), v, v_min, v_max, format, power); + { + char value_buf[64]; + snprintf(value_buf, sizeof(value_buf), format, v ? *v : 0.0f); + automation_record_last_item("slider", label, true, value_buf); + } m_last_slider_status.hovered = ImGui::IsItemHovered(); m_last_slider_status.edited = ImGui::IsItemEdited(); @@ -1324,6 +1374,10 @@ bool ImGuiWrapper::combo(const std::string& label, const std::vector= 0 && selection < int(options.size())) ? options[selection] : std::string(); + automation_record_last_item("combo", label, true, current_value); + } return res; } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index af92140379..b0a4eae718 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -386,6 +386,11 @@ private: static const char* clipboard_get(void* user_data); static void clipboard_set(void* user_data, const char* text); + // Automation recording: appends the most-recently-drawn ImGui item to the + // automation item table. No-op (single bool check) when automation is disabled. + void automation_record_last_item(const char* type, const std::string& label, + bool has_value, const std::string& value); + LastSliderStatus m_last_slider_status; ImFont* default_font = nullptr; ImFont* bold_font = nullptr;