diff --git a/.gitmodules b/.gitmodules index f8f217492..76084a5f0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,9 +10,6 @@ [submodule "third_party/capstone"] path = third_party/capstone url = https://github.com/xenia-project/capstone.git -[submodule "third_party/elemental-forms"] - path = third_party/elemental-forms - url = https://github.com/xenia-project/elemental-forms.git [submodule "build_tools"] path = build_tools url = https://github.com/xenia-project/build-tools.git diff --git a/premake5.lua b/premake5.lua index 1f07221eb..80d0b8c1e 100644 --- a/premake5.lua +++ b/premake5.lua @@ -159,7 +159,6 @@ solution("xenia") -- Include third party files first so they don't have to deal with gflags. include("third_party/capstone.lua") - include("third_party/elemental-forms") include("third_party/glew.lua") include("third_party/imgui.lua") include("third_party/libav.lua") diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index d6e4dc760..d1e966c49 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -9,7 +9,7 @@ #include "xenia/app/emulator_window.h" -#include "third_party/elemental-forms/src/el/elements.h" +#include "third_party/imgui/imgui.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" #include "xenia/base/platform.h" @@ -17,6 +17,8 @@ #include "xenia/base/threading.h" #include "xenia/emulator.h" #include "xenia/gpu/graphics_system.h" +#include "xenia/ui/imgui_dialog.h" +#include "xenia/ui/imgui_drawer.h" namespace xe { namespace app { @@ -221,11 +223,10 @@ void EmulatorWindow::CpuTimeScalarSetDouble() { void EmulatorWindow::CpuBreakIntoDebugger() { auto debugger = emulator()->debugger(); if (!debugger) { - auto message_form = new el::MessageForm(window_->root_element(), - TBIDC("debug_error_window")); - message_form->Show("Xenia Debugger", - "Xenia must be launched with the --debug flag in order " - "to enable debugging."); + xe::ui::ImGuiDialog::ShowMessageBox(window_.get(), "Xenia Debugger", + "Xenia must be launched with the " + "--debug flag in order to enable " + "debugging."); return; } if (debugger->execution_state() == debug::ExecutionState::kRunning) { diff --git a/src/xenia/app/main_resources.rc b/src/xenia/app/main_resources.rc index 5db04ad85..c60d50b55 100644 --- a/src/xenia/app/main_resources.rc +++ b/src/xenia/app/main_resources.rc @@ -1,5 +1 @@ //{{NO_DEPENDENCIES}} - -#include "third_party\\elemental-forms\\resources.rc" - -//IDR_xe_ui_main_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png" diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 234270a81..5d1f49714 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -8,7 +8,6 @@ project("xenia-app") targetname("xenia") language("C++") links({ - "elemental-forms", "gflags", "imgui", "xenia-apu", @@ -33,7 +32,6 @@ project("xenia-app") defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() @@ -46,7 +44,6 @@ project("xenia-app") }) resincludedirs({ project_root, - project_root.."/third_party/elemental-forms", }) filter("platforms:Windows") diff --git a/src/xenia/base/premake5.lua b/src/xenia/base/premake5.lua index 378ed3fc1..21ed7720e 100644 --- a/src/xenia/base/premake5.lua +++ b/src/xenia/base/premake5.lua @@ -8,7 +8,6 @@ project("xenia-base") defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() diff --git a/src/xenia/cpu/backend/x64/x64_assembler.cc b/src/xenia/cpu/backend/x64/x64_assembler.cc index 887785124..122a374d6 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.cc +++ b/src/xenia/cpu/backend/x64/x64_assembler.cc @@ -98,8 +98,8 @@ bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder, } function->set_debug_info(std::move(debug_info)); - static_cast(function) - ->Setup(reinterpret_cast(machine_code), code_size); + static_cast(function)->Setup( + reinterpret_cast(machine_code), code_size); return true; } diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 4049560d1..2b7ab26f2 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -465,8 +465,8 @@ bool XexModule::SetupLibraryImports(const char* name, GuestFunction::ExternHandler handler = nullptr; if (kernel_export) { if (kernel_export->function_data.trampoline) { - handler = (GuestFunction::ExternHandler) - kernel_export->function_data.trampoline; + handler = (GuestFunction::ExternHandler)kernel_export + ->function_data.trampoline; } else { handler = (GuestFunction::ExternHandler)kernel_export->function_data.shim; diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc index 8ded22b76..3b6ebaa62 100644 --- a/src/xenia/debug/ui/debug_window.cc +++ b/src/xenia/debug/ui/debug_window.cc @@ -17,7 +17,6 @@ #include "third_party/capstone/include/capstone/capstone.h" #include "third_party/capstone/include/capstone/x86.h" -#include "third_party/elemental-forms/src/el/util/clipboard.h" #include "third_party/imgui/imgui.h" #include "third_party/imgui/imgui_internal.h" #include "xenia/base/clock.h" @@ -87,10 +86,6 @@ bool DebugWindow::Initialize() { return false; } - window_->on_closed.AddListener([this](UIEvent* e) { - // Kill now while we have a GL context. - imgui_drawer_.reset(); - }); loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); }); // Main menu. @@ -110,9 +105,8 @@ bool DebugWindow::Initialize() { auto provider = emulator_->display_window()->context()->provider(); window_->set_context(provider->CreateContext(window_.get())); - // Setup ImGui. - imgui_drawer_ = std::make_unique(window_.get()); - imgui_drawer_->SetupDefaultInput(); + // Enable imgui input. + window_->set_imgui_input_enabled(true); window_->on_painting.AddListener([this](UIEvent* e) { DrawFrame(); }); @@ -125,16 +119,7 @@ bool DebugWindow::Initialize() { void DebugWindow::DrawFrame() { xe::ui::GraphicsContextLock lock(window_->context()); - auto& io = ImGui::GetIO(); - auto current_tick_count = Clock::QueryHostTickCount(); - io.DeltaTime = (current_tick_count - last_draw_tick_count_) / - static_cast(Clock::host_tick_frequency()); - last_draw_tick_count_ = current_tick_count; - - io.DisplaySize = ImVec2(static_cast(window_->width()), - static_cast(window_->height())); - - ImGui::NewFrame(); + auto& io = window_->imgui_drawer()->GetIO(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0)); ImGui::Begin("main_window", nullptr, @@ -174,7 +159,7 @@ void DebugWindow::DrawFrame() { ImGui::InvisibleButton("##vsplitter0", ImVec2(kSplitterWidth, top_panes_height)); if (ImGui::IsItemActive()) { - function_pane_width += ImGui::GetIO().MouseDelta.x; + function_pane_width += io.MouseDelta.x; function_pane_width = xe::clamp(function_pane_width, 30.0f, FLT_MAX); } ImGui::SameLine(); @@ -186,7 +171,7 @@ void DebugWindow::DrawFrame() { ImGui::InvisibleButton("##vsplitter1", ImVec2(kSplitterWidth, top_panes_height)); if (ImGui::IsItemActive()) { - source_pane_width += ImGui::GetIO().MouseDelta.x; + source_pane_width += io.MouseDelta.x; source_pane_width = xe::clamp(source_pane_width, 30.0f, FLT_MAX); } ImGui::SameLine(); @@ -198,7 +183,7 @@ void DebugWindow::DrawFrame() { ImGui::InvisibleButton("##vsplitter2", ImVec2(kSplitterWidth, top_panes_height)); if (ImGui::IsItemActive()) { - registers_pane_width += ImGui::GetIO().MouseDelta.x; + registers_pane_width += io.MouseDelta.x; registers_pane_width = xe::clamp(registers_pane_width, 30.0f, FLT_MAX); } ImGui::SameLine(); @@ -226,7 +211,7 @@ void DebugWindow::DrawFrame() { ImGui::EndChild(); ImGui::InvisibleButton("##hsplitter0", ImVec2(-1, kSplitterWidth)); if (ImGui::IsItemActive()) { - bottom_panes_height -= ImGui::GetIO().MouseDelta.y; + bottom_panes_height -= io.MouseDelta.y; bottom_panes_height = xe::clamp(bottom_panes_height, 30.0f, FLT_MAX); } ImGui::BeginChild("##log_pane", ImVec2(log_pane_width, bottom_panes_height), @@ -237,7 +222,7 @@ void DebugWindow::DrawFrame() { ImGui::InvisibleButton("##vsplitter3", ImVec2(kSplitterWidth, bottom_panes_height)); if (ImGui::IsItemActive()) { - breakpoints_pane_width -= ImGui::GetIO().MouseDelta.x; + breakpoints_pane_width -= io.MouseDelta.x; breakpoints_pane_width = xe::clamp(breakpoints_pane_width, 30.0f, FLT_MAX); } ImGui::SameLine(); @@ -397,7 +382,7 @@ void DebugWindow::DrawSourcePane() { ImGui::Dummy(ImVec2(16, 0)); ImGui::SameLine(); if (ImGui::Button("Copy")) { - el::util::Clipboard::SetText("TODO"); + // TODO(benvanik): move clipboard abstraction into ui? } ImGui::SameLine(); ImGui::Dummy(ImVec2(4, 0)); diff --git a/src/xenia/debug/ui/debug_window.h b/src/xenia/debug/ui/debug_window.h index 6ebc2f0bc..f62c644ab 100644 --- a/src/xenia/debug/ui/debug_window.h +++ b/src/xenia/debug/ui/debug_window.h @@ -21,12 +21,6 @@ #include "xenia/ui/window.h" #include "xenia/xbox.h" -namespace xe { -namespace ui { -class ImGuiDrawer; -} // namespace ui -} // namespace xe - namespace xe { namespace debug { namespace ui { @@ -95,7 +89,6 @@ class DebugWindow : public DebugListener { Debugger* debugger_ = nullptr; xe::ui::Loop* loop_ = nullptr; std::unique_ptr window_; - std::unique_ptr imgui_drawer_; uint64_t last_draw_tick_count_ = 0; uintptr_t capstone_handle_ = 0; diff --git a/src/xenia/debug/ui/premake5.lua b/src/xenia/debug/ui/premake5.lua index 7467afdcb..97658fb13 100644 --- a/src/xenia/debug/ui/premake5.lua +++ b/src/xenia/debug/ui/premake5.lua @@ -7,7 +7,6 @@ project("xenia-debug-ui") kind("StaticLib") language("C++") links({ - "elemental-forms", "glew", "imgui", "xenia-base", @@ -22,6 +21,5 @@ project("xenia-debug-ui") }) includedirs({ project_root.."/build_tools/third_party/gflags/src", - project_root.."/third_party/elemental-forms/src", }) local_platform_files() diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 745b85ea9..d6bdd393d 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -11,7 +11,6 @@ #include -#include "third_party/elemental-forms/src/el/elements.h" #include "xenia/apu/audio_system.h" #include "xenia/base/assert.h" #include "xenia/base/clock.h" @@ -28,6 +27,7 @@ #include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/memory.h" +#include "xenia/ui/imgui_dialog.h" #include "xenia/vfs/devices/disc_image_device.h" #include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/stfs_container_device.h" @@ -334,11 +334,9 @@ bool Emulator::ExceptionCallback(Exception* ex) { // Display a dialog telling the user the guest has crashed. display_window()->loop()->PostSynchronous([&]() { - auto message_form = new el::MessageForm(display_window()->root_element(), - TBIDC("crash_form")); - message_form->Show("Uh-oh!", - "The guest has crashed.\n\n" - "Xenia has now paused itself."); + xe::ui::ImGuiDialog::ShowMessageBox(display_window(), "Uh-oh!", + "The guest has crashed.\n\n" + "Xenia has now paused itself."); }); // Now suspend ourself (we should be a guest thread). diff --git a/src/xenia/gpu/gl4/premake5.lua b/src/xenia/gpu/gl4/premake5.lua index 2498a0fe1..838014a88 100644 --- a/src/xenia/gpu/gl4/premake5.lua +++ b/src/xenia/gpu/gl4/premake5.lua @@ -7,7 +7,6 @@ project("xenia-gpu-gl4") kind("StaticLib") language("C++") links({ - "elemental-forms", "glew", "xenia-base", "xenia-gpu", @@ -20,7 +19,6 @@ project("xenia-gpu-gl4") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() @@ -32,7 +30,6 @@ project("xenia-gpu-gl4-trace-viewer") kind("WindowedApp") language("C++") links({ - "elemental-forms", "gflags", "glew", "imgui", @@ -62,7 +59,6 @@ project("xenia-gpu-gl4-trace-viewer") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) files({ @@ -88,7 +84,6 @@ project("xenia-gpu-gl4-trace-dump") kind("ConsoleApp") language("C++") links({ - "elemental-forms", "gflags", "glew", "imgui", @@ -115,7 +110,6 @@ project("xenia-gpu-gl4-trace-dump") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) files({ diff --git a/src/xenia/gpu/premake5.lua b/src/xenia/gpu/premake5.lua index 9a40508f0..ef9dfa233 100644 --- a/src/xenia/gpu/premake5.lua +++ b/src/xenia/gpu/premake5.lua @@ -7,7 +7,6 @@ project("xenia-gpu") kind("StaticLib") language("C++") links({ - "elemental-forms", "spirv-tools", "xenia-base", "xenia-ui", @@ -18,12 +17,11 @@ project("xenia-gpu") defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/third_party/spirv-tools/external/include", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() - + group("src") project("xenia-gpu-shader-compiler") uuid("ad76d3e4-4c62-439b-a0f6-f83fcf0e83c5") diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc index f14516569..93cfc3f39 100644 --- a/src/xenia/gpu/trace_viewer.cc +++ b/src/xenia/gpu/trace_viewer.cc @@ -112,7 +112,6 @@ bool TraceViewer::Setup() { window_->on_closed.AddListener([&](xe::ui::UIEvent* e) { loop_->Quit(); XELOGI("User-initiated death!"); - imgui_drawer_.reset(); exit(1); }); loop_->on_quit.AddListener([&](xe::ui::UIEvent* e) { window_.reset(); }); @@ -130,6 +129,8 @@ bool TraceViewer::Setup() { memory_ = emulator_->memory(); graphics_system_ = emulator_->graphics_system(); + window_->set_imgui_input_enabled(true); + window_->on_key_char.AddListener([&](xe::ui::KeyEvent* e) { if (e->key_code() == 0x74 /* VK_F5 */) { graphics_system_->ClearCaches(); @@ -139,26 +140,9 @@ bool TraceViewer::Setup() { player_ = std::make_unique(loop_.get(), graphics_system_); - imgui_drawer_ = std::make_unique(window_.get()); - imgui_drawer_->SetupDefaultInput(); - window_->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = ImGui::GetIO(); - auto current_ticks = Clock::QueryHostTickCount(); - static uint64_t last_ticks = 0; - io.DeltaTime = - (current_ticks - last_ticks) / float(Clock::host_tick_frequency()); - last_ticks = current_ticks; - - io.DisplaySize = - ImVec2(float(e->target()->width()), float(e->target()->height())); - - ImGui::NewFrame(); - DrawUI(); - ImGui::Render(); - // Continuous paint. window_->Invalidate(); }); @@ -185,7 +169,6 @@ void TraceViewer::Run() { player_.reset(); emulator_.reset(); - imgui_drawer_.reset(); window_.reset(); loop_.reset(); } diff --git a/src/xenia/gpu/trace_viewer.h b/src/xenia/gpu/trace_viewer.h index 51db4a148..fa066414f 100644 --- a/src/xenia/gpu/trace_viewer.h +++ b/src/xenia/gpu/trace_viewer.h @@ -21,7 +21,6 @@ namespace xe { namespace ui { -class ImGuiDrawer; class Loop; class Window; } // namespace ui @@ -62,8 +61,6 @@ class TraceViewer { GraphicsSystem* graphics_system_ = nullptr; std::unique_ptr player_; - std::unique_ptr imgui_drawer_; - private: enum class ShaderDisplayType : int { kUcode, diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc index 04c8455a7..0f11a4922 100644 --- a/src/xenia/hid/hid_demo.cc +++ b/src/xenia/hid/hid_demo.cc @@ -34,7 +34,6 @@ DEFINE_string(hid, "any", "Input system. Use: [any, nop, winkey, xinput]"); namespace xe { namespace hid { -std::unique_ptr imgui_drawer_; std::unique_ptr input_system_; std::vector> CreateInputDrivers( @@ -88,7 +87,6 @@ int hid_demo_main(const std::vector& args) { window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) { loop->Quit(); XELOGI("User-initiated death!"); - imgui_drawer_.reset(); exit(1); }); loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); }); @@ -105,30 +103,18 @@ int hid_demo_main(const std::vector& args) { graphics_provider = CreateDemoGraphicsProvider(window.get()); window->set_context(graphics_provider->CreateContext(window.get())); - // Initialize the ImGui renderer we'll use. - imgui_drawer_ = std::make_unique(window.get()); - imgui_drawer_->SetupDefaultInput(); - // Initialize input system and all drivers. input_system_ = std::make_unique(window.get()); auto drivers = CreateInputDrivers(window.get()); for (size_t i = 0; i < drivers.size(); ++i) { input_system_->AddDriver(std::move(drivers[i])); } + + window->Invalidate(); }); window->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = ImGui::GetIO(); - auto current_tick_count = Clock::QueryHostTickCount(); - static uint64_t last_draw_tick_count = 0; - io.DeltaTime = (current_tick_count - last_draw_tick_count) / - static_cast(Clock::host_tick_frequency()); - last_draw_tick_count = current_tick_count; - - io.DisplaySize = ImVec2(static_cast(window->width()), - static_cast(window->height())); - - ImGui::NewFrame(); + auto& io = window->imgui_drawer()->GetIO(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0)); ImGui::Begin("main_window", nullptr, ImGuiWindowFlags_NoMove | @@ -146,8 +132,6 @@ int hid_demo_main(const std::vector& args) { ImGui::End(); ImGui::PopStyleVar(); - ImGui::Render(); - // Continuous paint. window->Invalidate(); }); @@ -155,7 +139,6 @@ int hid_demo_main(const std::vector& args) { // Wait until we are exited. loop->AwaitQuit(); - imgui_drawer_.reset(); input_system_.reset(); loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); }); diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index 7757abeea..d0826215e 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -23,7 +23,6 @@ project("xenia-hid-demo") kind("WindowedApp") language("C++") links({ - "elemental-forms", "gflags", "glew", "imgui", @@ -41,7 +40,6 @@ project("xenia-hid-demo") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) files({ @@ -49,11 +47,9 @@ project("xenia-hid-demo") project_root.."/src/xenia/base/main_"..platform_suffix..".cc", }) files({ - project_root.."/third_party/elemental-forms/resources.rc", }) resincludedirs({ project_root, - project_root.."/third_party/elemental-forms", }) filter("platforms:Windows") diff --git a/src/xenia/hid/winkey/premake5.lua b/src/xenia/hid/winkey/premake5.lua index 6c7c77e72..6899ed6be 100644 --- a/src/xenia/hid/winkey/premake5.lua +++ b/src/xenia/hid/winkey/premake5.lua @@ -15,6 +15,5 @@ project("xenia-hid-winkey") }) includedirs({ project_root.."/build_tools/third_party/gflags/src", - project_root.."/third_party/elemental-forms/src", }) local_platform_files() diff --git a/src/xenia/kernel/kernel_module.cc b/src/xenia/kernel/kernel_module.cc index d26d9aa53..b8b68e032 100644 --- a/src/xenia/kernel/kernel_module.cc +++ b/src/xenia/kernel/kernel_module.cc @@ -99,8 +99,8 @@ uint32_t KernelModule::GetProcAddressByOrdinal(uint16_t ordinal) { cpu::GuestFunction::ExternHandler handler = nullptr; if (export_entry->function_data.trampoline) { - handler = (cpu::GuestFunction::ExternHandler) - export_entry->function_data.trampoline; + handler = (cpu::GuestFunction::ExternHandler)export_entry + ->function_data.trampoline; } else { handler = (cpu::GuestFunction::ExternHandler)export_entry->function_data.shim; diff --git a/src/xenia/kernel/premake5.lua b/src/xenia/kernel/premake5.lua index e26ea9ccb..3393cc45d 100644 --- a/src/xenia/kernel/premake5.lua +++ b/src/xenia/kernel/premake5.lua @@ -7,7 +7,6 @@ project("xenia-kernel") kind("StaticLib") language("C++") links({ - "elemental-forms", "xenia-apu", "xenia-base", "xenia-cpu", @@ -19,7 +18,6 @@ project("xenia-kernel") defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) recursive_platform_files() diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index 074d6db47..9d7984e42 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -7,12 +7,13 @@ ****************************************************************************** */ -#include "el/elemental_forms.h" +#include "third_party/imgui/imgui.h" #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_private.h" +#include "xenia/ui/imgui_dialog.h" #include "xenia/ui/window.h" #include "xenia/xbox.h" @@ -20,81 +21,66 @@ namespace xe { namespace kernel { namespace xam { +std::atomic xam_dialogs_shown_ = 0; + SHIM_CALL XamIsUIActive_shim(PPCContext* ppc_context, KernelState* kernel_state) { XELOGD("XamIsUIActive()"); - SHIM_SET_RETURN_32(el::ModalForm::is_any_visible()); + SHIM_SET_RETURN_32(xam_dialogs_shown_ > 0 ? 1 : 0); } -class MessageBoxWindow : public el::ModalForm { +class MessageBoxDialog : public xe::ui::ImGuiDialog { public: - explicit MessageBoxWindow(xe::threading::Fence* fence) - : ModalForm([fence]() { fence->Signal(); }) {} - ~MessageBoxWindow() override = default; - - // TODO(benvanik): icon. - void Show(el::Element* root_element, std::wstring title, std::wstring message, - std::vector buttons, uint32_t default_button, - uint32_t* out_chosen_button) { - title_ = std::move(title); - message_ = std::move(message); - buttons_ = std::move(buttons); - default_button_ = default_button; - out_chosen_button_ = out_chosen_button; - - // In case we are cancelled, always set default button. - *out_chosen_button_ = default_button; - - ModalForm::Show(root_element); - - EnsureFocus(); - } - - protected: - void BuildUI() override { - using namespace el::dsl; // NOLINT(build/namespaces) - - set_text(xe::to_string(title_)); - - auto button_bar = LayoutBoxNode().distribution_position( - LayoutDistributionPosition::kRightBottom); - for (int32_t i = 0; i < buttons_.size(); ++i) { - button_bar.child(ButtonNode(xe::to_string(buttons_[i]).c_str()) - .id(100 + i) - .data(100 + i)); + MessageBoxDialog(xe::ui::Window* window, std::wstring title, + std::wstring description, std::vector buttons, + uint32_t default_button, uint32_t* out_chosen_button) + : ImGuiDialog(window), + title_(xe::to_string(title)), + description_(xe::to_string(description)), + buttons_(std::move(buttons)), + default_button_(default_button), + out_chosen_button_(out_chosen_button) { + if (out_chosen_button) { + *out_chosen_button = default_button; } - - LoadNodeTree( - LayoutBoxNode() - .axis(Axis::kY) - .gravity(Gravity::kAll) - .position(LayoutPosition::kLeftTop) - .distribution(LayoutDistribution::kAvailable) - .distribution_position(LayoutDistributionPosition::kLeftTop) - .child(LabelNode(xe::to_string(message_).c_str())) - .child(button_bar)); - - auto default_button = GetElementById(100 + default_button_); - el::Button::set_auto_focus_state(true); - default_button->set_focus(el::FocusReason::kNavigation); } - bool OnEvent(const el::Event& ev) override { - if (ev.target->IsOfType() && - (ev.type == el::EventType::kClick || - ev.special_key == el::SpecialKey::kEnter)) { - int data_value = ev.target->data.as_integer(); - if (data_value) { - *out_chosen_button_ = data_value - 100; + void OnDraw(ImGuiIO& io) override { + bool first_draw = false; + if (!has_opened_) { + ImGui::OpenPopup(title_.c_str()); + has_opened_ = true; + first_draw = true; + } + if (ImGui::BeginPopupModal(title_.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text(description_.c_str()); + if (first_draw) { + ImGui::SetKeyboardFocusHere(); } - Die(); - return true; + for (size_t i = 0; i < buttons_.size(); ++i) { + auto button_name = xe::to_string(buttons_[i]); + if (ImGui::Button(button_name.c_str())) { + if (out_chosen_button_) { + *out_chosen_button_ = static_cast(i); + } + ImGui::CloseCurrentPopup(); + Close(); + } + ImGui::SameLine(); + } + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::EndPopup(); + } else { + Close(); } - return ModalForm::OnEvent(ev); } - std::wstring title_; - std::wstring message_; + private: + bool has_opened_ = false; + std::string title_; + std::string description_; std::vector buttons_; uint32_t default_button_ = 0; uint32_t* out_chosen_button_ = nullptr; @@ -142,8 +128,7 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, auto display_window = kernel_state->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { - auto root_element = display_window->root_element(); - auto window = new MessageBoxWindow(&fence); + // TODO(benvanik): setup icon states. switch (flags & 0xF) { case 0: // config.pszMainIcon = nullptr; @@ -158,10 +143,13 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, // config.pszMainIcon = TD_INFORMATION_ICON; break; } - window->Show(root_element, title, text, buttons, active_button, - &chosen_button); + (new MessageBoxDialog(display_window, title, text, buttons, active_button, + &chosen_button)) + ->Then(&fence); }); + ++xam_dialogs_shown_; fence.Wait(); + --xam_dialogs_shown_; } SHIM_SET_MEM_32(result_ptr, chosen_button); @@ -169,102 +157,81 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); } -class KeyboardInputWindow : public el::ModalForm { +class KeyboardInputDialog : public xe::ui::ImGuiDialog { public: - explicit KeyboardInputWindow(xe::threading::Fence* fence) - : ModalForm([fence]() { fence->Signal(); }) {} - ~KeyboardInputWindow() override = default; - - // TODO(benvanik): icon. - void Show(el::Element* root_element, std::wstring title, - std::wstring description, std::wstring default_text, - std::wstring* out_text, size_t max_length) { - title_ = std::move(title); - description_ = std::move(description); - default_text_ = std::move(default_text); - out_text_ = out_text; - max_length_ = max_length; - - // Incase we're cancelled, set as the default text. - if (out_text) { - *out_text = default_text; + KeyboardInputDialog(xe::ui::Window* window, std::wstring title, + std::wstring description, std::wstring default_text, + std::wstring* out_text, size_t max_length) + : ImGuiDialog(window), + title_(xe::to_string(title)), + description_(xe::to_string(description)), + default_text_(xe::to_string(default_text)), + out_text_(out_text), + max_length_(max_length) { + if (out_text_) { + *out_text_ = default_text; } - - ModalForm::Show(root_element); - - EnsureFocus(); + text_buffer_.resize(max_length); + std::strncpy(text_buffer_.data(), default_text_.c_str(), + std::min(text_buffer_.size() - 1, default_text_.size())); } - protected: - void BuildUI() override { - using namespace el::dsl; // NOLINT(build/namespaces) - - set_text(xe::to_string(title_)); - - LoadNodeTree( - LayoutBoxNode() - .axis(Axis::kY) - .gravity(Gravity::kAll) - .position(LayoutPosition::kLeftTop) - .min_width(300) - .distribution(LayoutDistribution::kAvailable) - .distribution_position(LayoutDistributionPosition::kLeftTop) - .child(LabelNode(xe::to_string(description_).c_str())) - .child(LayoutBoxNode() - .axis(Axis::kX) - .distribution(LayoutDistribution::kGravity) - .child(TextBoxNode() - .id("text_input") - .gravity(Gravity::kLeftRight) - .placeholder(xe::to_string(default_text_)) - .min_width(150) - .autofocus(true))) - .child(LayoutBoxNode() - .distribution_position( - LayoutDistributionPosition::kRightBottom) - .child(ButtonNode("OK").id("ok_button")) - .child(ButtonNode("Cancel").id("cancel_button")))); - } - - bool OnEvent(const el::Event& ev) override { - if (ev.special_key == el::SpecialKey::kEnter || - (ev.target->id() == TBIDC("ok_button") && - ev.type == el::EventType::kClick)) { - // Pressed enter or clicked OK - // Grab the text. - if (out_text_) { - *out_text_ = - xe::to_wstring(GetElementById("text_input")->text()); + void OnDraw(ImGuiIO& io) override { + bool first_draw = false; + if (!has_opened_) { + ImGui::OpenPopup(title_.c_str()); + has_opened_ = true; + first_draw = true; + } + if (ImGui::BeginPopupModal(title_.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextWrapped(description_.c_str()); + if (first_draw) { + ImGui::SetKeyboardFocusHere(); } - - Die(); - return true; - } else if (ev.target->id() == TBIDC("cancel_button") && - ev.type == el::EventType::kClick) { - // Cancel. - Die(); - return true; + if (ImGui::InputText("##body", text_buffer_.data(), text_buffer_.size(), + ImGuiInputTextFlags_EnterReturnsTrue)) { + if (out_text_) { + *out_text_ = xe::to_wstring(text_buffer_.data()); + } + ImGui::CloseCurrentPopup(); + Close(); + } + if (ImGui::Button("OK")) { + if (out_text_) { + *out_text_ = xe::to_wstring(text_buffer_.data()); + } + ImGui::CloseCurrentPopup(); + Close(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + Close(); + } + ImGui::Spacing(); + ImGui::EndPopup(); + } else { + Close(); } - - return ModalForm::OnEvent(ev); } - std::wstring title_; - std::wstring description_; - std::wstring default_text_; + private: + bool has_opened_ = false; + std::string title_; + std::string description_; + std::string default_text_; std::wstring* out_text_ = nullptr; + std::vector text_buffer_; size_t max_length_ = 0; }; // http://www.se7ensins.com/forums/threads/release-how-to-use-xshowkeyboardui-release.906568/ -dword_result_t XamShowKeyboardUI(dword_t r3, dword_t flags, +dword_result_t XamShowKeyboardUI(dword_t user_index, dword_t flags, lpwstring_t default_text, lpwstring_t title, lpwstring_t description, lpwstring_t buffer, dword_t buffer_length, pointer_t overlapped) { - // Unknown parameters. I've only seen zero. - assert_zero(r3); - if (!buffer) { return X_ERROR_INVALID_PARAMETER; } @@ -289,14 +256,15 @@ dword_result_t XamShowKeyboardUI(dword_t r3, dword_t flags, auto display_window = kernel_state()->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { - auto root_element = display_window->root_element(); - auto window = new KeyboardInputWindow(&fence); - window->Show(root_element, title ? title.value() : L"", - description ? description.value() : L"", - default_text ? default_text.value() : L"", &out_text, - buffer_length); + (new KeyboardInputDialog(display_window, title ? title.value() : L"", + description ? description.value() : L"", + default_text ? default_text.value() : L"", + &out_text, buffer_length)) + ->Then(&fence); }); + ++xam_dialogs_shown_; fence.Wait(); + --xam_dialogs_shown_; // Zero the output buffer. std::memset(buffer, 0, buffer_length * 2); @@ -341,46 +309,6 @@ dword_result_t XamShowDeviceSelectorUI(dword_t user_index, dword_t content_type, } DECLARE_XAM_EXPORT(XamShowDeviceSelectorUI, ExportTag::kImplemented); -class DirtyDiscWindow : public el::ModalForm { - public: - explicit DirtyDiscWindow(xe::threading::Fence* fence) - : ModalForm([fence]() { fence->Signal(); }) {} - ~DirtyDiscWindow() override = default; - - protected: - void BuildUI() override { - using namespace el::dsl; // NOLINT(build/namespaces) - - set_text("Disc Read Error"); - - LoadNodeTree( - LayoutBoxNode() - .axis(Axis::kY) - .gravity(Gravity::kAll) - .position(LayoutPosition::kLeftTop) - .distribution(LayoutDistribution::kAvailable) - .distribution_position(LayoutDistributionPosition::kLeftTop) - .child(LabelNode( - "There's been an issue reading content from the game disc.")) - .child(LabelNode( - "This is likely caused by bad or unimplemented file IO " - "calls.")) - .child(LayoutBoxNode() - .distribution_position( - LayoutDistributionPosition::kRightBottom) - .child(ButtonNode("Exit").id("exit_button")))); - } - - bool OnEvent(const el::Event& ev) override { - if (ev.target->id() == TBIDC("exit_button") && - ev.type == el::EventType::kClick) { - Die(); - return true; - } - return ModalForm::OnEvent(ev); - } -}; - SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context, KernelState* kernel_state) { uint32_t user_index = SHIM_GET_ARG_32(0); @@ -396,11 +324,15 @@ SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context, auto display_window = kernel_state->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { - auto root_element = display_window->root_element(); - auto window = new DirtyDiscWindow(&fence); - window->Show(root_element); + xe::ui::ImGuiDialog::ShowMessageBox( + display_window, "Disc Read Error", + "There's been an issue reading content from the game disc.\nThis is " + "likely caused by bad or unimplemented file IO calls.") + ->Then(&fence); }); + ++xam_dialogs_shown_; fence.Wait(); + --xam_dialogs_shown_; // This is death, and should never return. // TODO(benvanik): cleaner exit. diff --git a/src/xenia/premake5.lua b/src/xenia/premake5.lua index f5d11a6fc..4508226df 100644 --- a/src/xenia/premake5.lua +++ b/src/xenia/premake5.lua @@ -7,13 +7,11 @@ project("xenia-core") kind("StaticLib") language("C++") links({ - "elemental-forms", "xenia-base", }) defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) files({"*.h", "*.cc"}) diff --git a/src/xenia/ui/elemental_drawer.cc b/src/xenia/ui/elemental_drawer.cc deleted file mode 100644 index 6eb81fd32..000000000 --- a/src/xenia/ui/elemental_drawer.cc +++ /dev/null @@ -1,112 +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/ui/elemental_drawer.h" - -#include "el/graphics/bitmap_fragment.h" -#include "el/util/math.h" -#include "xenia/base/assert.h" -#include "xenia/base/logging.h" -#include "xenia/base/profiling.h" -#include "xenia/ui/window.h" - -namespace xe { -namespace ui { - -ElementalDrawer::ElementalBitmap::ElementalBitmap(ElementalDrawer* drawer, - int width, int height, - uint32_t* data) - : drawer_(drawer) { - assert(width == el::util::GetNearestPowerOfTwo(width)); - assert(height == el::util::GetNearestPowerOfTwo(height)); - width_ = width; - height_ = height; - - drawer_->FlushBitmap(this); - - auto immediate_drawer = drawer_->graphics_context_->immediate_drawer(); - texture_ = immediate_drawer->CreateTexture( - width, height, ImmediateTextureFilter::kLinear, true, - reinterpret_cast(data)); -} - -ElementalDrawer::ElementalBitmap::~ElementalBitmap() { - // Must flush and unbind before we delete the texture. - drawer_->FlushBitmap(this); -} - -void ElementalDrawer::ElementalBitmap::set_data(uint32_t* data) { - drawer_->FlushBitmap(this); - - auto immediate_drawer = drawer_->graphics_context_->immediate_drawer(); - immediate_drawer->UpdateTexture(texture_.get(), - reinterpret_cast(data)); -} - -ElementalDrawer::ElementalDrawer(xe::ui::Window* window) - : window_(window), graphics_context_(window->context()) { - static_assert(sizeof(ImmediateVertex) == sizeof(Vertex), - "Vertex types must match"); -} - -ElementalDrawer::~ElementalDrawer() = default; - -void ElementalDrawer::BeginPaint(int render_target_w, int render_target_h) { - Renderer::BeginPaint(render_target_w, render_target_h); - - auto immediate_drawer = graphics_context_->immediate_drawer(); - immediate_drawer->Begin(render_target_w, render_target_h); - - batch_.vertices = vertices_; -} - -void ElementalDrawer::EndPaint() { - Renderer::EndPaint(); - - auto immediate_drawer = graphics_context_->immediate_drawer(); - immediate_drawer->End(); -} - -std::unique_ptr ElementalDrawer::CreateBitmap( - int width, int height, uint32_t* data) { - auto bitmap = std::make_unique(this, width, height, data); - return std::unique_ptr(bitmap.release()); -} - -void ElementalDrawer::RenderBatch(Batch* batch) { - auto immediate_drawer = graphics_context_->immediate_drawer(); - - ImmediateDrawBatch draw_batch; - - draw_batch.vertices = reinterpret_cast(batch->vertices); - draw_batch.vertex_count = static_cast(batch->vertex_count); - immediate_drawer->BeginDrawBatch(draw_batch); - - ImmediateDraw draw; - draw.primitive_type = ImmediatePrimitiveType::kTriangles; - draw.count = static_cast(batch->vertex_count); - auto bitmap = static_cast(batch->bitmap); - draw.texture_handle = bitmap ? bitmap->texture_->handle : 0; - draw.scissor = current_clip_.w && current_clip_.h; - draw.scissor_rect[0] = current_clip_.x; - draw.scissor_rect[1] = - window_->height() - (current_clip_.y + current_clip_.h); - draw.scissor_rect[2] = current_clip_.w; - draw.scissor_rect[3] = current_clip_.h; - immediate_drawer->Draw(draw); - - immediate_drawer->EndDrawBatch(); -} - -void ElementalDrawer::set_clip_rect(const el::Rect& rect) { - current_clip_ = rect; -} - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/elemental_drawer.h b/src/xenia/ui/elemental_drawer.h deleted file mode 100644 index 34f5672a9..000000000 --- a/src/xenia/ui/elemental_drawer.h +++ /dev/null @@ -1,69 +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_UI_ELEMENTAL_DRAWER_H_ -#define XENIA_UI_ELEMENTAL_DRAWER_H_ - -#include -#include - -#include "el/graphics/renderer.h" -#include "xenia/ui/immediate_drawer.h" - -namespace xe { -namespace ui { - -class GraphicsContext; -class Window; - -class ElementalDrawer : public el::graphics::Renderer { - public: - ElementalDrawer(Window* window); - ~ElementalDrawer(); - - 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; - - protected: - class ElementalBitmap : public el::graphics::Bitmap { - public: - ElementalBitmap(ElementalDrawer* drawer, int width, int height, - uint32_t* data); - ~ElementalBitmap(); - - int width() override { return width_; } - int height() override { return height_; } - void set_data(uint32_t* data) override; - - ElementalDrawer* drawer_ = nullptr; - int width_ = 0; - int height_ = 0; - std::unique_ptr texture_; - }; - - static const uint32_t kMaxVertexBatchSize = 6 * 2048; - size_t max_vertex_batch_size() const override { return kMaxVertexBatchSize; } - - Window* window_ = nullptr; - GraphicsContext* graphics_context_ = nullptr; - - el::Rect current_clip_; - Vertex vertices_[kMaxVertexBatchSize]; -}; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_ELEMENTAL_DRAWER_H_ diff --git a/src/xenia/ui/gl/premake5.lua b/src/xenia/ui/gl/premake5.lua index e4dafaa2d..3f6258720 100644 --- a/src/xenia/ui/gl/premake5.lua +++ b/src/xenia/ui/gl/premake5.lua @@ -7,7 +7,6 @@ project("xenia-ui-gl") kind("StaticLib") language("C++") links({ - "elemental-forms", "glew", "xenia-base", "xenia-ui", @@ -17,7 +16,6 @@ project("xenia-ui-gl") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() @@ -29,7 +27,6 @@ project("xenia-ui-window-gl-demo") kind("WindowedApp") language("C++") links({ - "elemental-forms", "gflags", "glew", "imgui", @@ -45,7 +42,6 @@ project("xenia-ui-window-gl-demo") "GLEW_MX=1", }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) files({ @@ -54,9 +50,7 @@ project("xenia-ui-window-gl-demo") project_root.."/src/xenia/base/main_"..platform_suffix..".cc", }) files({ - project_root.."/third_party/elemental-forms/resources.rc", }) resincludedirs({ project_root, - project_root.."/third_party/elemental-forms", }) diff --git a/src/xenia/ui/imgui_dialog.cc b/src/xenia/ui/imgui_dialog.cc new file mode 100644 index 000000000..e4fda5d8b --- /dev/null +++ b/src/xenia/ui/imgui_dialog.cc @@ -0,0 +1,93 @@ +/** + ****************************************************************************** + * 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/imgui_dialog.h" + +#include "third_party/imgui/imgui.h" +#include "xenia/base/assert.h" +#include "xenia/ui/window.h" + +namespace xe { +namespace ui { + +ImGuiDialog::ImGuiDialog(Window* window) : window_(window) { + window_->AttachListener(this); + had_imgui_active_ = window_->is_imgui_input_enabled(); + window_->set_imgui_input_enabled(true); +} + +ImGuiDialog::~ImGuiDialog() { + window_->set_imgui_input_enabled(had_imgui_active_); + window_->DetachListener(this); + for (auto fence : waiting_fences_) { + fence->Signal(); + } +} + +void ImGuiDialog::Then(xe::threading::Fence* fence) { + waiting_fences_.push_back(fence); +} + +void ImGuiDialog::Close() { has_close_pending_ = true; } + +ImGuiIO& ImGuiDialog::GetIO() { return window_->imgui_drawer()->GetIO(); } + +void ImGuiDialog::OnPaint(UIEvent* e) { + // Keep imgui rendering every frame. + window_->Invalidate(); + + // Draw UI. + OnDraw(GetIO()); + + // Check to see if the UI closed itself and needs to be deleted. + if (has_close_pending_) { + OnClose(); + delete this; + } +} + +class MessageBoxDialog : public ImGuiDialog { + public: + MessageBoxDialog(Window* window, std::string title, std::string body) + : ImGuiDialog(window), title_(std::move(title)), body_(std::move(body)) {} + + void OnDraw(ImGuiIO& io) override { + if (!has_opened_) { + ImGui::OpenPopup(title_.c_str()); + has_opened_ = true; + } + if (ImGui::BeginPopupModal(title_.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + char* text = const_cast(body_.c_str()); + ImGui::InputTextMultiline( + "##body", text, body_.size(), ImVec2(0, 0), + ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly); + if (ImGui::Button("OK")) { + ImGui::CloseCurrentPopup(); + Close(); + } + ImGui::EndPopup(); + } else { + Close(); + } + } + + private: + bool has_opened_ = false; + std::string title_; + std::string body_; +}; + +ImGuiDialog* ImGuiDialog::ShowMessageBox(Window* window, std::string title, + std::string body) { + return new MessageBoxDialog(window, std::move(title), std::move(body)); +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/imgui_dialog.h b/src/xenia/ui/imgui_dialog.h new file mode 100644 index 000000000..3c9632c07 --- /dev/null +++ b/src/xenia/ui/imgui_dialog.h @@ -0,0 +1,60 @@ +/** + ****************************************************************************** + * 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_IMGUI_DIALOG_H_ +#define XENIA_UI_IMGUI_DIALOG_H_ + +#include + +#include "xenia/base/threading.h" +#include "xenia/ui/imgui_drawer.h" +#include "xenia/ui/window_listener.h" + +namespace xe { +namespace ui { + +class ImGuiDialog : private WindowListener { + public: + ~ImGuiDialog(); + + // Shows a simple message box containing a text message. + // Callers can want for the dialog to close with Wait(). + // Dialogs retain themselves and will delete themselves when closed. + static ImGuiDialog* ShowMessageBox(Window* window, std::string title, + std::string body); + + // A fence to signal when the dialog is closed. + void Then(xe::threading::Fence* fence); + + protected: + ImGuiDialog(Window* window); + + Window* window() const { return window_; } + ImGuiIO& GetIO(); + + // Closes the dialog and returns to any waiters. + void Close(); + + virtual void OnShow() {} + virtual void OnClose() {} + virtual void OnDraw(ImGuiIO& io) {} + + private: + void OnPaint(UIEvent* e) override; + + Window* window_ = nullptr; + bool had_imgui_active_ = false; + bool has_close_pending_ = false; + std::vector waiting_fences_; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_IMGUI_DIALOG_H_ diff --git a/src/xenia/ui/imgui_drawer.cc b/src/xenia/ui/imgui_drawer.cc index 5c6105a08..c79870250 100644 --- a/src/xenia/ui/imgui_drawer.cc +++ b/src/xenia/ui/imgui_drawer.cc @@ -11,6 +11,8 @@ #include "third_party/imgui/imgui.h" #include "xenia/base/assert.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" #include "xenia/ui/window.h" namespace xe { @@ -24,31 +26,42 @@ const char kProggyTinyCompressedDataBase85[10950 + 1] = static_assert(sizeof(ImmediateVertex) == sizeof(ImDrawVert), "Vertex types must match"); -ImGuiDrawer* ImGuiDrawer::global_drawer_ = nullptr; +ImGuiDrawer* ImGuiDrawer::current_drawer_ = nullptr; ImGuiDrawer::ImGuiDrawer(xe::ui::Window* window) : window_(window), graphics_context_(window->context()) { - assert_null(global_drawer_); - global_drawer_ = this; - Initialize(); } ImGuiDrawer::~ImGuiDrawer() { + auto previous_state = ImGui::GetInternalState(); + ImGui::SetInternalState(internal_state_.data()); ImGui::Shutdown(); + if (previous_state != internal_state_.data()) { + ImGui::SetInternalState(previous_state); + } - assert_true(global_drawer_ == this); - global_drawer_ = nullptr; + current_drawer_ = nullptr; } void ImGuiDrawer::Initialize() { + // Setup ImGui internal state. + // This will give us state we can swap to the ImGui globals when in use. + internal_state_.resize(ImGui::GetInternalStateSize()); + ImGui::SetInternalState(internal_state_.data(), true); + current_drawer_ = this; + auto& io = ImGui::GetIO(); + font_atlas_ = std::make_unique(); + io.Fonts = font_atlas_.get(); + SetupFont(); io.DeltaTime = 1.0f / 60.0f; io.RenderDrawListsFn = [](ImDrawData* data) { - global_drawer_->RenderDrawLists(data); + assert_not_null(current_drawer_); + current_drawer_->RenderDrawLists(data); }; auto& style = ImGui::GetStyle(); @@ -124,7 +137,7 @@ void ImGuiDrawer::Initialize() { } void ImGuiDrawer::SetupFont() { - auto& io = ImGui::GetIO(); + auto& io = GetIO(); ImFontConfig font_config; font_config.OversampleH = font_config.OversampleV = 1; @@ -136,6 +149,21 @@ void ImGuiDrawer::SetupFont() { io.Fonts->AddFontFromMemoryCompressedBase85TTF( kProggyTinyCompressedDataBase85, 10.0f, &font_config, font_glyph_ranges); + // TODO(benvanik): jp font on other platforms? + // https://github.com/Koruri/kibitaki looks really good, but is 1.5MiB. + const char* jp_font_path = "C:\\Windows\\Fonts\\msgothic.ttc"; + if (xe::filesystem::PathExists(xe::to_wstring(jp_font_path))) { + ImFontConfig jp_font_config; + jp_font_config.MergeMode = true; + jp_font_config.OversampleH = jp_font_config.OversampleV = 1; + jp_font_config.PixelSnapH = true; + jp_font_config.FontNo = 0; + io.Fonts->AddFontFromFileTTF(jp_font_path, 12.0f, &jp_font_config, + io.Fonts->GetGlyphRangesJapanese()); + } else { + XELOGW("Unable to load japanese font; jp characters will be boxes"); + } + unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); @@ -196,71 +224,81 @@ void ImGuiDrawer::RenderDrawLists(ImDrawData* data) { drawer->End(); } -void ImGuiDrawer::SetupDefaultInput() { - window_->on_key_char.AddListener([](xe::ui::KeyEvent* e) { - auto& io = ImGui::GetIO(); - if (e->key_code() > 0 && e->key_code() < 0x10000) { - io.AddInputCharacter(e->key_code()); - e->set_handled(true); - } - }); - window_->on_key_down.AddListener([](xe::ui::KeyEvent* e) { - auto& io = ImGui::GetIO(); - io.KeysDown[e->key_code()] = true; - switch (e->key_code()) { - case 16: { - io.KeyShift = true; - } break; - case 17: { - io.KeyCtrl = true; - } break; - } - }); - window_->on_key_up.AddListener([](xe::ui::KeyEvent* e) { - auto& io = ImGui::GetIO(); - io.KeysDown[e->key_code()] = false; - switch (e->key_code()) { - case 16: { - io.KeyShift = false; - } break; - case 17: { - io.KeyCtrl = false; - } break; - } - }); - window_->on_mouse_down.AddListener([](xe::ui::MouseEvent* e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e->x()), float(e->y())); - switch (e->button()) { - case xe::ui::MouseEvent::Button::kLeft: { - io.MouseDown[0] = true; - } break; - case xe::ui::MouseEvent::Button::kRight: { - io.MouseDown[1] = true; - } break; - } - }); - window_->on_mouse_move.AddListener([](xe::ui::MouseEvent* e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e->x()), float(e->y())); - }); - window_->on_mouse_up.AddListener([](xe::ui::MouseEvent* e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e->x()), float(e->y())); - switch (e->button()) { - case xe::ui::MouseEvent::Button::kLeft: { - io.MouseDown[0] = false; - } break; - case xe::ui::MouseEvent::Button::kRight: { - io.MouseDown[1] = false; - } break; - } - }); - window_->on_mouse_wheel.AddListener([](xe::ui::MouseEvent* e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e->x()), float(e->y())); - io.MouseWheel += float(e->dy() / 120.0f); - }); +ImGuiIO& ImGuiDrawer::GetIO() { + current_drawer_ = this; + ImGui::SetInternalState(internal_state_.data()); + return ImGui::GetIO(); +} + +void ImGuiDrawer::OnKeyDown(KeyEvent* e) { + auto& io = GetIO(); + io.KeysDown[e->key_code()] = true; + switch (e->key_code()) { + case 16: { + io.KeyShift = true; + } break; + case 17: { + io.KeyCtrl = true; + } break; + } +} + +void ImGuiDrawer::OnKeyUp(KeyEvent* e) { + auto& io = GetIO(); + io.KeysDown[e->key_code()] = false; + switch (e->key_code()) { + case 16: { + io.KeyShift = false; + } break; + case 17: { + io.KeyCtrl = false; + } break; + } +} + +void ImGuiDrawer::OnKeyChar(KeyEvent* e) { + auto& io = GetIO(); + if (e->key_code() > 0 && e->key_code() < 0x10000) { + io.AddInputCharacter(e->key_code()); + e->set_handled(true); + } +} + +void ImGuiDrawer::OnMouseDown(MouseEvent* e) { + auto& io = GetIO(); + io.MousePos = ImVec2(float(e->x()), float(e->y())); + switch (e->button()) { + case xe::ui::MouseEvent::Button::kLeft: { + io.MouseDown[0] = true; + } break; + case xe::ui::MouseEvent::Button::kRight: { + io.MouseDown[1] = true; + } break; + } +} + +void ImGuiDrawer::OnMouseMove(MouseEvent* e) { + auto& io = GetIO(); + io.MousePos = ImVec2(float(e->x()), float(e->y())); +} + +void ImGuiDrawer::OnMouseUp(MouseEvent* e) { + auto& io = GetIO(); + io.MousePos = ImVec2(float(e->x()), float(e->y())); + switch (e->button()) { + case xe::ui::MouseEvent::Button::kLeft: { + io.MouseDown[0] = false; + } break; + case xe::ui::MouseEvent::Button::kRight: { + io.MouseDown[1] = false; + } break; + } +} + +void ImGuiDrawer::OnMouseWheel(MouseEvent* e) { + auto& io = GetIO(); + io.MousePos = ImVec2(float(e->x()), float(e->y())); + io.MouseWheel += float(e->dy() / 120.0f); } } // namespace ui diff --git a/src/xenia/ui/imgui_drawer.h b/src/xenia/ui/imgui_drawer.h index af00f02a0..2bbfb0a99 100644 --- a/src/xenia/ui/imgui_drawer.h +++ b/src/xenia/ui/imgui_drawer.h @@ -11,10 +11,14 @@ #define XENIA_UI_IMGUI_DRAWER_H_ #include +#include #include "xenia/ui/immediate_drawer.h" +#include "xenia/ui/window_listener.h" struct ImDrawData; +struct ImFontAtlas; +struct ImGuiIO; namespace xe { namespace ui { @@ -22,12 +26,14 @@ namespace ui { class GraphicsContext; class Window; -class ImGuiDrawer { +class ImGuiDrawer : public WindowListener { public: ImGuiDrawer(Window* window); ~ImGuiDrawer(); - void SetupDefaultInput(); + void SetupDefaultInput() {} + + ImGuiIO& GetIO(); protected: void Initialize(); @@ -35,11 +41,21 @@ class ImGuiDrawer { void RenderDrawLists(ImDrawData* data); - static ImGuiDrawer* global_drawer_; + void OnKeyDown(KeyEvent* e) override; + void OnKeyUp(KeyEvent* e) override; + void OnKeyChar(KeyEvent* e) override; + void OnMouseDown(MouseEvent* e) override; + void OnMouseMove(MouseEvent* e) override; + void OnMouseUp(MouseEvent* e) override; + void OnMouseWheel(MouseEvent* e) override; + + static ImGuiDrawer* current_drawer_; Window* window_ = nullptr; GraphicsContext* graphics_context_ = nullptr; + std::vector internal_state_; + std::unique_ptr font_atlas_; std::unique_ptr font_texture_; }; diff --git a/src/xenia/ui/loop.cc b/src/xenia/ui/loop.cc index 9c9d477ff..4fc6ad1dc 100644 --- a/src/xenia/ui/loop.cc +++ b/src/xenia/ui/loop.cc @@ -9,34 +9,15 @@ #include "xenia/ui/loop.h" -#include "el/message_handler.h" -#include "el/util/metrics.h" -#include "el/util/timer.h" #include "xenia/base/assert.h" #include "xenia/base/threading.h" namespace xe { namespace ui { -// The loop designated as being the main loop elemental-forms will use for all -// activity. -// TODO(benvanik): refactor elemental-forms to now need a global. -Loop* elemental_loop_ = nullptr; - -Loop* Loop::elemental_loop() { return elemental_loop_; } - -void Loop::set_elemental_loop(Loop* loop) { - assert_null(elemental_loop_); - elemental_loop_ = loop; -} - Loop::Loop() = default; -Loop::~Loop() { - if (this == elemental_loop_) { - elemental_loop_ = nullptr; - } -} +Loop::~Loop() = default; void Loop::PostSynchronous(std::function fn) { if (is_on_loop_thread()) { @@ -54,36 +35,3 @@ void Loop::PostSynchronous(std::function fn) { } // namespace ui } // namespace xe - -void el::util::RescheduleTimer(uint64_t fire_time) { - assert_not_null(xe::ui::elemental_loop_); - if (fire_time == el::MessageHandler::kNotSoon) { - return; - } - - uint64_t now = el::util::GetTimeMS(); - uint64_t delay_millis = fire_time >= now ? fire_time - now : 0; - xe::ui::elemental_loop_->PostDelayed( - []() { - 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 - // of the platform timer again with the same time. - // ReschedulePlatformTimer(next_fire_time, true); - return; - } - - 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. - el::util::RescheduleTimer(el::MessageHandler::GetNextMessageFireTime()); - }, - delay_millis); -} diff --git a/src/xenia/ui/loop.h b/src/xenia/ui/loop.h index 6db86f31e..06e49e461 100644 --- a/src/xenia/ui/loop.h +++ b/src/xenia/ui/loop.h @@ -23,11 +23,6 @@ class Loop { public: static std::unique_ptr Create(); - static Loop* elemental_loop(); - // Sets the loop designated as being the main loop elemental-forms will use - // for all activity. - static void set_elemental_loop(Loop* loop); - Loop(); virtual ~Loop(); diff --git a/src/xenia/ui/premake5.lua b/src/xenia/ui/premake5.lua index 03e363acc..457d631af 100644 --- a/src/xenia/ui/premake5.lua +++ b/src/xenia/ui/premake5.lua @@ -7,13 +7,11 @@ project("xenia-ui") kind("StaticLib") language("C++") links({ - "elemental-forms", "xenia-base", }) defines({ }) includedirs({ - project_root.."/third_party/elemental-forms/src", project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index d94245014..020256dea 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -9,21 +9,11 @@ #include "xenia/ui/window.h" -#include "el/animation_manager.h" -#include "el/elemental_forms.h" -#include "el/io/file_manager.h" -#include "el/io/posix_file_system.h" -#include "el/io/win32_res_file_system_win.h" -#include "el/message_handler.h" -#include "el/text/font_manager.h" -#include "el/util/debug.h" -#include "el/util/metrics.h" -#include "el/util/string_table.h" -#include "el/util/timer.h" +#include "third_party/imgui/imgui.h" #include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" -#include "xenia/ui/elemental_drawer.h" +#include "xenia/ui/imgui_drawer.h" namespace xe { namespace ui { @@ -39,18 +29,6 @@ constexpr double kDoubleClickDistance = 5; constexpr int32_t kMouseWheelDetent = 120; -// Ref count of elemental initializations. -int32_t elemental_initialization_count_ = 0; - -class RootElement : public el::Element { - public: - explicit RootElement(Window* owner) : owner_(owner) {} - void OnInvalid() override { owner_->Invalidate(); } - - private: - Window* owner_ = nullptr; -}; - Window::Window(Loop* loop, const std::wstring& title) : loop_(loop), title_(title) {} @@ -59,101 +37,58 @@ Window::~Window() { assert_null(context_.get()); } -bool Window::InitializeElemental(Loop* loop, el::graphics::Renderer* renderer) { - GraphicsContextLock context_lock(context_.get()); - - if (++elemental_initialization_count_ > 1) { - return true; +void Window::AttachListener(WindowListener* listener) { + auto it = std::find(listeners_.begin(), listeners_.end(), listener); + if (it != listeners_.end()) { + return; } + listeners_.push_back(listener); + Invalidate(); +} - // Need to pass off the Loop we want Elemental to use. - // TODO(benvanik): give the callback to elemental instead. - Loop::set_elemental_loop(loop); - - if (!el::Initialize(renderer)) { - XELOGE("Failed to initialize elemental core"); - return false; +void Window::DetachListener(WindowListener* listener) { + auto it = std::find(listeners_.begin(), listeners_.end(), listener); + if (it == listeners_.end()) { + return; } + listeners_.erase(it); +} - el::io::FileManager::RegisterFileSystem( - std::make_unique("IDR_default_resources_")); - el::io::FileManager::RegisterFileSystem( - std::make_unique( - "third_party/elemental-forms/testbed/resources/")); - - // Load default translations. - el::util::StringTable::get()->Load("default_language/language_en.tb.txt"); - - // Load the default skin. Hosting controls may load additional skins later. - if (!LoadSkin("default_skin/skin.tb.txt")) { - XELOGE("Failed to load default skin"); - return false; +void Window::set_imgui_input_enabled(bool value) { + if (value == is_imgui_input_enabled_) { + return; + } + is_imgui_input_enabled_ = value; + if (!value) { + DetachListener(imgui_drawer_.get()); + } else { + AttachListener(imgui_drawer_.get()); } - -// 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("fonts/vera.ttf", "Default"); -#endif -#ifdef EL_FONT_RENDERER_TBBF - font_manager->AddFontInfo("fonts/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()); - assert(font != nullptr); - return true; } bool Window::OnCreate() { return true; } bool Window::MakeReady() { - renderer_ = std::make_unique(this); - - // Initialize elemental. - // TODO(benvanik): once? Do we care about multiple controls? - if (!InitializeElemental(loop_, renderer_.get())) { - XELOGE("Unable to initialize elemental-forms"); - 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, width(), height()}); - - // el::util::ShowDebugInfoSettingsForm(root_element_.get()); + imgui_drawer_ = std::make_unique(this); return true; } -void Window::OnMainMenuChange() {} +void Window::OnMainMenuChange() { + for (auto listener : listeners_) { + listener->OnMainMenuChange(); + } +} void Window::OnClose() { UIEvent e(this); + for (auto listener : listeners_) { + listener->OnClosing(&e); + } on_closing(&e); + for (auto listener : listeners_) { + listener->OnClosed(&e); + } on_closed(&e); } @@ -162,24 +97,12 @@ void Window::OnDestroy() { return; } - if (--elemental_initialization_count_ == 0) { - el::Shutdown(); - } + imgui_drawer_.reset(); // Context must go last. - root_element_.reset(); - renderer_.reset(); context_.reset(); } -bool Window::LoadLanguage(std::string filename) { - return el::util::StringTable::get()->Load(filename.c_str()); -} - -bool Window::LoadSkin(std::string filename) { - return el::Skin::get()->Load(filename.c_str()); -} - void Window::Layout() { UIEvent e(this); OnLayout(&e); @@ -187,213 +110,106 @@ void Window::Layout() { void Window::Invalidate() {} -void Window::OnResize(UIEvent* e) { on_resize(e); } +void Window::OnResize(UIEvent* e) { + for (auto listener : listeners_) { + listener->OnResize(e); + } +} void Window::OnLayout(UIEvent* e) { - on_layout(e); - if (!root_element()) { - return; + for (auto listener : listeners_) { + listener->OnLayout(e); } - // TODO(benvanik): subregion? - root_element()->set_rect({0, 0, width(), height()}); } void Window::OnPaint(UIEvent* e) { - if (!renderer()) { + if (!context_) { return; } ++frame_count_; ++fps_frame_count_; uint64_t now_ns = xe::Clock::QueryHostSystemTime(); - if (now_ns > fps_update_time_ + 1000 * 10000) { + if (now_ns > fps_update_time_ns_ + 1000 * 10000) { fps_ = static_cast( fps_frame_count_ / - (static_cast(now_ns - fps_update_time_) / 10000000.0)); - fps_update_time_ = now_ns; + (static_cast(now_ns - fps_update_time_ns_) / 10000000.0)); + fps_update_time_ns_ = now_ns; fps_frame_count_ = 0; } - // Update TB (run animations, handle deferred input, etc). - el::AnimationManager::Update(); - if (root_element()) { - root_element()->InvokeProcessStates(); - root_element()->InvokeProcess(); - } - GraphicsContextLock context_lock(context_.get()); - context_->BeginSwap(); - - on_painting(e); - - renderer()->BeginPaint(width(), height()); - - // Render entire control hierarchy. - if (root_element()) { - root_element()->InvokePaint(el::Element::PaintProps()); + // Prepare ImGui for use this frame. + auto& io = imgui_drawer_->GetIO(); + if (!last_paint_time_ns_) { + io.DeltaTime = 0.0f; + last_paint_time_ns_ = now_ns; + } else { + io.DeltaTime = (now_ns - last_paint_time_ns_) / 10000000.0f; + last_paint_time_ns_ = now_ns; } + io.DisplaySize = + ImVec2(static_cast(width()), static_cast(height())); + ImGui::NewFrame(); + context_->BeginSwap(); + for (auto listener : listeners_) { + listener->OnPainting(e); + } + on_painting(e); + for (auto listener : listeners_) { + listener->OnPaint(e); + } on_paint(e); - if (root_element() && kShowPresentFps) { - // Render debug overlay. - root_element()->computed_font()->DrawString( - 5, 5, el::Color(255, 0, 0), - el::format_string("Frame %lld", frame_count_)); - root_element()->computed_font()->DrawString( - 5, 20, el::Color(255, 0, 0), - el::format_string("Present FPS: %d", fps_)); + // Flush ImGui buffers before we swap. + ImGui::Render(); + + for (auto listener : listeners_) { + listener->OnPainted(e); } - - renderer()->EndPaint(); - on_painted(e); context_->EndSwap(); // If animations are running, reinvalidate immediately. - if (root_element()) { - if (el::AnimationManager::has_running_animations()) { - root_element()->Invalidate(); - } - if (kContinuousRepaint) { - // Force an immediate repaint, always. - root_element()->Invalidate(); - } - } else { - if (kContinuousRepaint) { - Invalidate(); - } + if (kContinuousRepaint) { + Invalidate(); } } -void Window::OnVisible(UIEvent* e) { on_visible(e); } +void Window::OnVisible(UIEvent* e) { + for (auto listener : listeners_) { + listener->OnVisible(e); + } +} -void Window::OnHidden(UIEvent* e) { on_hidden(e); } +void Window::OnHidden(UIEvent* e) { + for (auto listener : listeners_) { + listener->OnHidden(e); + } +} -void Window::OnGotFocus(UIEvent* e) { on_got_focus(e); } +void Window::OnGotFocus(UIEvent* e) { + for (auto listener : listeners_) { + listener->OnGotFocus(e); + } +} void Window::OnLostFocus(UIEvent* e) { modifier_shift_pressed_ = false; modifier_cntrl_pressed_ = false; modifier_alt_pressed_ = false; modifier_super_pressed_ = false; - last_click_time_ = 0; - on_lost_focus(e); -} - -el::ModifierKeys Window::GetModifierKeys() { - auto modifiers = el::ModifierKeys::kNone; - if (modifier_shift_pressed_) { - modifiers |= el::ModifierKeys::kShift; + for (auto listener : listeners_) { + listener->OnLostFocus(e); } - 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 Window::OnKeyPress(KeyEvent* e, bool is_down, bool is_char) { - if (!root_element()) { - return; - } - auto special_key = el::SpecialKey::kUndefined; if (!is_char) { 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(std::move(ev)); - e->set_handled(true); - return; - } - break; case 16: modifier_shift_pressed_ = is_down; break; @@ -409,71 +225,6 @@ void Window::OnKeyPress(KeyEvent* e, bool is_down, bool is_char) { break; } } - - if (!CheckShortcutKey(e, special_key, is_down)) { - int key_code = 0; - if (is_char) { - key_code = e->key_code(); - if (key_code < 32 || (key_code > 126 && key_code < 160)) { - key_code = 0; - } - } - e->set_handled(root_element()->InvokeKey(key_code, special_key, - GetModifierKeys(), is_down)); - } -} - -bool Window::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(std::move(ev))) { - return false; - } - e->set_handled(true); - return true; } void Window::OnKeyDown(KeyEvent* e) { @@ -481,6 +232,12 @@ void Window::OnKeyDown(KeyEvent* e) { if (e->is_handled()) { return; } + for (auto listener : listeners_) { + listener->OnKeyDown(e); + if (e->is_handled()) { + return; + } + } OnKeyPress(e, true, false); } @@ -489,12 +246,21 @@ void Window::OnKeyUp(KeyEvent* e) { if (e->is_handled()) { return; } + for (auto listener : listeners_) { + listener->OnKeyUp(e); + if (e->is_handled()) { + return; + } + } OnKeyPress(e, false, false); } void Window::OnKeyChar(KeyEvent* e) { OnKeyPress(e, true, true); on_key_char(e); + for (auto listener : listeners_) { + listener->OnKeyChar(e); + } OnKeyPress(e, false, true); } @@ -503,31 +269,11 @@ void Window::OnMouseDown(MouseEvent* e) { if (e->is_handled()) { return; } - 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; + for (auto listener : listeners_) { + listener->OnMouseDown(e); + if (e->is_handled()) { + return; } - 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)); } } @@ -536,11 +282,12 @@ void Window::OnMouseMove(MouseEvent* e) { if (e->is_handled()) { return; } - if (!root_element()) { - return; + for (auto listener : listeners_) { + listener->OnMouseMove(e); + if (e->is_handled()) { + return; + } } - root_element()->InvokePointerMove(e->x(), e->y(), GetModifierKeys(), kTouch); - e->set_handled(true); } void Window::OnMouseUp(MouseEvent* e) { @@ -548,24 +295,11 @@ void Window::OnMouseUp(MouseEvent* e) { if (e->is_handled()) { return; } - 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(std::move(ev)); + for (auto listener : listeners_) { + listener->OnMouseUp(e); + if (e->is_handled()) { + return; } - e->set_handled(true); } } @@ -574,12 +308,12 @@ void Window::OnMouseWheel(MouseEvent* e) { if (e->is_handled()) { return; } - if (!root_element()) { - return; + for (auto listener : listeners_) { + listener->OnMouseWheel(e); + if (e->is_handled()) { + return; + } } - e->set_handled(root_element()->InvokeWheel(e->x(), e->y(), e->dx(), - -e->dy() / kMouseWheelDetent, - GetModifierKeys())); } } // namespace ui diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index 76e6c45c1..cd7c64d4c 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -13,13 +13,12 @@ #include #include -#include "el/element.h" -#include "el/graphics/renderer.h" #include "xenia/base/delegate.h" #include "xenia/ui/graphics_context.h" #include "xenia/ui/loop.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/ui_event.h" +#include "xenia/ui/window_listener.h" namespace xe { namespace ui { @@ -27,6 +26,8 @@ namespace ui { typedef void* NativePlatformHandle; typedef void* NativeWindowHandle; +class ImGuiDrawer; + class Window { public: static std::unique_ptr Create(Loop* loop, const std::wstring& title); @@ -71,8 +72,12 @@ class Window { int32_t bottom) = 0; GraphicsContext* context() const { return context_.get(); } - el::graphics::Renderer* renderer() const { return renderer_.get(); } - el::Element* root_element() const { return root_element_.get(); } + ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); } + bool is_imgui_input_enabled() const { return is_imgui_input_enabled_; } + void set_imgui_input_enabled(bool value); + + void AttachListener(WindowListener* listener); + void DetachListener(WindowListener* listener); virtual bool Initialize() { return true; } void set_context(std::unique_ptr context) { @@ -82,9 +87,6 @@ class Window { } } - bool LoadLanguage(std::string filename); - bool LoadSkin(std::string filename); - void Layout(); virtual void Invalidate(); @@ -94,20 +96,10 @@ class Window { Delegate on_closing; Delegate on_closed; - Delegate on_command; - - Delegate on_resize; - Delegate on_layout; Delegate on_painting; Delegate on_paint; Delegate on_painted; - Delegate on_visible; - Delegate on_hidden; - - Delegate on_got_focus; - Delegate on_lost_focus; - Delegate on_key_down; Delegate on_key_up; Delegate on_key_char; @@ -122,9 +114,6 @@ class Window { virtual bool MakeReady(); - virtual bool InitializeElemental(Loop* loop, - el::graphics::Renderer* renderer); - virtual bool OnCreate(); virtual void OnMainMenuChange(); virtual void OnClose(); @@ -149,9 +138,7 @@ class Window { virtual void OnMouseUp(MouseEvent* e); virtual void OnMouseWheel(MouseEvent* e); - el::ModifierKeys GetModifierKeys(); void OnKeyPress(KeyEvent* e, bool is_down, bool is_char); - bool CheckShortcutKey(KeyEvent* e, el::SpecialKey special_key, bool is_down); Loop* loop_ = nullptr; std::unique_ptr main_menu_; @@ -160,24 +147,24 @@ class Window { int32_t height_ = 0; bool has_focus_ = true; bool is_cursor_visible_ = true; + bool is_imgui_input_enabled_ = false; std::unique_ptr context_; - std::unique_ptr renderer_; - std::unique_ptr root_element_; + std::unique_ptr imgui_drawer_; uint32_t frame_count_ = 0; uint32_t fps_ = 0; - uint64_t fps_update_time_ = 0; + uint64_t fps_update_time_ns_ = 0; uint64_t fps_frame_count_ = 0; + uint64_t last_paint_time_ns_ = 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; + + // All currently-attached listeners that get event notifications. + std::vector listeners_; }; } // namespace ui diff --git a/src/xenia/ui/window_demo.cc b/src/xenia/ui/window_demo.cc index b81fabbe1..75c74931b 100644 --- a/src/xenia/ui/window_demo.cc +++ b/src/xenia/ui/window_demo.cc @@ -11,7 +11,6 @@ #include -#include "third_party/elemental-forms/src/el/util/debug.h" #include "third_party/imgui/imgui.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" @@ -20,6 +19,7 @@ #include "xenia/base/profiling.h" #include "xenia/base/threading.h" #include "xenia/ui/graphics_provider.h" +#include "xenia/ui/imgui_dialog.h" #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/window.h" @@ -71,8 +71,7 @@ int window_demo_main(const std::vector& args) { // Create the graphics context used for drawing and setup the window. std::unique_ptr graphics_provider; - std::unique_ptr imgui_drawer; - loop->PostSynchronous([&window, &graphics_provider, &imgui_drawer]() { + loop->PostSynchronous([&window, &graphics_provider]() { // Create graphics provider and an initial context for the window. // The window will finish initialization wtih the context (loading // resources, etc). @@ -83,19 +82,14 @@ int window_demo_main(const std::vector& args) { GraphicsContextLock context_lock(window->context()); Profiler::set_window(window.get()); - // Initialize the ImGui renderer we'll use. - imgui_drawer = std::make_unique(window.get()); - imgui_drawer->SetupDefaultInput(); - - // Show the elemental-forms debug UI so we can see it working. - el::util::ShowDebugInfoSettingsForm(window->root_element()); + // Enable imgui input. + window->set_imgui_input_enabled(true); }); window->on_closed.AddListener( - [&loop, &graphics_provider, &imgui_drawer](xe::ui::UIEvent* e) { + [&loop, &graphics_provider](xe::ui::UIEvent* e) { loop->Quit(); XELOGI("User-initiated death!"); - imgui_drawer.reset(); graphics_provider.reset(); exit(1); }); @@ -110,23 +104,11 @@ int window_demo_main(const std::vector& args) { }); window->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = ImGui::GetIO(); - auto current_tick_count = Clock::QueryHostTickCount(); - static uint64_t last_draw_tick_count = 0; - io.DeltaTime = (current_tick_count - last_draw_tick_count) / - static_cast(Clock::host_tick_frequency()); - last_draw_tick_count = current_tick_count; - - io.DisplaySize = ImVec2(static_cast(window->width()), - static_cast(window->height())); - - ImGui::NewFrame(); + auto& io = window->imgui_drawer()->GetIO(); ImGui::ShowTestWindow(); ImGui::ShowMetricsWindow(); - ImGui::Render(); - // Continuous paint. window->Invalidate(); }); @@ -134,8 +116,6 @@ int window_demo_main(const std::vector& args) { // Wait until we are exited. loop->AwaitQuit(); - imgui_drawer.reset(); - loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); }); window.reset(); loop.reset(); diff --git a/src/xenia/ui/window_listener.h b/src/xenia/ui/window_listener.h new file mode 100644 index 000000000..0f07435fb --- /dev/null +++ b/src/xenia/ui/window_listener.h @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * 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_WINDOW_LISTENER_H_ +#define XENIA_UI_WINDOW_LISTENER_H_ + +#include "xenia/ui/ui_event.h" + +namespace xe { +namespace ui { + +// Virtual interface for types that want to listen for Window events. +// Use Window::AttachListener and Window::DetachListener to manage active +// listeners. +class WindowListener { + public: + virtual ~WindowListener() = default; + + virtual void OnMainMenuChange() {} + virtual void OnClosing(UIEvent* e) {} + virtual void OnClosed(UIEvent* e) {} + + virtual void OnResize(UIEvent* e) {} + virtual void OnLayout(UIEvent* e) {} + virtual void OnPainting(UIEvent* e) {} + virtual void OnPaint(UIEvent* e) {} + virtual void OnPainted(UIEvent* e) {} + + virtual void OnVisible(UIEvent* e) {} + virtual void OnHidden(UIEvent* e) {} + + virtual void OnGotFocus(UIEvent* e) {} + virtual void OnLostFocus(UIEvent* e) {} + + virtual void OnKeyDown(KeyEvent* e) {} + virtual void OnKeyUp(KeyEvent* e) {} + virtual void OnKeyChar(KeyEvent* e) {} + + virtual void OnMouseDown(MouseEvent* e) {} + virtual void OnMouseMove(MouseEvent* e) {} + virtual void OnMouseUp(MouseEvent* e) {} + virtual void OnMouseWheel(MouseEvent* e) {} +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOW_LISTENER_H_ diff --git a/third_party/elemental-forms b/third_party/elemental-forms deleted file mode 160000 index 3e7d961fe..000000000 --- a/third_party/elemental-forms +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3e7d961fea8cf8a857292d21106834c8566903f2 diff --git a/xenia-build b/xenia-build index 3764337df..f20d5d2f6 100755 --- a/xenia-build +++ b/xenia-build @@ -775,25 +775,13 @@ def find_xenia_source_files(): if name.endswith(('.cc', '.c', '.h', '.inl'))] -def find_elemental_source_files(): - """Gets all elemental-forms source files in the project. - - Returns: - A list of file paths. - """ - return [os.path.join(root, name) - for root, dirs, files in os.walk('third_party/elemental-forms/src/') - for name in files - if name.endswith(('.cc', '.c', '.h', '.inl'))] - - def find_all_source_files(): """Gets all interesting source files in the project. Returns: A list of file paths. """ - return find_xenia_source_files() + find_elemental_source_files() + return find_xenia_source_files() class LintCommand(Command):