Dropping elemental-forms and swapping out with imgui.

Too much code for such little use. This should simplify porting.
This commit is contained in:
Ben Vanik 2015-12-26 20:25:24 -08:00
parent 2071286040
commit 0e58208add
39 changed files with 644 additions and 1100 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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")

View File

@ -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) {

View File

@ -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"

View File

@ -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")

View File

@ -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()

View File

@ -98,8 +98,8 @@ bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder,
}
function->set_debug_info(std::move(debug_info));
static_cast<X64Function*>(function)
->Setup(reinterpret_cast<uint8_t*>(machine_code), code_size);
static_cast<X64Function*>(function)->Setup(
reinterpret_cast<uint8_t*>(machine_code), code_size);
return true;
}

View File

@ -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;

View File

@ -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<xe::ui::ImGuiDrawer>(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<float>(Clock::host_tick_frequency());
last_draw_tick_count_ = current_tick_count;
io.DisplaySize = ImVec2(static_cast<float>(window_->width()),
static_cast<float>(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));

View File

@ -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<xe::ui::Window> window_;
std::unique_ptr<xe::ui::ImGuiDrawer> imgui_drawer_;
uint64_t last_draw_tick_count_ = 0;
uintptr_t capstone_handle_ = 0;

View File

@ -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()

View File

@ -11,7 +11,6 @@
#include <gflags/gflags.h>
#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).

View File

@ -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({

View File

@ -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")

View File

@ -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<TracePlayer>(loop_.get(), graphics_system_);
imgui_drawer_ = std::make_unique<xe::ui::ImGuiDrawer>(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();
}

View File

@ -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<TracePlayer> player_;
std::unique_ptr<xe::ui::ImGuiDrawer> imgui_drawer_;
private:
enum class ShaderDisplayType : int {
kUcode,

View File

@ -34,7 +34,6 @@ DEFINE_string(hid, "any", "Input system. Use: [any, nop, winkey, xinput]");
namespace xe {
namespace hid {
std::unique_ptr<xe::ui::ImGuiDrawer> imgui_drawer_;
std::unique_ptr<xe::hid::InputSystem> input_system_;
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
@ -88,7 +87,6 @@ int hid_demo_main(const std::vector<std::wstring>& 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<std::wstring>& 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<xe::ui::ImGuiDrawer>(window.get());
imgui_drawer_->SetupDefaultInput();
// Initialize input system and all drivers.
input_system_ = std::make_unique<xe::hid::InputSystem>(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<float>(Clock::host_tick_frequency());
last_draw_tick_count = current_tick_count;
io.DisplaySize = ImVec2(static_cast<float>(window->width()),
static_cast<float>(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<std::wstring>& args) {
ImGui::End();
ImGui::PopStyleVar();
ImGui::Render();
// Continuous paint.
window->Invalidate();
});
@ -155,7 +139,6 @@ int hid_demo_main(const std::vector<std::wstring>& args) {
// Wait until we are exited.
loop->AwaitQuit();
imgui_drawer_.reset();
input_system_.reset();
loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); });

View File

@ -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")

View File

@ -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()

View File

@ -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;

View File

@ -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()

View File

@ -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<int> 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<std::wstring> 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<std::wstring> 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<el::Button>(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<el::Button>() &&
(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<uint32_t>(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<std::wstring> 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<el::TextBox>("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<char> 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<XAM_OVERLAPPED> 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.

View File

@ -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"})

View File

@ -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<uint8_t*>(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<uint8_t*>(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<el::graphics::Bitmap> ElementalDrawer::CreateBitmap(
int width, int height, uint32_t* data) {
auto bitmap = std::make_unique<ElementalBitmap>(this, width, height, data);
return std::unique_ptr<el::graphics::Bitmap>(bitmap.release());
}
void ElementalDrawer::RenderBatch(Batch* batch) {
auto immediate_drawer = graphics_context_->immediate_drawer();
ImmediateDrawBatch draw_batch;
draw_batch.vertices = reinterpret_cast<ImmediateVertex*>(batch->vertices);
draw_batch.vertex_count = static_cast<int>(batch->vertex_count);
immediate_drawer->BeginDrawBatch(draw_batch);
ImmediateDraw draw;
draw.primitive_type = ImmediatePrimitiveType::kTriangles;
draw.count = static_cast<int>(batch->vertex_count);
auto bitmap = static_cast<ElementalBitmap*>(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

View File

@ -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 <cstdint>
#include <memory>
#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<el::graphics::Bitmap> 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<ImmediateTexture> 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_

View File

@ -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",
})

View File

@ -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<char*>(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

View File

@ -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 <memory>
#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<xe::threading::Fence*> waiting_fences_;
};
} // namespace ui
} // namespace xe
#endif // XENIA_UI_IMGUI_DIALOG_H_

View File

@ -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<ImFontAtlas>();
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

View File

@ -11,10 +11,14 @@
#define XENIA_UI_IMGUI_DRAWER_H_
#include <memory>
#include <vector>
#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<uint8_t> internal_state_;
std::unique_ptr<ImFontAtlas> font_atlas_;
std::unique_ptr<ImmediateTexture> font_texture_;
};

View File

@ -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<void()> fn) {
if (is_on_loop_thread()) {
@ -54,36 +35,3 @@ void Loop::PostSynchronous(std::function<void()> 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);
}

View File

@ -23,11 +23,6 @@ class Loop {
public:
static std::unique_ptr<Loop> 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();

View File

@ -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()

View File

@ -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<el::io::Win32ResFileSystem>("IDR_default_resources_"));
el::io::FileManager::RegisterFileSystem(
std::make_unique<el::io::PosixFileSystem>(
"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<ElementalDrawer>(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<RootElement>(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<xe::ui::ImGuiDrawer>(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<uint32_t>(
fps_frame_count_ /
(static_cast<double>(now_ns - fps_update_time_) / 10000000.0));
fps_update_time_ = now_ns;
(static_cast<double>(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<float>(width()), static_cast<float>(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

View File

@ -13,13 +13,12 @@
#include <memory>
#include <string>
#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<Window> 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<GraphicsContext> 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<UIEvent*> on_closing;
Delegate<UIEvent*> on_closed;
Delegate<int> on_command;
Delegate<UIEvent*> on_resize;
Delegate<UIEvent*> on_layout;
Delegate<UIEvent*> on_painting;
Delegate<UIEvent*> on_paint;
Delegate<UIEvent*> on_painted;
Delegate<UIEvent*> on_visible;
Delegate<UIEvent*> on_hidden;
Delegate<UIEvent*> on_got_focus;
Delegate<UIEvent*> on_lost_focus;
Delegate<KeyEvent*> on_key_down;
Delegate<KeyEvent*> on_key_up;
Delegate<KeyEvent*> 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<MenuItem> 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<GraphicsContext> context_;
std::unique_ptr<el::graphics::Renderer> renderer_;
std::unique_ptr<el::Element> root_element_;
std::unique_ptr<ImGuiDrawer> 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<WindowListener*> listeners_;
};
} // namespace ui

View File

@ -11,7 +11,6 @@
#include <cstring>
#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<std::wstring>& args) {
// Create the graphics context used for drawing and setup the window.
std::unique_ptr<GraphicsProvider> graphics_provider;
std::unique_ptr<xe::ui::ImGuiDrawer> 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<std::wstring>& args) {
GraphicsContextLock context_lock(window->context());
Profiler::set_window(window.get());
// Initialize the ImGui renderer we'll use.
imgui_drawer = std::make_unique<xe::ui::ImGuiDrawer>(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<std::wstring>& 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<float>(Clock::host_tick_frequency());
last_draw_tick_count = current_tick_count;
io.DisplaySize = ImVec2(static_cast<float>(window->width()),
static_cast<float>(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<std::wstring>& args) {
// Wait until we are exited.
loop->AwaitQuit();
imgui_drawer.reset();
loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); });
window.reset();
loop.reset();

View File

@ -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_

@ -1 +0,0 @@
Subproject commit 3e7d961fea8cf8a857292d21106834c8566903f2

View File

@ -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):