xe::ui control for hosting a turbobadger UI.
This commit is contained in:
parent
dec0d12cc9
commit
4ec0655751
|
@ -58,6 +58,8 @@
|
|||
#define strdup _strdup
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
#undef DeleteBitmap
|
||||
#undef GetFirstChild
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
|
|
|
@ -9,19 +9,33 @@
|
|||
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/debug/ui/main_window.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
#include "third_party/turbobadger/src/tb/tb_msg.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_system.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
Application::Application() {}
|
||||
Application* current_application_ = nullptr;
|
||||
|
||||
Application::~Application() = default;
|
||||
Application* Application::current() {
|
||||
assert_not_null(current_application_);
|
||||
return current_application_;
|
||||
}
|
||||
|
||||
Application::Application() { current_application_ = this; }
|
||||
|
||||
Application::~Application() {
|
||||
assert_true(current_application_ == this);
|
||||
current_application_ = nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Application> Application::Create() {
|
||||
std::unique_ptr<Application> app(new Application());
|
||||
|
@ -64,3 +78,33 @@ void Application::Quit() {
|
|||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
// This doesn't really belong here (it belongs in tb_system_[linux/windows].cpp.
|
||||
// This is here since the proper implementations has not yet been done.
|
||||
void tb::TBSystem::RescheduleTimer(uint64_t fire_time) {
|
||||
if (fire_time == tb::TB_NOT_SOON) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = tb::TBSystem::GetTimeMS();
|
||||
uint64_t delay_millis = fire_time >= now ? fire_time - now : 0;
|
||||
xe::debug::ui::Application::current()->loop()->PostDelayed([]() {
|
||||
uint64_t next_fire_time = tb::TBMessageHandler::GetNextMessageFireTime();
|
||||
uint64_t now = tb::TBSystem::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;
|
||||
}
|
||||
|
||||
tb::TBMessageHandler::ProcessMessages();
|
||||
|
||||
// If we still have things to do (because we didn't process all messages,
|
||||
// or because there are new messages), we need to rescedule, so call
|
||||
// RescheduleTimer.
|
||||
tb::TBSystem::RescheduleTimer(
|
||||
tb::TBMessageHandler::GetNextMessageFireTime());
|
||||
}, delay_millis);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ class Application {
|
|||
~Application();
|
||||
|
||||
static std::unique_ptr<Application> Create();
|
||||
static Application* current();
|
||||
|
||||
xe::ui::Loop* loop() { return &loop_; }
|
||||
MainWindow* main_window() const { return main_window_.get(); }
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/debug/ui/turbo_badger_control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
@ -88,7 +89,7 @@ bool MainWindow::Initialize() {
|
|||
// Setup the GL control that actually does the drawing.
|
||||
// We run here in the loop and only touch it (and its context) on this
|
||||
// thread. That means some sync-fu when we want to swap.
|
||||
control_ = std::make_unique<xe::ui::gl::WGLControl>(loop());
|
||||
control_ = std::make_unique<TurboBadgerControl>(loop());
|
||||
AddChild(control_.get());
|
||||
|
||||
Resize(1440, 1200);
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include "xenia/debug/ui/application.h"
|
||||
#include "xenia/ui/gl/wgl_control.h"
|
||||
#include "xenia/ui/platform.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
|
@ -21,6 +20,8 @@ namespace xe {
|
|||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class TurboBadgerControl;
|
||||
|
||||
class MainWindow : public xe::ui::PlatformWindow {
|
||||
public:
|
||||
MainWindow(Application* app);
|
||||
|
@ -36,7 +37,7 @@ class MainWindow : public xe::ui::PlatformWindow {
|
|||
|
||||
Application* app_ = nullptr;
|
||||
xe::ui::PlatformMenu main_menu_;
|
||||
std::unique_ptr<xe::ui::gl::WGLControl> control_;
|
||||
std::unique_ptr<TurboBadgerControl> control_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
@ -0,0 +1,471 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/turbo_badger_control.h"
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/debug/ui/turbo_badger_renderer.h"
|
||||
|
||||
// TODO(benvanik): remove this.
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
#include "third_party/turbobadger/src/tb/tb_core.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_font_renderer.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_message_window.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_widget_animation.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
constexpr bool kContinuousRepaint = true;
|
||||
|
||||
// Enables long press behaviors (context menu, etc).
|
||||
constexpr bool kTouch = false;
|
||||
|
||||
constexpr uint64_t kDoubleClickDelayMillis = 600;
|
||||
constexpr double kDoubleClickDistance = 5;
|
||||
|
||||
constexpr int32_t kMouseWheelDetent = 120;
|
||||
|
||||
class RootWidget : public tb::TBWidget {
|
||||
public:
|
||||
RootWidget(TurboBadgerControl* owner) : owner_(owner) {}
|
||||
void OnInvalid() override { owner_->Invalidate(); }
|
||||
|
||||
private:
|
||||
TurboBadgerControl* owner_ = nullptr;
|
||||
};
|
||||
|
||||
bool TurboBadgerControl::InitializeTurboBadger(tb::TBRenderer* renderer) {
|
||||
static bool has_initialized = false;
|
||||
if (has_initialized) {
|
||||
return true;
|
||||
}
|
||||
has_initialized = true;
|
||||
|
||||
if (!tb::tb_core_init(
|
||||
renderer,
|
||||
"third_party/turbobadger/resources/language/lng_en.tb.txt")) {
|
||||
XELOGE("Failed to initialize turbobadger core");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the default skin, and override skin that contains the graphics
|
||||
// specific to the demo.
|
||||
if (!tb::g_tb_skin->Load(
|
||||
"third_party/turbobadger/resources/default_skin/skin.tb.txt",
|
||||
"third_party/turbobadger/Demo/demo01/skin/skin.tb.txt")) {
|
||||
XELOGE("Failed to load turbobadger skin");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register font renderers.
|
||||
#ifdef TB_FONT_RENDERER_TBBF
|
||||
void register_tbbf_font_renderer();
|
||||
register_tbbf_font_renderer();
|
||||
#endif
|
||||
#ifdef TB_FONT_RENDERER_STB
|
||||
void register_stb_font_renderer();
|
||||
register_stb_font_renderer();
|
||||
#endif
|
||||
#ifdef TB_FONT_RENDERER_FREETYPE
|
||||
void register_freetype_font_renderer();
|
||||
register_freetype_font_renderer();
|
||||
#endif
|
||||
|
||||
// Add fonts we can use to the font manager.
|
||||
#if defined(TB_FONT_RENDERER_STB) || defined(TB_FONT_RENDERER_FREETYPE)
|
||||
tb::g_font_manager->AddFontInfo("third_party/turbobadger/resources/vera.ttf",
|
||||
"Default");
|
||||
#endif
|
||||
#ifdef TB_FONT_RENDERER_TBBF
|
||||
tb::g_font_manager->AddFontInfo(
|
||||
"third_party/turbobadger/resources/default_font/"
|
||||
"segoe_white_with_shadow.tb.txt",
|
||||
"Default");
|
||||
#endif
|
||||
|
||||
// Set the default font description for widgets to one of the fonts we just
|
||||
// added.
|
||||
tb::TBFontDescription fd;
|
||||
fd.SetID(TBIDC("Default"));
|
||||
fd.SetSize(tb::g_tb_skin->GetDimensionConverter()->DpToPx(14));
|
||||
tb::g_font_manager->SetDefaultFontDescription(fd);
|
||||
|
||||
// Create the font now.
|
||||
auto font = tb::g_font_manager->CreateFontFace(
|
||||
tb::g_font_manager->GetDefaultFontDescription());
|
||||
return true;
|
||||
}
|
||||
|
||||
void TurboBadgerControl::ShutdownTurboBadger() { tb::tb_core_shutdown(); }
|
||||
|
||||
TurboBadgerControl::TurboBadgerControl(xe::ui::Loop* loop) : super(loop) {}
|
||||
|
||||
TurboBadgerControl::~TurboBadgerControl() = default;
|
||||
|
||||
bool TurboBadgerControl::Create() {
|
||||
if (!super::Create()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(benvanik): setup renderer?
|
||||
renderer_ = TBRendererGL4::Create();
|
||||
|
||||
if (!InitializeTurboBadger(renderer_.get())) {
|
||||
XELOGE("Unable to initialize turbobadger");
|
||||
return false;
|
||||
}
|
||||
|
||||
tb::TBWidgetsAnimationManager::Init();
|
||||
|
||||
// TODO(benvanik): setup widgets.
|
||||
root_widget_ = std::make_unique<RootWidget>(this);
|
||||
root_widget_->SetSkinBg(TBIDC("background"));
|
||||
root_widget_->SetRect(tb::TBRect(0, 0, 1000, 1000));
|
||||
|
||||
// Block animations during init.
|
||||
tb::TBAnimationBlocker anim_blocker;
|
||||
|
||||
// TODO(benvanik): dummy UI.
|
||||
auto message_window = new tb::TBMessageWindow(root_widget(), TBIDC(""));
|
||||
message_window->Show("Title", "Hello!");
|
||||
|
||||
// tb::ShowDebugInfoSettingsWindow(root_widget());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TurboBadgerControl::Destroy() {
|
||||
tb::TBWidgetsAnimationManager::Shutdown();
|
||||
super::Destroy();
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnLayout(xe::ui::UIEvent& e) {
|
||||
super::OnLayout(e);
|
||||
if (!root_widget()) {
|
||||
return;
|
||||
}
|
||||
// TODO(benvanik): subregion?
|
||||
root_widget()->SetRect(tb::TBRect(0, 0, width(), height()));
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnPaint(xe::ui::UIEvent& e) {
|
||||
super::OnPaint(e);
|
||||
|
||||
++frame_count_;
|
||||
++fps_frame_count_;
|
||||
uint64_t now_ns = xe::Clock::QueryHostSystemTime();
|
||||
if (now_ns > fps_update_time_ + 1000 * 10000) {
|
||||
fps_ = uint32_t(fps_frame_count_ /
|
||||
(double(now_ns - fps_update_time_) / 10000000.0));
|
||||
fps_update_time_ = now_ns;
|
||||
fps_frame_count_ = 0;
|
||||
}
|
||||
|
||||
// Update TB (run animations, handle deferred input, etc).
|
||||
tb::TBAnimationManager::Update();
|
||||
root_widget()->InvokeProcessStates();
|
||||
root_widget()->InvokeProcess();
|
||||
|
||||
renderer()->BeginPaint(width(), height());
|
||||
|
||||
// Render entire control hierarchy.
|
||||
root_widget()->InvokePaint(tb::TBWidget::PaintProps());
|
||||
|
||||
// Render debug overlay.
|
||||
root_widget()->GetFont()->DrawString(
|
||||
5, 5, tb::TBColor(255, 0, 0),
|
||||
tb::format_string("Frame %lld", frame_count_));
|
||||
if (kContinuousRepaint) {
|
||||
root_widget()->GetFont()->DrawString(5, 20, tb::TBColor(255, 0, 0),
|
||||
tb::format_string("FPS: %d", fps_));
|
||||
}
|
||||
|
||||
renderer()->EndPaint();
|
||||
|
||||
// If animations are running, reinvalidate immediately.
|
||||
if (tb::TBAnimationManager::HasAnimationsRunning()) {
|
||||
root_widget()->Invalidate();
|
||||
}
|
||||
if (kContinuousRepaint) {
|
||||
// Force an immediate repaint, always.
|
||||
root_widget()->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnGotFocus(xe::ui::UIEvent& e) {
|
||||
super::OnGotFocus(e);
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnLostFocus(xe::ui::UIEvent& e) {
|
||||
super::OnLostFocus(e);
|
||||
modifier_shift_pressed_ = false;
|
||||
modifier_cntrl_pressed_ = false;
|
||||
modifier_alt_pressed_ = false;
|
||||
modifier_super_pressed_ = false;
|
||||
last_click_time_ = 0;
|
||||
}
|
||||
|
||||
tb::MODIFIER_KEYS TurboBadgerControl::GetModifierKeys() {
|
||||
auto modifiers = tb::TB_MODIFIER_NONE;
|
||||
if (modifier_shift_pressed_) {
|
||||
modifiers |= tb::MODIFIER_KEYS::TB_SHIFT;
|
||||
}
|
||||
if (modifier_cntrl_pressed_) {
|
||||
modifiers |= tb::MODIFIER_KEYS::TB_CTRL;
|
||||
}
|
||||
if (modifier_alt_pressed_) {
|
||||
modifiers |= tb::MODIFIER_KEYS::TB_ALT;
|
||||
}
|
||||
if (modifier_super_pressed_) {
|
||||
modifiers |= tb::MODIFIER_KEYS::TB_SUPER;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnKeyPress(xe::ui::KeyEvent& e, bool is_down) {
|
||||
tb::SPECIAL_KEY special_key = tb::SPECIAL_KEY::TB_KEY_UNDEFINED;
|
||||
switch (e.key_code()) {
|
||||
case 38:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_UP;
|
||||
break;
|
||||
case 39:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_RIGHT;
|
||||
break;
|
||||
case 40:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_DOWN;
|
||||
break;
|
||||
case 37:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_LEFT;
|
||||
break;
|
||||
case 112:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F1;
|
||||
break;
|
||||
case 113:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F2;
|
||||
break;
|
||||
case 114:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F3;
|
||||
break;
|
||||
case 115:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F4;
|
||||
break;
|
||||
case 116:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F5;
|
||||
break;
|
||||
case 117:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F6;
|
||||
break;
|
||||
case 118:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F7;
|
||||
break;
|
||||
case 119:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F8;
|
||||
break;
|
||||
case 120:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F9;
|
||||
break;
|
||||
case 121:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F10;
|
||||
break;
|
||||
case 122:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F11;
|
||||
break;
|
||||
case 123:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_F12;
|
||||
break;
|
||||
case 33:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_PAGE_UP;
|
||||
break;
|
||||
case 34:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_PAGE_DOWN;
|
||||
break;
|
||||
case 36:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_HOME;
|
||||
break;
|
||||
case 35:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_END;
|
||||
break;
|
||||
case 45:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_INSERT;
|
||||
break;
|
||||
case 9:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_TAB;
|
||||
break;
|
||||
case 46:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_DELETE;
|
||||
break;
|
||||
case 8:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_BACKSPACE;
|
||||
break;
|
||||
case 13:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_ENTER;
|
||||
break;
|
||||
case 27:
|
||||
special_key = tb::SPECIAL_KEY::TB_KEY_ESC;
|
||||
break;
|
||||
case 93:
|
||||
if (!is_down && tb::TBWidget::focused_widget) {
|
||||
tb::TBWidgetEvent ev(tb::EVENT_TYPE_CONTEXT_MENU);
|
||||
ev.modifierkeys = GetModifierKeys();
|
||||
tb::TBWidget::focused_widget->InvokeEvent(ev);
|
||||
e.set_handled(true);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 16:
|
||||
modifier_shift_pressed_ = is_down;
|
||||
break;
|
||||
case 17:
|
||||
modifier_cntrl_pressed_ = is_down;
|
||||
break;
|
||||
// case xx:
|
||||
// // alt ??
|
||||
// modifier_alt_pressed_ = is_down;
|
||||
// break;
|
||||
case 91:
|
||||
modifier_super_pressed_ = is_down;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CheckShortcutKey(e, special_key, is_down)) {
|
||||
e.set_handled(root_widget()->InvokeKey(
|
||||
special_key != tb::SPECIAL_KEY::TB_KEY_UNDEFINED ? e.key_code() : 0,
|
||||
special_key, GetModifierKeys(), is_down));
|
||||
}
|
||||
}
|
||||
|
||||
bool TurboBadgerControl::CheckShortcutKey(xe::ui::KeyEvent& e,
|
||||
tb::SPECIAL_KEY special_key,
|
||||
bool is_down) {
|
||||
bool shortcut_key = modifier_cntrl_pressed_;
|
||||
if (!tb::TBWidget::focused_widget || !is_down || !shortcut_key) {
|
||||
return false;
|
||||
}
|
||||
bool reverse_key = modifier_shift_pressed_;
|
||||
int upper_key = e.key_code();
|
||||
if (upper_key >= 'a' && upper_key <= 'z') {
|
||||
upper_key += 'A' - 'a';
|
||||
}
|
||||
tb::TBID id;
|
||||
if (upper_key == 'X') {
|
||||
id = TBIDC("cut");
|
||||
} else if (upper_key == 'C' || special_key == tb::TB_KEY_INSERT) {
|
||||
id = TBIDC("copy");
|
||||
} else if (upper_key == 'V' ||
|
||||
(special_key == tb::TB_KEY_INSERT && reverse_key)) {
|
||||
id = TBIDC("paste");
|
||||
} else if (upper_key == 'A') {
|
||||
id = TBIDC("selectall");
|
||||
} else if (upper_key == 'Z' || upper_key == 'Y') {
|
||||
bool undo = upper_key == 'Z';
|
||||
if (reverse_key) {
|
||||
undo = !undo;
|
||||
}
|
||||
id = undo ? TBIDC("undo") : TBIDC("redo");
|
||||
} else if (upper_key == 'N') {
|
||||
id = TBIDC("new");
|
||||
} else if (upper_key == 'O') {
|
||||
id = TBIDC("open");
|
||||
} else if (upper_key == 'S') {
|
||||
id = TBIDC("save");
|
||||
} else if (upper_key == 'W') {
|
||||
id = TBIDC("close");
|
||||
} else if (special_key == tb::TB_KEY_PAGE_UP) {
|
||||
id = TBIDC("prev_doc");
|
||||
} else if (special_key == tb::TB_KEY_PAGE_DOWN) {
|
||||
id = TBIDC("next_doc");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
tb::TBWidgetEvent ev(tb::EVENT_TYPE_SHORTCUT);
|
||||
ev.modifierkeys = GetModifierKeys();
|
||||
ev.ref_id = id;
|
||||
if (!tb::TBWidget::focused_widget->InvokeEvent(ev)) {
|
||||
return false;
|
||||
}
|
||||
e.set_handled(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnKeyDown(xe::ui::KeyEvent& e) {
|
||||
super::OnKeyDown(e);
|
||||
OnKeyPress(e, true);
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnKeyUp(xe::ui::KeyEvent& e) {
|
||||
super::OnKeyUp(e);
|
||||
OnKeyPress(e, false);
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnMouseDown(xe::ui::MouseEvent& e) {
|
||||
super::OnMouseDown(e);
|
||||
// TODO(benvanik): more button types.
|
||||
if (e.button() == xe::ui::MouseEvent::Button::kLeft) {
|
||||
// Simulated click count support.
|
||||
// TODO(benvanik): move into Control?
|
||||
uint64_t now = xe::Clock::QueryHostUptimeMillis();
|
||||
if (now < last_click_time_ + kDoubleClickDelayMillis) {
|
||||
double distance_moved = std::sqrt(std::pow(e.x() - last_click_x_, 2.0) +
|
||||
std::pow(e.y() - last_click_y_, 2.0));
|
||||
if (distance_moved < kDoubleClickDistance) {
|
||||
++last_click_counter_;
|
||||
} else {
|
||||
last_click_counter_ = 1;
|
||||
}
|
||||
} else {
|
||||
last_click_counter_ = 1;
|
||||
}
|
||||
last_click_x_ = e.x();
|
||||
last_click_y_ = e.y();
|
||||
last_click_time_ = now;
|
||||
|
||||
e.set_handled(root_widget()->InvokePointerDown(
|
||||
e.x(), e.y(), last_click_counter_, GetModifierKeys(), kTouch));
|
||||
}
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnMouseMove(xe::ui::MouseEvent& e) {
|
||||
super::OnMouseMove(e);
|
||||
root_widget()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
|
||||
e.set_handled(true);
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnMouseUp(xe::ui::MouseEvent& e) {
|
||||
super::OnMouseUp(e);
|
||||
if (e.button() == xe::ui::MouseEvent::Button::kLeft) {
|
||||
e.set_handled(root_widget()->InvokePointerUp(e.x(), e.y(),
|
||||
GetModifierKeys(), kTouch));
|
||||
} else if (e.button() == xe::ui::MouseEvent::Button::kRight) {
|
||||
root_widget()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
|
||||
if (tb::TBWidget::hovered_widget) {
|
||||
int x = e.x();
|
||||
int y = e.y();
|
||||
tb::TBWidget::hovered_widget->ConvertFromRoot(x, y);
|
||||
tb::TBWidgetEvent ev(tb::EVENT_TYPE_CONTEXT_MENU, x, y, kTouch,
|
||||
GetModifierKeys());
|
||||
tb::TBWidget::hovered_widget->InvokeEvent(ev);
|
||||
}
|
||||
e.set_handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TurboBadgerControl::OnMouseWheel(xe::ui::MouseEvent& e) {
|
||||
super::OnMouseWheel(e);
|
||||
e.set_handled(root_widget()->InvokeWheel(
|
||||
e.x(), e.y(), e.dx(), -e.dy() / kMouseWheelDetent, GetModifierKeys()));
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_
|
||||
#define XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/ui/gl/wgl_control.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
#include "third_party/turbobadger/src/tb/tb_core.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_widgets.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class TurboBadgerControl : public xe::ui::gl::WGLControl {
|
||||
public:
|
||||
TurboBadgerControl(xe::ui::Loop* loop);
|
||||
~TurboBadgerControl() override;
|
||||
|
||||
static bool InitializeTurboBadger(tb::TBRenderer* renderer);
|
||||
static void ShutdownTurboBadger();
|
||||
|
||||
tb::TBRenderer* renderer() const { return renderer_.get(); }
|
||||
tb::TBWidget* root_widget() const { return root_widget_.get(); }
|
||||
|
||||
protected:
|
||||
using super = xe::ui::gl::WGLControl;
|
||||
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
void OnLayout(xe::ui::UIEvent& e) override;
|
||||
void OnPaint(xe::ui::UIEvent& e) override;
|
||||
|
||||
void OnGotFocus(xe::ui::UIEvent& e) override;
|
||||
void OnLostFocus(xe::ui::UIEvent& e) override;
|
||||
|
||||
tb::MODIFIER_KEYS GetModifierKeys();
|
||||
|
||||
void OnKeyPress(xe::ui::KeyEvent& e, bool is_down);
|
||||
bool CheckShortcutKey(xe::ui::KeyEvent& e, tb::SPECIAL_KEY special_key,
|
||||
bool is_down);
|
||||
void OnKeyDown(xe::ui::KeyEvent& e) override;
|
||||
void OnKeyUp(xe::ui::KeyEvent& e) override;
|
||||
|
||||
void OnMouseDown(xe::ui::MouseEvent& e) override;
|
||||
void OnMouseMove(xe::ui::MouseEvent& e) override;
|
||||
void OnMouseUp(xe::ui::MouseEvent& e) override;
|
||||
void OnMouseWheel(xe::ui::MouseEvent& e) override;
|
||||
|
||||
std::unique_ptr<tb::TBRenderer> renderer_;
|
||||
std::unique_ptr<tb::TBWidget> root_widget_;
|
||||
|
||||
uint32_t frame_count_ = 0;
|
||||
uint32_t fps_ = 0;
|
||||
uint64_t fps_update_time_ = 0;
|
||||
uint64_t fps_frame_count_ = 0;
|
||||
|
||||
bool modifier_shift_pressed_ = false;
|
||||
bool modifier_cntrl_pressed_ = false;
|
||||
bool modifier_alt_pressed_ = false;
|
||||
bool modifier_super_pressed_ = false;
|
||||
uint64_t last_click_time_ = 0;
|
||||
int last_click_x_ = 0;
|
||||
int last_click_y_ = 0;
|
||||
int last_click_counter_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_
|
|
@ -0,0 +1,297 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/turbo_badger_renderer.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
#include "third_party/turbobadger/src/tb/tb_types.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_bitmap_fragment.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_system.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
using namespace tb;
|
||||
|
||||
class TBRendererGL4::TBBitmapGL4 : public tb::TBBitmap {
|
||||
public:
|
||||
TBBitmapGL4(TBRendererGL4* renderer);
|
||||
~TBBitmapGL4();
|
||||
|
||||
bool Init(int width, int height, uint32_t* data);
|
||||
int Width() override { return width_; }
|
||||
int Height() override { return height_; }
|
||||
void SetData(uint32_t* data) override;
|
||||
|
||||
TBRendererGL4* renderer_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
GLuint handle_ = 0;
|
||||
GLuint64 gpu_handle_ = 0;
|
||||
};
|
||||
|
||||
TBRendererGL4::TBBitmapGL4::TBBitmapGL4(TBRendererGL4* renderer)
|
||||
: renderer_(renderer) {}
|
||||
|
||||
TBRendererGL4::TBBitmapGL4::~TBBitmapGL4() {
|
||||
// Must flush and unbind before we delete the texture
|
||||
renderer_->FlushBitmap(this);
|
||||
glMakeTextureHandleNonResidentARB(gpu_handle_);
|
||||
glDeleteTextures(1, &handle_);
|
||||
}
|
||||
|
||||
bool TBRendererGL4::TBBitmapGL4::Init(int width, int height, uint32_t* data) {
|
||||
assert(width == TBGetNearestPowerOfTwo(width));
|
||||
assert(height == TBGetNearestPowerOfTwo(height));
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &handle_);
|
||||
glTextureParameteri(handle_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(handle_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(handle_, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTextureParameteri(handle_, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTextureStorage2D(handle_, 1, GL_RGBA8, width_, height_);
|
||||
|
||||
gpu_handle_ = glGetTextureHandleARB(handle_);
|
||||
glMakeTextureHandleResidentARB(gpu_handle_);
|
||||
|
||||
SetData(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBRendererGL4::TBBitmapGL4::SetData(uint32_t* data) {
|
||||
renderer_->FlushBitmap(this);
|
||||
glTextureSubImage2D(handle_, 0, 0, 0, width_, height_, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
}
|
||||
|
||||
TBRendererGL4::TBRendererGL4()
|
||||
: vertex_buffer_(VERTEX_BATCH_SIZE * sizeof(Vertex)) {}
|
||||
|
||||
TBRendererGL4::~TBRendererGL4() {
|
||||
vertex_buffer_.Shutdown();
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
glDeleteProgram(program_);
|
||||
}
|
||||
|
||||
std::unique_ptr<TBRendererGL4> TBRendererGL4::Create() {
|
||||
auto renderer = std::make_unique<TBRendererGL4>();
|
||||
if (!renderer->Initialize()) {
|
||||
XELOGE("Failed to initialize TurboBadger GL4 renderer");
|
||||
return nullptr;
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
bool TBRendererGL4::Initialize() {
|
||||
if (!vertex_buffer_.Initialize()) {
|
||||
XELOGE("Failed to initialize circular buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string header =
|
||||
"\n\
|
||||
#version 450 \n\
|
||||
#extension GL_ARB_bindless_texture : require \n\
|
||||
#extension GL_ARB_explicit_uniform_location : require \n\
|
||||
#extension GL_ARB_shading_language_420pack : require \n\
|
||||
precision highp float; \n\
|
||||
precision highp int; \n\
|
||||
layout(std140, column_major) uniform; \n\
|
||||
layout(std430, column_major) buffer; \n\
|
||||
";
|
||||
const std::string vertex_shader_source = header +
|
||||
"\n\
|
||||
layout(location = 0) uniform mat4 projection_matrix; \n\
|
||||
layout(location = 0) in vec2 in_pos; \n\
|
||||
layout(location = 1) in vec4 in_color; \n\
|
||||
layout(location = 2) in vec2 in_uv; \n\
|
||||
layout(location = 0) out vec4 vtx_color; \n\
|
||||
layout(location = 1) out vec2 vtx_uv; \n\
|
||||
void main() { \n\
|
||||
gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0); \n\
|
||||
vtx_color = in_color; \n\
|
||||
vtx_uv = in_uv; \n\
|
||||
} \n\
|
||||
";
|
||||
const std::string fragment_shader_source = header +
|
||||
"\n\
|
||||
layout(location = 1, bindless_sampler) uniform sampler2D texture_sampler; \n\
|
||||
layout(location = 2) uniform float texture_mix; \n\
|
||||
layout(location = 0) in vec4 vtx_color; \n\
|
||||
layout(location = 1) in vec2 vtx_uv; \n\
|
||||
layout(location = 0) out vec4 oC; \n\
|
||||
void main() { \n\
|
||||
oC = vtx_color; \n\
|
||||
if (texture_mix > 0.0) { \n\
|
||||
vec4 color = texture(texture_sampler, vtx_uv); \n\
|
||||
oC *= color.rgba; \n\
|
||||
} \n\
|
||||
} \n\
|
||||
";
|
||||
|
||||
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||
const char* vertex_shader_source_ptr = vertex_shader_source.c_str();
|
||||
GLint vertex_shader_source_length = GLint(vertex_shader_source.size());
|
||||
glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr,
|
||||
&vertex_shader_source_length);
|
||||
glCompileShader(vertex_shader);
|
||||
|
||||
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
const char* fragment_shader_source_ptr = fragment_shader_source.c_str();
|
||||
GLint fragment_shader_source_length = GLint(fragment_shader_source.size());
|
||||
glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr,
|
||||
&fragment_shader_source_length);
|
||||
glCompileShader(fragment_shader);
|
||||
|
||||
program_ = glCreateProgram();
|
||||
glAttachShader(program_, vertex_shader);
|
||||
glAttachShader(program_, fragment_shader);
|
||||
glLinkProgram(program_);
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
glCreateVertexArrays(1, &vao_);
|
||||
glEnableVertexArrayAttrib(vao_, 0);
|
||||
glVertexArrayAttribBinding(vao_, 0, 0);
|
||||
glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE,
|
||||
offsetof(Vertex, x));
|
||||
glEnableVertexArrayAttrib(vao_, 1);
|
||||
glVertexArrayAttribBinding(vao_, 1, 0);
|
||||
glVertexArrayAttribFormat(vao_, 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
offsetof(Vertex, col));
|
||||
glEnableVertexArrayAttrib(vao_, 2);
|
||||
glVertexArrayAttribBinding(vao_, 2, 0);
|
||||
glVertexArrayAttribFormat(vao_, 2, 2, GL_FLOAT, GL_FALSE,
|
||||
offsetof(Vertex, u));
|
||||
glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_.handle(), 0,
|
||||
sizeof(Vertex));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TBBitmap* TBRendererGL4::CreateBitmap(int width, int height, uint32_t* data) {
|
||||
auto bitmap = std::make_unique<TBBitmapGL4>(this);
|
||||
if (!bitmap->Init(width, height, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return bitmap.release();
|
||||
}
|
||||
|
||||
void TBRendererGL4::SetClipRect(const TBRect& rect) {
|
||||
Flush();
|
||||
glScissor(m_clip_rect.x, m_screen_rect.h - (m_clip_rect.y + m_clip_rect.h),
|
||||
m_clip_rect.w, m_clip_rect.h);
|
||||
}
|
||||
|
||||
void TBRendererGL4::BeginPaint(int render_target_w, int render_target_h) {
|
||||
TBRendererBatcher::BeginPaint(render_target_w, render_target_h);
|
||||
|
||||
glEnablei(GL_BLEND, 0);
|
||||
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
glViewport(0, 0, render_target_w, render_target_h);
|
||||
glScissor(0, 0, render_target_w, render_target_h);
|
||||
|
||||
float left = 0.0f;
|
||||
float right = float(render_target_w);
|
||||
float bottom = float(render_target_h);
|
||||
float top = 0.0f;
|
||||
float z_near = -1.0f;
|
||||
float z_far = 1.0f;
|
||||
float projection[16] = {0};
|
||||
projection[0] = 2.0f / (right - left);
|
||||
projection[5] = 2.0f / (top - bottom);
|
||||
projection[10] = -2.0f / (z_far - z_near);
|
||||
projection[12] = -(right + left) / (right - left);
|
||||
projection[13] = -(top + bottom) / (top - bottom);
|
||||
projection[14] = -(z_far + z_near) / (z_far - z_near);
|
||||
projection[15] = 1.0f;
|
||||
glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, projection);
|
||||
|
||||
current_bitmap_ = nullptr;
|
||||
|
||||
glUseProgram(program_);
|
||||
glBindVertexArray(vao_);
|
||||
}
|
||||
|
||||
void TBRendererGL4::EndPaint() {
|
||||
TBRendererBatcher::EndPaint();
|
||||
|
||||
Flush();
|
||||
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void TBRendererGL4::Flush() {
|
||||
if (!draw_command_count_) {
|
||||
return;
|
||||
}
|
||||
vertex_buffer_.Flush();
|
||||
for (size_t i = 0; i < draw_command_count_; ++i) {
|
||||
glDrawArrays(draw_commands_[i].prim_type,
|
||||
GLint(draw_commands_[i].vertex_offset),
|
||||
GLsizei(draw_commands_[i].vertex_count));
|
||||
}
|
||||
draw_command_count_ = 0;
|
||||
// TODO(benvanik): don't finish here.
|
||||
vertex_buffer_.WaitUntilClean();
|
||||
}
|
||||
|
||||
void TBRendererGL4::RenderBatch(Batch* batch) {
|
||||
auto bitmap = static_cast<TBBitmapGL4*>(batch->bitmap);
|
||||
if (bitmap != current_bitmap_) {
|
||||
current_bitmap_ = bitmap;
|
||||
Flush();
|
||||
glProgramUniformHandleui64ARB(program_, 1,
|
||||
bitmap ? bitmap->gpu_handle_ : 0);
|
||||
glProgramUniform1f(program_, 2, bitmap ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
if (draw_command_count_ + 1 > kMaxCommands) {
|
||||
Flush();
|
||||
}
|
||||
size_t total_length = sizeof(Vertex) * batch->vertex_count;
|
||||
if (!vertex_buffer_.CanAcquire(total_length)) {
|
||||
Flush();
|
||||
}
|
||||
auto allocation = vertex_buffer_.Acquire(total_length);
|
||||
|
||||
// TODO(benvanik): custom batcher that lets us use the ringbuffer memory
|
||||
// without a copy.
|
||||
std::memcpy(allocation.host_ptr, batch->vertex, total_length);
|
||||
|
||||
if (draw_command_count_ &&
|
||||
draw_commands_[draw_command_count_ - 1].prim_type == GL_TRIANGLES) {
|
||||
// Coalesce.
|
||||
assert_always("haven't seen this yet");
|
||||
auto& prev_command = draw_commands_[draw_command_count_ - 1];
|
||||
prev_command.vertex_count += batch->vertex_count;
|
||||
} else {
|
||||
auto& command = draw_commands_[draw_command_count_++];
|
||||
command.prim_type = GL_TRIANGLES;
|
||||
command.vertex_offset = allocation.offset / sizeof(Vertex);
|
||||
command.vertex_count = batch->vertex_count;
|
||||
}
|
||||
|
||||
vertex_buffer_.Commit(std::move(allocation));
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_
|
||||
#define XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/ui/gl/circular_buffer.h"
|
||||
#include "xenia/ui/gl/gl.h"
|
||||
|
||||
#include "third_party/turbobadger/src/tb/tb_renderer.h"
|
||||
#include "third_party/turbobadger/src/tb/tb_renderer_batcher.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class TBRendererGL4 : public tb::TBRendererBatcher {
|
||||
public:
|
||||
TBRendererGL4();
|
||||
~TBRendererGL4() override;
|
||||
|
||||
static std::unique_ptr<TBRendererGL4> Create();
|
||||
|
||||
void BeginPaint(int render_target_w, int render_target_h) override;
|
||||
void EndPaint() override;
|
||||
|
||||
tb::TBBitmap* CreateBitmap(int width, int height, uint32_t* data) override;
|
||||
|
||||
void RenderBatch(Batch* batch) override;
|
||||
void SetClipRect(const tb::TBRect& rect) override;
|
||||
|
||||
private:
|
||||
class TBBitmapGL4;
|
||||
|
||||
bool Initialize();
|
||||
void Flush();
|
||||
|
||||
GLuint program_ = 0;
|
||||
GLuint vao_ = 0;
|
||||
xe::ui::gl::CircularBuffer vertex_buffer_;
|
||||
|
||||
static const size_t kMaxCommands = 512;
|
||||
struct {
|
||||
GLenum prim_type;
|
||||
size_t vertex_offset;
|
||||
size_t vertex_count;
|
||||
} draw_commands_[kMaxCommands] = {0};
|
||||
uint32_t draw_command_count_ = 0;
|
||||
TBBitmapGL4* current_bitmap_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_
|
|
@ -77,11 +77,15 @@
|
|||
<ClInclude Include="..\..\base\main.h" />
|
||||
<ClInclude Include="application.h" />
|
||||
<ClInclude Include="main_window.h" />
|
||||
<ClInclude Include="turbo_badger_control.h" />
|
||||
<ClInclude Include="turbo_badger_renderer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\base\main_win.cc" />
|
||||
<ClCompile Include="application.cc" />
|
||||
<ClCompile Include="main_window.cc" />
|
||||
<ClCompile Include="turbo_badger_control.cc" />
|
||||
<ClCompile Include="turbo_badger_renderer.cc" />
|
||||
<ClCompile Include="xe-debug-ui.cc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
|
|
@ -27,6 +27,12 @@
|
|||
<ClInclude Include="application.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="turbo_badger_control.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="turbo_badger_renderer.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\base\main_win.cc">
|
||||
|
@ -41,5 +47,11 @@
|
|||
<ClCompile Include="application.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="turbo_badger_control.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="turbo_badger_renderer.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -77,10 +77,11 @@ LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_PAINT: {
|
||||
invalidated_ = false;
|
||||
ValidateRect(hWnd, nullptr);
|
||||
SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::WM_PAINT");
|
||||
{
|
||||
GLContextLock context_lock(&context_);
|
||||
wglSwapIntervalEXT(0);
|
||||
|
||||
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
|
||||
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
|
||||
|
@ -100,6 +101,7 @@ LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::SwapBuffers");
|
||||
SwapBuffers(context_.dc());
|
||||
}
|
||||
return 0;
|
||||
} break;
|
||||
}
|
||||
return Win32Control::WndProc(hWnd, message, wParam, lParam);
|
||||
|
|
|
@ -21,6 +21,7 @@ class Loop {
|
|||
virtual ~Loop() = default;
|
||||
|
||||
virtual void Post(std::function<void()> fn) = 0;
|
||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
||||
|
||||
virtual void Quit() = 0;
|
||||
virtual void AwaitQuit() = 0;
|
||||
|
|
|
@ -121,10 +121,13 @@ void Win32Control::OnResize(UIEvent& e) {
|
|||
}
|
||||
|
||||
void Win32Control::Invalidate() {
|
||||
if (invalidated_) {
|
||||
return;
|
||||
}
|
||||
invalidated_ = true;
|
||||
InvalidateRect(hwnd_, nullptr, FALSE);
|
||||
for (auto& child_control : children_) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control.get());
|
||||
win32_control->Invalidate();
|
||||
child_control->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,8 +237,15 @@ LRESULT Win32Control::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
break;
|
||||
}
|
||||
|
||||
case WM_PAINT:
|
||||
case WM_PAINT: {
|
||||
if (flags_ & kFlagOwnPaint) {
|
||||
return 0; // ignore
|
||||
}
|
||||
invalidated_ = false;
|
||||
auto e = UIEvent(this);
|
||||
OnPaint(e);
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
if (flags_ & kFlagOwnPaint) {
|
||||
return 0; // ignore
|
||||
|
|
|
@ -54,7 +54,8 @@ class Win32Control : public Control {
|
|||
virtual LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
HWND hwnd_;
|
||||
HWND hwnd_ = nullptr;
|
||||
bool invalidated_ = true;
|
||||
|
||||
private:
|
||||
bool HandleMouse(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
|
|
@ -28,6 +28,8 @@ class PostedFn {
|
|||
};
|
||||
|
||||
Win32Loop::Win32Loop() : thread_id_(0) {
|
||||
timer_queue_ = CreateTimerQueue();
|
||||
|
||||
xe::threading::Fence init_fence;
|
||||
thread_ = std::thread([&init_fence, this]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
|
@ -49,6 +51,14 @@ Win32Loop::Win32Loop() : thread_id_(0) {
|
|||
Win32Loop::~Win32Loop() {
|
||||
Quit();
|
||||
thread_.join();
|
||||
|
||||
DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE);
|
||||
std::lock_guard<xe::mutex> lock(pending_timers_mutex_);
|
||||
while (!pending_timers_.empty()) {
|
||||
auto timer = pending_timers_.back();
|
||||
pending_timers_.pop_back();
|
||||
delete timer;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Loop::ThreadMain() {
|
||||
|
@ -82,6 +92,38 @@ void Win32Loop::Post(std::function<void()> fn) {
|
|||
}
|
||||
}
|
||||
|
||||
void Win32Loop::TimerQueueCallback(void* context, uint8_t) {
|
||||
auto timer = reinterpret_cast<PendingTimer*>(context);
|
||||
auto loop = timer->loop;
|
||||
auto fn = std::move(timer->fn);
|
||||
DeleteTimerQueueTimer(timer->timer_queue, timer->timer_handle, NULL);
|
||||
{
|
||||
std::lock_guard<xe::mutex> lock(loop->pending_timers_mutex_);
|
||||
loop->pending_timers_.remove(timer);
|
||||
}
|
||||
delete timer;
|
||||
loop->Post(std::move(fn));
|
||||
}
|
||||
|
||||
void Win32Loop::PostDelayed(std::function<void()> fn, uint64_t delay_millis) {
|
||||
if (!delay_millis) {
|
||||
Post(std::move(fn));
|
||||
return;
|
||||
}
|
||||
auto timer = new PendingTimer();
|
||||
timer->loop = this;
|
||||
timer->timer_queue = timer_queue_;
|
||||
timer->fn = std::move(fn);
|
||||
{
|
||||
std::lock_guard<xe::mutex> lock(pending_timers_mutex_);
|
||||
pending_timers_.push_back(timer);
|
||||
}
|
||||
CreateTimerQueueTimer(&timer->timer_handle, timer_queue_,
|
||||
WAITORTIMERCALLBACK(TimerQueueCallback), timer,
|
||||
DWORD(delay_millis), 0,
|
||||
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
|
||||
}
|
||||
|
||||
void Win32Loop::Quit() {
|
||||
assert_true(thread_id_ != 0);
|
||||
PostThreadMessage(thread_id_, kWmWin32LoopQuit,
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
#ifndef XENIA_UI_WIN32_WIN32_LOOP_H_
|
||||
#define XENIA_UI_WIN32_WIN32_LOOP_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
@ -29,16 +29,30 @@ class Win32Loop : public Loop {
|
|||
~Win32Loop() override;
|
||||
|
||||
void Post(std::function<void()> fn) override;
|
||||
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;
|
||||
|
||||
void Quit() override;
|
||||
void AwaitQuit() override;
|
||||
|
||||
private:
|
||||
struct PendingTimer {
|
||||
Win32Loop* loop;
|
||||
HANDLE timer_queue;
|
||||
HANDLE timer_handle;
|
||||
std::function<void()> fn;
|
||||
};
|
||||
|
||||
void ThreadMain();
|
||||
|
||||
static void TimerQueueCallback(void* context, uint8_t);
|
||||
|
||||
std::thread thread_;
|
||||
DWORD thread_id_;
|
||||
xe::threading::Fence quit_fence_;
|
||||
|
||||
HANDLE timer_queue_;
|
||||
xe::mutex pending_timers_mutex_;
|
||||
std::list<PendingTimer*> pending_timers_;
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 654b731db496c6014daf48384e74f517fe6905ac
|
||||
Subproject commit b5f4e50b9d8626a27ec111cc00fef1438062fbb8
|
|
@ -126,11 +126,17 @@ Global
|
|||
{21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.ActiveCfg = Release|x64
|
||||
{21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.Build.0 = Release|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.ActiveCfg = Debug|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.Build.0 = Debug|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.Build.0 = Debug|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.ActiveCfg = Release|x64
|
||||
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.Build.0 = Release|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.ActiveCfg = Release|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.Build.0 = Release|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.Build.0 = Debug|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Release|x64.ActiveCfg = Release|x64
|
||||
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Release|x64.Build.0 = Release|x64
|
||||
{93533067-6449-4691-88A8-026EBCFDCA97}.Checked|x64.ActiveCfg = Checked|x64
|
||||
{93533067-6449-4691-88A8-026EBCFDCA97}.Checked|x64.Build.0 = Checked|x64
|
||||
{93533067-6449-4691-88A8-026EBCFDCA97}.Debug|x64.ActiveCfg = Debug|x64
|
||||
|
|
Loading…
Reference in New Issue