diff --git a/.gitmodules b/.gitmodules index 51b659e4d..aa8fa6ebd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,6 @@ [submodule "third_party/libav-xma-bin"] path = third_party/libav-xma-bin url = https://github.com/xenia-project/libav-xma-bin.git -[submodule "third_party/turbobadger"] - path = third_party/turbobadger - url = https://github.com/xenia-project/turbobadger.git +[submodule "third_party/elemental-forms"] + path = third_party/elemental-forms + url = https://github.com/xenia-project/elemental-forms.git diff --git a/build/Xenia.Cpp.x64.Common.props b/build/Xenia.Cpp.x64.Common.props index cdf59d98d..0124f7ef7 100644 --- a/build/Xenia.Cpp.x64.Common.props +++ b/build/Xenia.Cpp.x64.Common.props @@ -9,7 +9,7 @@ true - $(SolutionDir)third_party\flatbuffers\include\;$(SolutionDir)third_party\turbobadger\src\;$(SolutionDir)third_party\gflags\src\;$(SolutionDir)src\;$(SolutionDir)third_party;$(SolutionDir) + $(SolutionDir)third_party\flatbuffers\include\;$(SolutionDir)third_party\turbobadger\src\;$(SolutionDir)third_party\gflags\src\;$(SolutionDir)third_party\elemental-forms\src\;$(SolutionDir)src\;$(SolutionDir)third_party;$(SolutionDir) GLEW_STATIC=1;GLEW_MX=1;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32;_WIN64=1;_AMD64=1;MICROPROFILE_MAX_THREADS=128;CAPSTONE_X86_ATT_DISABLE;CAPSTONE_DIET_NO;CAPSTONE_X86_REDUCE_NO;CAPSTONE_HAS_X86;CAPSTONE_USE_SYS_DYN_MEM;XBYAK_NO_OP_NAMES;%(PreprocessorDefinitions) Level4 true diff --git a/libxenia.vcxproj b/libxenia.vcxproj index b2dfafd35..7c620cc22 100644 --- a/libxenia.vcxproj +++ b/libxenia.vcxproj @@ -166,11 +166,13 @@ + + @@ -394,6 +396,7 @@ + @@ -401,6 +404,7 @@ + @@ -556,7 +560,7 @@ true - libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib + libelemental.lib;libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib $(SolutionDir)third_party\libav-xma-bin\lib\Debug;%(AdditionalLibraryDirectories) @@ -573,7 +577,7 @@ true - libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib + libelemental.lib;libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib @@ -592,7 +596,7 @@ true - libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib + libelemental.lib;libxenia-base.lib;libavcodec.a;libavutil.a;libgflags.lib;libglew.lib diff --git a/libxenia.vcxproj.filters b/libxenia.vcxproj.filters index d15ccd065..c71877768 100644 --- a/libxenia.vcxproj.filters +++ b/libxenia.vcxproj.filters @@ -730,6 +730,12 @@ src\xenia\ui\gl + + src\xenia\ui + + + src\xenia\ui\gl + @@ -1374,6 +1380,12 @@ src\xenia\ui\gl + + src\xenia\ui + + + src\xenia\ui\gl + diff --git a/src/xenia/debug/ui/application.cc b/src/xenia/debug/ui/application.cc index 3e03aebd2..8a0928ffb 100644 --- a/src/xenia/debug/ui/application.cc +++ b/src/xenia/debug/ui/application.cc @@ -9,6 +9,9 @@ #include "xenia/debug/ui/application.h" +#include "el/message_handler.h" +#include "el/util/metrics.h" +#include "el/util/timer.h" #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/platform.h" @@ -16,10 +19,6 @@ #include "xenia/debug/ui/main_window.h" #include "xenia/profiling.h" -#include "third_party/turbobadger/src/tb/message_handler.h" -#include "third_party/turbobadger/src/tb/util/metrics.h" -#include "third_party/turbobadger/src/tb/util/timer.h" - namespace xe { namespace debug { namespace ui { @@ -82,16 +81,16 @@ void Application::Quit() { // This doesn't really belong here (it belongs in tb_system_[linux/windows].cpp. // This is here since the proper implementations has not yet been done. -void tb::util::RescheduleTimer(uint64_t fire_time) { - if (fire_time == tb::MessageHandler::kNotSoon) { +void el::util::RescheduleTimer(uint64_t fire_time) { + if (fire_time == el::MessageHandler::kNotSoon) { return; } - uint64_t now = tb::util::GetTimeMS(); + uint64_t now = el::util::GetTimeMS(); uint64_t delay_millis = fire_time >= now ? fire_time - now : 0; xe::debug::ui::Application::current()->loop()->PostDelayed([]() { - uint64_t next_fire_time = tb::MessageHandler::GetNextMessageFireTime(); - uint64_t now = tb::util::GetTimeMS(); + uint64_t next_fire_time = el::MessageHandler::GetNextMessageFireTime(); + uint64_t now = el::util::GetTimeMS(); if (now < next_fire_time) { // We timed out *before* we were supposed to (the OS is not playing nice). // Calling ProcessMessages now won't achieve a thing so force a reschedule @@ -100,11 +99,11 @@ void tb::util::RescheduleTimer(uint64_t fire_time) { return; } - tb::MessageHandler::ProcessMessages(); + el::MessageHandler::ProcessMessages(); // If we still have things to do (because we didn't process all messages, // or because there are new messages), we need to rescedule, so call // RescheduleTimer. - tb::util::RescheduleTimer(tb::MessageHandler::GetNextMessageFireTime()); + el::util::RescheduleTimer(el::MessageHandler::GetNextMessageFireTime()); }, delay_millis); } diff --git a/src/xenia/debug/ui/main_window.cc b/src/xenia/debug/ui/main_window.cc index 147deca70..1e4890a01 100644 --- a/src/xenia/debug/ui/main_window.cc +++ b/src/xenia/debug/ui/main_window.cc @@ -13,7 +13,9 @@ #include "xenia/base/logging.h" #include "xenia/base/platform.h" #include "xenia/base/threading.h" -#include "xenia/debug/ui/turbo_badger_control.h" + +// TODO(benvanik): platform based. +#include "xenia/ui/gl/wgl_elemental_control.h" namespace xe { namespace debug { @@ -89,7 +91,7 @@ bool MainWindow::Initialize() { // Setup the GL control that actually does the drawing. // We run here in the loop and only touch it (and its context) on this // thread. That means some sync-fu when we want to swap. - control_ = std::make_unique(loop()); + control_ = std::make_unique(loop()); AddChild(control_.get()); Resize(1440, 1200); diff --git a/src/xenia/debug/ui/main_window.h b/src/xenia/debug/ui/main_window.h index cd82e495e..2aded1926 100644 --- a/src/xenia/debug/ui/main_window.h +++ b/src/xenia/debug/ui/main_window.h @@ -13,6 +13,7 @@ #include #include "xenia/debug/ui/application.h" +#include "xenia/ui/elemental_control.h" #include "xenia/ui/platform.h" #include "xenia/ui/window.h" @@ -20,8 +21,6 @@ namespace xe { namespace debug { namespace ui { -class TurboBadgerControl; - class MainWindow : public xe::ui::PlatformWindow { public: MainWindow(Application* app); @@ -37,7 +36,7 @@ class MainWindow : public xe::ui::PlatformWindow { Application* app_ = nullptr; xe::ui::PlatformMenu main_menu_; - std::unique_ptr control_; + std::unique_ptr control_; }; } // namespace ui diff --git a/src/xenia/debug/ui/turbo_badger_control.cc b/src/xenia/debug/ui/turbo_badger_control.cc deleted file mode 100644 index 28e8ae07e..000000000 --- a/src/xenia/debug/ui/turbo_badger_control.cc +++ /dev/null @@ -1,1322 +0,0 @@ -/** - ****************************************************************************** - * 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 "xenia/debug/ui/turbo_badger_control.h" - -#include "xenia/base/assert.h" -#include "xenia/base/clock.h" -#include "xenia/base/logging.h" -#include "xenia/debug/ui/turbo_badger_renderer.h" - -// TODO(benvanik): remove this. -#include "xenia/debug/ui/application.h" - -#include "third_party/turbobadger/src/tb/animation_manager.h" -#include "third_party/turbobadger/src/tb/parsing/parse_node.h" -#include "third_party/turbobadger/src/tb/resources/font_manager.h" -#include "third_party/turbobadger/src/tb/turbo_badger.h" -#include "third_party/turbobadger/src/tb/util/string.h" - -#include "third_party/turbobadger/src/tb/tb_message_window.h" -#include "third_party/turbobadger/src/tb/tb_scroll_container.h" -#include "third_party/turbobadger/src/tb/tb_text_box.h" -#include "third_party/turbobadger/src/tb/tb_toggle_container.h" - -namespace xe { -namespace debug { -namespace ui { - -constexpr bool kContinuousRepaint = false; - -// Enables long press behaviors (context menu, etc). -constexpr bool kTouch = false; - -constexpr uint64_t kDoubleClickDelayMillis = 600; -constexpr double kDoubleClickDistance = 5; - -constexpr int32_t kMouseWheelDetent = 120; - -class RootElement : public tb::Element { - public: - RootElement(TurboBadgerControl* owner) : owner_(owner) {} - void OnInvalid() override { owner_->Invalidate(); } - - private: - TurboBadgerControl* owner_ = nullptr; -}; - -bool TurboBadgerControl::InitializeTurboBadger( - tb::graphics::Renderer* renderer) { - static bool has_initialized = false; - if (has_initialized) { - return true; - } - has_initialized = true; - - if (!tb::Initialize( - renderer, - "third_party/turbobadger/resources/language/lng_en.tb.txt")) { - XELOGE("Failed to initialize turbobadger core"); - return false; - } - - // Load the default skin, and override skin that contains the graphics - // specific to the demo. - if (!tb::resources::Skin::get()->Load( - "third_party/turbobadger/resources/default_skin/skin.tb.txt", - "third_party/turbobadger/Demo/demo01/skin/skin.tb.txt")) { - XELOGE("Failed to load turbobadger skin"); - return false; - } - -// Register font renderers. -#ifdef TB_FONT_RENDERER_TBBF - void register_tbbf_font_renderer(); - register_tbbf_font_renderer(); -#endif -#ifdef TB_FONT_RENDERER_STB - void register_stb_font_renderer(); - register_stb_font_renderer(); -#endif -#ifdef TB_FONT_RENDERER_FREETYPE - void register_freetype_font_renderer(); - register_freetype_font_renderer(); -#endif - auto font_manager = tb::resources::FontManager::get(); - -// Add fonts we can use to the font manager. -#if defined(TB_FONT_RENDERER_STB) || defined(TB_FONT_RENDERER_FREETYPE) - font_manager->AddFontInfo("third_party/turbobadger/resources/vera.ttf", - "Default"); -#endif -#ifdef TB_FONT_RENDERER_TBBF - font_manager->AddFontInfo( - "third_party/turbobadger/resources/default_font/" - "segoe_white_with_shadow.tb.txt", - "Default"); -#endif - - // Set the default font description for elements to one of the fonts we just - // added. - tb::FontDescription fd; - fd.set_id(TBIDC("Default")); - fd.SetSize(tb::resources::Skin::get()->GetDimensionConverter()->DpToPx(14)); - font_manager->SetDefaultFontDescription(fd); - - // Create the font now. - auto font = - font_manager->CreateFontFace(font_manager->GetDefaultFontDescription()); - return true; -} - -void TurboBadgerControl::ShutdownTurboBadger() { tb::Shutdown(); } - -TurboBadgerControl::TurboBadgerControl(xe::ui::Loop* loop) : super(loop) {} - -TurboBadgerControl::~TurboBadgerControl() = default; - -using tb::parsing::ParseNode; - -class TextNode { - public: - TextNode(const char* name, - std::vector> properties = {}, - std::vector children = {}) { - node_ = ParseNode::Create(name); - for (auto& prop : properties) { - set(prop.first, prop.second); - } - for (auto& child : children) { - node_->Add(child.node_); - } - } - - TextNode& set(const char* key, int32_t value) { - return set(key, tb::Value(value)); - } - - TextNode& set(const char* key, float value) { - return set(key, tb::Value(value)); - } - - TextNode& set(const char* key, const char* value) { - return set(key, tb::Value(value)); - } - - TextNode& set(const char* key, const std::string& value) { - return set(key, tb::Value(value.c_str())); - } - - TextNode& set(const char* key, tb::Rect value) { - auto va = new tb::ValueArray(); - va->AddInteger(value.x); - va->AddInteger(value.y); - va->AddInteger(value.w); - va->AddInteger(value.h); - auto node = node_->GetNode(key, ParseNode::MissingPolicy::kCreate); - node->GetValue().set_array(va, tb::Value::Set::kTakeOwnership); - return *this; - } - - TextNode& set(const char* key, tb::Value& value) { - auto node = node_->GetNode(key, ParseNode::MissingPolicy::kCreate); - node->TakeValue(value); - return *this; - } - - TextNode& child_list(std::initializer_list children) { - for (auto& child : children) { - node_->Add(child.node_); - } - return *this; - } - - ParseNode* node_ = nullptr; -}; - -namespace node { -using tb::Align; -using tb::Axis; -using tb::EditType; -using ElementState = tb::Element::State; -using tb::Gravity; -using tb::LayoutDistribution; -using tb::LayoutDistributionPosition; -using tb::LayoutOrder; -using tb::LayoutOverflow; -using tb::LayoutPosition; -using tb::LayoutSize; -using tb::Rect; -using tb::ScrollMode; -using tb::TextAlign; -using tb::ToggleAction; -using tb::Visibility; - -std::string operator"" _px(uint64_t value) { - return std::to_string(value) + "px"; -} -std::string operator"" _dp(uint64_t value) { - return std::to_string(value) + "dp"; -} -std::string operator"" _mm(uint64_t value) { - return std::to_string(value) + "mm"; -} - -struct Dimension { - Dimension(int32_t value) : value(std::to_string(value) + "px") {} - Dimension(const char* value) : value(value) {} - Dimension(std::string value) : value(std::move(value)) {} - operator std::string() { return value; } - std::string value; -}; -struct Id { - Id(int32_t value) : is_int(true), int_value(value) {} - Id(const char* value) : str_value(value) {} - Id(std::string value) : str_value(std::move(value)) {} - void set(TextNode* node, const char* key) { - if (is_int) { - node->set(key, int_value); - } else { - node->set(key, str_value); - } - } - bool is_int = false; - int32_t int_value = 0; - std::string str_value; -}; - -template -struct ElementNode : public TextNode { - using R = T; - ElementNode(const char* name, - std::vector> properties = {}, - std::vector children = {}) - : TextNode(name, std::move(properties), std::move(children)) {} - // TBID - R& id(Id value) { - value.set(this, "id"); - return *reinterpret_cast(this); - } - // TBID - R& group_id(Id value) { - value.set(this, "group-id"); - return *reinterpret_cast(this); - } - // TBID - R& skin(Id value) { - value.set(this, "skin"); - return *reinterpret_cast(this); - } - // Number - R& data(int32_t value) { - set("data", value); - return *reinterpret_cast(this); - } - R& data(float value) { - set("data", value); - return *reinterpret_cast(this); - } - // - R& is_group_root(bool value) { - set("is-group-root", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& is_focusable(bool value) { - set("is-focusable", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& is_long_clickable(bool value) { - set("want-long-click", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& ignore_input(bool value) { - set("ignore-input", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& opacity(float value) { - set("opacity", value); - return *reinterpret_cast(this); - } - // - R& connection(std::string value) { - set("connection", value); - return *reinterpret_cast(this); - } - // - R& axis(Axis value) { - set("axis", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& gravity(Gravity value) { - set("gravity", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& visibility(Visibility value) { - set("visibility", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& state(ElementState value) { - set("state", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& is_enabled(bool value) { - set("state", - to_string(value ? ElementState::kNone : ElementState::kDisabled)); - return *reinterpret_cast(this); - } - // Rect - R& rect(Rect value) { - set("rect", std::move(value)); - return *reinterpret_cast(this); - } - // - R& width(Dimension value) { - set("lp>width", value); - return *reinterpret_cast(this); - } - // - R& min_width(Dimension value) { - set("lp>min-width", value); - return *reinterpret_cast(this); - } - // - R& max_width(Dimension value) { - set("lp>max-width", value); - return *reinterpret_cast(this); - } - // - R& preferred_width(Dimension value) { - set("lp>pref-width", value); - return *reinterpret_cast(this); - } - // - R& height(Dimension value) { - set("lp>height", value); - return *reinterpret_cast(this); - } - // - R& min_height(Dimension value) { - set("lp>min-height", value); - return *reinterpret_cast(this); - } - // - R& max_height(Dimension value) { - set("lp>max-height", value); - return *reinterpret_cast(this); - } - // - R& preferred_height(Dimension value) { - set("lp>pref-height", value); - return *reinterpret_cast(this); - } - // - R& tooltip(std::string value) { - set("tooltip", value); - return *reinterpret_cast(this); - } - // The Element will be focused automatically the first time its Window is - // activated. - R& autofocus(bool value) { - set("autofocus", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& font(const char* name, int32_t size_px) { - set("font>name", name); - set("font>size", size_px); - return *reinterpret_cast(this); - } - // - R& font_name(std::string value) { - set("font>name", value); - return *reinterpret_cast(this); - } - // - R& font_size(Dimension value) { - set("font>size", value); - return *reinterpret_cast(this); - } - - R& clone(TextNode node) { - node_->Add(CloneNode(node).node_); - return *reinterpret_cast(this); - } - R& child(TextNode node) { - node_->Add(node.node_); - return *reinterpret_cast(this); - } - R& children() { return *reinterpret_cast(this); } - template - R& children(TextNode child_node, C... other_children) { - child(child_node); - children(other_children...); - return *reinterpret_cast(this); - } -}; -struct ButtonNode : public ElementNode { - using R = ButtonNode; - ButtonNode(const char* text = nullptr) : ElementNode("Button") { - if (text) { - this->text(text); - } - } - // - R& text(std::string value) { - set("text", value); - return *reinterpret_cast(this); - } - // - R& toggle_mode(bool value) { - set("toggle-mode", value ? 1 : 0); - return *reinterpret_cast(this); - } -}; -struct SelectInlineNode : public ElementNode { - using R = SelectInlineNode; - SelectInlineNode() : ElementNode("SelectInline") {} - SelectInlineNode(int32_t default_value, int32_t min_value, int32_t max_value) - : ElementNode("SelectInline") { - value(default_value); - min(min_value); - max(max_value); - } - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } - // - R& min(int32_t value) { - set("min", value); - return *reinterpret_cast(this); - } - // - R& max(int32_t value) { - set("max", value); - return *reinterpret_cast(this); - } -}; -struct LabelContainerNode : public ElementNode { - using R = LabelContainerNode; - LabelContainerNode(const char* text, std::vector children = {}) - : ElementNode("LabelContainer", {}, std::move(children)) { - if (text) { - this->text(text); - } - } - // - R& text(std::string value) { - set("text", value); - return *reinterpret_cast(this); - } -}; -struct TextBoxNode : public ElementNode { - using R = TextBoxNode; - TextBoxNode(const char* text = nullptr) : ElementNode("TextBox") { - if (text) { - this->text(text); - } - } - // - R& text(std::string value) { - set("text", value); - return *reinterpret_cast(this); - } - // - R& is_multiline(bool value) { - set("multiline", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& is_styling(bool value) { - set("styling", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& is_read_only(bool value) { - set("readonly", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& is_wrapping(bool value) { - set("wrap", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& adapt_to_content(bool value) { - set("adapt-to-content", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& virtual_width(Dimension value) { - set("virtual-width", value); - return *reinterpret_cast(this); - } - // - R& placeholder(std::string value) { - set("placeholder", value); - return *reinterpret_cast(this); - } - // - R& type(EditType value) { - set("type", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct LayoutNode : public ElementNode { - using R = LayoutNode; - LayoutNode(std::vector children = {}) - : ElementNode("Layout", {}, std::move(children)) {} - // - R& spacing(Dimension value) { - set("spacing", value); - return *reinterpret_cast(this); - } - // - R& size(LayoutSize value) { - set("size", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& position(LayoutPosition value) { - set("position", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& overflow(LayoutOverflow value) { - set("overflow", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& distribution(LayoutDistribution value) { - set("distribution", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& distribution_position(LayoutDistributionPosition value) { - set("distribution-position", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct ScrollContainerNode : public ElementNode { - using R = ScrollContainerNode; - ScrollContainerNode(std::vector children = {}) - : ElementNode("ScrollContainer", {}, std::move(children)) {} - // - R& adapt_content(bool value) { - set("adapt-content", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& adapt_to_content(bool value) { - set("adapt-to-content", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& scroll_mode(ScrollMode value) { - set("scroll-mode", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct TabContainerNode : public ElementNode { - using R = TabContainerNode; - TabContainerNode(std::vector children = {}) - : ElementNode("TabContainer", {}, std::move(children)) {} - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } - // - R& align(Align value) { - set("align", tb::to_string(value)); - return *reinterpret_cast(this); - } - // content? - // root? - TabContainerNode& tab(TextNode tab_button, TextNode tab_content) { - auto tabs_node = node_->GetNode("tabs", ParseNode::MissingPolicy::kCreate); - tabs_node->Add(tab_button.node_); - node_->Add(tab_content.node_); - return *this; - } -}; -struct ScrollBarNode : public ElementNode { - using R = ScrollBarNode; - ScrollBarNode() : ElementNode("ScrollBar") {} - // - R& value(float value) { - set("value", value); - return *reinterpret_cast(this); - } - // - R& axis(Axis value) { - set("axis", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct SliderNode : public ElementNode { - using R = SliderNode; - SliderNode() : ElementNode("Slider") {} - // - R& value(float value) { - set("value", value); - return *reinterpret_cast(this); - } - // - R& min(float value) { - set("min", value); - return *reinterpret_cast(this); - } - // - R& max(float value) { - set("max", value); - return *reinterpret_cast(this); - } - // - R& axis(Axis value) { - set("axis", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct Item { - std::string id; - std::string text; - Item(std::string id, std::string text) : id(id), text(text) {} -}; -template -struct ItemListElementNode : public ElementNode { - ItemListElementNode(const char* name) : ElementNode(name) {} - T& item(std::string text) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - auto node = ParseNode::Create("item"); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(text.c_str())); - node->Add(text_node); - items_node->Add(node); - return *reinterpret_cast(this); - } - T& item(std::string id, std::string text) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - auto node = ParseNode::Create("item"); - auto id_node = ParseNode::Create("id"); - id_node->TakeValue(tb::Value(id.c_str())); - node->Add(id_node); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(text.c_str())); - node->Add(text_node); - items_node->Add(node); - return *reinterpret_cast(this); - } - T& item(int32_t id, std::string text) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - auto node = ParseNode::Create("item"); - auto id_node = ParseNode::Create("id"); - id_node->TakeValue(tb::Value(id)); - node->Add(id_node); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(text.c_str())); - node->Add(text_node); - items_node->Add(node); - return *reinterpret_cast(this); - } - T& items(std::initializer_list items) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - for (auto& item : items) { - auto node = ParseNode::Create("item"); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(item.c_str())); - node->Add(text_node); - items_node->Add(node); - } - return *reinterpret_cast(this); - } - T& items(std::initializer_list> items) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - for (auto& item : items) { - auto node = ParseNode::Create("item"); - auto id_node = ParseNode::Create("id"); - id_node->TakeValue(tb::Value(item.first)); - node->Add(id_node); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(item.second.c_str())); - node->Add(text_node); - items_node->Add(node); - } - return *reinterpret_cast(this); - } - T& items(std::initializer_list items) { - auto items_node = - node_->GetNode("items", ParseNode::MissingPolicy::kCreate); - for (auto& item : items) { - auto node = ParseNode::Create("item"); - auto id_node = ParseNode::Create("id"); - id_node->TakeValue(tb::Value(item.id.c_str())); - node->Add(id_node); - auto text_node = ParseNode::Create("text"); - text_node->TakeValue(tb::Value(item.text.c_str())); - node->Add(text_node); - items_node->Add(node); - } - return *reinterpret_cast(this); - } -}; -struct SelectListNode : public ItemListElementNode { - using R = SelectListNode; - SelectListNode() : ItemListElementNode("SelectList") {} - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } -}; -struct SelectDropdownNode : public ItemListElementNode { - using R = SelectDropdownNode; - SelectDropdownNode() : ItemListElementNode("SelectDropdown") {} - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } -}; -struct CheckBoxNode : public ElementNode { - using R = CheckBoxNode; - CheckBoxNode() : ElementNode("CheckBox") {} - // - R& value(bool value) { - set("value", value ? 1 : 0); - return *reinterpret_cast(this); - } -}; -struct RadioButtonNode : public ElementNode { - using R = RadioButtonNode; - RadioButtonNode() : ElementNode("RadioButton") {} - // - R& value(bool value) { - set("value", value ? 1 : 0); - return *reinterpret_cast(this); - } -}; -struct LabelNode : public ElementNode { - using R = LabelNode; - LabelNode(const char* text = nullptr) : ElementNode("Label") { - if (text) { - this->text(text); - } - } - // - R& text(std::string value) { - set("text", value); - return *reinterpret_cast(this); - } - // - R& text_align(TextAlign value) { - set("text-align", tb::to_string(value)); - return *reinterpret_cast(this); - } -}; -struct SkinImageNode : public ElementNode { - using R = SkinImageNode; - SkinImageNode() : ElementNode("SkinImage") {} -}; -struct SeparatorNode : public ElementNode { - using R = SeparatorNode; - SeparatorNode() : ElementNode("Separator") {} -}; -struct ProgressSpinnerNode : public ElementNode { - using R = ProgressSpinnerNode; - ProgressSpinnerNode() : ElementNode("ProgressSpinner") {} - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } -}; -struct ContainerNode : public ElementNode { - using R = ContainerNode; - ContainerNode() : ElementNode("Container") {} -}; -struct SectionNode : public ElementNode { - using R = SectionNode; - SectionNode(const char* text = nullptr) : ElementNode("Section") { - if (text) { - this->text(text); - } - } - R& content(TextNode child) { return this->child(child); } - // - R& value(int32_t value) { - set("value", value); - return *reinterpret_cast(this); - } - // - R& text(std::string value) { - set("text", value); - return *reinterpret_cast(this); - } -}; -struct ToggleContainerNode : public ElementNode { - using R = ToggleContainerNode; - ToggleContainerNode() : ElementNode("ToggleContainer") {} - // - R& value(bool value) { - set("value", value ? 1 : 0); - return *reinterpret_cast(this); - } - // - R& toggle_action(ToggleAction value) { - set("toggle", tb::to_string(value)); - return *reinterpret_cast(this); - } - // - R& invert(bool value) { - set("invert", value ? 1 : 0); - return *reinterpret_cast(this); - } -}; -struct ImageElementNode : public ElementNode { - using R = ImageElementNode; - ImageElementNode(const char* filename = nullptr) - : ElementNode("ImageElement") { - if (filename) { - this->filename(filename); - } - } - // - R& filename(std::string value) { - set("filename", value); - return *reinterpret_cast(this); - } -}; -struct CloneNode : public TextNode { - using R = CloneNode; - CloneNode(TextNode& source) : TextNode(source.node_->GetName()) { - node_->GetValue().Copy(source.node_->GetValue()); - node_->CloneChildren(source.node_); - } -}; -} // namespace node -TextNode BuildSomeControl() { - using namespace node; - return LayoutNode() - .axis(Axis::kX) - .child(LabelNode("foo")) - .child(LabelNode("bar")); -} -TextNode BuildUI() { - using namespace node; - auto rep_tree = LayoutNode().axis(Axis::kX).child_list({ - LabelNode("item"), ButtonNode("button"), - }); - return LayoutNode() - .id("foo") - .position(LayoutPosition::kLeftTop) - .axis(Axis::kY) - .children( - TabContainerNode() - .gravity(Gravity::kAll) - .axis(Axis::kX) - .tab(ButtonNode("Foo"), CloneNode(rep_tree)) - .tab(ButtonNode("Foo0"), CloneNode(rep_tree)) - .tab(ButtonNode("Foo1"), LabelNode("bar1")), - SectionNode("controls:") - .content(LayoutNode() - .child(BuildSomeControl()) - .child(BuildSomeControl())), - LabelNode("distribution: preferred").width(32_dp).font_size(3_mm), - LayoutNode() - .distribution(LayoutDistribution::kPreferred) - .child(ButtonNode("tab 0")) - .child(TextBoxNode("foo").type(EditType::kPassword)) - .children(ToggleContainerNode() - .value(true) - .toggle_action(ToggleAction::kExpanded) - .child(TextBoxNode() - .placeholder("@search") - .gravity(Gravity::kLeftRight) - .type(EditType::kSearch)), - ButtonNode("fffoo").is_enabled(false)) - .child(ButtonNode("tab 0")) - .child(ButtonNode("tab 0")) - .clone(rep_tree) - .children(ButtonNode("tab 1"), ButtonNode("tab 2"), - ButtonNode("tab 3"), ButtonNode("tab 4")), - SelectInlineNode(4, 0, 40), SelectListNode().items({ - {1, "a"}, {2, "b"}, - }), - SelectListNode().item("a").item("id", "b").item(5, "c"), - SelectDropdownNode().value(1).items({ - Item("1", "a"), Item("2", "b"), - }), - SelectDropdownNode().value(1).items({ - {1, "a"}, {2, "b"}, - }), - SelectDropdownNode().value(1).items({ - "a", "b", "c", - })); -} -bool TurboBadgerControl::Create() { - if (!super::Create()) { - return false; - } - - // TODO(benvanik): setup renderer? - renderer_ = TBRendererGL4::Create(context()); - - if (!InitializeTurboBadger(renderer_.get())) { - XELOGE("Unable to initialize turbobadger"); - return false; - } - - // TODO(benvanik): setup elements. - root_element_ = std::make_unique(this); - root_element_->SetSkinBg(TBIDC("background")); - root_element_->set_rect({0, 0, 1000, 1000}); - - // ParseNode node; - ////node.ReadData("Label: text: \"distribution: preferred\""); - // auto tf = ParseNode::Create("Label"); - // auto tt = ParseNode::Create("text"); - // tt->TakeValue(tb::Value("hello")); - // tf->Add(tt); - // node.Add(tf); - // tb::g_elements_reader->LoadNodeTree(root_element_.get(), &node); - /* - Layout: position: left top, axis: y - Label: text: "distribution: preferred" - Layout: distribution: preferred - Button: text: tab 1 - Button: text: tab 2 - Button: text: tab 3 - Button: text: tab 4 - TextBox: placeholder: @search, gravity: left right, type: "search" - Label: text: "distribution: available" - Layout: distribution: available - Button: text: tab 1 - Button: text: tab 2 - Button: text: tab 3 - Button: text: tab 4 - TextBox: placeholder: @search, gravity: left right, type: "search" - */ - - auto n = BuildUI(); - ParseNode r; - r.Add(n.node_); - root_element_.get()->LoadNodeTree(&r); - // n.node_->Clear(); - // delete n.node_; - - // Block animations during init. - tb::AnimationBlocker anim_blocker; - - // TODO(benvanik): dummy UI. - auto message_window = new tb::MessageWindow(root_element(), TBIDC("")); - message_window->Show("Title", "Hello!"); - - // tb::ShowDebugInfoSettingsWindow(root_element()); - - return true; -} - -void TurboBadgerControl::Destroy() { - tb::Shutdown(); - super::Destroy(); -} - -void TurboBadgerControl::OnLayout(xe::ui::UIEvent& e) { - super::OnLayout(e); - if (!root_element()) { - return; - } - // TODO(benvanik): subregion? - root_element()->set_rect({0, 0, width(), height()}); -} - -void TurboBadgerControl::OnPaint(xe::ui::UIEvent& e) { - super::OnPaint(e); - if (!root_element()) { - return; - } - - ++frame_count_; - ++fps_frame_count_; - uint64_t now_ns = xe::Clock::QueryHostSystemTime(); - if (now_ns > fps_update_time_ + 1000 * 10000) { - fps_ = uint32_t(fps_frame_count_ / - (double(now_ns - fps_update_time_) / 10000000.0)); - fps_update_time_ = now_ns; - fps_frame_count_ = 0; - } - - // Update TB (run animations, handle deferred input, etc). - tb::AnimationManager::Update(); - root_element()->InvokeProcessStates(); - root_element()->InvokeProcess(); - - renderer()->BeginPaint(width(), height()); - - // Render entire control hierarchy. - root_element()->InvokePaint(tb::Element::PaintProps()); - - // Render debug overlay. - root_element()->GetFont()->DrawString( - 5, 5, tb::Color(255, 0, 0), - tb::util::format_string("Frame %lld", frame_count_)); - if (kContinuousRepaint) { - root_element()->GetFont()->DrawString( - 5, 20, tb::Color(255, 0, 0), tb::util::format_string("FPS: %d", fps_)); - } - - renderer()->EndPaint(); - - // If animations are running, reinvalidate immediately. - if (tb::AnimationManager::HasAnimationsRunning()) { - root_element()->Invalidate(); - } - if (kContinuousRepaint) { - // Force an immediate repaint, always. - root_element()->Invalidate(); - } -} - -void TurboBadgerControl::OnGotFocus(xe::ui::UIEvent& e) { - super::OnGotFocus(e); -} - -void TurboBadgerControl::OnLostFocus(xe::ui::UIEvent& e) { - super::OnLostFocus(e); - modifier_shift_pressed_ = false; - modifier_cntrl_pressed_ = false; - modifier_alt_pressed_ = false; - modifier_super_pressed_ = false; - last_click_time_ = 0; -} - -tb::ModifierKeys TurboBadgerControl::GetModifierKeys() { - auto modifiers = tb::ModifierKeys::kNone; - if (modifier_shift_pressed_) { - modifiers |= tb::ModifierKeys::kShift; - } - if (modifier_cntrl_pressed_) { - modifiers |= tb::ModifierKeys::kCtrl; - } - if (modifier_alt_pressed_) { - modifiers |= tb::ModifierKeys::kAlt; - } - if (modifier_super_pressed_) { - modifiers |= tb::ModifierKeys::kSuper; - } - return modifiers; -} - -void TurboBadgerControl::OnKeyPress(xe::ui::KeyEvent& e, bool is_down) { - if (!root_element()) { - return; - } - auto special_key = tb::SpecialKey::kUndefined; - switch (e.key_code()) { - case 38: - special_key = tb::SpecialKey::kUp; - break; - case 39: - special_key = tb::SpecialKey::kRight; - break; - case 40: - special_key = tb::SpecialKey::kDown; - break; - case 37: - special_key = tb::SpecialKey::kLeft; - break; - case 112: - special_key = tb::SpecialKey::kF1; - break; - case 113: - special_key = tb::SpecialKey::kF2; - break; - case 114: - special_key = tb::SpecialKey::kF3; - break; - case 115: - special_key = tb::SpecialKey::kF4; - break; - case 116: - special_key = tb::SpecialKey::kF5; - break; - case 117: - special_key = tb::SpecialKey::kF6; - break; - case 118: - special_key = tb::SpecialKey::kF7; - break; - case 119: - special_key = tb::SpecialKey::kF8; - break; - case 120: - special_key = tb::SpecialKey::kF9; - break; - case 121: - special_key = tb::SpecialKey::kF10; - break; - case 122: - special_key = tb::SpecialKey::kF11; - break; - case 123: - special_key = tb::SpecialKey::kF12; - break; - case 33: - special_key = tb::SpecialKey::kPageUp; - break; - case 34: - special_key = tb::SpecialKey::kPageDown; - break; - case 36: - special_key = tb::SpecialKey::kHome; - break; - case 35: - special_key = tb::SpecialKey::kEnd; - break; - case 45: - special_key = tb::SpecialKey::kInsert; - break; - case 9: - special_key = tb::SpecialKey::kTab; - break; - case 46: - special_key = tb::SpecialKey::kDelete; - break; - case 8: - special_key = tb::SpecialKey::kBackspace; - break; - case 13: - special_key = tb::SpecialKey::kEnter; - break; - case 27: - special_key = tb::SpecialKey::kEsc; - break; - case 93: - if (!is_down && tb::Element::focused_element) { - tb::ElementEvent ev(tb::EventType::kContextMenu); - ev.modifierkeys = GetModifierKeys(); - tb::Element::focused_element->InvokeEvent(ev); - e.set_handled(true); - return; - } - break; - case 16: - modifier_shift_pressed_ = is_down; - break; - case 17: - modifier_cntrl_pressed_ = is_down; - break; - // case xx: - // // alt ?? - // modifier_alt_pressed_ = is_down; - // break; - case 91: - modifier_super_pressed_ = is_down; - break; - } - - if (!CheckShortcutKey(e, special_key, is_down)) { - e.set_handled(root_element()->InvokeKey( - special_key != tb::SpecialKey::kUndefined ? e.key_code() : 0, - special_key, GetModifierKeys(), is_down)); - } -} - -bool TurboBadgerControl::CheckShortcutKey(xe::ui::KeyEvent& e, - tb::SpecialKey special_key, - bool is_down) { - bool shortcut_key = modifier_cntrl_pressed_; - if (!tb::Element::focused_element || !is_down || !shortcut_key) { - return false; - } - bool reverse_key = modifier_shift_pressed_; - int upper_key = e.key_code(); - if (upper_key >= 'a' && upper_key <= 'z') { - upper_key += 'A' - 'a'; - } - tb::TBID id; - if (upper_key == 'X') { - id = TBIDC("cut"); - } else if (upper_key == 'C' || special_key == tb::SpecialKey::kInsert) { - id = TBIDC("copy"); - } else if (upper_key == 'V' || - (special_key == tb::SpecialKey::kInsert && reverse_key)) { - id = TBIDC("paste"); - } else if (upper_key == 'A') { - id = TBIDC("selectall"); - } else if (upper_key == 'Z' || upper_key == 'Y') { - bool undo = upper_key == 'Z'; - if (reverse_key) { - undo = !undo; - } - id = undo ? TBIDC("undo") : TBIDC("redo"); - } else if (upper_key == 'N') { - id = TBIDC("new"); - } else if (upper_key == 'O') { - id = TBIDC("open"); - } else if (upper_key == 'S') { - id = TBIDC("save"); - } else if (upper_key == 'W') { - id = TBIDC("close"); - } else if (special_key == tb::SpecialKey::kPageUp) { - id = TBIDC("prev_doc"); - } else if (special_key == tb::SpecialKey::kPageDown) { - id = TBIDC("next_doc"); - } else { - return false; - } - - tb::ElementEvent ev(tb::EventType::kShortcut); - ev.modifierkeys = GetModifierKeys(); - ev.ref_id = id; - if (!tb::Element::focused_element->InvokeEvent(ev)) { - return false; - } - e.set_handled(true); - return true; -} - -void TurboBadgerControl::OnKeyDown(xe::ui::KeyEvent& e) { - super::OnKeyDown(e); - OnKeyPress(e, true); -} - -void TurboBadgerControl::OnKeyUp(xe::ui::KeyEvent& e) { - super::OnKeyUp(e); - OnKeyPress(e, false); -} - -void TurboBadgerControl::OnMouseDown(xe::ui::MouseEvent& e) { - super::OnMouseDown(e); - if (!root_element()) { - return; - } - // TODO(benvanik): more button types. - if (e.button() == xe::ui::MouseEvent::Button::kLeft) { - // Simulated click count support. - // TODO(benvanik): move into Control? - uint64_t now = xe::Clock::QueryHostUptimeMillis(); - if (now < last_click_time_ + kDoubleClickDelayMillis) { - double distance_moved = std::sqrt(std::pow(e.x() - last_click_x_, 2.0) + - std::pow(e.y() - last_click_y_, 2.0)); - if (distance_moved < kDoubleClickDistance) { - ++last_click_counter_; - } else { - last_click_counter_ = 1; - } - } else { - last_click_counter_ = 1; - } - last_click_x_ = e.x(); - last_click_y_ = e.y(); - last_click_time_ = now; - - e.set_handled(root_element()->InvokePointerDown( - e.x(), e.y(), last_click_counter_, GetModifierKeys(), kTouch)); - } -} - -void TurboBadgerControl::OnMouseMove(xe::ui::MouseEvent& e) { - super::OnMouseMove(e); - if (!root_element()) { - return; - } - root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch); - e.set_handled(true); -} - -void TurboBadgerControl::OnMouseUp(xe::ui::MouseEvent& e) { - super::OnMouseUp(e); - if (!root_element()) { - return; - } - if (e.button() == xe::ui::MouseEvent::Button::kLeft) { - e.set_handled(root_element()->InvokePointerUp(e.x(), e.y(), - GetModifierKeys(), kTouch)); - } else if (e.button() == xe::ui::MouseEvent::Button::kRight) { - root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch); - if (tb::Element::hovered_element) { - int x = e.x(); - int y = e.y(); - tb::Element::hovered_element->ConvertFromRoot(x, y); - tb::ElementEvent ev(tb::EventType::kContextMenu, x, y, kTouch, - GetModifierKeys()); - tb::Element::hovered_element->InvokeEvent(ev); - } - e.set_handled(true); - } -} - -void TurboBadgerControl::OnMouseWheel(xe::ui::MouseEvent& e) { - super::OnMouseWheel(e); - if (!root_element()) { - return; - } - e.set_handled(root_element()->InvokeWheel( - e.x(), e.y(), e.dx(), -e.dy() / kMouseWheelDetent, GetModifierKeys())); -} - -} // namespace ui -} // namespace debug -} // namespace xe diff --git a/src/xenia/debug/ui/turbo_badger_control.h b/src/xenia/debug/ui/turbo_badger_control.h deleted file mode 100644 index 8d670b52a..000000000 --- a/src/xenia/debug/ui/turbo_badger_control.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - ****************************************************************************** - * 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_TURBO_BADGER_CONTROL_H_ -#define XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_ - -#include - -#include "xenia/ui/gl/wgl_control.h" -#include "xenia/ui/loop.h" - -#include "third_party/turbobadger/src/tb/element.h" - -namespace xe { -namespace debug { -namespace ui { - -class TurboBadgerControl : public xe::ui::gl::WGLControl { - public: - TurboBadgerControl(xe::ui::Loop* loop); - ~TurboBadgerControl() override; - - static bool InitializeTurboBadger(tb::graphics::Renderer* renderer); - static void ShutdownTurboBadger(); - - tb::graphics::Renderer* renderer() const { return renderer_.get(); } - tb::Element* root_element() const { return root_element_.get(); } - - protected: - using super = xe::ui::gl::WGLControl; - - bool Create() override; - void Destroy() override; - - void OnLayout(xe::ui::UIEvent& e) override; - void OnPaint(xe::ui::UIEvent& e) override; - - void OnGotFocus(xe::ui::UIEvent& e) override; - void OnLostFocus(xe::ui::UIEvent& e) override; - - tb::ModifierKeys GetModifierKeys(); - - void OnKeyPress(xe::ui::KeyEvent& e, bool is_down); - bool CheckShortcutKey(xe::ui::KeyEvent& e, tb::SpecialKey special_key, - bool is_down); - void OnKeyDown(xe::ui::KeyEvent& e) override; - void OnKeyUp(xe::ui::KeyEvent& e) override; - - void OnMouseDown(xe::ui::MouseEvent& e) override; - void OnMouseMove(xe::ui::MouseEvent& e) override; - void OnMouseUp(xe::ui::MouseEvent& e) override; - void OnMouseWheel(xe::ui::MouseEvent& e) override; - - std::unique_ptr renderer_; - std::unique_ptr root_element_; - - uint32_t frame_count_ = 0; - uint32_t fps_ = 0; - uint64_t fps_update_time_ = 0; - uint64_t fps_frame_count_ = 0; - - bool modifier_shift_pressed_ = false; - bool modifier_cntrl_pressed_ = false; - bool modifier_alt_pressed_ = false; - bool modifier_super_pressed_ = false; - uint64_t last_click_time_ = 0; - int last_click_x_ = 0; - int last_click_y_ = 0; - int last_click_counter_ = 0; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_ diff --git a/src/xenia/debug/ui/turbo_badger_renderer.h b/src/xenia/debug/ui/turbo_badger_renderer.h deleted file mode 100644 index 48e874c1a..000000000 --- a/src/xenia/debug/ui/turbo_badger_renderer.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - ****************************************************************************** - * 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_TURBO_BADGER_RENDERER_H_ -#define XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_ - -#include -#include - -#include "xenia/ui/gl/circular_buffer.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/gl/gl.h" - -#include "third_party/turbobadger/src/tb/graphics/batching_renderer.h" - -namespace xe { -namespace debug { -namespace ui { - -class TBRendererGL4 : public tb::graphics::BatchingRenderer { - public: - TBRendererGL4(xe::ui::gl::GLContext* context); - ~TBRendererGL4() override; - - static std::unique_ptr Create(xe::ui::gl::GLContext* context); - - void BeginPaint(int render_target_w, int render_target_h) override; - void EndPaint() override; - - tb::graphics::Bitmap* CreateBitmap(int width, int height, - uint32_t* data) override; - - void RenderBatch(Batch* batch) override; - void SetClipRect(const tb::Rect& rect) override; - - private: - class TBBitmapGL4; - - bool Initialize(); - void Flush(); - - xe::ui::gl::GLContext* context_ = nullptr; - - GLuint program_ = 0; - GLuint vao_ = 0; - xe::ui::gl::CircularBuffer vertex_buffer_; - - static const size_t kMaxCommands = 512; - struct { - GLenum prim_type; - size_t vertex_offset; - size_t vertex_count; - } draw_commands_[kMaxCommands] = {0}; - uint32_t draw_command_count_ = 0; - TBBitmapGL4* current_bitmap_ = nullptr; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_ diff --git a/src/xenia/debug/ui/xe-debug-ui.vcxproj b/src/xenia/debug/ui/xe-debug-ui.vcxproj index 43210310c..ebac6c8f5 100644 --- a/src/xenia/debug/ui/xe-debug-ui.vcxproj +++ b/src/xenia/debug/ui/xe-debug-ui.vcxproj @@ -77,15 +77,11 @@ - - - - diff --git a/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters b/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters index 13e4aef1f..eabdda76d 100644 --- a/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters +++ b/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters @@ -16,6 +16,9 @@ {9a5724c2-5473-4d53-93b4-26531201f38d} + + {f0ac4999-4700-4b41-b73d-c6dc8b23c5e6} + @@ -27,12 +30,6 @@ src\xenia\debug\ui - - src\xenia\debug\ui - - - src\xenia\debug\ui - @@ -47,11 +44,5 @@ src\xenia\debug\ui - - src\xenia\debug\ui - - - src\xenia\debug\ui - \ No newline at end of file diff --git a/src/xenia/ui/control.h b/src/xenia/ui/control.h index 8c13866a8..becdc17b6 100644 --- a/src/xenia/ui/control.h +++ b/src/xenia/ui/control.h @@ -87,7 +87,7 @@ class Control { protected: explicit Control(uint32_t flags); - virtual bool Create() = 0; + virtual bool Create() { return true; } virtual void Destroy() {} virtual void OnCreate() {} diff --git a/src/xenia/ui/elemental_control.cc b/src/xenia/ui/elemental_control.cc new file mode 100644 index 000000000..1033f956f --- /dev/null +++ b/src/xenia/ui/elemental_control.cc @@ -0,0 +1,478 @@ +/** + ****************************************************************************** + * 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 "xenia/ui/elemental_control.h" + +#include "el/animation_manager.h" +#include "el/elemental_forms.h" +#include "el/text/font_manager.h" +#include "xenia/base/assert.h" +#include "xenia/base/clock.h" +#include "xenia/base/logging.h" + +namespace xe { +namespace ui { + +constexpr bool kContinuousRepaint = false; + +// Enables long press behaviors (context menu, etc). +constexpr bool kTouch = false; + +constexpr uint64_t kDoubleClickDelayMillis = 600; +constexpr double kDoubleClickDistance = 5; + +constexpr int32_t kMouseWheelDetent = 120; + +class RootElement : public el::Element { + public: + RootElement(ElementalControl* owner) : owner_(owner) {} + void OnInvalid() override { owner_->Invalidate(); } + + private: + ElementalControl* owner_ = nullptr; +}; + +bool ElementalControl::InitializeElemental(el::graphics::Renderer* renderer) { + static bool has_initialized = false; + if (has_initialized) { + return true; + } + has_initialized = true; + + if (!el::Initialize( + renderer, + "third_party/turbobadger/resources/language/lng_en.tb.txt")) { + XELOGE("Failed to initialize turbobadger core"); + return false; + } + + // Load the default skin, and override skin that contains the graphics + // specific to the demo. + if (!el::Skin::get()->Load( + "third_party/elemental-forms/resources/default_skin/skin.tb.txt", + "third_party/elemental-forms/testbed/resources/skin/skin.tb.txt")) { + XELOGE("Failed to load turbobadger skin"); + return false; + } + +// Register font renderers. +#ifdef EL_FONT_RENDERER_TBBF + void register_tbbf_font_renderer(); + register_tbbf_font_renderer(); +#endif +#ifdef EL_FONT_RENDERER_STB + void register_stb_font_renderer(); + register_stb_font_renderer(); +#endif +#ifdef EL_FONT_RENDERER_FREETYPE + void register_freetype_font_renderer(); + register_freetype_font_renderer(); +#endif + auto font_manager = el::text::FontManager::get(); + +// Add fonts we can use to the font manager. +#if defined(EL_FONT_RENDERER_STB) || defined(EL_FONT_RENDERER_FREETYPE) + font_manager->AddFontInfo("third_party/elemental-forms/resources/vera.ttf", + "Default"); +#endif +#ifdef EL_FONT_RENDERER_TBBF + font_manager->AddFontInfo( + "third_party/elemental-forms/resources/default_font/" + "segoe_white_with_shadow.tb.txt", + "Default"); +#endif + + // Set the default font description for elements to one of the fonts we just + // added. + el::FontDescription fd; + fd.set_id(TBIDC("Default")); + fd.set_size(el::Skin::get()->dimension_converter()->DpToPx(14)); + font_manager->set_default_font_description(fd); + + // Create the font now. + auto font = + font_manager->CreateFontFace(font_manager->default_font_description()); + return true; +} + +ElementalControl::ElementalControl(Loop* loop, uint32_t flags) : super(flags) {} + +ElementalControl::~ElementalControl() = default; + +bool ElementalControl::Create() { + if (!super::Create()) { + return false; + } + + // Create subclass renderer (GL, etc). + renderer_ = CreateRenderer(); + + // Initialize elemental. + // TODO(benvanik): once? Do we care about multiple controls? + if (!InitializeElemental(renderer_.get())) { + XELOGE("Unable to initialize turbobadger"); + return false; + } + + // TODO(benvanik): setup elements. + root_element_ = std::make_unique(this); + root_element_->set_background_skin(TBIDC("background")); + root_element_->set_rect({0, 0, 1000, 1000}); + + // Block animations during init. + el::AnimationBlocker anim_blocker; + + // TODO(benvanik): dummy UI. + auto message_window = new el::MessageWindow(root_element(), TBIDC("")); + message_window->Show("Title", "Hello!"); + + // el::ShowDebugInfoSettingsWindow(root_element()); + + return true; +} + +void ElementalControl::Destroy() { + el::Shutdown(); + super::Destroy(); +} + +void ElementalControl::OnLayout(UIEvent& e) { + super::OnLayout(e); + if (!root_element()) { + return; + } + // TODO(benvanik): subregion? + root_element()->set_rect({0, 0, width(), height()}); +} + +void ElementalControl::OnPaint(UIEvent& e) { + super::OnPaint(e); + if (!root_element()) { + return; + } + + ++frame_count_; + ++fps_frame_count_; + uint64_t now_ns = xe::Clock::QueryHostSystemTime(); + if (now_ns > fps_update_time_ + 1000 * 10000) { + fps_ = uint32_t(fps_frame_count_ / + (double(now_ns - fps_update_time_) / 10000000.0)); + fps_update_time_ = now_ns; + fps_frame_count_ = 0; + } + + // Update TB (run animations, handle deferred input, etc). + el::AnimationManager::Update(); + root_element()->InvokeProcessStates(); + root_element()->InvokeProcess(); + + renderer()->BeginPaint(width(), height()); + + // Render entire control hierarchy. + root_element()->InvokePaint(el::Element::PaintProps()); + + // Render debug overlay. + root_element()->computed_font()->DrawString( + 5, 5, el::Color(255, 0, 0), + el::format_string("Frame %lld", frame_count_)); + if (kContinuousRepaint) { + root_element()->computed_font()->DrawString( + 5, 20, el::Color(255, 0, 0), el::format_string("FPS: %d", fps_)); + } + + renderer()->EndPaint(); + + // If animations are running, reinvalidate immediately. + if (el::AnimationManager::has_running_animations()) { + root_element()->Invalidate(); + } + if (kContinuousRepaint) { + // Force an immediate repaint, always. + root_element()->Invalidate(); + } +} + +void ElementalControl::OnGotFocus(UIEvent& e) { super::OnGotFocus(e); } + +void ElementalControl::OnLostFocus(UIEvent& e) { + super::OnLostFocus(e); + modifier_shift_pressed_ = false; + modifier_cntrl_pressed_ = false; + modifier_alt_pressed_ = false; + modifier_super_pressed_ = false; + last_click_time_ = 0; +} + +el::ModifierKeys ElementalControl::GetModifierKeys() { + auto modifiers = el::ModifierKeys::kNone; + if (modifier_shift_pressed_) { + modifiers |= el::ModifierKeys::kShift; + } + if (modifier_cntrl_pressed_) { + modifiers |= el::ModifierKeys::kCtrl; + } + if (modifier_alt_pressed_) { + modifiers |= el::ModifierKeys::kAlt; + } + if (modifier_super_pressed_) { + modifiers |= el::ModifierKeys::kSuper; + } + return modifiers; +} + +void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down) { + if (!root_element()) { + return; + } + auto special_key = el::SpecialKey::kUndefined; + switch (e.key_code()) { + case 38: + special_key = el::SpecialKey::kUp; + break; + case 39: + special_key = el::SpecialKey::kRight; + break; + case 40: + special_key = el::SpecialKey::kDown; + break; + case 37: + special_key = el::SpecialKey::kLeft; + break; + case 112: + special_key = el::SpecialKey::kF1; + break; + case 113: + special_key = el::SpecialKey::kF2; + break; + case 114: + special_key = el::SpecialKey::kF3; + break; + case 115: + special_key = el::SpecialKey::kF4; + break; + case 116: + special_key = el::SpecialKey::kF5; + break; + case 117: + special_key = el::SpecialKey::kF6; + break; + case 118: + special_key = el::SpecialKey::kF7; + break; + case 119: + special_key = el::SpecialKey::kF8; + break; + case 120: + special_key = el::SpecialKey::kF9; + break; + case 121: + special_key = el::SpecialKey::kF10; + break; + case 122: + special_key = el::SpecialKey::kF11; + break; + case 123: + special_key = el::SpecialKey::kF12; + break; + case 33: + special_key = el::SpecialKey::kPageUp; + break; + case 34: + special_key = el::SpecialKey::kPageDown; + break; + case 36: + special_key = el::SpecialKey::kHome; + break; + case 35: + special_key = el::SpecialKey::kEnd; + break; + case 45: + special_key = el::SpecialKey::kInsert; + break; + case 9: + special_key = el::SpecialKey::kTab; + break; + case 46: + special_key = el::SpecialKey::kDelete; + break; + case 8: + special_key = el::SpecialKey::kBackspace; + break; + case 13: + special_key = el::SpecialKey::kEnter; + break; + case 27: + special_key = el::SpecialKey::kEsc; + break; + case 93: + if (!is_down && el::Element::focused_element) { + el::Event ev(el::EventType::kContextMenu); + ev.modifierkeys = GetModifierKeys(); + el::Element::focused_element->InvokeEvent(ev); + e.set_handled(true); + return; + } + break; + case 16: + modifier_shift_pressed_ = is_down; + break; + case 17: + modifier_cntrl_pressed_ = is_down; + break; + // case xx: + // // alt ?? + // modifier_alt_pressed_ = is_down; + // break; + case 91: + modifier_super_pressed_ = is_down; + break; + } + + if (!CheckShortcutKey(e, special_key, is_down)) { + e.set_handled(root_element()->InvokeKey( + special_key != el::SpecialKey::kUndefined ? e.key_code() : 0, + special_key, GetModifierKeys(), is_down)); + } +} + +bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, + bool is_down) { + bool shortcut_key = modifier_cntrl_pressed_; + if (!el::Element::focused_element || !is_down || !shortcut_key) { + return false; + } + bool reverse_key = modifier_shift_pressed_; + int upper_key = e.key_code(); + if (upper_key >= 'a' && upper_key <= 'z') { + upper_key += 'A' - 'a'; + } + el::TBID id; + if (upper_key == 'X') { + id = TBIDC("cut"); + } else if (upper_key == 'C' || special_key == el::SpecialKey::kInsert) { + id = TBIDC("copy"); + } else if (upper_key == 'V' || + (special_key == el::SpecialKey::kInsert && reverse_key)) { + id = TBIDC("paste"); + } else if (upper_key == 'A') { + id = TBIDC("selectall"); + } else if (upper_key == 'Z' || upper_key == 'Y') { + bool undo = upper_key == 'Z'; + if (reverse_key) { + undo = !undo; + } + id = undo ? TBIDC("undo") : TBIDC("redo"); + } else if (upper_key == 'N') { + id = TBIDC("new"); + } else if (upper_key == 'O') { + id = TBIDC("open"); + } else if (upper_key == 'S') { + id = TBIDC("save"); + } else if (upper_key == 'W') { + id = TBIDC("close"); + } else if (special_key == el::SpecialKey::kPageUp) { + id = TBIDC("prev_doc"); + } else if (special_key == el::SpecialKey::kPageDown) { + id = TBIDC("next_doc"); + } else { + return false; + } + + el::Event ev(el::EventType::kShortcut); + ev.modifierkeys = GetModifierKeys(); + ev.ref_id = id; + if (!el::Element::focused_element->InvokeEvent(ev)) { + return false; + } + e.set_handled(true); + return true; +} + +void ElementalControl::OnKeyDown(KeyEvent& e) { + super::OnKeyDown(e); + OnKeyPress(e, true); +} + +void ElementalControl::OnKeyUp(KeyEvent& e) { + super::OnKeyUp(e); + OnKeyPress(e, false); +} + +void ElementalControl::OnMouseDown(MouseEvent& e) { + super::OnMouseDown(e); + if (!root_element()) { + return; + } + // TODO(benvanik): more button types. + if (e.button() == MouseEvent::Button::kLeft) { + // Simulated click count support. + // TODO(benvanik): move into Control? + uint64_t now = xe::Clock::QueryHostUptimeMillis(); + if (now < last_click_time_ + kDoubleClickDelayMillis) { + double distance_moved = std::sqrt(std::pow(e.x() - last_click_x_, 2.0) + + std::pow(e.y() - last_click_y_, 2.0)); + if (distance_moved < kDoubleClickDistance) { + ++last_click_counter_; + } else { + last_click_counter_ = 1; + } + } else { + last_click_counter_ = 1; + } + last_click_x_ = e.x(); + last_click_y_ = e.y(); + last_click_time_ = now; + + e.set_handled(root_element()->InvokePointerDown( + e.x(), e.y(), last_click_counter_, GetModifierKeys(), kTouch)); + } +} + +void ElementalControl::OnMouseMove(MouseEvent& e) { + super::OnMouseMove(e); + if (!root_element()) { + return; + } + root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch); + e.set_handled(true); +} + +void ElementalControl::OnMouseUp(MouseEvent& e) { + super::OnMouseUp(e); + if (!root_element()) { + return; + } + if (e.button() == MouseEvent::Button::kLeft) { + e.set_handled(root_element()->InvokePointerUp(e.x(), e.y(), + GetModifierKeys(), kTouch)); + } else if (e.button() == MouseEvent::Button::kRight) { + root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch); + if (el::Element::hovered_element) { + int x = e.x(); + int y = e.y(); + el::Element::hovered_element->ConvertFromRoot(x, y); + el::Event ev(el::EventType::kContextMenu, x, y, kTouch, + GetModifierKeys()); + el::Element::hovered_element->InvokeEvent(ev); + } + e.set_handled(true); + } +} + +void ElementalControl::OnMouseWheel(MouseEvent& e) { + super::OnMouseWheel(e); + if (!root_element()) { + return; + } + e.set_handled(root_element()->InvokeWheel( + e.x(), e.y(), e.dx(), -e.dy() / kMouseWheelDetent, GetModifierKeys())); +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/elemental_control.h b/src/xenia/ui/elemental_control.h new file mode 100644 index 000000000..f39fa2c12 --- /dev/null +++ b/src/xenia/ui/elemental_control.h @@ -0,0 +1,80 @@ +/** + ****************************************************************************** + * 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_UI_ELEMENTAL_CONTROL_H_ +#define XENIA_UI_ELEMENTAL_CONTROL_H_ + +#include + +#include "el/element.h" +#include "el/graphics/renderer.h" +#include "xenia/ui/control.h" +#include "xenia/ui/loop.h" +#include "xenia/ui/platform.h" + +namespace xe { +namespace ui { + +class ElementalControl : public PlatformControl { + public: + ElementalControl(Loop* loop, uint32_t flags); + ~ElementalControl() override; + + el::graphics::Renderer* renderer() const { return renderer_.get(); } + el::Element* root_element() const { return root_element_.get(); } + + protected: + using super = PlatformControl; + + bool InitializeElemental(el::graphics::Renderer* renderer); + virtual std::unique_ptr CreateRenderer() = 0; + + bool Create() override; + void Destroy() override; + + void OnLayout(UIEvent& e) override; + void OnPaint(UIEvent& e) override; + + void OnGotFocus(UIEvent& e) override; + void OnLostFocus(UIEvent& e) override; + + el::ModifierKeys GetModifierKeys(); + + void OnKeyPress(KeyEvent& e, bool is_down); + bool CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, bool is_down); + void OnKeyDown(KeyEvent& e) override; + void OnKeyUp(KeyEvent& e) override; + + void OnMouseDown(MouseEvent& e) override; + void OnMouseMove(MouseEvent& e) override; + void OnMouseUp(MouseEvent& e) override; + void OnMouseWheel(MouseEvent& e) override; + + std::unique_ptr renderer_; + std::unique_ptr root_element_; + + uint32_t frame_count_ = 0; + uint32_t fps_ = 0; + uint64_t fps_update_time_ = 0; + uint64_t fps_frame_count_ = 0; + + bool modifier_shift_pressed_ = false; + bool modifier_cntrl_pressed_ = false; + bool modifier_alt_pressed_ = false; + bool modifier_super_pressed_ = false; + uint64_t last_click_time_ = 0; + int last_click_x_ = 0; + int last_click_y_ = 0; + int last_click_counter_ = 0; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_ELEMENTAL_CONTROL_H_ diff --git a/src/xenia/debug/ui/turbo_badger_renderer.cc b/src/xenia/ui/gl/wgl_elemental_control.cc similarity index 53% rename from src/xenia/debug/ui/turbo_badger_renderer.cc rename to src/xenia/ui/gl/wgl_elemental_control.cc index a40f9de39..b1093a194 100644 --- a/src/xenia/debug/ui/turbo_badger_renderer.cc +++ b/src/xenia/ui/gl/wgl_elemental_control.cc @@ -7,44 +7,195 @@ ****************************************************************************** */ -#include "xenia/debug/ui/turbo_badger_renderer.h" +#include "xenia/ui/gl/wgl_elemental_control.h" +#include + +#include "el/graphics/batching_renderer.h" +#include "el/graphics/bitmap_fragment.h" +#include "el/util/math.h" #include "xenia/base/assert.h" #include "xenia/base/logging.h" - -#include "third_party/turbobadger/src/tb/graphics/bitmap_fragment.h" -#include "third_party/turbobadger/src/tb/util/math.h" +#include "xenia/profiling.h" +#include "xenia/ui/gl/circular_buffer.h" +#include "xenia/ui/gl/gl_context.h" +#include "xenia/ui/gl/gl.h" namespace xe { -namespace debug { namespace ui { +namespace gl { -using namespace tb; - -class TBRendererGL4::TBBitmapGL4 : public tb::graphics::Bitmap { +class GL4BatchingRenderer : public el::graphics::BatchingRenderer { public: - TBBitmapGL4(xe::ui::gl::GLContext* context, TBRendererGL4* renderer); - ~TBBitmapGL4(); + GL4BatchingRenderer(GLContext* context); + ~GL4BatchingRenderer() override; - bool Init(int width, int height, uint32_t* data); - int Width() override { return width_; } - int Height() override { return height_; } - void SetData(uint32_t* data) override; + static std::unique_ptr Create(GLContext* context); - xe::ui::gl::GLContext* context_ = nullptr; - TBRendererGL4* renderer_ = nullptr; - int width_ = 0; - int height_ = 0; - GLuint handle_ = 0; - GLuint64 gpu_handle_ = 0; + void BeginPaint(int render_target_w, int render_target_h) override; + void EndPaint() override; + + std::unique_ptr CreateBitmap(int width, int height, + uint32_t* data) override; + + void RenderBatch(Batch* batch) override; + void set_clip_rect(const el::Rect& rect) override; + + private: + class GL4Bitmap : public el::graphics::Bitmap { + public: + GL4Bitmap(GLContext* context, GL4BatchingRenderer* renderer); + ~GL4Bitmap(); + + bool Init(int width, int height, uint32_t* data); + int width() override { return width_; } + int height() override { return height_; } + void set_data(uint32_t* data) override; + + GLContext* context_ = nullptr; + GL4BatchingRenderer* renderer_ = nullptr; + int width_ = 0; + int height_ = 0; + GLuint handle_ = 0; + GLuint64 gpu_handle_ = 0; + }; + + bool Initialize(); + void Flush(); + + GLContext* context_ = nullptr; + + GLuint program_ = 0; + GLuint vao_ = 0; + CircularBuffer vertex_buffer_; + + static const size_t kMaxCommands = 512; + struct { + GLenum prim_type; + size_t vertex_offset; + size_t vertex_count; + } draw_commands_[kMaxCommands] = {0}; + uint32_t draw_command_count_ = 0; + GL4Bitmap* current_bitmap_ = nullptr; }; -TBRendererGL4::TBBitmapGL4::TBBitmapGL4(xe::ui::gl::GLContext* context, - TBRendererGL4* renderer) +WGLElementalControl::WGLElementalControl(Loop* loop) + : ElementalControl(loop, Flags::kFlagOwnPaint), loop_(loop) {} + +WGLElementalControl::~WGLElementalControl() = default; + +bool WGLElementalControl::Create() { + HINSTANCE hInstance = GetModuleHandle(nullptr); + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wcex.lpfnWndProc = Win32Control::WndProcThunk; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = nullptr; + wcex.hIconSm = nullptr; + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = L"XeniaWglElementalClass"; + if (!RegisterClassEx(&wcex)) { + XELOGE("WGL RegisterClassEx failed"); + return false; + } + + // Create window. + DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY; + DWORD window_ex_style = 0; + hwnd_ = + CreateWindowEx(window_ex_style, L"XeniaWglElementalClass", L"Xenia", + window_style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, parent_hwnd(), nullptr, hInstance, this); + if (!hwnd_) { + XELOGE("WGL CreateWindow failed"); + return false; + } + + if (!context_.Initialize(hwnd_)) { + XEFATAL( + "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " + "you have the latest drivers for your GPU and that it supports OpenGL " + "4.5. See http://xenia.jp/faq/ for more information."); + return false; + } + + context_.AssertExtensionsPresent(); + + SetFocus(hwnd_); + + return super::Create(); +} + +std::unique_ptr WGLElementalControl::CreateRenderer() { + return GL4BatchingRenderer::Create(&context_); +} + +void WGLElementalControl::OnLayout(UIEvent& e) { + Control::ResizeToFill(); + super::OnLayout(e); +} + +LRESULT WGLElementalControl::WndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) { + switch (message) { + case WM_PAINT: { + invalidated_ = false; + ValidateRect(hWnd, nullptr); + SCOPE_profile_cpu_i("gpu", "xe::ui::gl::WGLElementalControl::WM_PAINT"); + { + GLContextLock context_lock(&context_); + + float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f}; + glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color); + + if (current_paint_callback_) { + current_paint_callback_(); + current_paint_callback_ = nullptr; + } + + UIEvent e(this); + OnPaint(e); + + // TODO(benvanik): profiler present. + Profiler::Present(); + } + { + SCOPE_profile_cpu_i("gpu", + "xe::ui::gl::WGLElementalControl::SwapBuffers"); + SwapBuffers(context_.dc()); + } + return 0; + } break; + } + return Win32Control::WndProc(hWnd, message, wParam, lParam); +} + +void WGLElementalControl::SynchronousRepaint( + std::function paint_callback) { + SCOPE_profile_cpu_f("gpu"); + + // We may already have a pending paint from a previous request when we + // were minimized. We just overwrite it. + current_paint_callback_ = std::move(paint_callback); + + // This will not return until the WM_PAINT has completed. + // Note, if we are minimized this won't do anything. + RedrawWindow(hwnd(), nullptr, nullptr, + RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN); +} + +GL4BatchingRenderer::GL4Bitmap::GL4Bitmap(GLContext* context, + GL4BatchingRenderer* renderer) : context_(context), renderer_(renderer) {} -TBRendererGL4::TBBitmapGL4::~TBBitmapGL4() { - xe::ui::gl::GLContextLock lock(context_); +GL4BatchingRenderer::GL4Bitmap::~GL4Bitmap() { + GLContextLock lock(context_); // Must flush and unbind before we delete the texture. renderer_->FlushBitmap(this); @@ -52,9 +203,10 @@ TBRendererGL4::TBBitmapGL4::~TBBitmapGL4() { glDeleteTextures(1, &handle_); } -bool TBRendererGL4::TBBitmapGL4::Init(int width, int height, uint32_t* data) { - assert(width == tb::util::GetNearestPowerOfTwo(width)); - assert(height == tb::util::GetNearestPowerOfTwo(height)); +bool GL4BatchingRenderer::GL4Bitmap::Init(int width, int height, + uint32_t* data) { + assert(width == el::util::GetNearestPowerOfTwo(width)); + assert(height == el::util::GetNearestPowerOfTwo(height)); width_ = width; height_ = height; @@ -68,32 +220,30 @@ bool TBRendererGL4::TBBitmapGL4::Init(int width, int height, uint32_t* data) { gpu_handle_ = glGetTextureHandleARB(handle_); glMakeTextureHandleResidentARB(gpu_handle_); - SetData(data); + set_data(data); return true; } -void TBRendererGL4::TBBitmapGL4::SetData(uint32_t* data) { +void GL4BatchingRenderer::GL4Bitmap::set_data(uint32_t* data) { renderer_->FlushBitmap(this); glTextureSubImage2D(handle_, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, data); } -TBRendererGL4::TBRendererGL4(xe::ui::gl::GLContext* context) - : context_(context), - vertex_buffer_(graphics::BatchingRenderer::kVertexBatchSize * - sizeof(Vertex)) {} +GL4BatchingRenderer::GL4BatchingRenderer(GLContext* context) + : context_(context), vertex_buffer_(kVertexBatchSize * sizeof(Vertex)) {} -TBRendererGL4::~TBRendererGL4() { - xe::ui::gl::GLContextLock lock(context_); +GL4BatchingRenderer::~GL4BatchingRenderer() { + GLContextLock lock(context_); vertex_buffer_.Shutdown(); glDeleteVertexArrays(1, &vao_); glDeleteProgram(program_); } -std::unique_ptr TBRendererGL4::Create( - xe::ui::gl::GLContext* context) { - auto renderer = std::make_unique(context); +std::unique_ptr GL4BatchingRenderer::Create( + GLContext* context) { + auto renderer = std::make_unique(context); if (!renderer->Initialize()) { XELOGE("Failed to initialize TurboBadger GL4 renderer"); return nullptr; @@ -101,7 +251,7 @@ std::unique_ptr TBRendererGL4::Create( return renderer; } -bool TBRendererGL4::Initialize() { +bool GL4BatchingRenderer::Initialize() { if (!vertex_buffer_.Initialize()) { XELOGE("Failed to initialize circular buffer"); return false; @@ -188,23 +338,23 @@ void main() { \n\ return true; } -graphics::Bitmap* TBRendererGL4::CreateBitmap(int width, int height, - uint32_t* data) { - auto bitmap = std::make_unique(context_, this); +std::unique_ptr GL4BatchingRenderer::CreateBitmap( + int width, int height, uint32_t* data) { + auto bitmap = std::make_unique(context_, this); if (!bitmap->Init(width, height, data)) { return nullptr; } - return bitmap.release(); + return std::unique_ptr(bitmap.release()); } -void TBRendererGL4::SetClipRect(const Rect& rect) { +void GL4BatchingRenderer::set_clip_rect(const el::Rect& rect) { Flush(); glScissor(clip_rect_.x, screen_rect_.h - (clip_rect_.y + clip_rect_.h), clip_rect_.w, clip_rect_.h); } -void TBRendererGL4::BeginPaint(int render_target_w, int render_target_h) { - tb::graphics::BatchingRenderer::BeginPaint(render_target_w, render_target_h); +void GL4BatchingRenderer::BeginPaint(int render_target_w, int render_target_h) { + BatchingRenderer::BeginPaint(render_target_w, render_target_h); glEnablei(GL_BLEND, 0); glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -237,8 +387,8 @@ void TBRendererGL4::BeginPaint(int render_target_w, int render_target_h) { glBindVertexArray(vao_); } -void TBRendererGL4::EndPaint() { - tb::graphics::BatchingRenderer::EndPaint(); +void GL4BatchingRenderer::EndPaint() { + BatchingRenderer::EndPaint(); Flush(); @@ -246,7 +396,7 @@ void TBRendererGL4::EndPaint() { glBindVertexArray(0); } -void TBRendererGL4::Flush() { +void GL4BatchingRenderer::Flush() { if (!draw_command_count_) { return; } @@ -261,8 +411,8 @@ void TBRendererGL4::Flush() { vertex_buffer_.WaitUntilClean(); } -void TBRendererGL4::RenderBatch(Batch* batch) { - auto bitmap = static_cast(batch->bitmap); +void GL4BatchingRenderer::RenderBatch(Batch* batch) { + auto bitmap = static_cast(batch->bitmap); if (bitmap != current_bitmap_) { current_bitmap_ = bitmap; Flush(); @@ -300,6 +450,6 @@ void TBRendererGL4::RenderBatch(Batch* batch) { vertex_buffer_.Commit(std::move(allocation)); } +} // namespace gl } // namespace ui -} // namespace debug } // namespace xe diff --git a/src/xenia/ui/gl/wgl_elemental_control.h b/src/xenia/ui/gl/wgl_elemental_control.h new file mode 100644 index 000000000..deb0627e7 --- /dev/null +++ b/src/xenia/ui/gl/wgl_elemental_control.h @@ -0,0 +1,55 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2014 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ +#define XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ + +#include + +#include "xenia/base/threading.h" +#include "xenia/ui/elemental_control.h" +#include "xenia/ui/gl/gl_context.h" +#include "xenia/ui/loop.h" + +namespace xe { +namespace ui { +namespace gl { + +class WGLElementalControl : public ElementalControl { + public: + WGLElementalControl(Loop* loop); + ~WGLElementalControl() override; + + GLContext* context() { return &context_; } + + void SynchronousRepaint(std::function paint_callback); + + protected: + using super = ElementalControl; + + std::unique_ptr CreateRenderer() override; + + bool Create() override; + + void OnLayout(UIEvent& e) override; + + LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) override; + + private: + Loop* loop_; + GLContext context_; + std::function current_paint_callback_; +}; + +} // namespace gl +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ diff --git a/src/xenia/ui/platform.h b/src/xenia/ui/platform.h index 6ba60300f..6aaf04811 100644 --- a/src/xenia/ui/platform.h +++ b/src/xenia/ui/platform.h @@ -12,6 +12,7 @@ #define XENIA_UI_PLATFORM_H_ // TODO(benvanik): only on windows. +#include "xenia/ui/win32/win32_control.h" #include "xenia/ui/win32/win32_file_picker.h" #include "xenia/ui/win32/win32_loop.h" #include "xenia/ui/win32/win32_menu_item.h" @@ -20,6 +21,7 @@ namespace xe { namespace ui { +using PlatformControl = xe::ui::win32::Win32Control; using PlatformFilePicker = xe::ui::win32::Win32FilePicker; using PlatformLoop = xe::ui::win32::Win32Loop; using PlatformMenu = xe::ui::win32::Win32MenuItem; diff --git a/third_party/elemental-forms b/third_party/elemental-forms new file mode 160000 index 000000000..b7322a960 --- /dev/null +++ b/third_party/elemental-forms @@ -0,0 +1 @@ +Subproject commit b7322a960063620938d76156c0ce4a4328788e71 diff --git a/third_party/turbobadger b/third_party/turbobadger deleted file mode 160000 index 5075e6995..000000000 --- a/third_party/turbobadger +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5075e6995e7946c6d14cd86a813281f19f5853a9 diff --git a/xenia.sln b/xenia.sln index 5729c0965..db7d23ddd 100644 --- a/xenia.sln +++ b/xenia.sln @@ -14,6 +14,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libxenia", "libxenia.vcxpro ProjectSection(ProjectDependencies) = postProject {AE4AF147-715A-4C24-8BFA-136332DED28F} = {AE4AF147-715A-4C24-8BFA-136332DED28F} {93533067-6449-4691-88A8-026EBCFDCA97} = {93533067-6449-4691-88A8-026EBCFDCA97} + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD} = {156102D7-F2DD-4618-B2EB-2DFE607EE6DD} {838020F9-94AA-4314-996D-69B923C45D39} = {838020F9-94AA-4314-996D-69B923C45D39} EndProjectSection EndProject @@ -52,8 +53,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xe-gpu-trace-viewer", "src\ {838020F9-94AA-4314-996D-69B923C45D39} = {838020F9-94AA-4314-996D-69B923C45D39} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libturbobadger", "third_party\turbobadger\libturbobadger.vcxproj", "{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xe-debug-ui", "src\xenia\debug\ui\xe-debug-ui.vcxproj", "{C5BA52F0-C86B-4817-921C-CCA257FC04BE}" ProjectSection(ProjectDependencies) = postProject {AE4AF147-715A-4C24-8BFA-136332DED28F} = {AE4AF147-715A-4C24-8BFA-136332DED28F} @@ -64,6 +63,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xe-debug-ui", "src\xenia\de EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libxenia-base", "libxenia-base.vcxproj", "{93533067-6449-4691-88A8-026EBCFDCA97}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libelemental", "third_party\elemental-forms\libelemental.vcxproj", "{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elemental-forms-demo", "third_party\elemental-forms\testbed\elemental-testbed.vcxproj", "{C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Checked|x64 = Checked|x64 @@ -125,14 +128,8 @@ Global {21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Debug|x64.Build.0 = Debug|x64 {21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.ActiveCfg = Release|x64 {21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.Build.0 = Release|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.ActiveCfg = Debug|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.Build.0 = Debug|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.ActiveCfg = Debug|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.Build.0 = Debug|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.ActiveCfg = Release|x64 - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.Build.0 = Release|x64 - {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.ActiveCfg = Release|x64 - {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.Build.0 = Release|x64 + {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.ActiveCfg = Debug|x64 + {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.Build.0 = Debug|x64 {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.ActiveCfg = Debug|x64 {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.Build.0 = Debug|x64 {C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Release|x64.ActiveCfg = Release|x64 @@ -143,6 +140,18 @@ Global {93533067-6449-4691-88A8-026EBCFDCA97}.Debug|x64.Build.0 = Debug|x64 {93533067-6449-4691-88A8-026EBCFDCA97}.Release|x64.ActiveCfg = Release|x64 {93533067-6449-4691-88A8-026EBCFDCA97}.Release|x64.Build.0 = Release|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.ActiveCfg = Checked|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.Build.0 = Checked|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.ActiveCfg = Debug|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.Build.0 = Debug|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.ActiveCfg = Release|x64 + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.Build.0 = Release|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Checked|x64.ActiveCfg = Checked|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Checked|x64.Build.0 = Checked|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Debug|x64.ActiveCfg = Debug|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Debug|x64.Build.0 = Debug|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Release|x64.ActiveCfg = Release|x64 + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -155,7 +164,8 @@ Global {6EC54AD0-4F5B-48D9-B820-43DF2F0DC83C} = {9C5BDD9E-831B-4AEE-957F-0E88ADED79C6} {9B8AC22F-9147-490F-BE03-3B8BA31990A8} = {9C5BDD9E-831B-4AEE-957F-0E88ADED79C6} {21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC} = {345BD157-B21D-4989-9CE4-FA3C90FFC095} - {156102D7-F2DD-4618-B2EB-2DFE607EE6DD} = {FCCBE57F-ECAE-420A-8A82-4B85F722C272} {C5BA52F0-C86B-4817-921C-CCA257FC04BE} = {345BD157-B21D-4989-9CE4-FA3C90FFC095} + {156102D7-F2DD-4618-B2EB-2DFE607EE6DD} = {FCCBE57F-ECAE-420A-8A82-4B85F722C272} + {C3FDE1FE-1FCB-4156-BB37-2E38F5C2DFE7} = {9C5BDD9E-831B-4AEE-957F-0E88ADED79C6} EndGlobalSection EndGlobal