mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 05:45:38 +00:00
Android: Basic touchscreen controller implementation
This commit is contained in:
parent
89e9373037
commit
5d91c011a6
|
@ -4,6 +4,7 @@
|
|||
#include "YBaseLib/String.h"
|
||||
#include "android_audio_stream.h"
|
||||
#include "android_gles_host_display.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/game_list.h"
|
||||
#include "core/host_display.h"
|
||||
|
@ -330,6 +331,51 @@ void AndroidHostInterface::SurfaceChanged(ANativeWindow* window, int format, int
|
|||
m_display->ChangeRenderWindow(window);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetControllerType(u32 index, std::string_view type_name)
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
{
|
||||
m_controller_type_names[index] = type_name;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string type_name_copy(type_name);
|
||||
RunOnEmulationThread([this, index, type_name_copy]() {
|
||||
Log_InfoPrintf("Changing controller slot %d to %s", index, type_name_copy.c_str());
|
||||
m_controller_type_names[index] = std::move(type_name_copy);
|
||||
|
||||
m_controllers[index] = Controller::Create(m_controller_type_names[index]);
|
||||
m_system->SetController(index, m_controllers[index]);
|
||||
}, false);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code, bool pressed)
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
RunOnEmulationThread([this, index, button_code, pressed]() {
|
||||
if (!m_controllers[index])
|
||||
return;
|
||||
|
||||
m_controllers[index]->SetButtonState(button_code, pressed);
|
||||
}, false);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::ConnectControllers()
|
||||
{
|
||||
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
|
||||
{
|
||||
if (m_controller_type_names[i].empty())
|
||||
continue;
|
||||
|
||||
Log_InfoPrintf("Connecting controller '%s' to port %u", m_controller_type_names[i].c_str(), i);
|
||||
m_controllers[i] = Controller::Create(m_controller_type_names[i]);
|
||||
if (m_controllers[i])
|
||||
m_system->SetController(i, m_controllers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
{
|
||||
Log::GetInstance().SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||
|
@ -433,6 +479,22 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j
|
|||
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerType, jobject obj, jint index, jstring controller_type)
|
||||
{
|
||||
GetNativeClass(env, obj)->SetControllerType(index, JStringToString(env, controller_type));
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerButtonState, jobject obj, jint index, jint button_code, jboolean pressed)
|
||||
{
|
||||
GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type, jstring button_name)
|
||||
{
|
||||
std::optional<s32> code = Controller::GetButtonCodeByName(JStringToString(env, controller_type), JStringToString(env, button_name));
|
||||
return code.value_or(-1);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_cache_path, jstring j_redump_dat_path, jarray j_search_directories, jboolean search_recursively)
|
||||
{
|
||||
const std::string cache_path = JStringToString(env, j_cache_path);
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
#include "YBaseLib/Event.h"
|
||||
#include "YBaseLib/Timer.h"
|
||||
#include "core/host_interface.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class Controller;
|
||||
|
||||
class AndroidHostInterface final : public HostInterface
|
||||
{
|
||||
public:
|
||||
|
@ -26,7 +30,15 @@ public:
|
|||
|
||||
void SurfaceChanged(ANativeWindow* window, int format, int width, int height);
|
||||
|
||||
void SetControllerType(u32 index, std::string_view type_name);
|
||||
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
||||
|
||||
private:
|
||||
enum : u32
|
||||
{
|
||||
NUM_CONTROLLERS = 2
|
||||
};
|
||||
|
||||
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, std::string initial_filename,
|
||||
std::string initial_state_filename);
|
||||
|
||||
|
@ -36,6 +48,8 @@ private:
|
|||
|
||||
void DrawFPSWindow();
|
||||
|
||||
void ConnectControllers() override;
|
||||
|
||||
jobject m_java_object = {};
|
||||
|
||||
std::mutex m_callback_mutex;
|
||||
|
@ -45,4 +59,7 @@ private:
|
|||
std::atomic_bool m_emulation_thread_stop_request{false};
|
||||
std::atomic_bool m_emulation_thread_start_result{false};
|
||||
Event m_emulation_thread_started;
|
||||
|
||||
std::array<std::string, NUM_CONTROLLERS> m_controller_type_names;
|
||||
std::array<std::shared_ptr<Controller>, NUM_CONTROLLERS> m_controllers;
|
||||
};
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".EmulationActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
|
|
|
@ -22,4 +22,9 @@ public class AndroidHostInterface
|
|||
public native void stopEmulationThread();
|
||||
|
||||
public native void surfaceChanged(Surface surface, int format, int width, int height);
|
||||
|
||||
// TODO: Find a better place for this.
|
||||
public native void setControllerType(int index, String typeName);
|
||||
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
||||
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.view.SurfaceHolder;
|
|||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
|
||||
|
@ -26,6 +27,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
*/
|
||||
AndroidHostInterface mHostInterface;
|
||||
|
||||
/**
|
||||
* Touchscreen controller overlay
|
||||
*/
|
||||
TouchscreenControllerView mTouchscreenController;
|
||||
|
||||
/**
|
||||
* Whether or not the system UI should be auto-hidden after
|
||||
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
|
||||
|
@ -153,6 +159,12 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
mHostInterface = AndroidHostInterface.create();
|
||||
if (mHostInterface == null)
|
||||
throw new InstantiationError("Failed to create host interface");
|
||||
|
||||
// Create touchscreen controller.
|
||||
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
||||
mTouchscreenController = new TouchscreenControllerView(this);
|
||||
activityLayout.addView(mTouchscreenController);
|
||||
mTouchscreenController.init(0, "DigitalController", mHostInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* TODO: document your custom view class.
|
||||
*/
|
||||
public class TouchscreenControllerButtonView extends View {
|
||||
private Drawable mUnpressedDrawable;
|
||||
private Drawable mPressedDrawable;
|
||||
private boolean mPressed = false;
|
||||
private int mButtonCode = -1;
|
||||
private String mButtonName = "";
|
||||
private ButtonStateChangedListener mListener;
|
||||
|
||||
public interface ButtonStateChangedListener
|
||||
{
|
||||
void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed);
|
||||
}
|
||||
|
||||
|
||||
public TouchscreenControllerButtonView(Context context) {
|
||||
super(context);
|
||||
init(context,null, 0);
|
||||
}
|
||||
|
||||
public TouchscreenControllerButtonView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context,attrs, 0);
|
||||
}
|
||||
|
||||
public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context,attrs, defStyle);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||
// Load attributes
|
||||
final TypedArray a = getContext().obtainStyledAttributes(
|
||||
attrs, R.styleable.TouchscreenControllerButtonView, defStyle, 0);
|
||||
|
||||
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_unpressedDrawable)) {
|
||||
mUnpressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_unpressedDrawable);
|
||||
mUnpressedDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_pressedDrawable)) {
|
||||
mPressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_pressedDrawable);
|
||||
mPressedDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
int paddingLeft = getPaddingLeft();
|
||||
int paddingTop = getPaddingTop();
|
||||
int paddingRight = getPaddingRight();
|
||||
int paddingBottom = getPaddingBottom();
|
||||
|
||||
int contentWidth = getWidth() - paddingLeft - paddingRight;
|
||||
int contentHeight = getHeight() - paddingTop - paddingBottom;
|
||||
|
||||
// Draw the example drawable on top of the text.
|
||||
Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable;
|
||||
if (drawable != null) {
|
||||
drawable.setBounds(paddingLeft, paddingTop,
|
||||
paddingLeft + contentWidth, paddingTop + contentHeight);
|
||||
drawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
final boolean oldState = mPressed;
|
||||
|
||||
switch (event.getAction())
|
||||
{
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
{
|
||||
mPressed = true;
|
||||
invalidate();
|
||||
|
||||
if (mListener != null && !oldState)
|
||||
mListener.onButtonStateChanged(this, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
{
|
||||
mPressed = false;
|
||||
invalidate();
|
||||
|
||||
if (mListener != null && oldState)
|
||||
mListener.onButtonStateChanged(this, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public boolean isPressed()
|
||||
{
|
||||
return mPressed;
|
||||
}
|
||||
|
||||
public String getButtonName() {
|
||||
return mButtonName;
|
||||
}
|
||||
|
||||
public void setButtonName(String buttonName) {
|
||||
mButtonName = buttonName;
|
||||
}
|
||||
|
||||
public int getButtonCode()
|
||||
{
|
||||
return mButtonCode;
|
||||
}
|
||||
|
||||
public void setButtonCode(int code)
|
||||
{
|
||||
mButtonCode = code;
|
||||
}
|
||||
|
||||
public Drawable getPressedDrawable() {
|
||||
return mPressedDrawable;
|
||||
}
|
||||
|
||||
public void setPressedDrawable(Drawable pressedDrawable) {
|
||||
mPressedDrawable = pressedDrawable;
|
||||
}
|
||||
|
||||
public Drawable getUnpressedDrawable() {
|
||||
return mUnpressedDrawable;
|
||||
}
|
||||
|
||||
public void setUnpressedDrawable(Drawable unpressedDrawable) {
|
||||
mUnpressedDrawable = unpressedDrawable;
|
||||
}
|
||||
|
||||
public void setButtonStateChangedListener(ButtonStateChangedListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* TODO: document your custom view class.
|
||||
*/
|
||||
public class TouchscreenControllerView extends FrameLayout implements TouchscreenControllerButtonView.ButtonStateChangedListener {
|
||||
private int mControllerIndex;
|
||||
private String mControllerType;
|
||||
private AndroidHostInterface mHostInterface;
|
||||
|
||||
public TouchscreenControllerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TouchscreenControllerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public TouchscreenControllerView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void init(int controllerIndex, String controllerType,
|
||||
AndroidHostInterface hostInterface) {
|
||||
mControllerIndex = controllerIndex;
|
||||
mControllerType = controllerType;
|
||||
mHostInterface = hostInterface;
|
||||
|
||||
if (mHostInterface != null)
|
||||
mHostInterface.setControllerType(controllerIndex, controllerType);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true);
|
||||
|
||||
// TODO: Make dynamic, editable.
|
||||
linkButton(view, R.id.controller_button_up, "Up");
|
||||
linkButton(view, R.id.controller_button_right, "Right");
|
||||
linkButton(view, R.id.controller_button_down, "Down");
|
||||
linkButton(view, R.id.controller_button_left, "Left");
|
||||
linkButton(view, R.id.controller_button_triangle, "Triangle");
|
||||
linkButton(view, R.id.controller_button_circle, "Circle");
|
||||
linkButton(view, R.id.controller_button_cross, "Cross");
|
||||
linkButton(view, R.id.controller_button_square, "Square");
|
||||
}
|
||||
|
||||
private void linkButton(View view, int id, String buttonName)
|
||||
{
|
||||
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView)view.findViewById(id);
|
||||
buttonView.setButtonName(buttonName);
|
||||
buttonView.setButtonStateChangedListener(this);
|
||||
|
||||
if (mHostInterface != null)
|
||||
{
|
||||
int code = mHostInterface.getControllerButtonCode(mControllerType, buttonName);
|
||||
buttonView.setButtonCode(code);
|
||||
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
|
||||
|
||||
if (code < 0) {
|
||||
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
|
||||
"for '%s'", buttonName, mControllerType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed) {
|
||||
if (mHostInterface == null || view.getButtonCode() < 0)
|
||||
return;
|
||||
|
||||
mHostInterface.setControllerButtonState(mControllerIndex, view.getButtonCode(), pressed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,27 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m5.2917,5.2917 l15.875,15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m5.2917,21.1667 l15.875,-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,27 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m5.2917,5.2917 l15.875,15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m5.2917,21.1667 l15.875,-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M5.2917,5.2917L21.1667,5.2917v15.875L5.2917,21.1667v-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeColor="#ff00ff"
|
||||
android:strokeLineCap="square"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M5.2917,5.2917L21.1667,5.2917v15.875L5.2917,21.1667v-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeColor="#ff00ff"
|
||||
android:strokeLineCap="square"/>
|
||||
</vector>
|
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M5.2917,19.0398L21.1667,19.0398l-7.9375,-13.7481 -7.8548,13.6049"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#00ff00"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M5.2917,19.0398L21.1667,19.0398l-7.9375,-13.7481 -7.8548,13.6049"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#00ff00"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m21.1667,17.1979 l-7.9375,7.9375 -7.9375,-7.9375L5.2917,2.6458L21.1667,2.6458Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m21.1667,17.1979 l-7.9375,7.9375 -7.9375,-7.9375L5.2917,2.6458L21.1667,2.6458Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/frameLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0099cc"
|
||||
|
|
|
@ -39,4 +39,8 @@
|
|||
<string name="title_activity_emulation">EmulationActivity</string>
|
||||
<string name="dummy_button">Dummy Button</string>
|
||||
<string name="dummy_content">DUMMY\nCONTENT</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="title_activity_test_controller">TestControllerActivity</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue