333 lines
8.0 KiB
C++
333 lines
8.0 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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/window.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "third_party/imgui/imgui.h"
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/clock.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/ui/imgui_drawer.h"
|
|
|
|
namespace xe {
|
|
namespace ui {
|
|
|
|
constexpr bool kContinuousRepaint = false;
|
|
constexpr bool kShowPresentFps = kContinuousRepaint;
|
|
|
|
// Enables long press behaviors (context menu, etc).
|
|
constexpr bool kTouch = false;
|
|
|
|
constexpr uint64_t kDoubleClickDelayMillis = 600;
|
|
constexpr double kDoubleClickDistance = 5;
|
|
|
|
constexpr int32_t kMouseWheelDetent = 120;
|
|
|
|
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());
|
|
}
|
|
|
|
void Window::AttachListener(WindowListener* listener) {
|
|
if (in_listener_loop_) {
|
|
pending_listener_attaches_.push_back(listener);
|
|
return;
|
|
}
|
|
auto it = std::find(listeners_.begin(), listeners_.end(), listener);
|
|
if (it != listeners_.end()) {
|
|
return;
|
|
}
|
|
listeners_.push_back(listener);
|
|
Invalidate();
|
|
}
|
|
|
|
void Window::DetachListener(WindowListener* listener) {
|
|
if (in_listener_loop_) {
|
|
pending_listener_detaches_.push_back(listener);
|
|
return;
|
|
}
|
|
auto it = std::find(listeners_.begin(), listeners_.end(), listener);
|
|
if (it == listeners_.end()) {
|
|
return;
|
|
}
|
|
listeners_.erase(it);
|
|
}
|
|
|
|
void Window::ForEachListener(std::function<void(WindowListener*)> fn) {
|
|
assert_false(in_listener_loop_);
|
|
in_listener_loop_ = true;
|
|
for (auto listener : listeners_) {
|
|
fn(listener);
|
|
}
|
|
in_listener_loop_ = false;
|
|
while (!pending_listener_attaches_.empty()) {
|
|
auto listener = pending_listener_attaches_.back();
|
|
pending_listener_attaches_.pop_back();
|
|
AttachListener(listener);
|
|
}
|
|
while (!pending_listener_detaches_.empty()) {
|
|
auto listener = pending_listener_detaches_.back();
|
|
pending_listener_detaches_.pop_back();
|
|
DetachListener(listener);
|
|
}
|
|
}
|
|
|
|
void Window::TryForEachListener(std::function<bool(WindowListener*)> fn) {
|
|
assert_false(in_listener_loop_);
|
|
in_listener_loop_ = true;
|
|
for (auto listener : listeners_) {
|
|
if (fn(listener)) {
|
|
break;
|
|
}
|
|
}
|
|
in_listener_loop_ = false;
|
|
while (!pending_listener_attaches_.empty()) {
|
|
auto listener = pending_listener_attaches_.back();
|
|
pending_listener_attaches_.pop_back();
|
|
AttachListener(listener);
|
|
}
|
|
while (!pending_listener_detaches_.empty()) {
|
|
auto listener = pending_listener_detaches_.back();
|
|
pending_listener_detaches_.pop_back();
|
|
DetachListener(listener);
|
|
}
|
|
}
|
|
|
|
void Window::set_imgui_input_enabled(bool value) {
|
|
if (value == is_imgui_input_enabled_) {
|
|
return;
|
|
}
|
|
is_imgui_input_enabled_ = value;
|
|
if (!value) {
|
|
DetachListener(imgui_drawer_.get());
|
|
} else {
|
|
AttachListener(imgui_drawer_.get());
|
|
}
|
|
}
|
|
|
|
bool Window::OnCreate() { return true; }
|
|
|
|
bool Window::MakeReady() {
|
|
imgui_drawer_ = std::make_unique<xe::ui::ImGuiDrawer>(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Window::OnMainMenuChange() {
|
|
ForEachListener([](auto listener) { listener->OnMainMenuChange(); });
|
|
}
|
|
|
|
void Window::OnClose() {
|
|
UIEvent e(this);
|
|
ForEachListener([&e](auto listener) { listener->OnClosing(&e); });
|
|
on_closing(&e);
|
|
ForEachListener([&e](auto listener) { listener->OnClosed(&e); });
|
|
on_closed(&e);
|
|
}
|
|
|
|
void Window::OnDestroy() {
|
|
if (!context_) {
|
|
return;
|
|
}
|
|
|
|
imgui_drawer_.reset();
|
|
|
|
// Context must go last.
|
|
context_.reset();
|
|
}
|
|
|
|
void Window::Layout() {
|
|
UIEvent e(this);
|
|
OnLayout(&e);
|
|
}
|
|
|
|
void Window::Invalidate() {}
|
|
|
|
void Window::OnResize(UIEvent* e) {
|
|
ForEachListener([e](auto listener) { listener->OnResize(e); });
|
|
}
|
|
|
|
void Window::OnLayout(UIEvent* e) {
|
|
ForEachListener([e](auto listener) { listener->OnLayout(e); });
|
|
}
|
|
|
|
void Window::OnPaint(UIEvent* e) {
|
|
if (!context_) {
|
|
return;
|
|
}
|
|
|
|
++frame_count_;
|
|
++fps_frame_count_;
|
|
uint64_t now_ns = xe::Clock::QueryHostSystemTime();
|
|
if (now_ns > fps_update_time_ns_ + 1000 * 10000) {
|
|
fps_ = static_cast<uint32_t>(
|
|
fps_frame_count_ /
|
|
(static_cast<double>(now_ns - fps_update_time_ns_) / 10000000.0));
|
|
fps_update_time_ns_ = now_ns;
|
|
fps_frame_count_ = 0;
|
|
}
|
|
|
|
GraphicsContextLock context_lock(context_.get());
|
|
|
|
// Prepare ImGui for use this frame.
|
|
auto& io = imgui_drawer_->GetIO();
|
|
if (!last_paint_time_ns_) {
|
|
io.DeltaTime = 0.0f;
|
|
last_paint_time_ns_ = now_ns;
|
|
} else {
|
|
io.DeltaTime = (now_ns - last_paint_time_ns_) / 10000000.0f;
|
|
last_paint_time_ns_ = now_ns;
|
|
}
|
|
io.DisplaySize =
|
|
ImVec2(static_cast<float>(width()), static_cast<float>(height()));
|
|
ImGui::NewFrame();
|
|
|
|
context_->BeginSwap();
|
|
ForEachListener([e](auto listener) { listener->OnPainting(e); });
|
|
on_painting(e);
|
|
ForEachListener([e](auto listener) { listener->OnPaint(e); });
|
|
on_paint(e);
|
|
|
|
// Flush ImGui buffers before we swap.
|
|
ImGui::Render();
|
|
|
|
ForEachListener([e](auto listener) { listener->OnPainted(e); });
|
|
on_painted(e);
|
|
|
|
context_->EndSwap();
|
|
|
|
// If animations are running, reinvalidate immediately.
|
|
if (kContinuousRepaint) {
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
void Window::OnVisible(UIEvent* e) {
|
|
ForEachListener([e](auto listener) { listener->OnVisible(e); });
|
|
}
|
|
|
|
void Window::OnHidden(UIEvent* e) {
|
|
ForEachListener([e](auto listener) { listener->OnHidden(e); });
|
|
}
|
|
|
|
void Window::OnGotFocus(UIEvent* e) {
|
|
ForEachListener([e](auto listener) { listener->OnGotFocus(e); });
|
|
}
|
|
|
|
void Window::OnLostFocus(UIEvent* e) {
|
|
modifier_shift_pressed_ = false;
|
|
modifier_cntrl_pressed_ = false;
|
|
modifier_alt_pressed_ = false;
|
|
modifier_super_pressed_ = false;
|
|
ForEachListener([e](auto listener) { listener->OnLostFocus(e); });
|
|
}
|
|
|
|
void Window::OnKeyPress(KeyEvent* e, bool is_down, bool is_char) {
|
|
if (!is_char) {
|
|
switch (e->key_code()) {
|
|
case 16:
|
|
modifier_shift_pressed_ = is_down;
|
|
break;
|
|
case 17:
|
|
modifier_cntrl_pressed_ = is_down;
|
|
break;
|
|
// case xx:
|
|
// // alt ??
|
|
// modifier_alt_pressed_ = is_down;
|
|
// break;
|
|
case 91:
|
|
modifier_super_pressed_ = is_down;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Window::OnKeyDown(KeyEvent* e) {
|
|
on_key_down(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnKeyDown(e);
|
|
return e->is_handled();
|
|
});
|
|
OnKeyPress(e, true, false);
|
|
}
|
|
|
|
void Window::OnKeyUp(KeyEvent* e) {
|
|
on_key_up(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnKeyUp(e);
|
|
return e->is_handled();
|
|
});
|
|
OnKeyPress(e, false, false);
|
|
}
|
|
|
|
void Window::OnKeyChar(KeyEvent* e) {
|
|
OnKeyPress(e, true, true);
|
|
on_key_char(e);
|
|
ForEachListener([e](auto listener) { listener->OnKeyChar(e); });
|
|
OnKeyPress(e, false, true);
|
|
}
|
|
|
|
void Window::OnMouseDown(MouseEvent* e) {
|
|
on_mouse_down(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnMouseDown(e);
|
|
return e->is_handled();
|
|
});
|
|
}
|
|
|
|
void Window::OnMouseMove(MouseEvent* e) {
|
|
on_mouse_move(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnMouseMove(e);
|
|
return e->is_handled();
|
|
});
|
|
}
|
|
|
|
void Window::OnMouseUp(MouseEvent* e) {
|
|
on_mouse_up(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnMouseUp(e);
|
|
return e->is_handled();
|
|
});
|
|
}
|
|
|
|
void Window::OnMouseWheel(MouseEvent* e) {
|
|
on_mouse_wheel(e);
|
|
if (e->is_handled()) {
|
|
return;
|
|
}
|
|
TryForEachListener([e](auto listener) {
|
|
listener->OnMouseWheel(e);
|
|
return e->is_handled();
|
|
});
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace xe
|