| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/aiks/aiks_playground_inspector.h" |
| |
| #include <initializer_list> |
| |
| #include "impeller/core/capture.h" |
| #include "impeller/entity/entity_pass.h" |
| #include "impeller/renderer/context.h" |
| #include "third_party/imgui/imgui.h" |
| #include "third_party/imgui/imgui_internal.h" |
| |
| namespace impeller { |
| |
| static const char* kElementsWindowName = "Elements"; |
| static const char* kPropertiesWindowName = "Properties"; |
| |
| static const std::initializer_list<std::string> kSupportedDocuments = { |
| EntityPass::kCaptureDocumentName}; |
| |
| AiksInspector::AiksInspector() = default; |
| |
| const std::optional<Picture>& AiksInspector::RenderInspector( |
| AiksContext& aiks_context, |
| const std::function<std::optional<Picture>()>& picture_callback) { |
| //---------------------------------------------------------------------------- |
| /// Configure the next frame. |
| /// |
| |
| RenderCapture(aiks_context.GetContext()->capture); |
| |
| //---------------------------------------------------------------------------- |
| /// Configure the next frame. |
| /// |
| |
| if (ImGui::IsKeyPressed(ImGuiKey_Z)) { |
| wireframe_ = !wireframe_; |
| aiks_context.GetContentContext().SetWireframe(wireframe_); |
| } |
| |
| if (ImGui::IsKeyPressed(ImGuiKey_C)) { |
| capturing_ = !capturing_; |
| if (capturing_) { |
| aiks_context.GetContext()->capture = |
| CaptureContext::MakeAllowlist({kSupportedDocuments}); |
| } |
| } |
| if (!capturing_) { |
| hovered_element_ = nullptr; |
| selected_element_ = nullptr; |
| aiks_context.GetContext()->capture = CaptureContext::MakeInactive(); |
| std::optional<Picture> new_picture = picture_callback(); |
| |
| // If the new picture doesn't have a pass, that means it was already moved |
| // into the inspector. Simply re-emit the last received valid picture. |
| if (!new_picture.has_value() || new_picture->pass) { |
| last_picture_ = std::move(new_picture); |
| } |
| } |
| |
| return last_picture_; |
| } |
| |
| void AiksInspector::HackResetDueToTextureLeaks() { |
| last_picture_.reset(); |
| } |
| |
| static const auto kPropertiesProcTable = CaptureProcTable{ |
| .boolean = |
| [](CaptureBooleanProperty& p) { |
| ImGui::Checkbox(p.label.c_str(), &p.value); |
| }, |
| .integer = |
| [](CaptureIntegerProperty& p) { |
| if (p.options.range.has_value()) { |
| ImGui::SliderInt(p.label.c_str(), &p.value, |
| static_cast<int>(p.options.range->min), |
| static_cast<int>(p.options.range->max)); |
| return; |
| } |
| ImGui::InputInt(p.label.c_str(), &p.value); |
| }, |
| .scalar = |
| [](CaptureScalarProperty& p) { |
| if (p.options.range.has_value()) { |
| ImGui::SliderFloat(p.label.c_str(), &p.value, p.options.range->min, |
| p.options.range->max); |
| return; |
| } |
| ImGui::DragFloat(p.label.c_str(), &p.value, 0.01); |
| }, |
| .point = |
| [](CapturePointProperty& p) { |
| if (p.options.range.has_value()) { |
| ImGui::SliderFloat2(p.label.c_str(), |
| reinterpret_cast<float*>(&p.value), |
| p.options.range->min, p.options.range->max); |
| return; |
| } |
| ImGui::DragFloat2(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 0.01); |
| }, |
| .vector3 = |
| [](CaptureVector3Property& p) { |
| if (p.options.range.has_value()) { |
| ImGui::SliderFloat3(p.label.c_str(), |
| reinterpret_cast<float*>(&p.value), |
| p.options.range->min, p.options.range->max); |
| return; |
| } |
| ImGui::DragFloat3(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 0.01); |
| }, |
| .rect = |
| [](CaptureRectProperty& p) { |
| ImGui::DragFloat4(p.label.c_str(), reinterpret_cast<float*>(&p.value), |
| 0.01); |
| }, |
| .color = |
| [](CaptureColorProperty& p) { |
| ImGui::ColorEdit4(p.label.c_str(), |
| reinterpret_cast<float*>(&p.value)); |
| }, |
| .matrix = |
| [](CaptureMatrixProperty& p) { |
| float* pointer = reinterpret_cast<float*>(&p.value); |
| ImGui::DragFloat4((p.label + " X basis").c_str(), pointer, 0.001); |
| ImGui::DragFloat4((p.label + " Y basis").c_str(), pointer + 4, 0.001); |
| ImGui::DragFloat4((p.label + " Z basis").c_str(), pointer + 8, 0.001); |
| ImGui::DragFloat4((p.label + " Translation").c_str(), pointer + 12, |
| 0.001); |
| }, |
| .string = |
| [](CaptureStringProperty& p) { |
| ImGui::InputTextEx(p.label.c_str(), "", |
| // Fine as long as it's read-only. |
| const_cast<char*>(p.value.c_str()), p.value.size(), |
| ImVec2(0, 0), ImGuiInputTextFlags_ReadOnly); |
| }, |
| }; |
| |
| void AiksInspector::RenderCapture(CaptureContext& capture_context) { |
| if (!capturing_) { |
| return; |
| } |
| |
| auto document = capture_context.GetDocument(EntityPass::kCaptureDocumentName); |
| |
| //---------------------------------------------------------------------------- |
| /// Setup a shared dockspace to collect the capture windows. |
| /// |
| |
| ImGui::SetNextWindowBgAlpha(0.5); |
| ImGui::Begin("Capture"); |
| auto dockspace_id = ImGui::GetID("CaptureDockspace"); |
| if (!ImGui::DockBuilderGetNode(dockspace_id)) { |
| ImGui::SetWindowSize(ImVec2(370, 680)); |
| ImGui::SetWindowPos(ImVec2(640, 55)); |
| |
| ImGui::DockBuilderRemoveNode(dockspace_id); |
| ImGui::DockBuilderAddNode(dockspace_id); |
| |
| ImGuiID opposite_id; |
| ImGuiID up_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.6, |
| nullptr, &opposite_id); |
| ImGuiID down_id = ImGui::DockBuilderSplitNode(opposite_id, ImGuiDir_Down, |
| 0.0, nullptr, nullptr); |
| ImGui::DockBuilderDockWindow(kElementsWindowName, up_id); |
| ImGui::DockBuilderDockWindow(kPropertiesWindowName, down_id); |
| |
| ImGui::DockBuilderFinish(dockspace_id); |
| } |
| ImGui::DockSpace(dockspace_id); |
| ImGui::End(); // Capture window. |
| |
| //---------------------------------------------------------------------------- |
| /// Element hierarchy window. |
| /// |
| |
| ImGui::Begin(kElementsWindowName); |
| auto root_element = document.GetElement(); |
| hovered_element_ = nullptr; |
| if (root_element) { |
| RenderCaptureElement(*root_element); |
| } |
| ImGui::End(); // Hierarchy window. |
| |
| if (selected_element_) { |
| //---------------------------------------------------------------------------- |
| /// Properties window. |
| /// |
| |
| ImGui::Begin(kPropertiesWindowName); |
| { |
| selected_element_->properties.Iterate([&](CaptureProperty& property) { |
| property.Invoke(kPropertiesProcTable); |
| }); |
| } |
| ImGui::End(); // Inspector window. |
| |
| //---------------------------------------------------------------------------- |
| /// Selected coverage highlighting. |
| /// |
| |
| auto coverage_property = |
| selected_element_->properties.FindFirstByLabel("Coverage"); |
| if (coverage_property) { |
| auto coverage = coverage_property->AsRect(); |
| if (coverage.has_value()) { |
| Scalar scale = ImGui::GetWindowDpiScale(); |
| ImGui::GetBackgroundDrawList()->AddRect( |
| ImVec2(coverage->GetLeft() / scale, |
| coverage->GetTop() / scale), // p_min |
| ImVec2(coverage->GetRight() / scale, |
| coverage->GetBottom() / scale), // p_max |
| 0x992222FF, // col |
| 0.0, // rounding |
| ImDrawFlags_None, // flags |
| 8.0); // thickness |
| } |
| } |
| } |
| |
| //---------------------------------------------------------------------------- |
| /// Hover coverage highlight. |
| /// |
| |
| if (hovered_element_) { |
| auto coverage_property = |
| hovered_element_->properties.FindFirstByLabel("Coverage"); |
| if (coverage_property) { |
| auto coverage = coverage_property->AsRect(); |
| if (coverage.has_value()) { |
| Scalar scale = ImGui::GetWindowDpiScale(); |
| ImGui::GetBackgroundDrawList()->AddRect( |
| ImVec2(coverage->GetLeft() / scale, |
| coverage->GetTop() / scale), // p_min |
| ImVec2(coverage->GetRight() / scale, |
| coverage->GetBottom() / scale), // p_max |
| 0x66FF2222, // col |
| 0.0, // rounding |
| ImDrawFlags_None, // flags |
| 8.0); // thickness |
| } |
| } |
| } |
| } |
| |
| void AiksInspector::RenderCaptureElement(CaptureElement& element) { |
| ImGui::PushID(&element); |
| |
| bool is_selected = selected_element_ == &element; |
| bool has_children = element.children.Count() > 0; |
| |
| bool opened = ImGui::TreeNodeEx( |
| element.label.c_str(), (is_selected ? ImGuiTreeNodeFlags_Selected : 0) | |
| (has_children ? 0 : ImGuiTreeNodeFlags_Leaf) | |
| ImGuiTreeNodeFlags_SpanFullWidth | |
| ImGuiTreeNodeFlags_OpenOnArrow | |
| ImGuiTreeNodeFlags_DefaultOpen); |
| if (ImGui::IsItemClicked()) { |
| selected_element_ = &element; |
| } |
| if (ImGui::IsItemHovered()) { |
| hovered_element_ = &element; |
| } |
| if (opened) { |
| element.children.Iterate( |
| [&](CaptureElement& child) { RenderCaptureElement(child); }); |
| ImGui::TreePop(); |
| } |
| ImGui::PopID(); |
| } |
| |
| } // namespace impeller |