Massive refactoring of xenia::ui and GL swap behavior.
This seems to dramatically improve most games (especially with --vsync=false), though it may cause swap issues with others. New code should be easier to port, and enables elemental-forms to be drawn for any emulator UI.
This commit is contained in:
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/control.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
Control::Control(uint32_t flags)
|
||||
: flags_(flags),
|
||||
parent_(nullptr),
|
||||
width_(0),
|
||||
height_(0),
|
||||
is_cursor_visible_(true),
|
||||
is_enabled_(true),
|
||||
is_visible_(true),
|
||||
has_focus_(false) {}
|
||||
|
||||
Control::~Control() { children_.clear(); }
|
||||
|
||||
void Control::AddChild(Control* child_control) {
|
||||
AddChild(ControlPtr(child_control, [](Control* control) {}));
|
||||
}
|
||||
|
||||
void Control::AddChild(std::unique_ptr<Control> child_control) {
|
||||
AddChild(ControlPtr(child_control.release(),
|
||||
[](Control* control) { delete control; }));
|
||||
}
|
||||
|
||||
void Control::AddChild(ControlPtr control) {
|
||||
assert_null(control->parent());
|
||||
control->parent_ = this;
|
||||
auto control_ptr = control.get();
|
||||
children_.emplace_back(std::move(control));
|
||||
OnChildAdded(control_ptr);
|
||||
}
|
||||
|
||||
Control::ControlPtr Control::RemoveChild(Control* child_control) {
|
||||
assert_true(child_control->parent() == this);
|
||||
for (auto& it = children_.begin(); it != children_.end(); ++it) {
|
||||
if (it->get() == child_control) {
|
||||
auto control_ptr = std::move(*it);
|
||||
child_control->parent_ = nullptr;
|
||||
children_.erase(it);
|
||||
OnChildRemoved(child_control);
|
||||
return control_ptr;
|
||||
}
|
||||
}
|
||||
return ControlPtr(nullptr, [](Control*) {});
|
||||
}
|
||||
|
||||
void Control::Layout() {
|
||||
auto e = UIEvent(this);
|
||||
OnLayout(e);
|
||||
for (auto& child_control : children_) {
|
||||
child_control->OnLayout(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnResize(UIEvent& e) { on_resize(e); }
|
||||
|
||||
void Control::OnLayout(UIEvent& e) { on_layout(e); }
|
||||
|
||||
void Control::OnPaint(UIEvent& e) { on_paint(e); }
|
||||
|
||||
void Control::OnVisible(UIEvent& e) { on_visible(e); }
|
||||
|
||||
void Control::OnHidden(UIEvent& e) { on_hidden(e); }
|
||||
|
||||
void Control::OnGotFocus(UIEvent& e) { on_got_focus(e); }
|
||||
|
||||
void Control::OnLostFocus(UIEvent& e) { on_lost_focus(e); }
|
||||
|
||||
void Control::OnKeyDown(KeyEvent& e) {
|
||||
on_key_down(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnKeyDown(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnKeyUp(KeyEvent& e) {
|
||||
on_key_up(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnKeyUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnKeyChar(KeyEvent& e) {
|
||||
on_key_char(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnKeyChar(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnMouseDown(MouseEvent& e) {
|
||||
on_mouse_down(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnMouseDown(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnMouseMove(MouseEvent& e) {
|
||||
on_mouse_move(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnMouseMove(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnMouseUp(MouseEvent& e) {
|
||||
on_mouse_up(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnMouseUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::OnMouseWheel(MouseEvent& e) {
|
||||
on_mouse_wheel(e);
|
||||
if (parent_ && !e.is_handled()) {
|
||||
parent_->OnMouseWheel(e);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_CONTROL_H_
|
||||
#define XENIA_UI_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Control {
|
||||
public:
|
||||
typedef std::unique_ptr<Control, void (*)(Control*)> ControlPtr;
|
||||
enum Flags {
|
||||
// Control paints itself, so disable platform drawing.
|
||||
kFlagOwnPaint = 1 << 1,
|
||||
};
|
||||
|
||||
virtual ~Control();
|
||||
|
||||
Control* parent() const { return parent_; }
|
||||
|
||||
size_t child_count() const { return children_.size(); }
|
||||
Control* child(size_t i) const { return children_[i].get(); }
|
||||
void AddChild(Control* child_control);
|
||||
void AddChild(std::unique_ptr<Control> child_control);
|
||||
void AddChild(ControlPtr child_control);
|
||||
ControlPtr RemoveChild(Control* child_control);
|
||||
|
||||
int32_t width() const { return width_; }
|
||||
int32_t height() const { return height_; }
|
||||
virtual void Resize(int32_t width, int32_t height) = 0;
|
||||
virtual void Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) = 0;
|
||||
void ResizeToFill() { ResizeToFill(0, 0, 0, 0); }
|
||||
virtual void ResizeToFill(int32_t pad_left, int32_t pad_top,
|
||||
int32_t pad_right, int32_t pad_bottom) = 0;
|
||||
void Layout();
|
||||
virtual void Invalidate() {}
|
||||
|
||||
// TODO(benvanik): colors/brushes/etc.
|
||||
// TODO(benvanik): fonts.
|
||||
|
||||
bool is_cursor_visible() const { return is_cursor_visible_; }
|
||||
virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; }
|
||||
|
||||
bool is_enabled() const { return is_enabled_; }
|
||||
virtual void set_enabled(bool value) { is_enabled_ = value; }
|
||||
|
||||
bool is_visible() const { return is_visible_; }
|
||||
virtual void set_visible(bool value) { is_visible_ = value; }
|
||||
|
||||
bool has_focus() const { return has_focus_; }
|
||||
virtual void set_focus(bool value) { has_focus_ = value; }
|
||||
|
||||
public:
|
||||
Delegate<UIEvent> on_resize;
|
||||
Delegate<UIEvent> on_layout;
|
||||
Delegate<UIEvent> on_paint;
|
||||
|
||||
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;
|
||||
|
||||
Delegate<MouseEvent> on_mouse_down;
|
||||
Delegate<MouseEvent> on_mouse_move;
|
||||
Delegate<MouseEvent> on_mouse_up;
|
||||
Delegate<MouseEvent> on_mouse_wheel;
|
||||
|
||||
protected:
|
||||
explicit Control(uint32_t flags);
|
||||
|
||||
virtual bool Create() { return true; }
|
||||
virtual void Destroy() {}
|
||||
|
||||
virtual void OnCreate() {}
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual void OnChildAdded(Control* child_control) {}
|
||||
virtual void OnChildRemoved(Control* child_control) {}
|
||||
|
||||
virtual void OnResize(UIEvent& e);
|
||||
virtual void OnLayout(UIEvent& e);
|
||||
virtual void OnPaint(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);
|
||||
|
||||
uint32_t flags_;
|
||||
Control* parent_;
|
||||
std::vector<ControlPtr> children_;
|
||||
|
||||
int32_t width_;
|
||||
int32_t height_;
|
||||
|
||||
bool is_cursor_visible_;
|
||||
bool is_enabled_;
|
||||
bool is_visible_;
|
||||
bool has_focus_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_CONTROL_H_
|
||||
@@ -1,85 +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_CONTROL_H_
|
||||
#define XENIA_UI_ELEMENTAL_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "el/element.h"
|
||||
#include "el/graphics/renderer.h"
|
||||
#include "xenia/ui/control.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/platform.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class ElementalControl : public PlatformControl {
|
||||
public:
|
||||
ElementalControl(Loop* loop, uint32_t flags);
|
||||
~ElementalControl() override;
|
||||
|
||||
el::graphics::Renderer* renderer() const { return renderer_.get(); }
|
||||
el::Element* root_element() const { return root_element_.get(); }
|
||||
|
||||
bool LoadLanguage(std::string filename);
|
||||
bool LoadSkin(std::string filename);
|
||||
|
||||
protected:
|
||||
using super = PlatformControl;
|
||||
|
||||
bool InitializeElemental(Loop* loop, el::graphics::Renderer* renderer);
|
||||
virtual std::unique_ptr<el::graphics::Renderer> CreateRenderer() = 0;
|
||||
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
void OnLayout(UIEvent& e) override;
|
||||
void OnPaint(UIEvent& e) override;
|
||||
|
||||
void OnGotFocus(UIEvent& e) override;
|
||||
void OnLostFocus(UIEvent& e) override;
|
||||
|
||||
el::ModifierKeys GetModifierKeys();
|
||||
|
||||
void OnKeyPress(KeyEvent& e, bool is_down, bool is_char);
|
||||
bool CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, bool is_down);
|
||||
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;
|
||||
|
||||
Loop* loop_ = nullptr;
|
||||
std::unique_ptr<el::graphics::Renderer> renderer_;
|
||||
std::unique_ptr<el::Element> root_element_;
|
||||
|
||||
uint32_t frame_count_ = 0;
|
||||
uint32_t fps_ = 0;
|
||||
uint64_t fps_update_time_ = 0;
|
||||
uint64_t fps_frame_count_ = 0;
|
||||
|
||||
bool modifier_shift_pressed_ = false;
|
||||
bool modifier_cntrl_pressed_ = false;
|
||||
bool modifier_alt_pressed_ = false;
|
||||
bool modifier_super_pressed_ = false;
|
||||
uint64_t last_click_time_ = 0;
|
||||
int last_click_x_ = 0;
|
||||
int last_click_y_ = 0;
|
||||
int last_click_counter_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_ELEMENTAL_CONTROL_H_
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef XENIA_UI_FILE_PICKER_H_
|
||||
#define XENIA_UI_FILE_PICKER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -27,6 +28,8 @@ class FilePicker {
|
||||
kDirectory = 1,
|
||||
};
|
||||
|
||||
static std::unique_ptr<FilePicker> Create();
|
||||
|
||||
FilePicker()
|
||||
: mode_(Mode::kOpen),
|
||||
type_(Type::kFile),
|
||||
|
||||
@@ -7,14 +7,27 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/win32/win32_file_picker.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32FilePicker : public FilePicker {
|
||||
public:
|
||||
Win32FilePicker();
|
||||
~Win32FilePicker() override;
|
||||
|
||||
bool Show(void* parent_window_handle) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
std::unique_ptr<FilePicker> FilePicker::Create() {
|
||||
return std::make_unique<Win32FilePicker>();
|
||||
}
|
||||
|
||||
class CDialogEventHandler : public IFileDialogEvents,
|
||||
public IFileDialogControlEvents {
|
||||
@@ -201,6 +214,5 @@ bool Win32FilePicker::Show(void* parent_window_handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/gpu/gl4/gl4_gpu_flags.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
@@ -28,7 +28,7 @@ GL4ElementalRenderer::GL4Bitmap::GL4Bitmap(GLContext* context,
|
||||
: context_(context), renderer_(renderer) {}
|
||||
|
||||
GL4ElementalRenderer::GL4Bitmap::~GL4Bitmap() {
|
||||
GLContextLock lock(context_);
|
||||
GraphicsContextLock lock(context_);
|
||||
|
||||
// Must flush and unbind before we delete the texture.
|
||||
renderer_->FlushBitmap(this);
|
||||
@@ -69,7 +69,7 @@ GL4ElementalRenderer::GL4ElementalRenderer(GLContext* context)
|
||||
vertex_buffer_(max_vertex_batch_size() * sizeof(Vertex)) {}
|
||||
|
||||
GL4ElementalRenderer::~GL4ElementalRenderer() {
|
||||
GLContextLock lock(context_);
|
||||
GraphicsContextLock lock(context_);
|
||||
vertex_buffer_.Shutdown();
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
glDeleteProgram(program_);
|
||||
@@ -77,6 +77,7 @@ GL4ElementalRenderer::~GL4ElementalRenderer() {
|
||||
|
||||
std::unique_ptr<GL4ElementalRenderer> GL4ElementalRenderer::Create(
|
||||
GLContext* context) {
|
||||
GraphicsContextLock lock(context);
|
||||
auto renderer = std::make_unique<GL4ElementalRenderer>(context);
|
||||
if (!renderer->Initialize()) {
|
||||
XELOGE("Failed to initialize TurboBadger GL4 renderer");
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/profiling.h"
|
||||
#include "xenia/ui/gl/gl_profiler_display.h"
|
||||
#include "xenia/ui/gl/gl4_elemental_renderer.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
DEFINE_bool(thread_safe_gl, false,
|
||||
"Only allow one GL context to be active at a time.");
|
||||
@@ -40,11 +43,20 @@ thread_local WGLEWContext* tls_wglew_context_ = nullptr;
|
||||
extern "C" GLEWContext* glewGetContext() { return tls_glew_context_; }
|
||||
extern "C" WGLEWContext* wglewGetContext() { return tls_wglew_context_; }
|
||||
|
||||
GLContext::GLContext() : hwnd_(nullptr), dc_(nullptr), glrc_(nullptr) {}
|
||||
std::unique_ptr<GLContext> GLContext::Create(Window* target_window) {
|
||||
auto context = std::unique_ptr<GLContext>(new GLContext(target_window));
|
||||
if (!context->Initialize(target_window)) {
|
||||
return nullptr;
|
||||
}
|
||||
context->AssertExtensionsPresent();
|
||||
return context;
|
||||
}
|
||||
|
||||
GLContext::GLContext(HWND hwnd, HGLRC glrc)
|
||||
: hwnd_(hwnd), dc_(nullptr), glrc_(glrc) {
|
||||
dc_ = GetDC(hwnd);
|
||||
GLContext::GLContext(Window* target_window) : GraphicsContext(target_window) {}
|
||||
|
||||
GLContext::GLContext(Window* target_window, HGLRC glrc)
|
||||
: GraphicsContext(target_window), glrc_(glrc) {
|
||||
dc_ = GetDC(HWND(target_window_->native_handle()));
|
||||
}
|
||||
|
||||
GLContext::~GLContext() {
|
||||
@@ -55,13 +67,13 @@ GLContext::~GLContext() {
|
||||
wglDeleteContext(glrc_);
|
||||
}
|
||||
if (dc_) {
|
||||
ReleaseDC(hwnd_, dc_);
|
||||
ReleaseDC(HWND(target_window_->native_handle()), dc_);
|
||||
}
|
||||
}
|
||||
|
||||
bool GLContext::Initialize(HWND hwnd) {
|
||||
hwnd_ = hwnd;
|
||||
dc_ = GetDC(hwnd);
|
||||
bool GLContext::Initialize(Window* target_window) {
|
||||
target_window_ = target_window;
|
||||
dc_ = GetDC(HWND(target_window_->native_handle()));
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd = {0};
|
||||
pfd.nSize = sizeof(pfd);
|
||||
@@ -152,12 +164,12 @@ bool GLContext::Initialize(HWND hwnd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<GLContext> GLContext::CreateShared() {
|
||||
std::unique_ptr<GraphicsContext> GLContext::CreateShared() {
|
||||
assert_not_null(glrc_);
|
||||
|
||||
HGLRC new_glrc = nullptr;
|
||||
{
|
||||
GLContextLock context_lock(this);
|
||||
GraphicsContextLock context_lock(this);
|
||||
|
||||
int context_flags = 0;
|
||||
// int profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
|
||||
@@ -178,7 +190,8 @@ std::unique_ptr<GLContext> GLContext::CreateShared() {
|
||||
}
|
||||
}
|
||||
|
||||
auto new_context = std::make_unique<GLContext>(hwnd_, new_glrc);
|
||||
auto new_context =
|
||||
std::unique_ptr<GLContext>(new GLContext(target_window_, new_glrc));
|
||||
if (!new_context->MakeCurrent()) {
|
||||
XELOGE("Could not make new GL context current");
|
||||
return nullptr;
|
||||
@@ -205,7 +218,7 @@ std::unique_ptr<GLContext> GLContext::CreateShared() {
|
||||
|
||||
new_context->ClearCurrent();
|
||||
|
||||
return new_context;
|
||||
return std::unique_ptr<GraphicsContext>(new_context.release());
|
||||
}
|
||||
|
||||
void FatalGLError(std::string error) {
|
||||
@@ -369,6 +382,14 @@ void GLContext::SetupDebugging() {
|
||||
this);
|
||||
}
|
||||
|
||||
std::unique_ptr<ProfilerDisplay> GLContext::CreateProfilerDisplay() {
|
||||
return std::make_unique<GLProfilerDisplay>(target_window_);
|
||||
}
|
||||
|
||||
std::unique_ptr<el::graphics::Renderer> GLContext::CreateElementalRenderer() {
|
||||
return GL4ElementalRenderer::Create(this);
|
||||
}
|
||||
|
||||
bool GLContext::MakeCurrent() {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
if (FLAGS_thread_safe_gl) {
|
||||
@@ -399,6 +420,17 @@ void GLContext::ClearCurrent() {
|
||||
}
|
||||
}
|
||||
|
||||
void GLContext::BeginSwap() {
|
||||
SCOPE_profile_cpu_i("gpu", "xe::ui::gl::GLContext::BeginSwap");
|
||||
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
|
||||
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
|
||||
}
|
||||
|
||||
void GLContext::EndSwap() {
|
||||
SCOPE_profile_cpu_i("gpu", "xe::ui::gl::GLContext::EndSwap");
|
||||
SwapBuffers(dc());
|
||||
}
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "xenia/ui/gl/blitter.h"
|
||||
#include "xenia/ui/gl/gl.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
|
||||
DECLARE_bool(thread_safe_gl);
|
||||
|
||||
@@ -23,25 +24,33 @@ namespace xe {
|
||||
namespace ui {
|
||||
namespace gl {
|
||||
|
||||
class GLContext {
|
||||
class GLContext : public GraphicsContext {
|
||||
public:
|
||||
GLContext();
|
||||
GLContext(HWND hwnd, HGLRC glrc);
|
||||
~GLContext();
|
||||
static std::unique_ptr<GLContext> Create(Window* target_window);
|
||||
|
||||
bool Initialize(HWND hwnd);
|
||||
void AssertExtensionsPresent();
|
||||
~GLContext() override;
|
||||
|
||||
HDC dc() const { return dc_; }
|
||||
|
||||
std::unique_ptr<GLContext> CreateShared();
|
||||
std::unique_ptr<GraphicsContext> CreateShared() override;
|
||||
std::unique_ptr<ProfilerDisplay> CreateProfilerDisplay() override;
|
||||
std::unique_ptr<el::graphics::Renderer> CreateElementalRenderer() override;
|
||||
|
||||
bool MakeCurrent();
|
||||
void ClearCurrent();
|
||||
bool MakeCurrent() override;
|
||||
void ClearCurrent() override;
|
||||
|
||||
void BeginSwap() override;
|
||||
void EndSwap() override;
|
||||
|
||||
Blitter* blitter() { return &blitter_; }
|
||||
|
||||
private:
|
||||
GLContext(Window* target_window);
|
||||
GLContext(Window* target_window, HGLRC glrc);
|
||||
|
||||
bool Initialize(Window* target_window);
|
||||
void AssertExtensionsPresent();
|
||||
|
||||
void SetupDebugging();
|
||||
void DebugMessage(GLenum source, GLenum type, GLuint id, GLenum severity,
|
||||
GLsizei length, const GLchar* message);
|
||||
@@ -49,9 +58,8 @@ class GLContext {
|
||||
DebugMessageThunk(GLenum source, GLenum type, GLuint id, GLenum severity,
|
||||
GLsizei length, const GLchar* message, GLvoid* user_param);
|
||||
|
||||
HWND hwnd_;
|
||||
HDC dc_;
|
||||
HGLRC glrc_;
|
||||
HDC dc_ = nullptr;
|
||||
HGLRC glrc_ = nullptr;
|
||||
|
||||
GLEWContext glew_context_;
|
||||
WGLEWContext wglew_context_;
|
||||
@@ -59,16 +67,6 @@ class GLContext {
|
||||
Blitter blitter_;
|
||||
};
|
||||
|
||||
struct GLContextLock {
|
||||
GLContextLock(GLContext* context) : context_(context) {
|
||||
context_->MakeCurrent();
|
||||
}
|
||||
~GLContextLock() { context_->ClearCurrent(); }
|
||||
|
||||
private:
|
||||
GLContext* context_;
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
@@ -132,47 +132,56 @@ const uint8_t profiler_font[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
GLProfilerDisplay::GLProfilerDisplay(xe::ui::gl::WGLControl* control)
|
||||
: control_(control),
|
||||
program_(0),
|
||||
vao_(0),
|
||||
font_texture_(0),
|
||||
font_handle_(0),
|
||||
GLProfilerDisplay::GLProfilerDisplay(xe::ui::Window* window)
|
||||
: window_(window),
|
||||
vertex_buffer_(MICROPROFILE_MAX_VERTICES * sizeof(Vertex) * 10,
|
||||
sizeof(Vertex)),
|
||||
draw_command_count_(0) {
|
||||
sizeof(Vertex)) {
|
||||
if (!SetupFont() || !SetupState() || !SetupShaders()) {
|
||||
// Hrm.
|
||||
assert_always();
|
||||
}
|
||||
|
||||
window_->on_painted.AddListener([this](UIEvent& e) { Profiler::Present(); });
|
||||
|
||||
// Pass through mouse events.
|
||||
control->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) {
|
||||
Profiler::OnMouseDown(e.button() == xe::ui::MouseEvent::Button::kLeft,
|
||||
e.button() == xe::ui::MouseEvent::Button::kRight);
|
||||
e.set_handled(true);
|
||||
window_->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnMouseDown(e.button() == xe::ui::MouseEvent::Button::kLeft,
|
||||
e.button() == xe::ui::MouseEvent::Button::kRight);
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
control->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) {
|
||||
Profiler::OnMouseUp();
|
||||
e.set_handled(true);
|
||||
window_->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnMouseUp();
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
control->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) {
|
||||
Profiler::OnMouseMove(e.x(), e.y());
|
||||
e.set_handled(true);
|
||||
window_->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnMouseMove(e.x(), e.y());
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
control->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) {
|
||||
Profiler::OnMouseWheel(e.x(), e.y(), -e.dy());
|
||||
e.set_handled(true);
|
||||
window_->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnMouseWheel(e.x(), e.y(), -e.dy());
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for toggle/mode keys and such.
|
||||
control->on_key_down.AddListener([](xe::ui::KeyEvent& e) {
|
||||
Profiler::OnKeyDown(e.key_code());
|
||||
// e.set_handled(true);
|
||||
window_->on_key_down.AddListener([](xe::ui::KeyEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnKeyDown(e.key_code());
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
control->on_key_up.AddListener([](xe::ui::KeyEvent& e) {
|
||||
Profiler::OnKeyUp(e.key_code());
|
||||
// e.set_handled(true);
|
||||
window_->on_key_up.AddListener([](xe::ui::KeyEvent& e) {
|
||||
if (Profiler::is_enabled()) {
|
||||
Profiler::OnKeyUp(e.key_code());
|
||||
e.set_handled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -336,9 +345,9 @@ GLProfilerDisplay::~GLProfilerDisplay() {
|
||||
glDeleteProgram(program_);
|
||||
}
|
||||
|
||||
uint32_t GLProfilerDisplay::width() const { return control_->width(); }
|
||||
uint32_t GLProfilerDisplay::width() const { return window_->width(); }
|
||||
|
||||
uint32_t GLProfilerDisplay::height() const { return control_->height(); }
|
||||
uint32_t GLProfilerDisplay::height() const { return window_->height(); }
|
||||
|
||||
void GLProfilerDisplay::Begin() {
|
||||
glEnablei(GL_BLEND, 0);
|
||||
@@ -347,7 +356,7 @@ void GLProfilerDisplay::Begin() {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
glViewport(0, 0, control_->width(), control_->height());
|
||||
glViewport(0, 0, width(), height());
|
||||
|
||||
float left = 0.0f;
|
||||
float right = float(width());
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
|
||||
#include "xenia/profiling.h"
|
||||
#include "xenia/ui/gl/circular_buffer.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/gl/wgl_control.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
@@ -21,7 +20,7 @@ namespace gl {
|
||||
|
||||
class GLProfilerDisplay : public ProfilerDisplay {
|
||||
public:
|
||||
GLProfilerDisplay(xe::ui::gl::WGLControl* control);
|
||||
GLProfilerDisplay(xe::ui::Window* window);
|
||||
virtual ~GLProfilerDisplay();
|
||||
|
||||
uint32_t width() const override;
|
||||
@@ -54,11 +53,11 @@ class GLProfilerDisplay : public ProfilerDisplay {
|
||||
void EndVertices(GLenum prim_type);
|
||||
void Flush();
|
||||
|
||||
xe::ui::gl::WGLControl* control_;
|
||||
GLuint program_;
|
||||
GLuint vao_;
|
||||
GLuint font_texture_;
|
||||
GLuint64 font_handle_;
|
||||
xe::ui::Window* window_ = nullptr;
|
||||
GLuint program_ = 0;
|
||||
GLuint vao_ = 0;
|
||||
GLuint font_texture_ = 0;
|
||||
GLuint64 font_handle_ = 0;
|
||||
CircularBuffer vertex_buffer_;
|
||||
|
||||
static const size_t kMaxCommands = 32;
|
||||
@@ -67,13 +66,13 @@ class GLProfilerDisplay : public ProfilerDisplay {
|
||||
size_t vertex_offset;
|
||||
size_t vertex_count;
|
||||
} draw_commands_[kMaxCommands];
|
||||
uint32_t draw_command_count_;
|
||||
uint32_t draw_command_count_ = 0;
|
||||
|
||||
CircularBuffer::Allocation current_allocation_;
|
||||
|
||||
struct {
|
||||
uint16_t char_offsets[256];
|
||||
} font_description_;
|
||||
} font_description_ = {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/gl/wgl_control.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace gl {
|
||||
|
||||
WGLControl::WGLControl(Loop* loop)
|
||||
: xe::ui::win32::Win32Control(Flags::kFlagOwnPaint), loop_(loop) {}
|
||||
|
||||
WGLControl::~WGLControl() = default;
|
||||
|
||||
bool WGLControl::Create() {
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
|
||||
WNDCLASSEX wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wcex.lpfnWndProc = Win32Control::WndProcThunk;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = hInstance;
|
||||
wcex.hIcon = nullptr;
|
||||
wcex.hIconSm = nullptr;
|
||||
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = L"XeniaWglClass";
|
||||
if (!RegisterClassEx(&wcex)) {
|
||||
XELOGE("WGL RegisterClassEx failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create window.
|
||||
DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY;
|
||||
DWORD window_ex_style = 0;
|
||||
hwnd_ =
|
||||
CreateWindowEx(window_ex_style, L"XeniaWglClass", L"Xenia", window_style,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
parent_hwnd(), nullptr, hInstance, this);
|
||||
if (!hwnd_) {
|
||||
XELOGE("WGL CreateWindow failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context_.Initialize(hwnd_)) {
|
||||
XEFATAL(
|
||||
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure "
|
||||
"you have the latest drivers for your GPU and that it supports OpenGL "
|
||||
"4.5. See http://xenia.jp/faq/ for more information.");
|
||||
return false;
|
||||
}
|
||||
|
||||
context_.AssertExtensionsPresent();
|
||||
|
||||
SetFocus(hwnd_);
|
||||
|
||||
OnCreate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WGLControl::OnLayout(UIEvent& e) { Control::ResizeToFill(); }
|
||||
|
||||
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_);
|
||||
|
||||
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
|
||||
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
|
||||
|
||||
if (current_paint_callback_) {
|
||||
current_paint_callback_();
|
||||
current_paint_callback_ = nullptr;
|
||||
}
|
||||
|
||||
UIEvent e(this);
|
||||
OnPaint(e);
|
||||
|
||||
// TODO(benvanik): profiler present.
|
||||
Profiler::Present();
|
||||
}
|
||||
{
|
||||
SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::SwapBuffers");
|
||||
SwapBuffers(context_.dc());
|
||||
}
|
||||
return 0;
|
||||
} break;
|
||||
}
|
||||
return Win32Control::WndProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void WGLControl::SynchronousRepaint(std::function<void()> paint_callback) {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
|
||||
// We may already have a pending paint from a previous request when we
|
||||
// were minimized. We just overwrite it.
|
||||
current_paint_callback_ = std::move(paint_callback);
|
||||
|
||||
// This will not return until the WM_PAINT has completed.
|
||||
// Note, if we are minimized this won't do anything.
|
||||
RedrawWindow(hwnd(), nullptr, nullptr,
|
||||
RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN);
|
||||
}
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_GL_WGL_CONTROL_H_
|
||||
#define XENIA_UI_GL_WGL_CONTROL_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/win32/win32_control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace gl {
|
||||
|
||||
class WGLControl : public xe::ui::win32::Win32Control {
|
||||
public:
|
||||
WGLControl(Loop* loop);
|
||||
~WGLControl() override;
|
||||
|
||||
GLContext* context() { return &context_; }
|
||||
|
||||
void SynchronousRepaint(std::function<void()> paint_callback);
|
||||
|
||||
protected:
|
||||
bool Create() override;
|
||||
|
||||
void OnLayout(UIEvent& e) override;
|
||||
|
||||
LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) override;
|
||||
|
||||
private:
|
||||
Loop* loop_;
|
||||
GLContext context_;
|
||||
std::function<void()> current_paint_callback_;
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_GL_WGL_CONTROL_H_
|
||||
@@ -1,140 +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/gl/wgl_elemental_control.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "el/util/math.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/profiling.h"
|
||||
#include "xenia/ui/gl/circular_buffer.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/gl/gl.h"
|
||||
#include "xenia/ui/gl/gl4_elemental_renderer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace gl {
|
||||
|
||||
WGLElementalControl::WGLElementalControl(Loop* loop)
|
||||
: ElementalControl(loop, Flags::kFlagOwnPaint), loop_(loop) {}
|
||||
|
||||
WGLElementalControl::~WGLElementalControl() = default;
|
||||
|
||||
bool WGLElementalControl::Create() {
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
|
||||
WNDCLASSEX wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wcex.lpfnWndProc = Win32Control::WndProcThunk;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = hInstance;
|
||||
wcex.hIcon = nullptr;
|
||||
wcex.hIconSm = nullptr;
|
||||
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = L"XeniaWglElementalClass";
|
||||
if (!RegisterClassEx(&wcex)) {
|
||||
XELOGE("WGL RegisterClassEx failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create window.
|
||||
DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY;
|
||||
DWORD window_ex_style = 0;
|
||||
hwnd_ =
|
||||
CreateWindowEx(window_ex_style, L"XeniaWglElementalClass", L"Xenia",
|
||||
window_style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
CW_USEDEFAULT, parent_hwnd(), nullptr, hInstance, this);
|
||||
if (!hwnd_) {
|
||||
XELOGE("WGL CreateWindow failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context_.Initialize(hwnd_)) {
|
||||
XEFATAL(
|
||||
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure "
|
||||
"you have the latest drivers for your GPU and that it supports OpenGL "
|
||||
"4.5. See http://xenia.jp/faq/ for more information.");
|
||||
return false;
|
||||
}
|
||||
|
||||
context_.AssertExtensionsPresent();
|
||||
|
||||
SetFocus(hwnd_);
|
||||
|
||||
return super::Create();
|
||||
}
|
||||
|
||||
std::unique_ptr<el::graphics::Renderer> WGLElementalControl::CreateRenderer() {
|
||||
return GL4ElementalRenderer::Create(&context_);
|
||||
}
|
||||
|
||||
void WGLElementalControl::OnLayout(UIEvent& e) {
|
||||
Control::ResizeToFill();
|
||||
super::OnLayout(e);
|
||||
}
|
||||
|
||||
LRESULT WGLElementalControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_PAINT: {
|
||||
invalidated_ = false;
|
||||
ValidateRect(hWnd, nullptr);
|
||||
SCOPE_profile_cpu_i("gpu", "xe::ui::gl::WGLElementalControl::WM_PAINT");
|
||||
{
|
||||
GLContextLock context_lock(&context_);
|
||||
|
||||
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
|
||||
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
|
||||
|
||||
if (current_paint_callback_) {
|
||||
current_paint_callback_();
|
||||
current_paint_callback_ = nullptr;
|
||||
}
|
||||
|
||||
UIEvent e(this);
|
||||
OnPaint(e);
|
||||
|
||||
// TODO(benvanik): profiler present.
|
||||
Profiler::Present();
|
||||
}
|
||||
{
|
||||
SCOPE_profile_cpu_i("gpu",
|
||||
"xe::ui::gl::WGLElementalControl::SwapBuffers");
|
||||
SwapBuffers(context_.dc());
|
||||
}
|
||||
return 0;
|
||||
} break;
|
||||
}
|
||||
return Win32Control::WndProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void WGLElementalControl::SynchronousRepaint(
|
||||
std::function<void()> paint_callback) {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
|
||||
// We may already have a pending paint from a previous request when we
|
||||
// were minimized. We just overwrite it.
|
||||
current_paint_callback_ = std::move(paint_callback);
|
||||
|
||||
// This will not return until the WM_PAINT has completed.
|
||||
// Note, if we are minimized this won't do anything.
|
||||
RedrawWindow(hwnd(), nullptr, nullptr,
|
||||
RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN);
|
||||
}
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_
|
||||
#define XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/elemental_control.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace gl {
|
||||
|
||||
class WGLElementalControl : public ElementalControl {
|
||||
public:
|
||||
WGLElementalControl(Loop* loop);
|
||||
~WGLElementalControl() override;
|
||||
|
||||
GLContext* context() { return &context_; }
|
||||
|
||||
void SynchronousRepaint(std::function<void()> paint_callback);
|
||||
|
||||
protected:
|
||||
using super = ElementalControl;
|
||||
|
||||
std::unique_ptr<el::graphics::Renderer> CreateRenderer() override;
|
||||
|
||||
bool Create() override;
|
||||
|
||||
void OnLayout(UIEvent& e) override;
|
||||
|
||||
LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) override;
|
||||
|
||||
private:
|
||||
Loop* loop_;
|
||||
GLContext context_;
|
||||
std::function<void()> current_paint_callback_;
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_
|
||||
@@ -7,27 +7,15 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
|
||||
#define XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
|
||||
|
||||
#include "xenia/ui/file_picker.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32FilePicker : public FilePicker {
|
||||
public:
|
||||
Win32FilePicker();
|
||||
~Win32FilePicker() override;
|
||||
GraphicsContext::GraphicsContext(Window* target_window)
|
||||
: target_window_(target_window) {}
|
||||
|
||||
bool Show(void* parent_window_handle) override;
|
||||
GraphicsContext::~GraphicsContext() = default;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
|
||||
58
src/xenia/ui/graphics_context.h
Normal file
58
src/xenia/ui/graphics_context.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_GRAPHICS_CONTEXT_H_
|
||||
#define XENIA_UI_GRAPHICS_CONTEXT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "el/graphics/renderer.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Window;
|
||||
|
||||
class GraphicsContext {
|
||||
public:
|
||||
virtual ~GraphicsContext();
|
||||
|
||||
Window* target_window() const { return target_window_; }
|
||||
|
||||
virtual std::unique_ptr<GraphicsContext> CreateShared() = 0;
|
||||
virtual std::unique_ptr<ProfilerDisplay> CreateProfilerDisplay() = 0;
|
||||
virtual std::unique_ptr<el::graphics::Renderer> CreateElementalRenderer() = 0;
|
||||
|
||||
virtual bool MakeCurrent() = 0;
|
||||
virtual void ClearCurrent() = 0;
|
||||
|
||||
virtual void BeginSwap() = 0;
|
||||
virtual void EndSwap() = 0;
|
||||
|
||||
protected:
|
||||
explicit GraphicsContext(Window* target_window);
|
||||
|
||||
Window* target_window_ = nullptr;
|
||||
};
|
||||
|
||||
struct GraphicsContextLock {
|
||||
GraphicsContextLock(GraphicsContext* context) : context_(context) {
|
||||
context_->MakeCurrent();
|
||||
}
|
||||
~GraphicsContextLock() { context_->ClearCurrent(); }
|
||||
|
||||
private:
|
||||
GraphicsContext* context_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_GRAPHICS_CONTEXT_H_
|
||||
82
src/xenia/ui/loop.cc
Normal file
82
src/xenia/ui/loop.cc
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
void Loop::PostSynchronous(std::function<void()> fn) {
|
||||
xe::threading::Fence fence;
|
||||
Post([&fn, &fence]() {
|
||||
fn();
|
||||
fence.Signal();
|
||||
});
|
||||
fence.Wait();
|
||||
}
|
||||
|
||||
} // 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);
|
||||
}
|
||||
@@ -11,20 +11,34 @@
|
||||
#define XENIA_UI_LOOP_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Loop {
|
||||
public:
|
||||
Loop() = default;
|
||||
virtual ~Loop() = default;
|
||||
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();
|
||||
|
||||
virtual void Post(std::function<void()> fn) = 0;
|
||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
||||
void PostSynchronous(std::function<void()> fn);
|
||||
|
||||
virtual void Quit() = 0;
|
||||
virtual void AwaitQuit() = 0;
|
||||
|
||||
Delegate<UIEvent> on_quit;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/win32/win32_loop.h"
|
||||
#include "xenia/ui/loop_win.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
const DWORD kWmWin32LoopPost = WM_APP + 0x100;
|
||||
const DWORD kWmWin32LoopQuit = WM_APP + 0x101;
|
||||
@@ -27,6 +26,8 @@ class PostedFn {
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Loop> Loop::Create() { return std::make_unique<Win32Loop>(); }
|
||||
|
||||
Win32Loop::Win32Loop() : thread_id_(0) {
|
||||
timer_queue_ = CreateTimerQueue();
|
||||
|
||||
@@ -81,6 +82,9 @@ void Win32Loop::ThreadMain() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UIEvent e(nullptr);
|
||||
on_quit(e);
|
||||
}
|
||||
|
||||
void Win32Loop::Post(std::function<void()> fn) {
|
||||
@@ -132,6 +136,5 @@ void Win32Loop::Quit() {
|
||||
|
||||
void Win32Loop::AwaitQuit() { quit_fence_.Wait(); }
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -7,8 +7,8 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_WIN32_WIN32_LOOP_H_
|
||||
#define XENIA_UI_WIN32_WIN32_LOOP_H_
|
||||
#ifndef XENIA_UI_LOOP_WIN_H_
|
||||
#define XENIA_UI_LOOP_WIN_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32Loop : public Loop {
|
||||
public:
|
||||
@@ -55,8 +54,7 @@ class Win32Loop : public Loop {
|
||||
std::list<PendingTimer*> pending_timers_;
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_WIN32_WIN32_LOOP_H_
|
||||
#endif // XENIA_UI_LOOP_WIN_H_
|
||||
5
src/xenia/ui/main_resources.rc
Normal file
5
src/xenia/ui/main_resources.rc
Normal file
@@ -0,0 +1,5 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
|
||||
#include "third_party\\elemental-forms\\resources.rc"
|
||||
|
||||
//IDR_xe_ui_main_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png"
|
||||
@@ -1,264 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/main_window.h"
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
enum Commands {
|
||||
IDC_FILE_EXIT,
|
||||
|
||||
IDC_CPU_TIME_SCALAR_RESET,
|
||||
IDC_CPU_TIME_SCALAR_HALF,
|
||||
IDC_CPU_TIME_SCALAR_DOUBLE,
|
||||
|
||||
IDC_CPU_PROFILER_TOGGLE_DISPLAY,
|
||||
IDC_CPU_PROFILER_TOGGLE_PAUSE,
|
||||
|
||||
IDC_GPU_TRACE_FRAME,
|
||||
IDC_GPU_CLEAR_CACHES,
|
||||
|
||||
IDC_WINDOW_FULLSCREEN,
|
||||
|
||||
IDC_HELP_WEBSITE,
|
||||
IDC_HELP_ABOUT,
|
||||
};
|
||||
|
||||
const std::wstring kBaseTitle = L"xenia";
|
||||
|
||||
MainWindow::MainWindow(Emulator* emulator)
|
||||
: PlatformWindow(&loop_, kBaseTitle),
|
||||
emulator_(emulator),
|
||||
main_menu_(MenuItem::Type::kNormal) {}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
std::unique_ptr<PlatformWindow> MainWindow::Create(Emulator* emulator) {
|
||||
std::unique_ptr<MainWindow> main_window(new MainWindow(emulator));
|
||||
|
||||
xe::threading::Fence fence;
|
||||
|
||||
main_window->loop()->Post([&main_window, &fence]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
xe::Profiler::ThreadEnter("Win32 Loop");
|
||||
|
||||
if (!main_window->Initialize()) {
|
||||
XEFATAL("Failed to initialize main window");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fence.Signal();
|
||||
});
|
||||
|
||||
fence.Wait();
|
||||
|
||||
return std::unique_ptr<PlatformWindow>(main_window.release());
|
||||
}
|
||||
|
||||
bool MainWindow::Initialize() {
|
||||
if (!PlatformWindow::Initialize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
on_key_down.AddListener([this](KeyEvent& e) {
|
||||
bool handled = true;
|
||||
switch (e.key_code()) {
|
||||
case 0x0D: { // numpad enter
|
||||
OnCommand(Commands::IDC_CPU_TIME_SCALAR_RESET);
|
||||
} break;
|
||||
case 0x6D: { // numpad minus
|
||||
OnCommand(Commands::IDC_CPU_TIME_SCALAR_HALF);
|
||||
} break;
|
||||
case 0x6B: { // numpad plus
|
||||
OnCommand(Commands::IDC_CPU_TIME_SCALAR_DOUBLE);
|
||||
} break;
|
||||
|
||||
case 0x73: { // VK_F4
|
||||
OnCommand(Commands::IDC_GPU_TRACE_FRAME);
|
||||
} break;
|
||||
case 0x74: { // VK_F5
|
||||
OnCommand(Commands::IDC_GPU_CLEAR_CACHES);
|
||||
} break;
|
||||
|
||||
case 0x7A: { // VK_F11
|
||||
OnCommand(Commands::IDC_WINDOW_FULLSCREEN);
|
||||
} break;
|
||||
case 0x1B: { // VK_ESCAPE
|
||||
// Allow users to escape fullscreen (but not enter it).
|
||||
if (is_fullscreen()) {
|
||||
ToggleFullscreen(false);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x70: { // VK_F1
|
||||
OnCommand(Commands::IDC_HELP_WEBSITE);
|
||||
} break;
|
||||
|
||||
default: { handled = false; } break;
|
||||
}
|
||||
e.set_handled(handled);
|
||||
});
|
||||
|
||||
// Main menu.
|
||||
// FIXME: This code is really messy.
|
||||
auto file_menu =
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kPopup, L"&File");
|
||||
{
|
||||
file_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_FILE_EXIT, L"E&xit", L"Alt+F4"));
|
||||
}
|
||||
main_menu_.AddChild(std::move(file_menu));
|
||||
|
||||
// CPU menu.
|
||||
auto cpu_menu =
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kPopup, L"&CPU");
|
||||
{
|
||||
cpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_RESET,
|
||||
L"&Reset Time Scalar", L"Numpad Enter"));
|
||||
cpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_HALF,
|
||||
L"Time Scalar /= 2", L"Numpad -"));
|
||||
cpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_DOUBLE,
|
||||
L"Time Scalar *= 2", L"Numpad +"));
|
||||
}
|
||||
cpu_menu->AddChild(
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kSeparator));
|
||||
{
|
||||
cpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_CPU_PROFILER_TOGGLE_DISPLAY,
|
||||
L"Toggle Profiler &Display", L"Tab"));
|
||||
cpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_CPU_PROFILER_TOGGLE_PAUSE,
|
||||
L"&Pause/Resume Profiler", L"`"));
|
||||
}
|
||||
main_menu_.AddChild(std::move(cpu_menu));
|
||||
|
||||
// GPU menu.
|
||||
auto gpu_menu =
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kPopup, L"&GPU");
|
||||
{
|
||||
gpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_GPU_TRACE_FRAME, L"&Trace Frame",
|
||||
L"F4"));
|
||||
}
|
||||
gpu_menu->AddChild(
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kSeparator));
|
||||
{
|
||||
gpu_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_GPU_CLEAR_CACHES,
|
||||
L"&Clear Caches", L"F5"));
|
||||
}
|
||||
main_menu_.AddChild(std::move(gpu_menu));
|
||||
|
||||
// Window menu.
|
||||
auto window_menu =
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kPopup, L"&Window");
|
||||
{
|
||||
window_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_WINDOW_FULLSCREEN,
|
||||
L"&Fullscreen", L"F11"));
|
||||
}
|
||||
main_menu_.AddChild(std::move(window_menu));
|
||||
|
||||
// Help menu.
|
||||
auto help_menu =
|
||||
std::make_unique<PlatformMenu>(MenuItem::Type::kPopup, L"&Help");
|
||||
{
|
||||
help_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_HELP_WEBSITE, L"&Website...",
|
||||
L"F1"));
|
||||
help_menu->AddChild(std::make_unique<PlatformMenu>(
|
||||
MenuItem::Type::kString, Commands::IDC_HELP_ABOUT, L"&About..."));
|
||||
}
|
||||
main_menu_.AddChild(std::move(help_menu));
|
||||
|
||||
set_menu(&main_menu_);
|
||||
|
||||
Resize(1280, 720);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::UpdateTitle() {
|
||||
std::wstring title(kBaseTitle);
|
||||
if (Clock::guest_time_scalar() != 1.0) {
|
||||
title += L" (@";
|
||||
title += xe::to_wstring(std::to_string(Clock::guest_time_scalar()));
|
||||
title += L"x)";
|
||||
}
|
||||
set_title(title);
|
||||
}
|
||||
|
||||
void MainWindow::OnClose() {
|
||||
loop_.Quit();
|
||||
|
||||
// TODO(benvanik): proper exit.
|
||||
XELOGI("User-initiated death!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void MainWindow::OnCommand(int id) {
|
||||
switch (id) {
|
||||
case IDC_FILE_EXIT: {
|
||||
Close();
|
||||
} break;
|
||||
|
||||
case IDC_CPU_TIME_SCALAR_RESET: {
|
||||
Clock::set_guest_time_scalar(1.0);
|
||||
UpdateTitle();
|
||||
} break;
|
||||
case IDC_CPU_TIME_SCALAR_HALF: {
|
||||
Clock::set_guest_time_scalar(Clock::guest_time_scalar() / 2.0);
|
||||
UpdateTitle();
|
||||
} break;
|
||||
case IDC_CPU_TIME_SCALAR_DOUBLE: {
|
||||
Clock::set_guest_time_scalar(Clock::guest_time_scalar() * 2.0);
|
||||
UpdateTitle();
|
||||
} break;
|
||||
case IDC_CPU_PROFILER_TOGGLE_DISPLAY: {
|
||||
Profiler::ToggleDisplay();
|
||||
} break;
|
||||
case IDC_CPU_PROFILER_TOGGLE_PAUSE: {
|
||||
Profiler::TogglePause();
|
||||
} break;
|
||||
|
||||
case IDC_GPU_TRACE_FRAME: {
|
||||
emulator()->graphics_system()->RequestFrameTrace();
|
||||
} break;
|
||||
case IDC_GPU_CLEAR_CACHES: {
|
||||
emulator()->graphics_system()->ClearCaches();
|
||||
} break;
|
||||
|
||||
case IDC_WINDOW_FULLSCREEN: {
|
||||
ToggleFullscreen(!is_fullscreen());
|
||||
} break;
|
||||
|
||||
case IDC_HELP_WEBSITE: {
|
||||
LaunchBrowser("http://xenia.jp");
|
||||
} break;
|
||||
case IDC_HELP_ABOUT: {
|
||||
LaunchBrowser("http://xenia.jp/about/");
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_MAIN_WINDOW_H_
|
||||
#define XENIA_UI_MAIN_WINDOW_H_
|
||||
|
||||
#include "xenia/ui/platform.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
class Emulator;
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class MainWindow : public PlatformWindow {
|
||||
public:
|
||||
~MainWindow() override;
|
||||
|
||||
static std::unique_ptr<PlatformWindow> Create(Emulator* emulator);
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
|
||||
private:
|
||||
explicit MainWindow(Emulator* emulator);
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void UpdateTitle();
|
||||
|
||||
void OnClose() override;
|
||||
void OnCommand(int id) override;
|
||||
|
||||
Emulator* emulator_;
|
||||
PlatformLoop loop_;
|
||||
PlatformMenu main_menu_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_MAIN_WINDOW_H_
|
||||
@@ -12,11 +12,27 @@
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
MenuItem::MenuItem(Type type) : type_(type), parent_item_(nullptr) {}
|
||||
std::unique_ptr<MenuItem> MenuItem::Create(Type type) {
|
||||
return MenuItem::Create(type, L"", L"", nullptr);
|
||||
}
|
||||
|
||||
std::unique_ptr<MenuItem> MenuItem::Create(Type type,
|
||||
const std::wstring& text) {
|
||||
return MenuItem::Create(type, text, L"", nullptr);
|
||||
}
|
||||
|
||||
std::unique_ptr<MenuItem> MenuItem::Create(Type type, const std::wstring& text,
|
||||
std::function<void()> callback) {
|
||||
return MenuItem::Create(type, text, L"", std::move(callback));
|
||||
}
|
||||
|
||||
MenuItem::MenuItem(Type type, const std::wstring& text,
|
||||
const std::wstring& hotkey)
|
||||
: type_(type), parent_item_(nullptr), text_(text), hotkey_(hotkey) {}
|
||||
const std::wstring& hotkey, std::function<void()> callback)
|
||||
: type_(type),
|
||||
parent_item_(nullptr),
|
||||
text_(text),
|
||||
hotkey_(hotkey),
|
||||
callback_(std::move(callback)) {}
|
||||
|
||||
MenuItem::~MenuItem() = default;
|
||||
|
||||
@@ -45,7 +61,13 @@ void MenuItem::RemoveChild(MenuItem* child_item) {
|
||||
}
|
||||
}
|
||||
|
||||
void MenuItem::OnSelected(UIEvent& e) { on_selected(e); }
|
||||
MenuItem* MenuItem::child(size_t index) { return children_[index].get(); }
|
||||
|
||||
void MenuItem::OnSelected(UIEvent& e) {
|
||||
if (callback_) {
|
||||
callback_();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
#ifndef XENIA_UI_MENU_ITEM_H_
|
||||
#define XENIA_UI_MENU_ITEM_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -30,6 +30,14 @@ class MenuItem {
|
||||
kString, // Menu is just a string
|
||||
};
|
||||
|
||||
static std::unique_ptr<MenuItem> Create(Type type);
|
||||
static std::unique_ptr<MenuItem> Create(Type type, const std::wstring& text);
|
||||
static std::unique_ptr<MenuItem> Create(Type type, const std::wstring& text,
|
||||
std::function<void()> callback);
|
||||
static std::unique_ptr<MenuItem> Create(Type type, const std::wstring& text,
|
||||
const std::wstring& hotkey,
|
||||
std::function<void()> callback);
|
||||
|
||||
virtual ~MenuItem();
|
||||
|
||||
MenuItem* parent_item() const { return parent_item_; }
|
||||
@@ -41,12 +49,11 @@ class MenuItem {
|
||||
void AddChild(std::unique_ptr<MenuItem> child_item);
|
||||
void AddChild(MenuItemPtr child_item);
|
||||
void RemoveChild(MenuItem* child_item);
|
||||
|
||||
Delegate<UIEvent> on_selected;
|
||||
MenuItem* child(size_t index);
|
||||
|
||||
protected:
|
||||
MenuItem(Type type);
|
||||
MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey);
|
||||
MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey,
|
||||
std::function<void()> callback);
|
||||
|
||||
virtual void OnChildAdded(MenuItem* child_item) {}
|
||||
virtual void OnChildRemoved(MenuItem* child_item) {}
|
||||
@@ -58,6 +65,7 @@ class MenuItem {
|
||||
std::vector<MenuItemPtr> children_;
|
||||
std::wstring text_;
|
||||
std::wstring hotkey_;
|
||||
std::function<void()> callback_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_PLATFORM_H_
|
||||
#define XENIA_UI_PLATFORM_H_
|
||||
|
||||
// TODO(benvanik): only on windows.
|
||||
#include "xenia/ui/win32/win32_control.h"
|
||||
#include "xenia/ui/win32/win32_file_picker.h"
|
||||
#include "xenia/ui/win32/win32_loop.h"
|
||||
#include "xenia/ui/win32/win32_menu_item.h"
|
||||
#include "xenia/ui/win32/win32_window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
using PlatformControl = xe::ui::win32::Win32Control;
|
||||
using PlatformFilePicker = xe::ui::win32::Win32FilePicker;
|
||||
using PlatformLoop = xe::ui::win32::Win32Loop;
|
||||
using PlatformMenu = xe::ui::win32::Win32MenuItem;
|
||||
using PlatformWindow = xe::ui::win32::Win32Window;
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_PLATFORM_H_
|
||||
@@ -13,23 +13,23 @@
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Control;
|
||||
class Window;
|
||||
|
||||
class UIEvent {
|
||||
public:
|
||||
UIEvent(Control* control = nullptr) : control_(control) {}
|
||||
UIEvent(Window* target = nullptr) : target_(target) {}
|
||||
virtual ~UIEvent() = default;
|
||||
|
||||
Control* control() const { return control_; }
|
||||
Window* target() const { return target_; }
|
||||
|
||||
private:
|
||||
Control* control_;
|
||||
Window* target_ = nullptr;
|
||||
};
|
||||
|
||||
class KeyEvent : public UIEvent {
|
||||
public:
|
||||
KeyEvent(Control* control, int key_code)
|
||||
: UIEvent(control), handled_(false), key_code_(key_code) {}
|
||||
KeyEvent(Window* target, int key_code)
|
||||
: UIEvent(target), key_code_(key_code) {}
|
||||
~KeyEvent() override = default;
|
||||
|
||||
bool is_handled() const { return handled_; }
|
||||
@@ -38,8 +38,8 @@ class KeyEvent : public UIEvent {
|
||||
int key_code() const { return key_code_; }
|
||||
|
||||
private:
|
||||
bool handled_;
|
||||
int key_code_;
|
||||
bool handled_ = false;
|
||||
int key_code_ = 0;
|
||||
};
|
||||
|
||||
class MouseEvent : public UIEvent {
|
||||
@@ -54,15 +54,9 @@ class MouseEvent : public UIEvent {
|
||||
};
|
||||
|
||||
public:
|
||||
MouseEvent(Control* control, Button button, int32_t x, int32_t y,
|
||||
MouseEvent(Window* target, Button button, int32_t x, int32_t y,
|
||||
int32_t dx = 0, int32_t dy = 0)
|
||||
: UIEvent(control),
|
||||
handled_(false),
|
||||
button_(button),
|
||||
x_(x),
|
||||
y_(y),
|
||||
dx_(dx),
|
||||
dy_(dy) {}
|
||||
: UIEvent(target), button_(button), x_(x), y_(y), dx_(dx), dy_(dy) {}
|
||||
~MouseEvent() override = default;
|
||||
|
||||
bool is_handled() const { return handled_; }
|
||||
@@ -75,12 +69,12 @@ class MouseEvent : public UIEvent {
|
||||
int32_t dy() const { return dy_; }
|
||||
|
||||
private:
|
||||
bool handled_;
|
||||
bool handled_ = false;
|
||||
Button button_;
|
||||
int32_t x_;
|
||||
int32_t y_;
|
||||
int32_t dx_;
|
||||
int32_t dy_;
|
||||
int32_t x_ = 0;
|
||||
int32_t y_ = 0;
|
||||
int32_t dx_ = 0;
|
||||
int32_t dy_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/win32/win32_control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
Win32Control::Win32Control(uint32_t flags) : Control(flags), hwnd_(nullptr) {}
|
||||
|
||||
Win32Control::~Win32Control() {
|
||||
if (hwnd_) {
|
||||
SetWindowLongPtr(hwnd_, GWLP_USERDATA, 0);
|
||||
CloseWindow(hwnd_);
|
||||
hwnd_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::OnCreate() {
|
||||
Control::OnCreate();
|
||||
|
||||
// Create all children, if needed.
|
||||
for (auto& child_control : children_) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control.get());
|
||||
if (!win32_control->hwnd()) {
|
||||
win32_control->Create();
|
||||
} else {
|
||||
SetParent(win32_control->hwnd(), hwnd());
|
||||
}
|
||||
win32_control->Layout();
|
||||
}
|
||||
|
||||
if (!is_cursor_visible_) {
|
||||
ShowCursor(FALSE);
|
||||
}
|
||||
if (!is_enabled_) {
|
||||
EnableWindow(hwnd_, false);
|
||||
}
|
||||
if (!is_visible_) {
|
||||
ShowWindow(hwnd_, SW_HIDE);
|
||||
}
|
||||
if (has_focus_) {
|
||||
SetFocus(hwnd_);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::OnDestroy() {
|
||||
// Destroy all children, if needed.
|
||||
for (auto& child_control : children_) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control.get());
|
||||
if (!win32_control->hwnd()) {
|
||||
win32_control->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::OnChildAdded(Control* child_control) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control);
|
||||
if (hwnd_) {
|
||||
if (!win32_control->hwnd()) {
|
||||
win32_control->Create();
|
||||
} else {
|
||||
SetParent(win32_control->hwnd(), hwnd());
|
||||
}
|
||||
child_control->Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::OnChildRemoved(Control* child_control) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control);
|
||||
if (win32_control->hwnd()) {
|
||||
SetParent(win32_control->hwnd(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::Resize(int32_t width, int32_t height) {
|
||||
MoveWindow(hwnd_, 0, 0, width, height, TRUE);
|
||||
}
|
||||
|
||||
void Win32Control::Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) {
|
||||
MoveWindow(hwnd_, left, top, right - left, bottom - top, TRUE);
|
||||
}
|
||||
|
||||
void Win32Control::ResizeToFill(int32_t pad_left, int32_t pad_top,
|
||||
int32_t pad_right, int32_t pad_bottom) {
|
||||
if (!parent_) {
|
||||
// TODO(benvanik): screen?
|
||||
return;
|
||||
}
|
||||
RECT parent_rect;
|
||||
auto parent_control = static_cast<Win32Control*>(parent_);
|
||||
GetClientRect(parent_control->hwnd(), &parent_rect);
|
||||
MoveWindow(hwnd_, parent_rect.left + pad_left, parent_rect.top + pad_top,
|
||||
parent_rect.right - pad_right - pad_left,
|
||||
parent_rect.bottom - pad_bottom - pad_top, TRUE);
|
||||
}
|
||||
|
||||
void Win32Control::OnResize(UIEvent& e) {
|
||||
RECT client_rect;
|
||||
GetClientRect(hwnd_, &client_rect);
|
||||
int32_t width = client_rect.right - client_rect.left;
|
||||
int32_t height = client_rect.bottom - client_rect.top;
|
||||
if (width != width_ || height != height_) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
Layout();
|
||||
for (auto& child_control : children_) {
|
||||
auto win32_control = static_cast<Win32Control*>(child_control.get());
|
||||
win32_control->OnResize(e);
|
||||
win32_control->Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::Invalidate() {
|
||||
if (invalidated_) {
|
||||
return;
|
||||
}
|
||||
invalidated_ = true;
|
||||
InvalidateRect(hwnd_, nullptr, FALSE);
|
||||
for (auto& child_control : children_) {
|
||||
child_control->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::set_cursor_visible(bool value) {
|
||||
if (is_cursor_visible_ == value) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
ShowCursor(TRUE);
|
||||
SetCursor(nullptr);
|
||||
} else {
|
||||
ShowCursor(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::set_enabled(bool value) {
|
||||
if (is_enabled_ == value) {
|
||||
return;
|
||||
}
|
||||
if (hwnd_) {
|
||||
EnableWindow(hwnd_, value);
|
||||
} else {
|
||||
is_enabled_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::set_visible(bool value) {
|
||||
if (is_visible_ == value) {
|
||||
return;
|
||||
}
|
||||
if (hwnd_) {
|
||||
ShowWindow(hwnd_, value ? SW_SHOWNOACTIVATE : SW_HIDE);
|
||||
} else {
|
||||
is_visible_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Control::set_focus(bool value) {
|
||||
if (has_focus_ == value) {
|
||||
return;
|
||||
}
|
||||
if (hwnd_) {
|
||||
if (value) {
|
||||
SetFocus(hwnd_);
|
||||
} else {
|
||||
SetFocus(nullptr);
|
||||
}
|
||||
} else {
|
||||
has_focus_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK Win32Control::WndProcThunk(HWND hWnd, UINT message,
|
||||
WPARAM wParam, LPARAM lParam) {
|
||||
Win32Control* control = nullptr;
|
||||
if (message == WM_NCCREATE) {
|
||||
auto create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
|
||||
control = reinterpret_cast<Win32Control*>(create_struct->lpCreateParams);
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(LONG_PTR)control);
|
||||
} else {
|
||||
control =
|
||||
reinterpret_cast<Win32Control*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
if (control) {
|
||||
return control->WndProc(hWnd, message, wParam, lParam);
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT Win32Control::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
||||
if (HandleMouse(message, wParam, lParam)) {
|
||||
SetFocus(hwnd_);
|
||||
return 0;
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
||||
if (HandleKeyboard(message, wParam, lParam)) {
|
||||
SetFocus(hwnd_);
|
||||
return 0;
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_NCCREATE:
|
||||
break;
|
||||
case WM_CREATE:
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
OnDestroy();
|
||||
break;
|
||||
|
||||
case WM_MOVING:
|
||||
break;
|
||||
case WM_MOVE:
|
||||
break;
|
||||
case WM_SIZING:
|
||||
break;
|
||||
case WM_SIZE: {
|
||||
auto e = UIEvent(this);
|
||||
OnResize(e);
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
break;
|
||||
case WM_DISPLAYCHANGE:
|
||||
break;
|
||||
|
||||
case WM_SHOWWINDOW: {
|
||||
if (wParam == TRUE) {
|
||||
auto e = UIEvent(this);
|
||||
OnVisible(e);
|
||||
} else {
|
||||
auto e = UIEvent(this);
|
||||
OnHidden(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_KILLFOCUS: {
|
||||
has_focus_ = false;
|
||||
auto e = UIEvent(this);
|
||||
OnLostFocus(e);
|
||||
break;
|
||||
}
|
||||
case WM_SETFOCUS: {
|
||||
has_focus_ = true;
|
||||
auto e = UIEvent(this);
|
||||
OnGotFocus(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
bool Win32Control::HandleMouse(UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
int32_t x = GET_X_LPARAM(lParam);
|
||||
int32_t y = GET_Y_LPARAM(lParam);
|
||||
if (message == WM_MOUSEWHEEL) {
|
||||
POINT pt = {x, y};
|
||||
ScreenToClient(hwnd_, &pt);
|
||||
x = pt.x;
|
||||
y = pt.y;
|
||||
}
|
||||
|
||||
MouseEvent::Button button = MouseEvent::Button::kNone;
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
button = MouseEvent::Button::kLeft;
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
button = MouseEvent::Button::kRight;
|
||||
break;
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP:
|
||||
button = MouseEvent::Button::kMiddle;
|
||||
break;
|
||||
case WM_XBUTTONDOWN:
|
||||
case WM_XBUTTONUP:
|
||||
switch (GET_XBUTTON_WPARAM(wParam)) {
|
||||
case XBUTTON1:
|
||||
button = MouseEvent::Button::kX1;
|
||||
break;
|
||||
case XBUTTON2:
|
||||
button = MouseEvent::Button::kX2;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
button = MouseEvent::Button::kNone;
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
button = MouseEvent::Button::kNone;
|
||||
dx = 0; // ?
|
||||
dy = GET_WHEEL_DELTA_WPARAM(wParam);
|
||||
break;
|
||||
default:
|
||||
// Double click/etc?
|
||||
return true;
|
||||
}
|
||||
|
||||
auto e = MouseEvent(this, button, x, y, dx, dy);
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_XBUTTONDOWN:
|
||||
OnMouseDown(e);
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_XBUTTONUP:
|
||||
OnMouseUp(e);
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
OnMouseMove(e);
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
OnMouseWheel(e);
|
||||
break;
|
||||
}
|
||||
return e.is_handled();
|
||||
}
|
||||
|
||||
bool Win32Control::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
auto e = KeyEvent(this, (int)wParam);
|
||||
switch (message) {
|
||||
case WM_KEYDOWN:
|
||||
OnKeyDown(e);
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
OnKeyUp(e);
|
||||
break;
|
||||
case WM_CHAR:
|
||||
OnKeyChar(e);
|
||||
break;
|
||||
}
|
||||
return e.is_handled();
|
||||
}
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/win32/win32_menu_item.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
Win32MenuItem::Win32MenuItem(Type type, int id, const std::wstring& text,
|
||||
const std::wstring& hotkey)
|
||||
: id_(id), MenuItem(type, text, hotkey) {
|
||||
switch (type) {
|
||||
case MenuItem::Type::kNormal:
|
||||
handle_ = CreateMenu();
|
||||
break;
|
||||
case MenuItem::Type::kPopup:
|
||||
handle_ = CreatePopupMenu();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Win32MenuItem::Win32MenuItem(Type type) : Win32MenuItem(type, 0, L"") {}
|
||||
|
||||
Win32MenuItem::Win32MenuItem(Type type, const std::wstring& text,
|
||||
const std::wstring& hotkey)
|
||||
: Win32MenuItem(type, 0, text, hotkey) {}
|
||||
|
||||
Win32MenuItem::~Win32MenuItem() {
|
||||
if (handle_) {
|
||||
DestroyMenu(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) {
|
||||
auto child_item = static_cast<Win32MenuItem*>(generic_child_item);
|
||||
|
||||
switch (child_item->type()) {
|
||||
case MenuItem::Type::kPopup:
|
||||
AppendMenuW(handle_, MF_POPUP,
|
||||
reinterpret_cast<UINT_PTR>(child_item->handle()),
|
||||
child_item->text().c_str());
|
||||
break;
|
||||
case MenuItem::Type::kSeparator:
|
||||
AppendMenuW(handle_, MF_SEPARATOR, child_item->id(), 0);
|
||||
break;
|
||||
case MenuItem::Type::kString:
|
||||
auto full_name = child_item->text();
|
||||
if (!child_item->hotkey().empty()) {
|
||||
full_name += L"\t" + child_item->hotkey();
|
||||
}
|
||||
AppendMenuW(handle_, MF_STRING, child_item->id(), full_name.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) {
|
||||
auto child_item = static_cast<Win32MenuItem*>(generic_child_item);
|
||||
//
|
||||
}
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_WIN32_WIN32_MENU_ITEM_H_
|
||||
#define XENIA_UI_WIN32_WIN32_MENU_ITEM_H_
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32MenuItem : public MenuItem {
|
||||
public:
|
||||
Win32MenuItem(Type type);
|
||||
Win32MenuItem(Type type, const std::wstring& text,
|
||||
const std::wstring& hotkey = L"");
|
||||
Win32MenuItem(Type type, int id, const std::wstring& text,
|
||||
const std::wstring& hotkey = L"");
|
||||
~Win32MenuItem() override;
|
||||
|
||||
HMENU handle() { return handle_; }
|
||||
int id() { return id_; }
|
||||
|
||||
protected:
|
||||
void OnChildAdded(MenuItem* child_item) override;
|
||||
void OnChildRemoved(MenuItem* child_item) override;
|
||||
|
||||
private:
|
||||
HMENU handle_;
|
||||
uint32_t position_; // Position within parent, if any
|
||||
int id_;
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_WIN32_WIN32_MENU_ITEM_H_
|
||||
@@ -1,250 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/win32/win32_window.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
Win32Window::Win32Window(Loop* loop, const std::wstring& title)
|
||||
: Window(loop, title) {}
|
||||
|
||||
Win32Window::~Win32Window() = default;
|
||||
|
||||
bool Win32Window::Initialize() {
|
||||
Create();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Window::Create() {
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
|
||||
WNDCLASSEX wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wcex.lpfnWndProc = Win32Control::WndProcThunk;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = hInstance;
|
||||
wcex.hIcon = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1);
|
||||
wcex.hIconSm = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1);
|
||||
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = L"XeniaWindowClass";
|
||||
if (!RegisterClassEx(&wcex)) {
|
||||
XELOGE("RegisterClassEx failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup initial size.
|
||||
DWORD window_style = WS_OVERLAPPEDWINDOW;
|
||||
DWORD window_ex_style = WS_EX_APPWINDOW | WS_EX_CONTROLPARENT;
|
||||
RECT rc = {0, 0, width_, height_};
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
// Create window.
|
||||
hwnd_ = CreateWindowEx(window_ex_style, L"XeniaWindowClass", title_.c_str(),
|
||||
window_style, rc.left, rc.top, rc.right - rc.left,
|
||||
rc.bottom - rc.top, nullptr, nullptr, hInstance, this);
|
||||
if (!hwnd_) {
|
||||
XELOGE("CreateWindow failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable flicks.
|
||||
ATOM atom = GlobalAddAtom(L"MicrosoftTabletPenServiceProperty");
|
||||
const DWORD_PTR dwHwndTabletProperty =
|
||||
TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click)
|
||||
// gesture
|
||||
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button
|
||||
// down (circle)
|
||||
TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, drag down,
|
||||
// drag up)
|
||||
TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING |
|
||||
TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA;
|
||||
SetProp(hwnd_, L"MicrosoftTabletPenServiceProperty",
|
||||
reinterpret_cast<HANDLE>(dwHwndTabletProperty));
|
||||
GlobalDeleteAtom(atom);
|
||||
|
||||
// Enable DWM elevation.
|
||||
EnableMMCSS();
|
||||
|
||||
ShowWindow(hwnd_, SW_SHOWNORMAL);
|
||||
UpdateWindow(hwnd_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Win32Window::EnableMMCSS() {
|
||||
HMODULE hLibrary = LoadLibrary(L"DWMAPI.DLL");
|
||||
if (!hLibrary) {
|
||||
return;
|
||||
}
|
||||
|
||||
typedef HRESULT(__stdcall * PDwmEnableMMCSS)(BOOL);
|
||||
PDwmEnableMMCSS pDwmEnableMMCSS =
|
||||
(PDwmEnableMMCSS)GetProcAddress(hLibrary, "DwmEnableMMCSS");
|
||||
if (pDwmEnableMMCSS) {
|
||||
pDwmEnableMMCSS(TRUE);
|
||||
}
|
||||
|
||||
typedef HRESULT(__stdcall * PDwmSetPresentParameters)(
|
||||
HWND, DWM_PRESENT_PARAMETERS*);
|
||||
PDwmSetPresentParameters pDwmSetPresentParameters =
|
||||
(PDwmSetPresentParameters)GetProcAddress(hLibrary,
|
||||
"DwmSetPresentParameters");
|
||||
if (pDwmSetPresentParameters) {
|
||||
DWM_PRESENT_PARAMETERS pp;
|
||||
std::memset(&pp, 0, sizeof(DWM_PRESENT_PARAMETERS));
|
||||
pp.cbSize = sizeof(DWM_PRESENT_PARAMETERS);
|
||||
pp.fQueue = FALSE;
|
||||
pp.cBuffer = 2;
|
||||
pp.fUseSourceRate = FALSE;
|
||||
pp.cRefreshesPerFrame = 1;
|
||||
pp.eSampling = DWM_SOURCE_FRAME_SAMPLING_POINT;
|
||||
pDwmSetPresentParameters(hwnd_, &pp);
|
||||
}
|
||||
|
||||
FreeLibrary(hLibrary);
|
||||
}
|
||||
|
||||
bool Win32Window::set_title(const std::wstring& title) {
|
||||
if (!Window::set_title(title)) {
|
||||
return false;
|
||||
}
|
||||
SetWindowText(hwnd_, title.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::Resize(int32_t width, int32_t height) {
|
||||
RECT rc = {0, 0, width, height};
|
||||
bool has_menu = menu_ ? true : false;
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu);
|
||||
if (true) {
|
||||
rc.right += 100 - rc.left;
|
||||
rc.left = 100;
|
||||
rc.bottom += 100 - rc.top;
|
||||
rc.top = 100;
|
||||
}
|
||||
Window::Resize(rc.left, rc.top, rc.right, rc.bottom);
|
||||
}
|
||||
|
||||
void Win32Window::Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) {
|
||||
RECT rc = {left, top, right, bottom};
|
||||
bool has_menu = menu_ ? true : false;
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu);
|
||||
Window::Resize(rc.left, rc.top, rc.right, rc.bottom);
|
||||
}
|
||||
|
||||
void Win32Window::ResizeToFill(int32_t pad_left, int32_t pad_top,
|
||||
int32_t pad_right, int32_t pad_bottom) {
|
||||
// TODO(benvanik): fullscreen.
|
||||
}
|
||||
|
||||
bool Win32Window::is_fullscreen() const {
|
||||
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
||||
return (style & WS_OVERLAPPEDWINDOW) != WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
|
||||
void Win32Window::ToggleFullscreen(bool fullscreen) {
|
||||
if (fullscreen == is_fullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
||||
if (fullscreen) {
|
||||
// Kill our borders and resize to take up entire primary monitor
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx
|
||||
MONITORINFO mi = {sizeof(mi)};
|
||||
if (GetWindowPlacement(hwnd_, &windowed_pos_) &&
|
||||
GetMonitorInfo(MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY),
|
||||
&mi)) {
|
||||
SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
||||
::SetMenu(hwnd_, NULL);
|
||||
|
||||
// Call into parent class to get around menu resizing code
|
||||
Window::Resize(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right,
|
||||
mi.rcMonitor.bottom);
|
||||
}
|
||||
} else {
|
||||
// Reinstate borders, resize to 1280x720
|
||||
SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
||||
SetWindowPlacement(hwnd_, &windowed_pos_);
|
||||
|
||||
Win32MenuItem* win_menu = reinterpret_cast<Win32MenuItem*>(menu_);
|
||||
if (win_menu) {
|
||||
::SetMenu(hwnd_, win_menu->handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Window::OnClose() {
|
||||
if (!closing_ && hwnd_) {
|
||||
closing_ = true;
|
||||
CloseWindow(hwnd_);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Window::OnSetMenu(MenuItem* menu) {
|
||||
Win32MenuItem* win_menu = reinterpret_cast<Win32MenuItem*>(menu);
|
||||
// Don't actually set the menu if we're fullscreen. We'll do that later.
|
||||
if (win_menu && !is_fullscreen()) {
|
||||
::SetMenu(hwnd_, win_menu->handle());
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_ACTIVATEAPP:
|
||||
if (wParam) {
|
||||
// Made active.
|
||||
OnShow();
|
||||
} else {
|
||||
// Made inactive.
|
||||
OnHide();
|
||||
}
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
closing_ = true;
|
||||
Close();
|
||||
break;
|
||||
|
||||
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
|
||||
return TABLET_DISABLE_PRESSANDHOLD | // disables press and hold
|
||||
// (right-click) gesture
|
||||
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up
|
||||
// (waves)
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen
|
||||
// button down (circle)
|
||||
TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward,
|
||||
// drag down, drag up)
|
||||
TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING |
|
||||
TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA;
|
||||
|
||||
case WM_COMMAND: {
|
||||
// TODO: Redirect this to MenuItem's on_selected delegate.
|
||||
OnCommand(LOWORD(wParam));
|
||||
} break;
|
||||
}
|
||||
|
||||
return Win32Control::WndProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_WIN32_WIN32_WINDOW_H_
|
||||
#define XENIA_UI_WIN32_WIN32_WINDOW_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/ui/win32/win32_control.h"
|
||||
#include "xenia/ui/win32/win32_menu_item.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32Window : public Window<Win32Control> {
|
||||
public:
|
||||
Win32Window(Loop* loop, const std::wstring& title);
|
||||
~Win32Window() override;
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
bool set_title(const std::wstring& title) override;
|
||||
|
||||
void Resize(int32_t width, int32_t height) override;
|
||||
void Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) override;
|
||||
void ResizeToFill(int32_t pad_left, int32_t pad_top, int32_t pad_right,
|
||||
int32_t pad_bottom) override;
|
||||
|
||||
bool is_fullscreen() const override;
|
||||
void ToggleFullscreen(bool fullscreen) override;
|
||||
|
||||
protected:
|
||||
bool Create() override;
|
||||
void OnClose() override;
|
||||
|
||||
void OnSetMenu(MenuItem*) override;
|
||||
|
||||
LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) override;
|
||||
|
||||
private:
|
||||
void EnableMMCSS();
|
||||
|
||||
bool closing_ = false;
|
||||
|
||||
WINDOWPLACEMENT windowed_pos_;
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_WIN32_WIN32_WINDOW_H_
|
||||
@@ -2,12 +2,12 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/elemental_control.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "el/util/debug.h"
|
||||
@@ -28,6 +28,7 @@ namespace xe {
|
||||
namespace ui {
|
||||
|
||||
constexpr bool kContinuousRepaint = false;
|
||||
constexpr bool kShowPresentFps = kContinuousRepaint;
|
||||
|
||||
// Enables long press behaviors (context menu, etc).
|
||||
constexpr bool kTouch = false;
|
||||
@@ -37,19 +38,26 @@ constexpr double kDoubleClickDistance = 5;
|
||||
|
||||
constexpr int32_t kMouseWheelDetent = 120;
|
||||
|
||||
Loop* elemental_loop_ = nullptr;
|
||||
|
||||
class RootElement : public el::Element {
|
||||
public:
|
||||
RootElement(ElementalControl* owner) : owner_(owner) {}
|
||||
RootElement(Window* owner) : owner_(owner) {}
|
||||
void OnInvalid() override { owner_->Invalidate(); }
|
||||
|
||||
private:
|
||||
ElementalControl* owner_ = nullptr;
|
||||
Window* owner_ = nullptr;
|
||||
};
|
||||
|
||||
bool ElementalControl::InitializeElemental(Loop* loop,
|
||||
el::graphics::Renderer* renderer) {
|
||||
Window::Window(Loop* loop, const std::wstring& title)
|
||||
: loop_(loop), title_(title) {}
|
||||
|
||||
Window::~Window() {
|
||||
// Context must have been cleaned up already in OnDestroy.
|
||||
assert_null(context_.get());
|
||||
}
|
||||
|
||||
bool Window::InitializeElemental(Loop* loop, el::graphics::Renderer* renderer) {
|
||||
GraphicsContextLock context_lock(context_.get());
|
||||
|
||||
static bool has_initialized = false;
|
||||
if (has_initialized) {
|
||||
return true;
|
||||
@@ -58,7 +66,7 @@ bool ElementalControl::InitializeElemental(Loop* loop,
|
||||
|
||||
// Need to pass off the Loop we want Elemental to use.
|
||||
// TODO(benvanik): give the callback to elemental instead.
|
||||
elemental_loop_ = loop;
|
||||
Loop::set_elemental_loop(loop);
|
||||
|
||||
if (!el::Initialize(renderer)) {
|
||||
XELOGE("Failed to initialize elemental core");
|
||||
@@ -116,18 +124,10 @@ bool ElementalControl::InitializeElemental(Loop* loop,
|
||||
return true;
|
||||
}
|
||||
|
||||
ElementalControl::ElementalControl(Loop* loop, uint32_t flags)
|
||||
: super(flags), loop_(loop) {}
|
||||
bool Window::OnCreate() { return true; }
|
||||
|
||||
ElementalControl::~ElementalControl() = default;
|
||||
|
||||
bool ElementalControl::Create() {
|
||||
if (!super::Create()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create subclass renderer (GL, etc).
|
||||
renderer_ = CreateRenderer();
|
||||
bool Window::MakeReady() {
|
||||
renderer_ = context_->CreateElementalRenderer();
|
||||
|
||||
// Initialize elemental.
|
||||
// TODO(benvanik): once? Do we care about multiple controls?
|
||||
@@ -139,26 +139,49 @@ bool ElementalControl::Create() {
|
||||
// TODO(benvanik): setup elements.
|
||||
root_element_ = std::make_unique<RootElement>(this);
|
||||
root_element_->set_background_skin(TBIDC("background"));
|
||||
root_element_->set_rect({0, 0, 1000, 1000});
|
||||
root_element_->set_rect({0, 0, width(), height()});
|
||||
|
||||
// el::util::ShowDebugInfoSettingsWindow(root_element_.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElementalControl::LoadLanguage(std::string filename) {
|
||||
void Window::OnMainMenuChange() {}
|
||||
|
||||
void Window::OnClose() {
|
||||
auto e = UIEvent(this);
|
||||
on_closing(e);
|
||||
on_closed(e);
|
||||
}
|
||||
|
||||
void Window::OnDestroy() {
|
||||
el::Shutdown();
|
||||
|
||||
// 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 ElementalControl::LoadSkin(std::string filename) {
|
||||
bool Window::LoadSkin(std::string filename) {
|
||||
return el::Skin::get()->Load(filename.c_str());
|
||||
}
|
||||
|
||||
void ElementalControl::Destroy() {
|
||||
el::Shutdown();
|
||||
super::Destroy();
|
||||
void Window::Layout() {
|
||||
auto e = UIEvent(this);
|
||||
OnLayout(e);
|
||||
}
|
||||
|
||||
void ElementalControl::OnLayout(UIEvent& e) {
|
||||
super::OnLayout(e);
|
||||
void Window::Invalidate() {}
|
||||
|
||||
void Window::OnResize(UIEvent& e) { on_resize(e); }
|
||||
|
||||
void Window::OnLayout(UIEvent& e) {
|
||||
on_layout(e);
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -166,9 +189,8 @@ void ElementalControl::OnLayout(UIEvent& e) {
|
||||
root_element()->set_rect({0, 0, width(), height()});
|
||||
}
|
||||
|
||||
void ElementalControl::OnPaint(UIEvent& e) {
|
||||
super::OnPaint(e);
|
||||
if (!root_element()) {
|
||||
void Window::OnPaint(UIEvent& e) {
|
||||
if (!renderer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,47 +206,74 @@ void ElementalControl::OnPaint(UIEvent& e) {
|
||||
|
||||
// Update TB (run animations, handle deferred input, etc).
|
||||
el::AnimationManager::Update();
|
||||
root_element()->InvokeProcessStates();
|
||||
root_element()->InvokeProcess();
|
||||
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.
|
||||
root_element()->InvokePaint(el::Element::PaintProps());
|
||||
if (root_element()) {
|
||||
root_element()->InvokePaint(el::Element::PaintProps());
|
||||
}
|
||||
|
||||
// Render debug overlay.
|
||||
root_element()->computed_font()->DrawString(
|
||||
5, 5, el::Color(255, 0, 0),
|
||||
el::format_string("Frame %lld", frame_count_));
|
||||
if (kContinuousRepaint) {
|
||||
on_paint(e);
|
||||
|
||||
if (root_element() && kShowPresentFps) {
|
||||
// Render debug overlay.
|
||||
root_element()->computed_font()->DrawString(
|
||||
5, 20, el::Color(255, 0, 0), el::format_string("FPS: %d", fps_));
|
||||
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_));
|
||||
}
|
||||
|
||||
renderer()->EndPaint();
|
||||
|
||||
on_painted(e);
|
||||
|
||||
context_->EndSwap();
|
||||
|
||||
// If animations are running, reinvalidate immediately.
|
||||
if (el::AnimationManager::has_running_animations()) {
|
||||
root_element()->Invalidate();
|
||||
}
|
||||
if (kContinuousRepaint) {
|
||||
// Force an immediate repaint, always.
|
||||
root_element()->Invalidate();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ElementalControl::OnGotFocus(UIEvent& e) { super::OnGotFocus(e); }
|
||||
void Window::OnVisible(UIEvent& e) { on_visible(e); }
|
||||
|
||||
void ElementalControl::OnLostFocus(UIEvent& e) {
|
||||
super::OnLostFocus(e);
|
||||
void Window::OnHidden(UIEvent& e) { on_hidden(e); }
|
||||
|
||||
void Window::OnGotFocus(UIEvent& e) { on_got_focus(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 ElementalControl::GetModifierKeys() {
|
||||
el::ModifierKeys Window::GetModifierKeys() {
|
||||
auto modifiers = el::ModifierKeys::kNone;
|
||||
if (modifier_shift_pressed_) {
|
||||
modifiers |= el::ModifierKeys::kShift;
|
||||
@@ -241,7 +290,7 @@ el::ModifierKeys ElementalControl::GetModifierKeys() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) {
|
||||
void Window::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) {
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -364,8 +413,8 @@ void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key,
|
||||
bool 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;
|
||||
@@ -417,24 +466,32 @@ bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key,
|
||||
return true;
|
||||
}
|
||||
|
||||
void ElementalControl::OnKeyDown(KeyEvent& e) {
|
||||
super::OnKeyDown(e);
|
||||
void Window::OnKeyDown(KeyEvent& e) {
|
||||
on_key_down(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
OnKeyPress(e, true, false);
|
||||
}
|
||||
|
||||
void ElementalControl::OnKeyUp(KeyEvent& e) {
|
||||
super::OnKeyUp(e);
|
||||
void Window::OnKeyUp(KeyEvent& e) {
|
||||
on_key_up(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
OnKeyPress(e, false, false);
|
||||
}
|
||||
|
||||
void ElementalControl::OnKeyChar(KeyEvent& e) {
|
||||
super::OnKeyChar(e);
|
||||
void Window::OnKeyChar(KeyEvent& e) {
|
||||
OnKeyPress(e, true, true);
|
||||
OnKeyPress(e, false, true);
|
||||
}
|
||||
|
||||
void ElementalControl::OnMouseDown(MouseEvent& e) {
|
||||
super::OnMouseDown(e);
|
||||
void Window::OnMouseDown(MouseEvent& e) {
|
||||
on_mouse_down(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -463,8 +520,11 @@ void ElementalControl::OnMouseDown(MouseEvent& e) {
|
||||
}
|
||||
}
|
||||
|
||||
void ElementalControl::OnMouseMove(MouseEvent& e) {
|
||||
super::OnMouseMove(e);
|
||||
void Window::OnMouseMove(MouseEvent& e) {
|
||||
on_mouse_move(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -472,8 +532,11 @@ void ElementalControl::OnMouseMove(MouseEvent& e) {
|
||||
e.set_handled(true);
|
||||
}
|
||||
|
||||
void ElementalControl::OnMouseUp(MouseEvent& e) {
|
||||
super::OnMouseUp(e);
|
||||
void Window::OnMouseUp(MouseEvent& e) {
|
||||
on_mouse_up(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -494,8 +557,11 @@ void ElementalControl::OnMouseUp(MouseEvent& e) {
|
||||
}
|
||||
}
|
||||
|
||||
void ElementalControl::OnMouseWheel(MouseEvent& e) {
|
||||
super::OnMouseWheel(e);
|
||||
void Window::OnMouseWheel(MouseEvent& e) {
|
||||
on_mouse_wheel(e);
|
||||
if (e.is_handled()) {
|
||||
return;
|
||||
}
|
||||
if (!root_element()) {
|
||||
return;
|
||||
}
|
||||
@@ -505,32 +571,3 @@ void ElementalControl::OnMouseWheel(MouseEvent& e) {
|
||||
|
||||
} // namespace ui
|
||||
} // 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 el::util::RescheduleTimer(uint64_t fire_time) {
|
||||
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);
|
||||
}
|
||||
@@ -10,10 +10,13 @@
|
||||
#ifndef XENIA_UI_WINDOW_H_
|
||||
#define XENIA_UI_WINDOW_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "el/element.h"
|
||||
#include "el/graphics/renderer.h"
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/ui/control.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
@@ -21,14 +24,22 @@
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
template <typename T>
|
||||
class Window : public T {
|
||||
public:
|
||||
~Window() override = default;
|
||||
typedef void* NativeWindowHandle;
|
||||
|
||||
virtual bool Initialize() { return true; }
|
||||
class Window {
|
||||
public:
|
||||
static std::unique_ptr<Window> Create(Loop* loop, const std::wstring& title);
|
||||
|
||||
virtual ~Window();
|
||||
|
||||
Loop* loop() const { return loop_; }
|
||||
virtual NativeWindowHandle native_handle() const = 0;
|
||||
|
||||
MenuItem* main_menu() const { return main_menu_.get(); }
|
||||
void set_main_menu(std::unique_ptr<MenuItem> main_menu) {
|
||||
main_menu_ = std::move(main_menu);
|
||||
OnMainMenuChange();
|
||||
}
|
||||
|
||||
const std::wstring& title() const { return title_; }
|
||||
virtual bool set_title(const std::wstring& title) {
|
||||
@@ -39,65 +50,124 @@ class Window : public T {
|
||||
return true;
|
||||
}
|
||||
|
||||
MenuItem* menu() const { return menu_; }
|
||||
void set_menu(MenuItem* menu) {
|
||||
if (menu == menu_) {
|
||||
return;
|
||||
}
|
||||
menu_ = menu;
|
||||
OnSetMenu(menu);
|
||||
}
|
||||
|
||||
void Close() {
|
||||
auto e = UIEvent(this);
|
||||
on_closing(e);
|
||||
|
||||
OnClose();
|
||||
|
||||
e = UIEvent(this);
|
||||
on_closed(e);
|
||||
}
|
||||
|
||||
virtual bool is_fullscreen() const { return false; }
|
||||
virtual void ToggleFullscreen(bool fullscreen) {}
|
||||
|
||||
bool has_focus() const { return has_focus_; }
|
||||
virtual void set_focus(bool value) { has_focus_ = value; }
|
||||
|
||||
bool is_cursor_visible() const { return is_cursor_visible_; }
|
||||
virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; }
|
||||
|
||||
int32_t width() const { return width_; }
|
||||
int32_t height() const { return height_; }
|
||||
virtual void Resize(int32_t width, int32_t height) = 0;
|
||||
virtual void Resize(int32_t left, int32_t top, int32_t right,
|
||||
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(); }
|
||||
|
||||
virtual bool Initialize() { return true; }
|
||||
void set_context(std::unique_ptr<GraphicsContext> context) {
|
||||
context_ = std::move(context);
|
||||
}
|
||||
virtual bool MakeReady();
|
||||
|
||||
bool LoadLanguage(std::string filename);
|
||||
bool LoadSkin(std::string filename);
|
||||
|
||||
void Layout();
|
||||
virtual void Invalidate();
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
public:
|
||||
Delegate<UIEvent> on_shown;
|
||||
Delegate<UIEvent> on_hidden;
|
||||
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;
|
||||
|
||||
Delegate<MouseEvent> on_mouse_down;
|
||||
Delegate<MouseEvent> on_mouse_move;
|
||||
Delegate<MouseEvent> on_mouse_up;
|
||||
Delegate<MouseEvent> on_mouse_wheel;
|
||||
|
||||
protected:
|
||||
Window(Loop* loop, const std::wstring& title)
|
||||
: T(0), loop_(loop), title_(title) {}
|
||||
Window(Loop* loop, const std::wstring& title);
|
||||
|
||||
void OnShow() {
|
||||
if (is_visible_) {
|
||||
return;
|
||||
}
|
||||
is_visible_ = true;
|
||||
auto e = UIEvent(this);
|
||||
on_shown(e);
|
||||
}
|
||||
bool InitializeElemental(Loop* loop, el::graphics::Renderer* renderer);
|
||||
|
||||
void OnHide() {
|
||||
if (!is_visible_) {
|
||||
return;
|
||||
}
|
||||
is_visible_ = false;
|
||||
auto e = UIEvent(this);
|
||||
on_hidden(e);
|
||||
}
|
||||
virtual bool OnCreate();
|
||||
virtual void OnMainMenuChange();
|
||||
virtual void OnClose();
|
||||
virtual void OnDestroy();
|
||||
|
||||
virtual void OnClose() {}
|
||||
virtual void OnResize(UIEvent& e);
|
||||
virtual void OnLayout(UIEvent& e);
|
||||
virtual void OnPaint(UIEvent& e);
|
||||
|
||||
virtual void OnSetMenu(MenuItem* menu) {}
|
||||
virtual void OnVisible(UIEvent& e);
|
||||
virtual void OnHidden(UIEvent& e);
|
||||
|
||||
virtual void OnCommand(int id) {}
|
||||
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);
|
||||
|
||||
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_;
|
||||
std::wstring title_;
|
||||
MenuItem* menu_ = nullptr;
|
||||
int32_t width_ = 0;
|
||||
int32_t height_ = 0;
|
||||
bool has_focus_ = true;
|
||||
bool is_cursor_visible_ = true;
|
||||
|
||||
std::unique_ptr<GraphicsContext> context_;
|
||||
std::unique_ptr<el::graphics::Renderer> renderer_;
|
||||
std::unique_ptr<el::Element> root_element_;
|
||||
|
||||
uint32_t frame_count_ = 0;
|
||||
uint32_t fps_ = 0;
|
||||
uint64_t fps_update_time_ = 0;
|
||||
uint64_t fps_frame_count_ = 0;
|
||||
|
||||
bool modifier_shift_pressed_ = false;
|
||||
bool modifier_cntrl_pressed_ = false;
|
||||
bool modifier_alt_pressed_ = false;
|
||||
bool modifier_super_pressed_ = false;
|
||||
uint64_t last_click_time_ = 0;
|
||||
int last_click_x_ = 0;
|
||||
int last_click_y_ = 0;
|
||||
int last_click_counter_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
569
src/xenia/ui/window_win.cc
Normal file
569
src/xenia/ui/window_win.cc
Normal file
@@ -0,0 +1,569 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/ui/window_win.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
std::unique_ptr<Window> Window::Create(Loop* loop, const std::wstring& title) {
|
||||
return std::make_unique<Win32Window>(loop, title);
|
||||
}
|
||||
|
||||
Win32Window::Win32Window(Loop* loop, const std::wstring& title)
|
||||
: Window(loop, title) {}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
OnDestroy();
|
||||
if (hwnd_) {
|
||||
SetWindowLongPtr(hwnd_, GWLP_USERDATA, 0);
|
||||
CloseWindow(hwnd_);
|
||||
hwnd_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Win32Window::Initialize() { return OnCreate(); }
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
|
||||
WNDCLASSEX wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wcex.lpfnWndProc = Win32Window::WndProcThunk;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = hInstance;
|
||||
wcex.hIcon = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1);
|
||||
wcex.hIconSm = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1);
|
||||
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = L"XeniaWindowClass";
|
||||
if (!RegisterClassEx(&wcex)) {
|
||||
XELOGE("RegisterClassEx failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup initial size.
|
||||
DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
|
||||
DWORD window_ex_style = WS_EX_APPWINDOW | WS_EX_CONTROLPARENT;
|
||||
RECT rc = {0, 0, width_, height_};
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
// Create window.
|
||||
hwnd_ = CreateWindowEx(window_ex_style, L"XeniaWindowClass", title_.c_str(),
|
||||
window_style, rc.left, rc.top, rc.right - rc.left,
|
||||
rc.bottom - rc.top, nullptr, nullptr, hInstance, this);
|
||||
if (!hwnd_) {
|
||||
XELOGE("CreateWindow failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable flicks.
|
||||
ATOM atom = GlobalAddAtom(L"MicrosoftTabletPenServiceProperty");
|
||||
const DWORD_PTR dwHwndTabletProperty =
|
||||
TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click)
|
||||
// gesture
|
||||
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button
|
||||
// down (circle)
|
||||
TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, drag down,
|
||||
// drag up)
|
||||
TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING |
|
||||
TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA;
|
||||
SetProp(hwnd_, L"MicrosoftTabletPenServiceProperty",
|
||||
reinterpret_cast<HANDLE>(dwHwndTabletProperty));
|
||||
GlobalDeleteAtom(atom);
|
||||
|
||||
// Enable DWM elevation.
|
||||
EnableMMCSS();
|
||||
|
||||
ShowWindow(hwnd_, SW_SHOWNORMAL);
|
||||
UpdateWindow(hwnd_);
|
||||
|
||||
// Initial state.
|
||||
if (!is_cursor_visible_) {
|
||||
ShowCursor(FALSE);
|
||||
}
|
||||
if (has_focus_) {
|
||||
SetFocus(hwnd_);
|
||||
}
|
||||
|
||||
return super::OnCreate();
|
||||
}
|
||||
|
||||
void Win32Window::EnableMMCSS() {
|
||||
HMODULE hLibrary = LoadLibrary(L"DWMAPI.DLL");
|
||||
if (!hLibrary) {
|
||||
return;
|
||||
}
|
||||
|
||||
typedef HRESULT(__stdcall * PDwmEnableMMCSS)(BOOL);
|
||||
PDwmEnableMMCSS pDwmEnableMMCSS =
|
||||
(PDwmEnableMMCSS)GetProcAddress(hLibrary, "DwmEnableMMCSS");
|
||||
if (pDwmEnableMMCSS) {
|
||||
pDwmEnableMMCSS(TRUE);
|
||||
}
|
||||
|
||||
typedef HRESULT(__stdcall * PDwmSetPresentParameters)(
|
||||
HWND, DWM_PRESENT_PARAMETERS*);
|
||||
PDwmSetPresentParameters pDwmSetPresentParameters =
|
||||
(PDwmSetPresentParameters)GetProcAddress(hLibrary,
|
||||
"DwmSetPresentParameters");
|
||||
if (pDwmSetPresentParameters) {
|
||||
DWM_PRESENT_PARAMETERS pp;
|
||||
std::memset(&pp, 0, sizeof(DWM_PRESENT_PARAMETERS));
|
||||
pp.cbSize = sizeof(DWM_PRESENT_PARAMETERS);
|
||||
pp.fQueue = FALSE;
|
||||
pp.cBuffer = 2;
|
||||
pp.fUseSourceRate = FALSE;
|
||||
pp.cRefreshesPerFrame = 1;
|
||||
pp.eSampling = DWM_SOURCE_FRAME_SAMPLING_POINT;
|
||||
pDwmSetPresentParameters(hwnd_, &pp);
|
||||
}
|
||||
|
||||
FreeLibrary(hLibrary);
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
super::OnDestroy();
|
||||
hwnd_ = nullptr;
|
||||
}
|
||||
|
||||
void Win32Window::OnClose() {
|
||||
if (!closing_ && hwnd_) {
|
||||
closing_ = true;
|
||||
CloseWindow(hwnd_);
|
||||
}
|
||||
super::OnClose();
|
||||
}
|
||||
|
||||
bool Win32Window::set_title(const std::wstring& title) {
|
||||
if (!super::set_title(title)) {
|
||||
return false;
|
||||
}
|
||||
SetWindowText(hwnd_, title.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Window::is_fullscreen() const {
|
||||
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
||||
return (style & WS_OVERLAPPEDWINDOW) != WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
|
||||
void Win32Window::ToggleFullscreen(bool fullscreen) {
|
||||
if (fullscreen == is_fullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
||||
if (fullscreen) {
|
||||
// Kill our borders and resize to take up entire primary monitor.
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx
|
||||
MONITORINFO mi = {sizeof(mi)};
|
||||
if (GetWindowPlacement(hwnd_, &windowed_pos_) &&
|
||||
GetMonitorInfo(MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY),
|
||||
&mi)) {
|
||||
SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
||||
::SetMenu(hwnd_, NULL);
|
||||
|
||||
// Call into parent class to get around menu resizing code.
|
||||
Resize(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right,
|
||||
mi.rcMonitor.bottom);
|
||||
}
|
||||
} else {
|
||||
// Reinstate borders, resize to 1280x720
|
||||
SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
||||
SetWindowPlacement(hwnd_, &windowed_pos_);
|
||||
|
||||
auto main_menu = reinterpret_cast<Win32MenuItem*>(main_menu_.get());
|
||||
if (main_menu) {
|
||||
::SetMenu(hwnd_, main_menu->handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Window::set_cursor_visible(bool value) {
|
||||
if (is_cursor_visible_ == value) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
ShowCursor(TRUE);
|
||||
SetCursor(nullptr);
|
||||
} else {
|
||||
ShowCursor(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Window::set_focus(bool value) {
|
||||
if (has_focus_ == value) {
|
||||
return;
|
||||
}
|
||||
if (hwnd_) {
|
||||
if (value) {
|
||||
SetFocus(hwnd_);
|
||||
} else {
|
||||
SetFocus(nullptr);
|
||||
}
|
||||
} else {
|
||||
has_focus_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Window::Resize(int32_t width, int32_t height) {
|
||||
RECT rc = {0, 0, width, height};
|
||||
bool has_menu = main_menu_ ? true : false;
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu);
|
||||
if (true) {
|
||||
rc.right += 100 - rc.left;
|
||||
rc.left = 100;
|
||||
rc.bottom += 100 - rc.top;
|
||||
rc.top = 100;
|
||||
}
|
||||
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
||||
TRUE);
|
||||
}
|
||||
|
||||
void Win32Window::Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) {
|
||||
RECT rc = {left, top, right, bottom};
|
||||
bool has_menu = main_menu_ ? true : false;
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu);
|
||||
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
||||
TRUE);
|
||||
}
|
||||
|
||||
void Win32Window::OnResize(UIEvent& e) {
|
||||
RECT client_rect;
|
||||
GetClientRect(hwnd_, &client_rect);
|
||||
int32_t width = client_rect.right - client_rect.left;
|
||||
int32_t height = client_rect.bottom - client_rect.top;
|
||||
if (width != width_ || height != height_) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
Layout();
|
||||
}
|
||||
super::OnResize(e);
|
||||
}
|
||||
|
||||
void Win32Window::Invalidate() {
|
||||
super::Invalidate();
|
||||
InvalidateRect(hwnd_, nullptr, FALSE);
|
||||
}
|
||||
|
||||
void Win32Window::Close() {
|
||||
if (closing_) {
|
||||
return;
|
||||
}
|
||||
closing_ = true;
|
||||
Close();
|
||||
OnClose();
|
||||
DestroyWindow(hwnd_);
|
||||
}
|
||||
|
||||
void Win32Window::OnMainMenuChange() {
|
||||
auto main_menu = reinterpret_cast<Win32MenuItem*>(main_menu_.get());
|
||||
// Don't actually set the menu if we're fullscreen. We'll do that later.
|
||||
if (main_menu && !is_fullscreen()) {
|
||||
::SetMenu(hwnd_, main_menu->handle());
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK Win32Window::WndProcThunk(HWND hWnd, UINT message,
|
||||
WPARAM wParam, LPARAM lParam) {
|
||||
Win32Window* window = nullptr;
|
||||
if (message == WM_NCCREATE) {
|
||||
auto create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
|
||||
window = reinterpret_cast<Win32Window*>(create_struct->lpCreateParams);
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(LONG_PTR)window);
|
||||
} else {
|
||||
window =
|
||||
reinterpret_cast<Win32Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
if (window) {
|
||||
return window->WndProc(hWnd, message, wParam, lParam);
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
||||
if (HandleMouse(message, wParam, lParam)) {
|
||||
SetFocus(hwnd_);
|
||||
return 0;
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
||||
if (HandleKeyboard(message, wParam, lParam)) {
|
||||
SetFocus(hwnd_);
|
||||
return 0;
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_NCCREATE:
|
||||
break;
|
||||
case WM_CREATE:
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
OnDestroy();
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
closing_ = true;
|
||||
Close();
|
||||
OnClose();
|
||||
break;
|
||||
|
||||
case WM_MOVING:
|
||||
break;
|
||||
case WM_MOVE:
|
||||
break;
|
||||
case WM_SIZING:
|
||||
break;
|
||||
case WM_SIZE: {
|
||||
auto e = UIEvent(this);
|
||||
OnResize(e);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_PAINT: {
|
||||
ValidateRect(hwnd_, nullptr);
|
||||
auto e = UIEvent(this);
|
||||
OnPaint(e);
|
||||
return 0; // Ignored because of custom paint.
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 0; // Ignored because of custom paint.
|
||||
case WM_DISPLAYCHANGE:
|
||||
break;
|
||||
|
||||
case WM_ACTIVATEAPP:
|
||||
case WM_SHOWWINDOW: {
|
||||
if (wParam == TRUE) {
|
||||
auto e = UIEvent(this);
|
||||
OnVisible(e);
|
||||
} else {
|
||||
auto e = UIEvent(this);
|
||||
OnHidden(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_KILLFOCUS: {
|
||||
has_focus_ = false;
|
||||
auto e = UIEvent(this);
|
||||
OnLostFocus(e);
|
||||
break;
|
||||
}
|
||||
case WM_SETFOCUS: {
|
||||
has_focus_ = true;
|
||||
auto e = UIEvent(this);
|
||||
OnGotFocus(e);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
|
||||
return
|
||||
// disables press and hold (right-click) gesture
|
||||
TABLET_DISABLE_PRESSANDHOLD |
|
||||
// disables UI feedback on pen up (waves)
|
||||
TABLET_DISABLE_PENTAPFEEDBACK |
|
||||
// disables UI feedback on pen button down (circle)
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK |
|
||||
// disables pen flicks (back, forward, drag down, drag up)
|
||||
TABLET_DISABLE_FLICKS | TABLET_DISABLE_TOUCHSWITCH |
|
||||
TABLET_DISABLE_SMOOTHSCROLLING | TABLET_DISABLE_TOUCHUIFORCEON |
|
||||
TABLET_ENABLE_MULTITOUCHDATA;
|
||||
|
||||
case WM_MENUCOMMAND: {
|
||||
// TODO: Redirect this to MenuItem's on_selected delegate.
|
||||
MENUINFO menu_info = {0};
|
||||
menu_info.cbSize = sizeof(menu_info);
|
||||
menu_info.fMask = MIM_MENUDATA;
|
||||
GetMenuInfo(HMENU(lParam), &menu_info);
|
||||
auto parent_item = reinterpret_cast<Win32MenuItem*>(menu_info.dwMenuData);
|
||||
auto child_item =
|
||||
reinterpret_cast<Win32MenuItem*>(parent_item->child(wParam));
|
||||
assert_not_null(child_item);
|
||||
UIEvent e(this);
|
||||
child_item->OnSelected(e);
|
||||
} break;
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
bool Win32Window::HandleMouse(UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
int32_t x = GET_X_LPARAM(lParam);
|
||||
int32_t y = GET_Y_LPARAM(lParam);
|
||||
if (message == WM_MOUSEWHEEL) {
|
||||
POINT pt = {x, y};
|
||||
ScreenToClient(hwnd_, &pt);
|
||||
x = pt.x;
|
||||
y = pt.y;
|
||||
}
|
||||
|
||||
MouseEvent::Button button = MouseEvent::Button::kNone;
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
button = MouseEvent::Button::kLeft;
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
button = MouseEvent::Button::kRight;
|
||||
break;
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP:
|
||||
button = MouseEvent::Button::kMiddle;
|
||||
break;
|
||||
case WM_XBUTTONDOWN:
|
||||
case WM_XBUTTONUP:
|
||||
switch (GET_XBUTTON_WPARAM(wParam)) {
|
||||
case XBUTTON1:
|
||||
button = MouseEvent::Button::kX1;
|
||||
break;
|
||||
case XBUTTON2:
|
||||
button = MouseEvent::Button::kX2;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
button = MouseEvent::Button::kNone;
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
button = MouseEvent::Button::kNone;
|
||||
dx = 0; // ?
|
||||
dy = GET_WHEEL_DELTA_WPARAM(wParam);
|
||||
break;
|
||||
default:
|
||||
// Double click/etc?
|
||||
return true;
|
||||
}
|
||||
|
||||
auto e = MouseEvent(this, button, x, y, dx, dy);
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_XBUTTONDOWN:
|
||||
OnMouseDown(e);
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_XBUTTONUP:
|
||||
OnMouseUp(e);
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
OnMouseMove(e);
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
OnMouseWheel(e);
|
||||
break;
|
||||
}
|
||||
return e.is_handled();
|
||||
}
|
||||
|
||||
bool Win32Window::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
auto e = KeyEvent(this, (int)wParam);
|
||||
switch (message) {
|
||||
case WM_KEYDOWN:
|
||||
OnKeyDown(e);
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
OnKeyUp(e);
|
||||
break;
|
||||
case WM_CHAR:
|
||||
OnKeyChar(e);
|
||||
break;
|
||||
}
|
||||
return e.is_handled();
|
||||
}
|
||||
|
||||
std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type,
|
||||
const std::wstring& text,
|
||||
const std::wstring& hotkey,
|
||||
std::function<void()> callback) {
|
||||
return std::make_unique<Win32MenuItem>(type, text, hotkey, callback);
|
||||
}
|
||||
|
||||
Win32MenuItem::Win32MenuItem(Type type, const std::wstring& text,
|
||||
const std::wstring& hotkey,
|
||||
std::function<void()> callback)
|
||||
: MenuItem(type, text, hotkey, std::move(callback)) {
|
||||
switch (type) {
|
||||
case MenuItem::Type::kNormal:
|
||||
handle_ = CreateMenu();
|
||||
break;
|
||||
case MenuItem::Type::kPopup:
|
||||
handle_ = CreatePopupMenu();
|
||||
break;
|
||||
}
|
||||
if (handle_) {
|
||||
MENUINFO menu_info = {0};
|
||||
menu_info.cbSize = sizeof(menu_info);
|
||||
menu_info.fMask = MIM_MENUDATA | MIM_STYLE;
|
||||
menu_info.dwMenuData = ULONG_PTR(this);
|
||||
menu_info.dwStyle = MNS_NOTIFYBYPOS;
|
||||
SetMenuInfo(handle_, &menu_info);
|
||||
}
|
||||
}
|
||||
|
||||
Win32MenuItem::~Win32MenuItem() {
|
||||
if (handle_) {
|
||||
DestroyMenu(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) {
|
||||
auto child_item = static_cast<Win32MenuItem*>(generic_child_item);
|
||||
|
||||
switch (child_item->type()) {
|
||||
case MenuItem::Type::kPopup:
|
||||
AppendMenuW(handle_, MF_POPUP,
|
||||
reinterpret_cast<UINT_PTR>(child_item->handle()),
|
||||
child_item->text().c_str());
|
||||
break;
|
||||
case MenuItem::Type::kSeparator:
|
||||
AppendMenuW(handle_, MF_SEPARATOR, UINT_PTR(child_item->handle_), 0);
|
||||
break;
|
||||
case MenuItem::Type::kString:
|
||||
auto full_name = child_item->text();
|
||||
if (!child_item->hotkey().empty()) {
|
||||
full_name += L"\t" + child_item->hotkey();
|
||||
}
|
||||
AppendMenuW(handle_, MF_STRING, UINT_PTR(child_item->handle_),
|
||||
full_name.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) {
|
||||
auto child_item = static_cast<Win32MenuItem*>(generic_child_item);
|
||||
//
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -7,45 +7,49 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_WIN32_WIN32_CONTROL_H_
|
||||
#define XENIA_UI_WIN32_WIN32_CONTROL_H_
|
||||
#ifndef XENIA_UI_WINDOW_WIN_H_
|
||||
#define XENIA_UI_WINDOW_WIN_H_
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/control.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace win32 {
|
||||
|
||||
class Win32Control : public Control {
|
||||
class Win32Window : public Window {
|
||||
using super = Window;
|
||||
|
||||
public:
|
||||
~Win32Control() override;
|
||||
Win32Window(Loop* loop, const std::wstring& title);
|
||||
~Win32Window() override;
|
||||
|
||||
NativeWindowHandle native_handle() const override { return hwnd_; }
|
||||
HWND hwnd() const { return hwnd_; }
|
||||
HWND parent_hwnd() const {
|
||||
return parent_ ? static_cast<Win32Control*>(parent_)->hwnd() : nullptr;
|
||||
}
|
||||
|
||||
bool set_title(const std::wstring& title) override;
|
||||
|
||||
bool is_fullscreen() const override;
|
||||
void ToggleFullscreen(bool fullscreen) override;
|
||||
|
||||
void set_cursor_visible(bool value) override;
|
||||
void set_focus(bool value) override;
|
||||
|
||||
void Resize(int32_t width, int32_t height) override;
|
||||
void Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) override;
|
||||
void ResizeToFill(int32_t pad_left, int32_t pad_top, int32_t pad_right,
|
||||
int32_t pad_bottom) override;
|
||||
void Invalidate() override;
|
||||
|
||||
void set_cursor_visible(bool value) override;
|
||||
void set_enabled(bool value) override;
|
||||
void set_visible(bool value) override;
|
||||
void set_focus(bool value) override;
|
||||
bool Initialize() override;
|
||||
void Invalidate() override;
|
||||
void Close() override;
|
||||
|
||||
protected:
|
||||
explicit Win32Control(uint32_t flags);
|
||||
|
||||
void OnCreate() override;
|
||||
bool OnCreate() override;
|
||||
void OnMainMenuChange() override;
|
||||
void OnDestroy() override;
|
||||
|
||||
void OnChildAdded(Control* child_control) override;
|
||||
void OnChildRemoved(Control* child_control) override;
|
||||
void OnClose() override;
|
||||
|
||||
void OnResize(UIEvent& e) override;
|
||||
|
||||
@@ -54,16 +58,37 @@ class Win32Control : public Control {
|
||||
virtual LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
HWND hwnd_ = nullptr;
|
||||
bool invalidated_ = true;
|
||||
|
||||
private:
|
||||
void EnableMMCSS();
|
||||
bool HandleMouse(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
bool HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
HWND hwnd_ = nullptr;
|
||||
bool invalidated_ = true;
|
||||
bool closing_ = false;
|
||||
|
||||
WINDOWPLACEMENT windowed_pos_;
|
||||
};
|
||||
|
||||
class Win32MenuItem : public MenuItem {
|
||||
public:
|
||||
Win32MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey,
|
||||
std::function<void()> callback);
|
||||
~Win32MenuItem() override;
|
||||
|
||||
HMENU handle() { return handle_; }
|
||||
|
||||
using MenuItem::OnSelected;
|
||||
|
||||
protected:
|
||||
void OnChildAdded(MenuItem* child_item) override;
|
||||
void OnChildRemoved(MenuItem* child_item) override;
|
||||
|
||||
private:
|
||||
HMENU handle_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace win32
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_WIN32_WIN32_CONTROL_H_
|
||||
#endif // XENIA_UI_WINDOW_WIN_H_
|
||||
Reference in New Issue
Block a user