mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Add option to search for games
This commit is contained in:
@@ -63,11 +63,6 @@ extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setSurfaceSize(JNIEnv *env, jclass clazz, jint width, jint height,
|
||||
jboolean is_main_canvas) {
|
||||
WindowHandleInfo *windowHandleInfo;
|
||||
if (is_main_canvas)
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_main;
|
||||
else
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_pad;
|
||||
gui_getWindowInfo().width = width;
|
||||
gui_getWindowInfo().height = height;
|
||||
}
|
||||
@@ -125,18 +120,34 @@ public:
|
||||
: m_onGameIconLoadedMID(onGameIconLoadedMID),
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
|
||||
|
||||
void onIconLoaded(TitleId titleId, const Image &iconData) override {
|
||||
void onIconLoaded(TitleId titleId) override {
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
jlong jTitleId = *reinterpret_cast<jlong *>(&titleId);
|
||||
jintArray jIconData = env->NewIntArray(iconData.m_width * iconData.m_height);
|
||||
env->SetIntArrayRegion(jIconData, 0, iconData.m_width * iconData.m_height,
|
||||
reinterpret_cast<const jint *>(iconData.m_image));
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, jTitleId,
|
||||
jIconData, iconData.m_width, iconData.m_height);
|
||||
env->DeleteLocalRef(jIconData);
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, jTitleId);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_getGameIcon(JNIEnv *env, jclass clazz, jlong title_id) {
|
||||
TitleId titleId = *reinterpret_cast<TitleId *>(&title_id);
|
||||
const Image &iconData = gui_getGameIcon(titleId);
|
||||
jintArray jIconData = env->NewIntArray(iconData.m_width * iconData.m_height);
|
||||
env->SetIntArrayRegion(jIconData, 0, iconData.m_width * iconData.m_height,
|
||||
reinterpret_cast<const jint *>(iconData.m_image));
|
||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
||||
|
||||
jmethodID createBitmapMethodID = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
||||
"([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
|
||||
jfieldID argb8888FieldID = env->GetStaticFieldID(bitmapConfigClass, "ARGB_8888",
|
||||
"Landroid/graphics/Bitmap$Config;");
|
||||
jobject argb8888Obj = env->GetStaticObjectField(bitmapConfigClass, argb8888FieldID);
|
||||
jobject bitmapObj = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodID, jIconData,
|
||||
iconData.m_width,
|
||||
iconData.m_height, argb8888Obj);
|
||||
return bitmapObj;
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setGameIconLoadedCallback(JNIEnv *env, jclass clazz,
|
||||
@@ -144,8 +155,7 @@ Java_info_cemu_Cemu_NativeLibrary_setGameIconLoadedCallback(JNIEnv *env, jclass
|
||||
jclass gameIconLoadedCallbackClass = env->GetObjectClass(game_icon_loaded_callback);
|
||||
jmethodID gameIconLoadedMID = env->GetMethodID(gameIconLoadedCallbackClass,
|
||||
"onGameIconLoaded",
|
||||
"(J[III)V");
|
||||
env->DeleteLocalRef(gameIconLoadedCallbackClass);
|
||||
"(J)V");
|
||||
g_gameIconLoadedCallback = std::make_shared<AndroidGameIconLoadedCallback>(gameIconLoadedMID,
|
||||
game_icon_loaded_callback);
|
||||
gui_setOnGameIconLoaded(g_gameIconLoadedCallback);
|
||||
@@ -183,7 +193,6 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
|
||||
jmethodID m_existsMid;
|
||||
JNIUtils::Scopedjclass m_fileUtilClass;
|
||||
std::function<void(JNIEnv *)> m_function = nullptr;
|
||||
std::atomic_bool m_functionFinished;
|
||||
std::mutex m_functionMutex;
|
||||
std::condition_variable m_functionCV;
|
||||
std::thread m_thread;
|
||||
@@ -192,19 +201,20 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
|
||||
std::atomic_bool m_continue = true;
|
||||
|
||||
bool callBooleanFunction(const std::filesystem::path &uri, jmethodID methodId) {
|
||||
std::unique_lock functionLock(m_functionMutex);
|
||||
m_functionFinished = false;
|
||||
bool condition;
|
||||
{
|
||||
std::lock_guard threadLock(m_threadMutex);
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
condition = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
};
|
||||
}
|
||||
std::lock_guard lock(m_functionMutex);
|
||||
bool functionFinished = false;
|
||||
bool condition = false;
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
condition = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
functionFinished = true;
|
||||
};
|
||||
m_threadCV.notify_one();
|
||||
m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; });
|
||||
{
|
||||
std::unique_lock threadLock(m_threadMutex);
|
||||
m_functionCV.wait(threadLock, [&]() -> bool { return functionFinished; });
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
@@ -233,7 +243,6 @@ public:
|
||||
return;
|
||||
m_function(*env);
|
||||
m_function = nullptr;
|
||||
m_functionFinished = true;
|
||||
m_functionCV.notify_one();
|
||||
}
|
||||
});
|
||||
@@ -246,48 +255,51 @@ public:
|
||||
}
|
||||
|
||||
int openContentUri(const std::filesystem::path &uri) override {
|
||||
std::unique_lock functionLock(m_functionMutex);
|
||||
m_functionFinished = false;
|
||||
int fd;
|
||||
{
|
||||
std::lock_guard threadLock(m_threadMutex);
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
};
|
||||
}
|
||||
std::lock_guard lock(m_functionMutex);
|
||||
bool functionFinished = false;
|
||||
int fd = -1;
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
|
||||
env->DeleteLocalRef(uriString);
|
||||
functionFinished = true;
|
||||
};
|
||||
m_threadCV.notify_one();
|
||||
m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; });
|
||||
{
|
||||
std::unique_lock threadLock(m_threadMutex);
|
||||
m_functionCV.wait(threadLock, [&]() { return functionFinished; });
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> listFiles(const std::filesystem::path &uri) override {
|
||||
std::unique_lock functionLock(m_functionMutex);
|
||||
m_functionFinished = false;
|
||||
std::lock_guard lock(m_functionMutex);
|
||||
bool functionFinished = false;
|
||||
std::vector<std::filesystem::path> paths;
|
||||
{
|
||||
std::lock_guard threadLock(m_threadMutex);
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallStaticObjectMethod(
|
||||
*m_fileUtilClass,
|
||||
m_listFilesMid, uriString));
|
||||
env->DeleteLocalRef(uriString);
|
||||
jsize arrayLength = env->GetArrayLength(pathsObjArray);
|
||||
paths.reserve(arrayLength);
|
||||
for (jsize i = 0; i < arrayLength; i++) {
|
||||
jstring pathStr = static_cast<jstring>(env->GetObjectArrayElement(
|
||||
pathsObjArray,
|
||||
i));
|
||||
paths.push_back(JNIUtils::JStringToString(env, pathStr));
|
||||
env->DeleteLocalRef(pathStr);
|
||||
}
|
||||
env->DeleteLocalRef(pathsObjArray);
|
||||
};
|
||||
}
|
||||
m_function = [&, this](JNIEnv *env) {
|
||||
jstring uriString = env->NewStringUTF(uri.c_str());
|
||||
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallStaticObjectMethod(
|
||||
*m_fileUtilClass,
|
||||
m_listFilesMid, uriString));
|
||||
env->DeleteLocalRef(uriString);
|
||||
jsize arrayLength = env->GetArrayLength(pathsObjArray);
|
||||
paths.reserve(arrayLength);
|
||||
for (jsize i = 0; i < arrayLength; i++) {
|
||||
jstring pathStr = static_cast<jstring>(env->GetObjectArrayElement(
|
||||
pathsObjArray,
|
||||
i));
|
||||
paths.push_back(JNIUtils::JStringToString(env, pathStr));
|
||||
env->DeleteLocalRef(pathStr);
|
||||
}
|
||||
env->DeleteLocalRef(pathsObjArray);
|
||||
functionFinished = true;
|
||||
};
|
||||
m_threadCV.notify_one();
|
||||
m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; });
|
||||
|
||||
{
|
||||
std::unique_lock threadLock(m_threadMutex);
|
||||
m_functionCV.wait(threadLock, [&]() { return functionFinished; });
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -618,4 +630,4 @@ Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceVolume(JNIEnv *env, jclass clazz
|
||||
auto &deviceVolume = tv ? g_config.data().tv_volume : g_config.data().pad_volume;
|
||||
deviceVolume = volume;
|
||||
g_config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ public class Game {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setIconData(int[] colors, int width, int height) {
|
||||
icon = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
|
||||
public void setIconData(Bitmap icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public Bitmap getIcon() {
|
||||
|
||||
@@ -1,23 +1,55 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> {
|
||||
HashMap<Long, Game> gameInfoMap;
|
||||
Vector<Long> gameTitleIds;
|
||||
public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> implements Filterable {
|
||||
private final Map<Long, Game> gameInfoMap;
|
||||
private final List<Long> gameTitleIds;
|
||||
private List<Long> filteredGameTitleIds;
|
||||
private final GameTitleClickAction gameTitleClickAction;
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence charSequence) {
|
||||
String gameTitle = charSequence.toString();
|
||||
if (gameTitle.isEmpty())
|
||||
filteredGameTitleIds = gameTitleIds;
|
||||
else
|
||||
filteredGameTitleIds = gameTitleIds.stream()
|
||||
.filter(titleId -> gameInfoMap.get(titleId).title.toLowerCase().contains(gameTitle.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
FilterResults filterResults = new FilterResults();
|
||||
filterResults.values = filteredGameTitleIds;
|
||||
return filterResults;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
|
||||
filteredGameTitleIds = (List<Long>) filterResults.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public interface GameTitleClickAction {
|
||||
void action(long titleId);
|
||||
}
|
||||
@@ -25,7 +57,8 @@ public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> {
|
||||
public GameAdapter(GameTitleClickAction gameTitleClickAction) {
|
||||
super();
|
||||
this.gameTitleClickAction = gameTitleClickAction;
|
||||
gameTitleIds = new Vector<>();
|
||||
gameTitleIds = new ArrayList<>();
|
||||
filteredGameTitleIds = new ArrayList<>();
|
||||
gameInfoMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -38,7 +71,7 @@ public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> {
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GameAdapter.ViewHolder holder, int position) {
|
||||
Game game = gameInfoMap.get(gameTitleIds.get(position));
|
||||
Game game = gameInfoMap.get(filteredGameTitleIds.get(position));
|
||||
if (game != null) {
|
||||
holder.icon.setImageBitmap(game.getIcon());
|
||||
holder.text.setText(game.getTitle());
|
||||
@@ -51,27 +84,28 @@ public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> {
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return gameTitleIds.size();
|
||||
return filteredGameTitleIds.size();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
int itemCount = gameTitleIds.size();
|
||||
notifyItemRangeRemoved(0, itemCount);
|
||||
notifyDataSetChanged();
|
||||
gameInfoMap.clear();
|
||||
gameTitleIds.clear();
|
||||
filteredGameTitleIds.clear();
|
||||
}
|
||||
|
||||
void addGameInfo(long titleId, String title) {
|
||||
gameTitleIds.addElement(titleId);
|
||||
gameTitleIds.add(titleId);
|
||||
gameInfoMap.put(titleId, new Game(titleId, title));
|
||||
notifyItemInserted(gameTitleIds.size() - 1);
|
||||
filteredGameTitleIds = gameTitleIds;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void setGameIcon(long titleId, int[] colors, int width, int height) {
|
||||
void setGameIcon(long titleId, Bitmap icon) {
|
||||
Game game = gameInfoMap.get(titleId);
|
||||
if (game != null) {
|
||||
game.setIconData(colors, width, height);
|
||||
notifyItemChanged(gameTitleIds.indexOf(titleId));
|
||||
game.setIconData(icon);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,19 @@ package info.cemu.Cemu;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -41,6 +47,19 @@ public class GamesFragment extends Fragment {
|
||||
intent.putExtra(EmulationActivity.GAME_TITLE_ID, titleId);
|
||||
startActivity(intent);
|
||||
});
|
||||
gameAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
if (gameAdapter.getItemCount() == 0) {
|
||||
if (!binding.searchText.getText().toString().isEmpty())
|
||||
binding.gamesListMessage.setText(R.string.no_games_found);
|
||||
binding.gamesListMessage.setVisibility(View.VISIBLE);
|
||||
|
||||
} else {
|
||||
binding.gamesListMessage.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
binding.gamesSwipeRefresh.setOnRefreshListener(() -> {
|
||||
gameAdapter.clear();
|
||||
NativeLibrary.reloadGameTitles();
|
||||
@@ -53,10 +72,30 @@ public class GamesFragment extends Fragment {
|
||||
NativeLibrary.requestGameIcon(titleId);
|
||||
});
|
||||
});
|
||||
NativeLibrary.setGameIconLoadedCallback((titleId, colors, width, height) -> {
|
||||
requireActivity().runOnUiThread(() -> gameAdapter.setGameIcon(titleId, colors, width, height));
|
||||
NativeLibrary.setGameIconLoadedCallback((titleId) -> {
|
||||
Bitmap icon = NativeLibrary.getGameIcon(titleId);
|
||||
requireActivity().runOnUiThread(() -> gameAdapter.setGameIcon(titleId, icon));
|
||||
});
|
||||
NativeLibrary.reloadGameTitles();
|
||||
|
||||
binding.searchText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
Log.i("TextChanged", charSequence.toString());
|
||||
gameAdapter.getFilter().filter(charSequence.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Surface;
|
||||
|
||||
@@ -49,13 +50,15 @@ public class NativeLibrary {
|
||||
public static native void setGameTitleLoadedCallback(GameTitleLoadedCallback gameTitleLoadedCallback);
|
||||
|
||||
public interface GameIconLoadedCallback {
|
||||
void onGameIconLoaded(long titleId, int[] colors, int width, int height);
|
||||
void onGameIconLoaded(long titleId);
|
||||
}
|
||||
|
||||
public static native void setGameIconLoadedCallback(GameIconLoadedCallback gameIconLoadedCallback);
|
||||
|
||||
public static native void requestGameIcon(long titleId);
|
||||
|
||||
public static native Bitmap getGameIcon(long titleId);
|
||||
|
||||
public static native void reloadGameTitles();
|
||||
|
||||
public static native void initializeActiveSettings(String dataPath, String cachePath);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M796,839L533,576Q503,602 463.04,616.5Q423.08,631 378,631Q269.84,631 194.92,556Q120,481 120,375Q120,269 195,194Q270,119 376.5,119Q483,119 557.5,194Q632,269 632,375.15Q632,418 618,458Q604,498 576,533L840,795L796,839ZM377,571Q458.25,571 515.13,513.5Q572,456 572,375Q572,294 515.13,236.5Q458.25,179 377,179Q294.92,179 237.46,236.5Q180,294 180,375Q180,456 237.46,513.5Q294.92,571 377,571Z"/>
|
||||
</vector>
|
||||
@@ -1,14 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/games_swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/games_recycler_view"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/layout_game" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="?attr/materialCardViewFilledStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/game_search_icon"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_search" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/search_games"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/games_list_message"
|
||||
style="@style/TextAppearance.Material3.TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/games_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp" />
|
||||
</LinearLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
@@ -91,4 +91,6 @@
|
||||
<string name="gamepad_channels">Gamepad channels</string>
|
||||
<string name="tv_volume">TV volume</string>
|
||||
<string name="pad_volume">Gamepad volume</string>
|
||||
<string name="search_games">Search games</string>
|
||||
<string name="no_games_found">No games were found</string>
|
||||
</resources>
|
||||
@@ -16,6 +16,11 @@ void gui_setOnGameTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &on
|
||||
g_gameTitleLoader.setOnTitleLoaded(onGameTitleLoaded);
|
||||
}
|
||||
|
||||
const Image& gui_getGameIcon(TitleId titleId)
|
||||
{
|
||||
return g_gameIconLoader.getGameIcon(titleId);
|
||||
}
|
||||
|
||||
void gui_setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback>& onGameIconLoaded)
|
||||
{
|
||||
g_gameIconLoader.setOnIconLoaded(onGameIconLoaded);
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "Image.h"
|
||||
|
||||
void gui_startGame(TitleId titleId);
|
||||
|
||||
|
||||
void gui_setOnGameTitleLoaded(const std::shared_ptr<class GameTitleLoadedCallback> &onGameTitleLoaded);
|
||||
|
||||
void gui_setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback>& onGameIconLoaded);
|
||||
|
||||
const Image& gui_getGameIcon(TitleId titleId);
|
||||
|
||||
void gui_requestGameIcon(TitleId titleId);
|
||||
|
||||
void gui_reloadGameTitles();
|
||||
|
||||
@@ -13,6 +13,10 @@ GameIconLoader::~GameIconLoader() {
|
||||
m_loaderThread.join();
|
||||
}
|
||||
|
||||
const Image &GameIconLoader::getGameIcon(TitleId titleId) {
|
||||
return m_iconCache.at(titleId);
|
||||
}
|
||||
|
||||
void GameIconLoader::requestIcon(TitleId titleId) {
|
||||
{
|
||||
std::lock_guard lock(m_threadMutex);
|
||||
@@ -39,7 +43,7 @@ void GameIconLoader::loadGameIcons() {
|
||||
continue;
|
||||
|
||||
if (auto iconIt = m_iconCache.find(titleId); iconIt != m_iconCache.end()) {
|
||||
if (m_onIconLoaded) m_onIconLoaded->onIconLoaded(titleId, iconIt->second);
|
||||
if (m_onIconLoaded) m_onIconLoaded->onIconLoaded(titleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -51,8 +55,8 @@ void GameIconLoader::loadGameIcons() {
|
||||
int width, height, channels;
|
||||
Image image(tgaData.value());
|
||||
if (image.isOk()) {
|
||||
if (m_onIconLoaded) m_onIconLoaded->onIconLoaded(titleId, image);
|
||||
m_iconCache.emplace(titleId, std::move(image));
|
||||
if (m_onIconLoaded) m_onIconLoaded->onIconLoaded(titleId);
|
||||
}
|
||||
} else {
|
||||
cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
class GameIconLoadedCallback {
|
||||
public:
|
||||
virtual void onIconLoaded(TitleId titleId, const Image &iconData) = 0;
|
||||
virtual void onIconLoaded(TitleId titleId) = 0;
|
||||
};
|
||||
|
||||
class GameIconLoader {
|
||||
@@ -21,6 +21,8 @@ public:
|
||||
|
||||
~GameIconLoader();
|
||||
|
||||
const Image &getGameIcon(TitleId titleId);
|
||||
|
||||
void setOnIconLoaded(const std::shared_ptr <GameIconLoadedCallback> &onIconLoaded);
|
||||
|
||||
void requestIcon(TitleId titleId);
|
||||
|
||||
Reference in New Issue
Block a user