[UI] Android ImGui touch and mouse input
This commit is contained in:
parent
3a065c35f0
commit
7b8281aee0
|
@ -1,8 +1,10 @@
|
||||||
package jp.xenia.emulator;
|
package jp.xenia.emulator;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -16,10 +18,7 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
System.loadLibrary("xenia-app");
|
System.loadLibrary("xenia-app");
|
||||||
}
|
}
|
||||||
|
|
||||||
private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener =
|
private final WindowSurfaceListener mWindowSurfaceListener = new WindowSurfaceListener();
|
||||||
new WindowSurfaceOnLayoutChangeListener();
|
|
||||||
private final WindowSurfaceHolderCallback mWindowSurfaceHolderCallback =
|
|
||||||
new WindowSurfaceHolderCallback();
|
|
||||||
|
|
||||||
// May be 0 while destroying (mainly while the superclass is).
|
// May be 0 while destroying (mainly while the superclass is).
|
||||||
private long mAppContext = 0;
|
private long mAppContext = 0;
|
||||||
|
@ -35,6 +34,8 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
private native void onWindowSurfaceLayoutChange(
|
private native void onWindowSurfaceLayoutChange(
|
||||||
long appContext, int left, int top, int right, int bottom);
|
long appContext, int left, int top, int right, int bottom);
|
||||||
|
|
||||||
|
private native boolean onWindowSurfaceMotionEvent(long appContext, MotionEvent event);
|
||||||
|
|
||||||
private native void onWindowSurfaceChanged(long appContext, Surface windowSurface);
|
private native void onWindowSurfaceChanged(long appContext, Surface windowSurface);
|
||||||
|
|
||||||
private native void paintWindow(long appContext, boolean forcePaint);
|
private native void paintWindow(long appContext, boolean forcePaint);
|
||||||
|
@ -48,8 +49,10 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
|
|
||||||
// Detach from the old surface.
|
// Detach from the old surface.
|
||||||
if (mWindowSurfaceView != null) {
|
if (mWindowSurfaceView != null) {
|
||||||
mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceHolderCallback);
|
mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceListener);
|
||||||
mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
|
mWindowSurfaceView.setOnTouchListener(null);
|
||||||
|
mWindowSurfaceView.setOnGenericMotionListener(null);
|
||||||
|
mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceListener);
|
||||||
mWindowSurfaceView = null;
|
mWindowSurfaceView = null;
|
||||||
if (mAppContext != 0) {
|
if (mAppContext != 0) {
|
||||||
onWindowSurfaceChanged(mAppContext, null);
|
onWindowSurfaceChanged(mAppContext, null);
|
||||||
|
@ -61,12 +64,12 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
mWindowSurfaceView = windowSurfaceView;
|
mWindowSurfaceView = windowSurfaceView;
|
||||||
// The native window code assumes that, when the surface exists, it covers the entire
|
|
||||||
// window.
|
|
||||||
// FIXME(Triang3l): This doesn't work if the layout has already been performed.
|
// FIXME(Triang3l): This doesn't work if the layout has already been performed.
|
||||||
mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
|
mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceListener);
|
||||||
|
mWindowSurfaceView.setOnGenericMotionListener(mWindowSurfaceListener);
|
||||||
|
mWindowSurfaceView.setOnTouchListener(mWindowSurfaceListener);
|
||||||
final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder();
|
final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder();
|
||||||
windowSurfaceHolder.addCallback(mWindowSurfaceHolderCallback);
|
windowSurfaceHolder.addCallback(mWindowSurfaceListener);
|
||||||
// If setting after the creation of the surface.
|
// If setting after the creation of the surface.
|
||||||
if (mAppContext != 0) {
|
if (mAppContext != 0) {
|
||||||
final Surface windowSurface = windowSurfaceHolder.getSurface();
|
final Surface windowSurface = windowSurfaceHolder.getSurface();
|
||||||
|
@ -115,7 +118,11 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WindowSurfaceOnLayoutChangeListener implements View.OnLayoutChangeListener {
|
private class WindowSurfaceListener implements
|
||||||
|
View.OnGenericMotionListener,
|
||||||
|
View.OnLayoutChangeListener,
|
||||||
|
View.OnTouchListener,
|
||||||
|
SurfaceHolder.Callback2 {
|
||||||
@Override
|
@Override
|
||||||
public void onLayoutChange(
|
public void onLayoutChange(
|
||||||
final View v, final int left, final int top, final int right, final int bottom,
|
final View v, final int left, final int top, final int right, final int bottom,
|
||||||
|
@ -124,9 +131,24 @@ public abstract class WindowedAppActivity extends Activity {
|
||||||
onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom);
|
onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
if (mAppContext == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return onWindowSurfaceMotionEvent(mAppContext, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
if (mAppContext == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return onWindowSurfaceMotionEvent(mAppContext, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WindowSurfaceHolderCallback implements SurfaceHolder.Callback2 {
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceCreated(final SurfaceHolder holder) {
|
public void surfaceCreated(final SurfaceHolder holder) {
|
||||||
if (mAppContext == 0) {
|
if (mAppContext == 0) {
|
||||||
|
|
|
@ -43,7 +43,8 @@ ImGuiDrawer::~ImGuiDrawer() {
|
||||||
window_->RemoveInputListener(this);
|
window_->RemoveInputListener(this);
|
||||||
if (internal_state_) {
|
if (internal_state_) {
|
||||||
ImGui::SetCurrentContext(internal_state_);
|
ImGui::SetCurrentContext(internal_state_);
|
||||||
if (ImGui::IsAnyMouseDown()) {
|
if (touch_pointer_id_ == TouchEvent::kPointerIDNone &&
|
||||||
|
ImGui::IsAnyMouseDown()) {
|
||||||
window_->ReleaseMouse();
|
window_->ReleaseMouse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,6 +214,9 @@ void ImGuiDrawer::Initialize() {
|
||||||
|
|
||||||
frame_time_tick_frequency_ = double(Clock::QueryHostTickFrequency());
|
frame_time_tick_frequency_ = double(Clock::QueryHostTickFrequency());
|
||||||
last_frame_time_ticks_ = Clock::QueryHostTickCount();
|
last_frame_time_ticks_ = Clock::QueryHostTickCount();
|
||||||
|
|
||||||
|
touch_pointer_id_ = TouchEvent::kPointerIDNone;
|
||||||
|
reset_mouse_position_after_next_frame_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::SetupFontTexture() {
|
void ImGuiDrawer::SetupFontTexture() {
|
||||||
|
@ -310,6 +314,11 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
|
||||||
RenderDrawLists(draw_data, ui_draw_context);
|
RenderDrawLists(draw_data, ui_draw_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reset_mouse_position_after_next_frame_) {
|
||||||
|
reset_mouse_position_after_next_frame_ = false;
|
||||||
|
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
if (dialogs_.empty()) {
|
if (dialogs_.empty()) {
|
||||||
// All dialogs have removed themselves during the draw, detach.
|
// All dialogs have removed themselves during the draw, detach.
|
||||||
presenter_->RemoveUIDrawerFromUIThread(this);
|
presenter_->RemoveUIDrawerFromUIThread(this);
|
||||||
|
@ -382,7 +391,7 @@ void ImGuiDrawer::OnKeyChar(KeyEvent& e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::OnMouseDown(MouseEvent& e) {
|
void ImGuiDrawer::OnMouseDown(MouseEvent& e) {
|
||||||
UpdateMousePosition(e);
|
SwitchToPhysicalMouseAndUpdateMousePosition(e);
|
||||||
auto& io = GetIO();
|
auto& io = GetIO();
|
||||||
int button = -1;
|
int button = -1;
|
||||||
switch (e.button()) {
|
switch (e.button()) {
|
||||||
|
@ -409,10 +418,12 @@ void ImGuiDrawer::OnMouseDown(MouseEvent& e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::OnMouseMove(MouseEvent& e) { UpdateMousePosition(e); }
|
void ImGuiDrawer::OnMouseMove(MouseEvent& e) {
|
||||||
|
SwitchToPhysicalMouseAndUpdateMousePosition(e);
|
||||||
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::OnMouseUp(MouseEvent& e) {
|
void ImGuiDrawer::OnMouseUp(MouseEvent& e) {
|
||||||
UpdateMousePosition(e);
|
SwitchToPhysicalMouseAndUpdateMousePosition(e);
|
||||||
auto& io = GetIO();
|
auto& io = GetIO();
|
||||||
int button = -1;
|
int button = -1;
|
||||||
switch (e.button()) {
|
switch (e.button()) {
|
||||||
|
@ -440,14 +451,48 @@ void ImGuiDrawer::OnMouseUp(MouseEvent& e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::OnMouseWheel(MouseEvent& e) {
|
void ImGuiDrawer::OnMouseWheel(MouseEvent& e) {
|
||||||
UpdateMousePosition(e);
|
SwitchToPhysicalMouseAndUpdateMousePosition(e);
|
||||||
auto& io = GetIO();
|
auto& io = GetIO();
|
||||||
io.MouseWheel += float(e.scroll_y()) / float(MouseEvent::kScrollPerDetent);
|
io.MouseWheel += float(e.scroll_y()) / float(MouseEvent::kScrollPerDetent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImGuiDrawer::OnTouchEvent(TouchEvent& e) {
|
||||||
|
auto& io = GetIO();
|
||||||
|
TouchEvent::Action action = e.action();
|
||||||
|
uint32_t pointer_id = e.pointer_id();
|
||||||
|
if (action == TouchEvent::Action::kDown) {
|
||||||
|
// The latest pointer needs to be controlling the ImGui mouse.
|
||||||
|
if (touch_pointer_id_ == TouchEvent::kPointerIDNone) {
|
||||||
|
// Switching from the mouse to touch input.
|
||||||
|
if (ImGui::IsAnyMouseDown()) {
|
||||||
|
std::memset(io.MouseDown, 0, sizeof(io.MouseDown));
|
||||||
|
window_->ReleaseMouse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
touch_pointer_id_ = pointer_id;
|
||||||
|
} else {
|
||||||
|
if (pointer_id != touch_pointer_id_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateMousePosition(e.x(), e.y());
|
||||||
|
if (action == TouchEvent::Action::kUp ||
|
||||||
|
action == TouchEvent::Action::kCancel) {
|
||||||
|
io.MouseDown[0] = false;
|
||||||
|
touch_pointer_id_ = TouchEvent::kPointerIDNone;
|
||||||
|
// Make sure that after a touch, the ImGui mouse isn't hovering over
|
||||||
|
// anything.
|
||||||
|
reset_mouse_position_after_next_frame_ = true;
|
||||||
|
} else {
|
||||||
|
io.MouseDown[0] = true;
|
||||||
|
reset_mouse_position_after_next_frame_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::ClearInput() {
|
void ImGuiDrawer::ClearInput() {
|
||||||
auto& io = GetIO();
|
auto& io = GetIO();
|
||||||
if (ImGui::IsAnyMouseDown()) {
|
if (touch_pointer_id_ == TouchEvent::kPointerIDNone &&
|
||||||
|
ImGui::IsAnyMouseDown()) {
|
||||||
window_->ReleaseMouse();
|
window_->ReleaseMouse();
|
||||||
}
|
}
|
||||||
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||||
|
@ -460,6 +505,8 @@ void ImGuiDrawer::ClearInput() {
|
||||||
io.KeySuper = false;
|
io.KeySuper = false;
|
||||||
std::memset(io.KeysDown, 0, sizeof(io.KeysDown));
|
std::memset(io.KeysDown, 0, sizeof(io.KeysDown));
|
||||||
io.ClearInputCharacters();
|
io.ClearInputCharacters();
|
||||||
|
touch_pointer_id_ = TouchEvent::kPointerIDNone;
|
||||||
|
reset_mouse_position_after_next_frame_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) {
|
void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) {
|
||||||
|
@ -487,12 +534,27 @@ void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiDrawer::UpdateMousePosition(const MouseEvent& e) {
|
void ImGuiDrawer::UpdateMousePosition(float x, float y) {
|
||||||
auto& io = GetIO();
|
auto& io = GetIO();
|
||||||
float physical_to_logical =
|
float physical_to_logical =
|
||||||
float(window_->GetMediumDpi()) / float(window_->GetDpi());
|
float(window_->GetMediumDpi()) / float(window_->GetDpi());
|
||||||
io.MousePos.x = e.x() * physical_to_logical;
|
io.MousePos.x = x * physical_to_logical;
|
||||||
io.MousePos.y = e.y() * physical_to_logical;
|
io.MousePos.y = y * physical_to_logical;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiDrawer::SwitchToPhysicalMouseAndUpdateMousePosition(
|
||||||
|
const MouseEvent& e) {
|
||||||
|
if (touch_pointer_id_ != TouchEvent::kPointerIDNone) {
|
||||||
|
touch_pointer_id_ = TouchEvent::kPointerIDNone;
|
||||||
|
auto& io = GetIO();
|
||||||
|
std::memset(io.MouseDown, 0, sizeof(io.MouseDown));
|
||||||
|
// Nothing needs to be done regarding CaptureMouse and ReleaseMouse - all
|
||||||
|
// buttons as well as mouse capture have been released when switching to
|
||||||
|
// touch input, the mouse is never captured during touch input, and now
|
||||||
|
// resetting to no buttons down (therefore not capturing).
|
||||||
|
}
|
||||||
|
reset_mouse_position_after_next_frame_ = false;
|
||||||
|
UpdateMousePosition(float(e.x()), float(e.y()));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -59,6 +59,7 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||||
void OnMouseMove(MouseEvent& e) override;
|
void OnMouseMove(MouseEvent& e) override;
|
||||||
void OnMouseUp(MouseEvent& e) override;
|
void OnMouseUp(MouseEvent& e) override;
|
||||||
void OnMouseWheel(MouseEvent& e) override;
|
void OnMouseWheel(MouseEvent& e) override;
|
||||||
|
void OnTouchEvent(TouchEvent& e) override;
|
||||||
// For now, no need for OnDpiChanged because redrawing is done continuously.
|
// For now, no need for OnDpiChanged because redrawing is done continuously.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -70,7 +71,8 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||||
|
|
||||||
void ClearInput();
|
void ClearInput();
|
||||||
void OnKey(KeyEvent& e, bool is_down);
|
void OnKey(KeyEvent& e, bool is_down);
|
||||||
void UpdateMousePosition(const MouseEvent& e);
|
void UpdateMousePosition(float x, float y);
|
||||||
|
void SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e);
|
||||||
|
|
||||||
Window* window_;
|
Window* window_;
|
||||||
size_t z_order_;
|
size_t z_order_;
|
||||||
|
@ -92,6 +94,16 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||||
// detaching the presenter.
|
// detaching the presenter.
|
||||||
std::unique_ptr<ImmediateTexture> font_texture_;
|
std::unique_ptr<ImmediateTexture> font_texture_;
|
||||||
|
|
||||||
|
// If there's an active pointer, the ImGui mouse is controlled by this touch.
|
||||||
|
// If it's TouchEvent::kPointerIDNone, the ImGui mouse is controlled by the
|
||||||
|
// mouse.
|
||||||
|
uint32_t touch_pointer_id_ = TouchEvent::kPointerIDNone;
|
||||||
|
// Whether after the next frame (since the mouse up event needs to be handled
|
||||||
|
// with the correct mouse position still), the ImGui mouse position should be
|
||||||
|
// reset (for instance, after releasing a touch), so it's not hovering over
|
||||||
|
// anything.
|
||||||
|
bool reset_mouse_position_after_next_frame_ = false;
|
||||||
|
|
||||||
double frame_time_tick_frequency_;
|
double frame_time_tick_frequency_;
|
||||||
uint64_t last_frame_time_ticks_;
|
uint64_t last_frame_time_ticks_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#ifndef XENIA_UI_UI_EVENT_H_
|
#ifndef XENIA_UI_UI_EVENT_H_
|
||||||
#define XENIA_UI_UI_EVENT_H_
|
#define XENIA_UI_UI_EVENT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
|
@ -156,6 +157,46 @@ class MouseEvent : public UIEvent {
|
||||||
int32_t scroll_y_ = 0;
|
int32_t scroll_y_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TouchEvent : public UIEvent {
|
||||||
|
public:
|
||||||
|
enum class Action {
|
||||||
|
kDown,
|
||||||
|
kUp,
|
||||||
|
// Should be treated as an up event, but without performing the usual action
|
||||||
|
// for releasing.
|
||||||
|
kCancel,
|
||||||
|
kMove,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Can be used by event listeners as the value for when there's no current
|
||||||
|
// pointer, for example.
|
||||||
|
static constexpr uint32_t kPointerIDNone = UINT32_MAX;
|
||||||
|
|
||||||
|
explicit TouchEvent(Window* target, uint32_t pointer_id, Action action,
|
||||||
|
float x, float y)
|
||||||
|
: UIEvent(target),
|
||||||
|
pointer_id_(pointer_id),
|
||||||
|
action_(action),
|
||||||
|
x_(x),
|
||||||
|
y_(y) {}
|
||||||
|
|
||||||
|
bool is_handled() const { return handled_; }
|
||||||
|
void set_handled(bool value) { handled_ = value; }
|
||||||
|
|
||||||
|
uint32_t pointer_id() { return pointer_id_; }
|
||||||
|
Action action() const { return action_; }
|
||||||
|
// Can be outside the boundaries of the surface.
|
||||||
|
float x() const { return x_; }
|
||||||
|
float y() const { return y_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool handled_ = false;
|
||||||
|
uint32_t pointer_id_;
|
||||||
|
Action action_;
|
||||||
|
float x_;
|
||||||
|
float y_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
|
|
|
@ -645,6 +645,19 @@ void Window::OnMouseWheel(MouseEvent& e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::OnTouchEvent(TouchEvent& e,
|
||||||
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
PropagateEventThroughInputListeners(
|
||||||
|
[&e](auto listener) {
|
||||||
|
listener->OnTouchEvent(e);
|
||||||
|
return e.is_handled();
|
||||||
|
},
|
||||||
|
destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Window::SendEventToListeners(
|
void Window::SendEventToListeners(
|
||||||
std::function<void(WindowListener*)> fn,
|
std::function<void(WindowListener*)> fn,
|
||||||
WindowDestructionReceiver& destruction_receiver) {
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
|
|
@ -597,6 +597,9 @@ class Window {
|
||||||
void OnMouseWheel(MouseEvent& e,
|
void OnMouseWheel(MouseEvent& e,
|
||||||
WindowDestructionReceiver& destruction_receiver);
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
|
||||||
|
void OnTouchEvent(TouchEvent& e,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ListenerIterationContext {
|
struct ListenerIterationContext {
|
||||||
explicit ListenerIterationContext(ListenerIterationContext* outer_context,
|
explicit ListenerIterationContext(ListenerIterationContext* outer_context,
|
||||||
|
|
|
@ -9,10 +9,16 @@
|
||||||
|
|
||||||
#include "xenia/ui/window_android.h"
|
#include "xenia/ui/window_android.h"
|
||||||
|
|
||||||
|
#include <android/input.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/ui/surface_android.h"
|
#include "xenia/ui/surface_android.h"
|
||||||
#include "xenia/ui/windowed_app_context_android.h"
|
#include "xenia/ui/windowed_app_context_android.h"
|
||||||
|
|
||||||
|
@ -55,6 +61,181 @@ void AndroidWindow::OnActivitySurfaceLayoutChange() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AndroidWindow::OnActivitySurfaceMotionEvent(jobject event) {
|
||||||
|
auto& android_app_context =
|
||||||
|
static_cast<const AndroidWindowedAppContext&>(app_context());
|
||||||
|
JNIEnv* jni_env = android_app_context.ui_thread_jni_env();
|
||||||
|
const AndroidWindowedAppContext::JniIDs& jni_ids =
|
||||||
|
android_app_context.jni_ids();
|
||||||
|
|
||||||
|
int32_t source =
|
||||||
|
jni_env->CallIntMethod(event, jni_ids.motion_event_get_source);
|
||||||
|
|
||||||
|
switch (source) {
|
||||||
|
case AINPUT_SOURCE_TOUCHSCREEN: {
|
||||||
|
// Returning true for all touch events regardless of whether they have
|
||||||
|
// been handled to keep receiving touch events for the pointers in the
|
||||||
|
// event (if returning false for a down event, no more events will be sent
|
||||||
|
// for the pointers in it), and also because there are multiple pointers
|
||||||
|
// in a single event, and different handler invocations may result in
|
||||||
|
// different is_handled.
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
int32_t action_and_pointer_index =
|
||||||
|
jni_env->CallIntMethod(event, jni_ids.motion_event_get_action);
|
||||||
|
int32_t action = action_and_pointer_index & AMOTION_EVENT_ACTION_MASK;
|
||||||
|
// For pointer ACTION_POINTER_DOWN and ACTION_POINTER_UP.
|
||||||
|
int32_t pointer_index = (action_and_pointer_index &
|
||||||
|
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
|
||||||
|
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||||
|
if (action == AMOTION_EVENT_ACTION_POINTER_DOWN ||
|
||||||
|
action == AMOTION_EVENT_ACTION_POINTER_UP) {
|
||||||
|
int32_t touch_pointer_index =
|
||||||
|
(action_and_pointer_index &
|
||||||
|
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
|
||||||
|
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||||
|
TouchEvent e(
|
||||||
|
this,
|
||||||
|
jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id,
|
||||||
|
touch_pointer_index),
|
||||||
|
(action == AMOTION_EVENT_ACTION_POINTER_DOWN)
|
||||||
|
? TouchEvent::Action::kDown
|
||||||
|
: TouchEvent::Action::kUp,
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x,
|
||||||
|
pointer_index),
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y,
|
||||||
|
pointer_index));
|
||||||
|
OnTouchEvent(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TouchEvent::Action touch_event_action;
|
||||||
|
switch (action) {
|
||||||
|
case AMOTION_EVENT_ACTION_DOWN:
|
||||||
|
touch_event_action = TouchEvent::Action::kDown;
|
||||||
|
break;
|
||||||
|
case AMOTION_EVENT_ACTION_UP:
|
||||||
|
touch_event_action = TouchEvent::Action::kUp;
|
||||||
|
break;
|
||||||
|
case AMOTION_EVENT_ACTION_MOVE:
|
||||||
|
touch_event_action = TouchEvent::Action::kMove;
|
||||||
|
break;
|
||||||
|
case AMOTION_EVENT_ACTION_CANCEL:
|
||||||
|
touch_event_action = TouchEvent::Action::kCancel;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int32_t touch_pointer_count = jni_env->CallIntMethod(
|
||||||
|
event, jni_ids.motion_event_get_pointer_count);
|
||||||
|
for (int32_t i = 0; i < touch_pointer_count; ++i) {
|
||||||
|
TouchEvent e(
|
||||||
|
this,
|
||||||
|
jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id,
|
||||||
|
i),
|
||||||
|
touch_event_action,
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x, i),
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y, i));
|
||||||
|
OnTouchEvent(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case AINPUT_SOURCE_MOUSE: {
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
// X and Y can be outside the View (have negative coordinates, or beyond
|
||||||
|
// the size of the element), and not only for ACTION_HOVER_EXIT (it's
|
||||||
|
// predeced by ACTION_HOVER_MOVE, at least on Android API level 30, also
|
||||||
|
// with out-of-bounds coordinates), when moving the mouse outside the
|
||||||
|
// View, or when starting moving the mouse when the pointer was previously
|
||||||
|
// outside the View in some cases.
|
||||||
|
int32_t mouse_x = int32_t(
|
||||||
|
std::min(float(GetActualPhysicalWidth()),
|
||||||
|
std::max(0.0f, jni_env->CallFloatMethod(
|
||||||
|
event, jni_ids.motion_event_get_x, 0))) +
|
||||||
|
0.5f);
|
||||||
|
int32_t mouse_y = int32_t(
|
||||||
|
std::min(float(GetActualPhysicalHeight()),
|
||||||
|
std::max(0.0f, jni_env->CallFloatMethod(
|
||||||
|
event, jni_ids.motion_event_get_y, 0))) +
|
||||||
|
0.5f);
|
||||||
|
static const MouseEvent::Button kMouseEventButtons[] = {
|
||||||
|
MouseEvent::Button::kLeft, MouseEvent::Button::kRight,
|
||||||
|
MouseEvent::Button::kMiddle, MouseEvent::Button::kX1,
|
||||||
|
MouseEvent::Button::kX2,
|
||||||
|
};
|
||||||
|
static constexpr uint32_t kUsedMouseButtonMask =
|
||||||
|
(UINT32_C(1) << xe::countof(kMouseEventButtons)) - 1;
|
||||||
|
uint32_t new_mouse_button_state = uint32_t(
|
||||||
|
jni_env->CallIntMethod(event, jni_ids.motion_event_get_button_state));
|
||||||
|
// OnMouseUp.
|
||||||
|
uint32_t mouse_buttons_remaining =
|
||||||
|
mouse_button_state_ & ~new_mouse_button_state & kUsedMouseButtonMask;
|
||||||
|
uint32_t mouse_button_index;
|
||||||
|
while (
|
||||||
|
xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) {
|
||||||
|
mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index);
|
||||||
|
MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x,
|
||||||
|
mouse_y);
|
||||||
|
OnMouseUp(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Generic OnMouseMove regardless of the action since any event can
|
||||||
|
// provide new coordinates.
|
||||||
|
{
|
||||||
|
MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y);
|
||||||
|
OnMouseMove(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// OnMouseWheel.
|
||||||
|
// The axis value may be outside -1...1 if multiple scrolls have occurred
|
||||||
|
// quickly.
|
||||||
|
int32_t scroll_x = int32_t(
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value,
|
||||||
|
AMOTION_EVENT_AXIS_HSCROLL, 0) *
|
||||||
|
float(MouseEvent::kScrollPerDetent));
|
||||||
|
int32_t scroll_y = int32_t(
|
||||||
|
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value,
|
||||||
|
AMOTION_EVENT_AXIS_VSCROLL, 0) *
|
||||||
|
float(MouseEvent::kScrollPerDetent));
|
||||||
|
if (scroll_x || scroll_y) {
|
||||||
|
MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y,
|
||||||
|
scroll_x, scroll_y);
|
||||||
|
OnMouseWheel(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// OnMouseDown.
|
||||||
|
mouse_buttons_remaining =
|
||||||
|
new_mouse_button_state & ~mouse_button_state_ & kUsedMouseButtonMask;
|
||||||
|
while (
|
||||||
|
xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) {
|
||||||
|
mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index);
|
||||||
|
MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x,
|
||||||
|
mouse_y);
|
||||||
|
OnMouseDown(e, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the button state for state differences.
|
||||||
|
mouse_button_state_ = new_mouse_button_state;
|
||||||
|
return true;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t AndroidWindow::GetLatestDpiImpl() const {
|
uint32_t AndroidWindow::GetLatestDpiImpl() const {
|
||||||
auto& android_app_context =
|
auto& android_app_context =
|
||||||
static_cast<const AndroidWindowedAppContext&>(app_context());
|
static_cast<const AndroidWindowedAppContext&>(app_context());
|
||||||
|
@ -62,6 +243,9 @@ uint32_t AndroidWindow::GetLatestDpiImpl() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidWindow::OpenImpl() {
|
bool AndroidWindow::OpenImpl() {
|
||||||
|
// Reset the input.
|
||||||
|
mouse_button_state_ = 0;
|
||||||
|
|
||||||
// The window is a proxy between the main activity and Xenia, so there can be
|
// The window is a proxy between the main activity and Xenia, so there can be
|
||||||
// only one open window for an activity.
|
// only one open window for an activity.
|
||||||
auto& android_app_context =
|
auto& android_app_context =
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#ifndef XENIA_UI_WINDOW_ANDROID_H_
|
#ifndef XENIA_UI_WINDOW_ANDROID_H_
|
||||||
#define XENIA_UI_WINDOW_ANDROID_H_
|
#define XENIA_UI_WINDOW_ANDROID_H_
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
#include "xenia/ui/menu_item.h"
|
#include "xenia/ui/menu_item.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@ class AndroidWindow : public Window {
|
||||||
uint32_t GetMediumDpi() const override { return 160; }
|
uint32_t GetMediumDpi() const override { return 160; }
|
||||||
|
|
||||||
void OnActivitySurfaceLayoutChange();
|
void OnActivitySurfaceLayoutChange();
|
||||||
|
bool OnActivitySurfaceMotionEvent(jobject event);
|
||||||
void OnActivitySurfaceChanged() { OnSurfaceChanged(true); }
|
void OnActivitySurfaceChanged() { OnSurfaceChanged(true); }
|
||||||
void PaintActivitySurface(bool force_paint) { OnPaint(force_paint); }
|
void PaintActivitySurface(bool force_paint) { OnPaint(force_paint); }
|
||||||
|
|
||||||
|
@ -42,6 +45,9 @@ class AndroidWindow : public Window {
|
||||||
std::unique_ptr<Surface> CreateSurfaceImpl(
|
std::unique_ptr<Surface> CreateSurfaceImpl(
|
||||||
Surface::TypeFlags allowed_types) override;
|
Surface::TypeFlags allowed_types) override;
|
||||||
void RequestPaintImpl() override;
|
void RequestPaintImpl() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t mouse_button_state_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dummy for the menu item - menus are controlled by the layout.
|
// Dummy for the menu item - menus are controlled by the layout.
|
||||||
|
|
|
@ -48,6 +48,8 @@ class WindowInputListener {
|
||||||
virtual void OnMouseMove(MouseEvent& e) {}
|
virtual void OnMouseMove(MouseEvent& e) {}
|
||||||
virtual void OnMouseUp(MouseEvent& e) {}
|
virtual void OnMouseUp(MouseEvent& e) {}
|
||||||
virtual void OnMouseWheel(MouseEvent& e) {}
|
virtual void OnMouseWheel(MouseEvent& e) {}
|
||||||
|
|
||||||
|
virtual void OnTouchEvent(TouchEvent& e) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
@ -130,6 +131,12 @@ void AndroidWindowedAppContext::JniActivityOnWindowSurfaceLayoutChange(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AndroidWindowedAppContext::JniActivityOnWindowSurfaceMotionEvent(
|
||||||
|
jobject event) {
|
||||||
|
return activity_window_ &&
|
||||||
|
activity_window_->OnActivitySurfaceMotionEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidWindowedAppContext::JniActivityOnWindowSurfaceChanged(
|
void AndroidWindowedAppContext::JniActivityOnWindowSurfaceChanged(
|
||||||
jobject window_surface_object) {
|
jobject window_surface_object) {
|
||||||
// Detach from the old surface.
|
// Detach from the old surface.
|
||||||
|
@ -250,6 +257,62 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env,
|
||||||
android_base_initialized_ = true;
|
android_base_initialized_ = true;
|
||||||
ui_thread_jni_env_->DeleteLocalRef(application_context_init_ref);
|
ui_thread_jni_env_->DeleteLocalRef(application_context_init_ref);
|
||||||
|
|
||||||
|
// Initialize common windowed app JNI IDs.
|
||||||
|
{
|
||||||
|
jclass motion_event_class_local_ref =
|
||||||
|
ui_thread_jni_env_->FindClass("android/view/MotionEvent");
|
||||||
|
if (!motion_event_class_local_ref) {
|
||||||
|
XELOGE(
|
||||||
|
"AndroidWindowedAppContext: Failed to find the motion event class");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
motion_event_class_ =
|
||||||
|
reinterpret_cast<jclass>(ui_thread_jni_env_->NewGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(motion_event_class_local_ref)));
|
||||||
|
ui_thread_jni_env_->DeleteLocalRef(
|
||||||
|
reinterpret_cast<jobject>(motion_event_class_local_ref));
|
||||||
|
}
|
||||||
|
if (!motion_event_class_) {
|
||||||
|
XELOGE(
|
||||||
|
"AndroidWindowedAppContext: Failed to create a global reference to the "
|
||||||
|
"motion event class");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool motion_event_ids_obtained = true;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_action = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getAction", "()I")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_axis_value = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getAxisValue", "(II)F")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_button_state = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getButtonState", "()I")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_pointer_id = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getPointerId", "(I)I")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_pointer_count =
|
||||||
|
ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getPointerCount", "()I")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_source = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getSource", "()I")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_x = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getX", "(I)F")) != nullptr;
|
||||||
|
motion_event_ids_obtained &=
|
||||||
|
(jni_ids_.motion_event_get_y = ui_thread_jni_env_->GetMethodID(
|
||||||
|
motion_event_class_, "getY", "(I)F")) != nullptr;
|
||||||
|
if (!motion_event_ids_obtained) {
|
||||||
|
XELOGE(
|
||||||
|
"AndroidWindowedAppContext: Failed to get the motion event class IDs");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize interfacing with the WindowedAppActivity.
|
// Initialize interfacing with the WindowedAppActivity.
|
||||||
activity_ = ui_thread_jni_env_->NewGlobalRef(activity);
|
activity_ = ui_thread_jni_env_->NewGlobalRef(activity);
|
||||||
if (!activity_) {
|
if (!activity_) {
|
||||||
|
@ -343,6 +406,12 @@ void AndroidWindowedAppContext::Shutdown() {
|
||||||
activity_ = nullptr;
|
activity_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::memset(&jni_ids_, 0, sizeof(jni_ids_));
|
||||||
|
if (motion_event_class_) {
|
||||||
|
ui_thread_jni_env_->DeleteGlobalRef(motion_event_class_);
|
||||||
|
motion_event_class_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (android_base_initialized_) {
|
if (android_base_initialized_) {
|
||||||
xe::ShutdownAndroidAppFromMainThread();
|
xe::ShutdownAndroidAppFromMainThread();
|
||||||
android_base_initialized_ = false;
|
android_base_initialized_ = false;
|
||||||
|
@ -495,6 +564,14 @@ Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceLayoutChange(
|
||||||
->JniActivityOnWindowSurfaceLayoutChange(left, top, right, bottom);
|
->JniActivityOnWindowSurfaceLayoutChange(left, top, right, bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceMotionEvent(
|
||||||
|
JNIEnv* jni_env, jobject activity, jlong app_context_ptr, jobject event) {
|
||||||
|
return jboolean(
|
||||||
|
reinterpret_cast<xe::ui::AndroidWindowedAppContext*>(app_context_ptr)
|
||||||
|
->JniActivityOnWindowSurfaceMotionEvent(event));
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceChanged(
|
Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceChanged(
|
||||||
JNIEnv* jni_env, jobject activity, jlong app_context_ptr,
|
JNIEnv* jni_env, jobject activity, jlong app_context_ptr,
|
||||||
|
|
|
@ -28,17 +28,35 @@ class WindowedApp;
|
||||||
|
|
||||||
class AndroidWindowedAppContext final : public WindowedAppContext {
|
class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
public:
|
public:
|
||||||
|
// Precached JNI references and IDs that may be used for windowed app purposes
|
||||||
|
// by external code.
|
||||||
|
struct JniIDs {
|
||||||
|
// android.view.MotionEvent.
|
||||||
|
jmethodID motion_event_get_action;
|
||||||
|
jmethodID motion_event_get_axis_value;
|
||||||
|
jmethodID motion_event_get_button_state;
|
||||||
|
jmethodID motion_event_get_pointer_count;
|
||||||
|
jmethodID motion_event_get_pointer_id;
|
||||||
|
jmethodID motion_event_get_source;
|
||||||
|
jmethodID motion_event_get_x;
|
||||||
|
jmethodID motion_event_get_y;
|
||||||
|
};
|
||||||
|
|
||||||
WindowedApp* app() const { return app_.get(); }
|
WindowedApp* app() const { return app_.get(); }
|
||||||
|
|
||||||
void NotifyUILoopOfPendingFunctions() override;
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
|
||||||
void PlatformQuitFromUIThread() override;
|
void PlatformQuitFromUIThread() override;
|
||||||
|
|
||||||
|
JNIEnv* ui_thread_jni_env() const { return ui_thread_jni_env_; }
|
||||||
|
|
||||||
uint32_t GetPixelDensity() const {
|
uint32_t GetPixelDensity() const {
|
||||||
return configuration_ ? uint32_t(AConfiguration_getDensity(configuration_))
|
return configuration_ ? uint32_t(AConfiguration_getDensity(configuration_))
|
||||||
: 160;
|
: 160;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JniIDs& jni_ids() const { return jni_ids_; }
|
||||||
|
|
||||||
int32_t window_surface_layout_left() const {
|
int32_t window_surface_layout_left() const {
|
||||||
return window_surface_layout_left_;
|
return window_surface_layout_left_;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +88,7 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
void JniActivityOnDestroy();
|
void JniActivityOnDestroy();
|
||||||
void JniActivityOnWindowSurfaceLayoutChange(jint left, jint top, jint right,
|
void JniActivityOnWindowSurfaceLayoutChange(jint left, jint top, jint right,
|
||||||
jint bottom);
|
jint bottom);
|
||||||
|
bool JniActivityOnWindowSurfaceMotionEvent(jobject event);
|
||||||
void JniActivityOnWindowSurfaceChanged(jobject window_surface_object);
|
void JniActivityOnWindowSurfaceChanged(jobject window_surface_object);
|
||||||
void JniActivityPaintWindow(bool force_paint);
|
void JniActivityPaintWindow(bool force_paint);
|
||||||
|
|
||||||
|
@ -117,6 +136,9 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
|
|
||||||
bool android_base_initialized_ = false;
|
bool android_base_initialized_ = false;
|
||||||
|
|
||||||
|
jclass motion_event_class_ = nullptr;
|
||||||
|
JniIDs jni_ids_ = {};
|
||||||
|
|
||||||
jobject activity_ = nullptr;
|
jobject activity_ = nullptr;
|
||||||
jmethodID activity_method_finish_ = nullptr;
|
jmethodID activity_method_finish_ = nullptr;
|
||||||
jmethodID activity_method_post_invalidate_window_surface_ = nullptr;
|
jmethodID activity_method_post_invalidate_window_surface_ = nullptr;
|
||||||
|
|
Loading…
Reference in New Issue