Dropping elemental-forms and swapping out with imgui.
Too much code for such little use. This should simplify porting.
This commit is contained in:
@@ -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
|
||||
@@ -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_
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
93
src/xenia/ui/imgui_dialog.cc
Normal file
93
src/xenia/ui/imgui_dialog.cc
Normal 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
|
||||
60
src/xenia/ui/imgui_dialog.h
Normal file
60
src/xenia/ui/imgui_dialog.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
54
src/xenia/ui/window_listener.h
Normal file
54
src/xenia/ui/window_listener.h
Normal 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_
|
||||
Reference in New Issue
Block a user