Implement image file picker on iOS, various buildfixes

This commit is contained in:
Henrik Rydgård committed 2025-05-19 20:41:16 +02:00
1 parent de7175cf04
commit 6035ef0652
11 files changed
+162 -45

No files matched your search

+6 -3
View File
@@ -7,6 +7,7 @@
#include <string_view>
#include "Common/System/System.h"
#include "Common/File/Path.h"
class Path;
@@ -91,9 +92,11 @@ inline void System_InputBoxGetString(RequesterToken token, std::string_view titl
}
// This one will pop up a special image browser if available. You can also pick
// images with the file browser below.
inline void System_BrowseForImage(RequesterToken token, std::string_view title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, token, callback, failedCallback, title, "", 0);
// images with the file browser below. If you provide savePath, iOS will be able to
// convert from HEIC as needed and then save to that path. If this happens, the intParam will
// be set to 1. Other backends will probably ignore it and set the intParam to 0.
inline void System_BrowseForImage(RequesterToken token, std::string_view title, Path savePath, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, token, callback, failedCallback, title, savePath.ToString(), 0);
}
enum class BrowseFileType {
+3
View File
@@ -37,6 +37,8 @@
#include "UI/DevScreens.h"
#include "UI/DriverManagerScreen.h"
#include "UI/DisplayLayoutScreen.h"
#include "UI/GameSettingsScreen.h"
#include "UI/OnScreenDisplay.h"
#if PPSSPP_PLATFORM(ANDROID)
@@ -122,6 +124,7 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) {
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
auto sy = GetI18NCategory(I18NCat::SYSTEM);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto ms = GetI18NCategory(I18NCat::MAINSETTINGS);
list->Add(new ItemHeader(sy->T("CPU Core")));
+44 -39
View File
@@ -101,7 +101,7 @@ extern AndroidAudioState *g_audioState;
#endif
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
static void SetMemStickDirDarwin(int requesterToken) {
void SetMemStickDirDarwin(int requesterToken) {
auto initialPath = g_Config.memStickDirectory;
INFO_LOG(Log::System, "Current path: %s", initialPath.c_str());
System_BrowseForFolder(requesterToken, "", initialPath, [](const std::string &value, int) {
@@ -1129,7 +1129,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) {
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
backgroundChoice_ = systemSettings->Add(new Choice(sy->T("Clear UI background")));
} else if (System_GetPropertyBool(SYSPROP_HAS_IMAGE_BROWSER)) {
} else if (System_GetPropertyBool(SYSPROP_HAS_IMAGE_BROWSER) || System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
backgroundChoice_ = systemSettings->Add(new Choice(sy->T("Set UI background...")));
} else {
backgroundChoice_ = nullptr;
@@ -1516,49 +1516,54 @@ UI::EventReturn GameSettingsScreen::OnChangeBackground(UI::EventParams &e) {
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
// The button is in clear mode.
File::Delete(bgPng);
File::Delete(bgJpg);
UIBackgroundShutdown();
RecreateViews();
} else {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
System_BrowseForImage(GetRequesterToken(), sy->T("Set UI background..."), [=](const std::string &value, int) {
if (!value.empty()) {
Path path(value);
// Check the file format. Don't rely on the file extension here due to scoped storage URLs.
FILE *f = File::OpenCFile(path, "rb");
uint8_t buffer[8];
ImageFileType type = ImageFileType::UNKNOWN;
if (f != nullptr && 8 == fread(buffer, 1, ARRAY_SIZE(buffer), f)) {
type = DetectImageFileType(buffer, ARRAY_SIZE(buffer));
}
std::string filename;
switch (type) {
case ImageFileType::JPEG:
filename = "background.jpg";
break;
case ImageFileType::PNG:
filename = "background.png";
break;
default:
break;
}
if (!filename.empty()) {
Path dest = GetSysDirectory(DIRECTORY_SYSTEM) / filename;
File::Copy(Path(value), dest);
} else {
g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Only JPG and PNG images are supported"), path.GetFilename(), 5.0);
}
}
// It will init again automatically. We can't init outside a frame on Vulkan.
UIBackgroundShutdown();
RecreateViews();
});
return UI::EVENT_DONE;
}
auto sy = GetI18NCategory(I18NCat::SYSTEM);
System_BrowseForImage(GetRequesterToken(), sy->T("Set UI background..."), bgJpg, [=](const std::string &value, int converted) {
if (converted == 1) {
// The platform code converted and saved the file to the desired path already.
INFO_LOG(Log::UI, "Converted file.");
} else if (!value.empty()) {
Path path(value);
// Check the file format. Don't rely on the file extension here due to scoped storage URLs.
FILE *f = File::OpenCFile(path, "rb");
uint8_t buffer[8];
ImageFileType type = ImageFileType::UNKNOWN;
if (f != nullptr && 8 == fread(buffer, 1, ARRAY_SIZE(buffer), f)) {
type = DetectImageFileType(buffer, ARRAY_SIZE(buffer));
}
std::string filename;
switch (type) {
case ImageFileType::JPEG:
filename = "background.jpg";
break;
case ImageFileType::PNG:
filename = "background.png";
break;
default:
break;
}
if (!filename.empty()) {
Path dest = GetSysDirectory(DIRECTORY_SYSTEM) / filename;
File::Copy(Path(value), dest);
} else {
g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Only JPG and PNG images are supported"), path.GetFilename(), 5.0);
}
}
// It will init again automatically. We can't init outside a frame on Vulkan.
UIBackgroundShutdown();
RecreateViews();
});
// Change to a browse or clear button.
return UI::EVENT_DONE;
}
+1
View File
@@ -210,3 +210,4 @@ private:
};
void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath);
void SetMemStickDirDarwin(int requesterToken);
+2
View File
@@ -12,6 +12,8 @@
<string>Your microphone may be used to emulate Go!Cam/Talkman, a microphone accessory</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Your local network needs to be accessed by PPSSPP for network multiplayer support.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to your photos is needed to allow setting a background image.</string>
<key>CFBundleDisplayName</key>
<string>PPSSPP</string>
<key>CFBundleExecutable</key>
+2 -1
View File
@@ -12,5 +12,6 @@
@interface PPSSPPViewControllerGL : GLKViewController <
iCadeEventDelegate, LocationHandlerDelegate, CameraFrameDelegate,
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController>
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController,
UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@end
+45
View File
@@ -24,6 +24,7 @@
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/System/NativeApp.h"
#include "Common/System/Request.h"
#include "Common/File/VFS/VFS.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Log.h"
@@ -102,6 +103,9 @@ id<PPSSPPViewController> sharedViewController;
IOSGLESContext *graphicsContext;
LocationHelper *locationHelper;
CameraHelper *cameraHelper;
int imageRequestId;
NSString *imageFilename;
}
@property (nonatomic, strong) EAGLContext* context;
@@ -586,6 +590,47 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
});
}
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId {
imageRequestId = requestId;
imageFilename = saveFilename;
NSLog(@"Picking photo to save to %@ (id: %d)", saveFilename, requestId);
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
UIImage *image = info[UIImagePickerControllerOriginalImage];
// Convert to JPEG with 90% quality
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
if (jpegData) {
// Do something with the JPEG data (e.g., save to file)
[jpegData writeToFile:imageFilename atomically:YES];
NSLog(@"Saved JPEG image to %@", imageFilename);
g_requestManager.PostSystemSuccess(imageRequestId, "", 1);
} else {
g_requestManager.PostSystemFailure(imageRequestId);
}
[picker dismissViewControllerAnimated:YES completion:nil];
[self hideKeyboard];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
NSLog(@"User cancelled image picker");
[picker dismissViewControllerAnimated:YES completion:nil];
// You can also call your custom callback or use the requestId here
g_requestManager.PostSystemFailure(imageRequestId);
[self hideKeyboard];
}
@end
void bindDefaultFBO()
+1
View File
@@ -22,6 +22,7 @@
- (void)willResignActive;
- (void)uiStateChanged;
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId;
@end
+3 -2
View File
@@ -10,9 +10,10 @@
@interface PPSSPPViewControllerMetal : UIViewController<
iCadeEventDelegate, LocationHandlerDelegate, CameraFrameDelegate,
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController>
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController,
UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@end
/** The Metal-compatibile view. */
@interface PPSSPPMetalView : UIView
@end
@end
+45
View File
@@ -16,6 +16,7 @@
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/System/NativeApp.h"
#include "Common/System/Request.h"
#include "Common/GraphicsContext.h"
#include "Common/Thread/ThreadUtil.h"
@@ -212,6 +213,9 @@ static std::thread g_renderLoopThread;
IOSVulkanContext *graphicsContext;
LocationHelper *locationHelper;
CameraHelper *cameraHelper;
int imageRequestId;
NSString *imageFilename;
}
@property (nonatomic) GCController *gameController __attribute__((weak_import));
@@ -726,6 +730,47 @@ extern float g_safeInsetBottom;
});
}
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId {
imageRequestId = requestId;
imageFilename = saveFilename;
NSLog(@"Picking photo to save to %@ (id: %d)", saveFilename, requestId);
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
UIImage *image = info[UIImagePickerControllerOriginalImage];
// Convert to JPEG with 90% quality
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
if (jpegData) {
// Do something with the JPEG data (e.g., save to file)
[jpegData writeToFile:imageFilename atomically:YES];
NSLog(@"Saved JPEG image to %@", imageFilename);
g_requestManager.PostSystemSuccess(imageRequestId, "", 1);
} else {
g_requestManager.PostSystemFailure(imageRequestId);
}
[picker dismissViewControllerAnimated:YES completion:nil];
[self hideKeyboard];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
NSLog(@"User cancelled image picker");
[picker dismissViewControllerAnimated:YES completion:nil];
// You can also call your custom callback or use the requestId here
g_requestManager.PostSystemFailure(imageRequestId);
[self hideKeyboard];
}
@end
@implementation PPSSPPMetalView
+10
View File
@@ -358,6 +358,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
return true;
case SYSPROP_HAS_FOLDER_BROWSER:
return true;
case SYSPROP_HAS_IMAGE_BROWSER:
return true;
case SYSPROP_HAS_OPEN_DIRECTORY:
return false;
case SYSPROP_HAS_BACK_BUTTON:
@@ -462,6 +464,14 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true);
return true;
}
case SystemRequestType::BROWSE_FOR_IMAGE:
{
NSString *filename = [NSString stringWithUTF8String:param2.c_str()];
dispatch_async(dispatch_get_main_queue(), ^{
[sharedViewController pickPhoto:filename requestId:requestId];
});
return true;
}
case SystemRequestType::CAMERA_COMMAND:
if (!strncmp(param1.c_str(), "startVideo", 10)) {
int width = 0, height = 0;