diff --git a/src/xenia/debug/ui/control.h b/src/xenia/debug/ui/control.h new file mode 100644 index 000000000..ff657e7fe --- /dev/null +++ b/src/xenia/debug/ui/control.h @@ -0,0 +1,49 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_CONTROL_H_ +#define XENIA_DEBUG_UI_CONTROL_H_ + +#include +#include + +#include "el/elements.h" +#include "el/event_handler.h" +#include "xenia/debug/ui/application.h" + +namespace xe { +namespace debug { +namespace ui { + +class Control { + public: + virtual ~Control() = default; + + el::LayoutBox* root_element() { return &root_element_; } + xe::ui::Loop* loop() const { return Application::current()->loop(); } + DebugClient* client() const { return client_; } + model::System* system() const { return Application::current()->system(); } + + virtual el::Element* BuildUI() = 0; + + virtual void Setup(xe::debug::DebugClient* client) = 0; + + protected: + Control() = default; + + el::LayoutBox root_element_; + std::unique_ptr handler_; + xe::debug::DebugClient* client_ = nullptr; +}; + +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_CONTROL_H_ diff --git a/src/xenia/debug/ui/views/cpu/call_stack_control.cc b/src/xenia/debug/ui/views/cpu/call_stack_control.cc new file mode 100644 index 000000000..12c466f92 --- /dev/null +++ b/src/xenia/debug/ui/views/cpu/call_stack_control.cc @@ -0,0 +1,152 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "el/animation_manager.h" +#include "xenia/base/string_buffer.h" +#include "xenia/debug/ui/views/cpu/call_stack_control.h" + +namespace xe { +namespace debug { +namespace ui { +namespace views { +namespace cpu { + +class CallStackItem : public el::GenericStringItem { + public: + CallStackItem(size_t ordinal, const ThreadCallStackFrame* frame) + : GenericStringItem(""), ordinal_(ordinal), frame_(frame) { + StringBuffer sb; + if (frame_->guest_pc) { + sb.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal_, frame_->host_pc, + frame_->guest_pc, frame_->name); + } else { + sb.AppendFormat(" %.2lld %.16llX %s", ordinal_, frame_->host_pc, + frame_->name); + } + str = sb.to_string(); + } + + private: + size_t ordinal_ = 0; + const ThreadCallStackFrame* frame_ = nullptr; +}; + +class CallStackItemElement : public el::LayoutBox { + public: + CallStackItemElement(CallStackItem* item, CallStackItemSource* source, + el::ListItemObserver* source_viewer, size_t index) + : item_(item), + source_(source), + source_viewer_(source_viewer), + index_(index) { + set_background_skin(TBIDC("ListItem")); + set_axis(el::Axis::kY); + set_gravity(el::Gravity::kAll); + set_layout_position(el::LayoutPosition::kLeftTop); + set_layout_distribution(el::LayoutDistribution::kAvailable); + set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop); + set_layout_size(el::LayoutSize::kAvailable); + set_paint_overflow_fadeout(false); + + using namespace el::dsl; // NOLINT(build/namespaces) + el::AnimationBlocker animation_blocker; + + auto node = LabelNode(item_->str.c_str()) + .gravity(Gravity::kLeft) + .ignore_input(true) + .text_align(el::TextAlign::kLeft); + content_root()->LoadNodeTree(node); + } + + bool OnEvent(const el::Event& ev) override { + return el::LayoutBox::OnEvent(ev); + } + + private: + CallStackItem* item_ = nullptr; + CallStackItemSource* source_ = nullptr; + el::ListItemObserver* source_viewer_ = nullptr; + size_t index_ = 0; +}; + +class CallStackItemSource : public el::ListItemSourceList { + public: + el::Element* CreateItemElement(size_t index, + el::ListItemObserver* viewer) override { + return new CallStackItemElement(at(index), this, viewer, index); + } +}; + +CallStackControl::CallStackControl() + : item_source_(new CallStackItemSource()) {} + +CallStackControl::~CallStackControl() = default; + +el::Element* CallStackControl::BuildUI() { + using namespace el::dsl; // NOLINT(build/namespaces) + el::AnimationBlocker animation_blocker; + + auto node = + LayoutBoxNode() + .gravity(Gravity::kAll) + .distribution(LayoutDistribution::kAvailable) + .child(ListBoxNode().id("stack_listbox").gravity(Gravity::kAll)); + + root_element_.set_gravity(Gravity::kAll); + root_element_.set_layout_distribution(LayoutDistribution::kAvailable); + root_element_.LoadNodeTree(node); + + auto stack_listbox = + root_element_.GetElementById(TBIDC("stack_listbox")); + stack_listbox->set_source(item_source_.get()); + stack_listbox->scroll_container()->set_scroll_mode( + el::ScrollMode::kAutoXAutoY); + + handler_ = std::make_unique(&root_element_); + + return &root_element_; +} + +void CallStackControl::Setup(DebugClient* client) { + client_ = client; + + system()->on_thread_call_stack_updated.AddListener( + [this](model::Thread* thread) { + if (thread == thread_) { + InvalidateCallStack(); + } + }); +} + +void CallStackControl::set_thread(model::Thread* thread) { + thread_ = thread; + InvalidateCallStack(); +} + +void CallStackControl::InvalidateCallStack() { + item_source_->clear(); + if (!thread_) { + return; + } + auto& call_stack = thread_->call_stack(); + for (size_t i = 0; i < call_stack.size(); ++i) { + auto& frame = call_stack[i]; + size_t ordinal = call_stack.size() - i - 1; + auto item = std::make_unique(ordinal, &frame); + item->tag.set_integer(static_cast(i)); + item_source_->push_back(std::move(item)); + } + item_source_->InvokeAllItemsRemoved(); +} + +} // namespace cpu +} // namespace views +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/views/cpu/call_stack_control.h b/src/xenia/debug/ui/views/cpu/call_stack_control.h new file mode 100644 index 000000000..91a487fa7 --- /dev/null +++ b/src/xenia/debug/ui/views/cpu/call_stack_control.h @@ -0,0 +1,51 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_ +#define XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_ + +#include +#include + +#include "xenia/debug/ui/control.h" + +namespace xe { +namespace debug { +namespace ui { +namespace views { +namespace cpu { + +class CallStackItemSource; + +class CallStackControl : public Control { + public: + CallStackControl(); + ~CallStackControl() override; + + el::Element* BuildUI() override; + + void Setup(xe::debug::DebugClient* client) override; + + model::Thread* thread() const { return thread_; } + void set_thread(model::Thread* thread); + + private: + void InvalidateCallStack(); + + model::Thread* thread_ = nullptr; + std::unique_ptr item_source_; +}; + +} // namespace cpu +} // namespace views +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_ diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.cc b/src/xenia/debug/ui/views/cpu/cpu_view.cc index 9e5a1a511..091d80b64 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.cc +++ b/src/xenia/debug/ui/views/cpu/cpu_view.cc @@ -80,7 +80,8 @@ el::Element* CpuView::BuildUI() { TabContainerNode() .gravity(Gravity::kAll) .align(Align::kLeft) - .tab(ButtonNode("Stack"), LabelNode("STACK")) + .tab(ButtonNode("Stack"), + LabelNode("").id("call_stack_placeholder")) .tab(ButtonNode("BPs"), LabelNode("BREAKPOINTS")); auto source_pane_node = @@ -104,7 +105,7 @@ el::Element* CpuView::BuildUI() { .gravity(Gravity::kAll) .axis(Axis::kX) .fixed_pane(FixedPane::kSecond) - .value(128) + .value(250) .pane(SplitContainerNode() .gravity(Gravity::kAll) .axis(Axis::kY) @@ -133,9 +134,15 @@ el::Element* CpuView::BuildUI() { root_element_.set_gravity(Gravity::kAll); root_element_.set_layout_distribution(LayoutDistribution::kAvailable); root_element_.LoadNodeTree(node); + + el::Label* call_stack_placeholder; root_element_.GetElementsById({ - // + {TBIDC("call_stack_placeholder"), &call_stack_placeholder}, }); + call_stack_placeholder->parent()->ReplaceChild(call_stack_placeholder, + call_stack_control_.BuildUI()); + call_stack_control_.Setup(client_); + handler_ = std::make_unique(&root_element_); handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"), @@ -154,7 +161,7 @@ el::Element* CpuView::BuildUI() { } else { current_thread_ = nullptr; } - UpdateThreadCallStack(current_thread_); + SwitchCurrentThread(current_thread_); return true; }); @@ -168,12 +175,6 @@ void CpuView::Setup(DebugClient* client) { [this]() { UpdateElementState(); }); system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); }); system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); }); - system()->on_thread_call_stack_updated.AddListener( - [this](model::Thread* thread) { - if (thread == current_thread_) { - UpdateThreadCallStack(thread); - } - }); } void CpuView::UpdateElementState() { @@ -241,28 +242,9 @@ void CpuView::UpdateThreadList() { } } -void CpuView::UpdateThreadCallStack(model::Thread* thread) { - auto textbox = - root_element_.GetElementById(TBIDC("source_textbox")); - if (!thread) { - textbox->set_text("no thread"); - return; - } - auto& call_stack = thread->call_stack(); - StringBuffer str; - for (size_t i = 0; i < call_stack.size(); ++i) { - auto& frame = call_stack[i]; - size_t ordinal = call_stack.size() - i - 1; - if (frame.guest_pc) { - str.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal, frame.host_pc, - frame.guest_pc, frame.name); - } else { - str.AppendFormat(" %.2lld %.16llX %s", ordinal, frame.host_pc, - frame.name); - } - str.Append('\n'); - } - textbox->set_text(str.to_string()); +void CpuView::SwitchCurrentThread(model::Thread* thread) { + current_thread_ = thread; + call_stack_control_.set_thread(thread); } } // namespace cpu diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.h b/src/xenia/debug/ui/views/cpu/cpu_view.h index 4f7b490cb..5fab34b65 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.h +++ b/src/xenia/debug/ui/views/cpu/cpu_view.h @@ -14,6 +14,7 @@ #include #include "xenia/debug/ui/view.h" +#include "xenia/debug/ui/views/cpu/call_stack_control.h" namespace xe { namespace debug { @@ -35,10 +36,13 @@ class CpuView : public View { void UpdateModuleList(); void UpdateFunctionList(); void UpdateThreadList(); - void UpdateThreadCallStack(model::Thread* thread); + + void SwitchCurrentThread(model::Thread* thread); // TODO(benvanik): better state machine. model::Thread* current_thread_ = nullptr; + + CallStackControl call_stack_control_; }; } // namespace cpu diff --git a/third_party/elemental-forms b/third_party/elemental-forms index 696a19055..4fdbd5fe7 160000 --- a/third_party/elemental-forms +++ b/third_party/elemental-forms @@ -1 +1 @@ -Subproject commit 696a19055a437e3a96d131bbb5803708e64e0146 +Subproject commit 4fdbd5fe789da92fd5e23240c63ed5aa2cba5ed2