boo/lib/mac/ApplicationCocoa.mm

303 lines
9.2 KiB
Plaintext
Raw Normal View History

2015-05-06 00:50:57 +00:00
#include <AppKit/AppKit.h>
2015-11-01 00:06:56 +00:00
#include <thread>
2015-05-06 00:50:57 +00:00
2015-09-02 19:09:13 +00:00
#include "boo/IApplication.hpp"
#include "boo/graphicsdev/Metal.hpp"
#include "lib/Common.hpp"
#include "lib/mac/CocoaCommon.hpp"
2015-05-06 00:50:57 +00:00
#include <logvisor/logvisor.hpp>
2015-11-18 23:55:25 +00:00
2015-12-27 04:20:07 +00:00
#if !__has_feature(objc_arc)
#error ARC Required
#endif
/* If set, application will terminate once client thread reaches end;
* main() will not get back control. Otherwise, main will get back control
* but App will not terminate in the normal Cocoa manner (possibly resulting
* in CoreAnimation warnings). */
#define COCOA_TERMINATE 1
namespace boo { class ApplicationCocoa; }
@interface AppDelegate : NSObject <NSApplicationDelegate> {
boo::ApplicationCocoa* m_app;
@public
2015-05-06 00:50:57 +00:00
}
2015-11-01 00:06:56 +00:00
- (id)initWithApp:(boo::ApplicationCocoa*)app;
2015-05-06 00:50:57 +00:00
@end
namespace boo {
2016-03-08 21:18:38 +00:00
static logvisor::Module Log("boo::ApplicationCocoa");
2016-02-24 21:07:48 +00:00
2018-10-16 03:13:57 +00:00
std::shared_ptr<IWindow> _WindowCocoaNew(SystemStringView title, MetalContext* metalCtx);
2016-02-24 21:07:48 +00:00
class ApplicationCocoa : public IApplication {
2015-11-01 00:06:56 +00:00
public:
IApplicationCallback& m_callback;
AppDelegate* m_appDelegate;
2015-11-01 00:06:56 +00:00
private:
const SystemString m_uniqueName;
const SystemString m_friendlyName;
const SystemString m_pname;
const std::vector<SystemString> m_args;
2016-02-24 21:07:48 +00:00
NSPanel* aboutPanel;
2016-02-24 21:07:48 +00:00
/* All windows */
std::unordered_map<uintptr_t, std::weak_ptr<IWindow>> m_windows;
2016-02-24 21:07:48 +00:00
MetalContext m_metalCtx;
2018-10-16 03:13:57 +00:00
#if 0
GLContext m_glCtx;
2018-10-16 03:13:57 +00:00
#endif
2016-02-24 21:07:48 +00:00
2019-08-31 21:20:30 +00:00
void _deletedWindow(IWindow* window) override {
m_windows.erase(window->getPlatformHandle());
}
2015-05-06 00:50:57 +00:00
public:
ApplicationCocoa(IApplicationCallback& callback,
SystemStringView uniqueName,
SystemStringView friendlyName,
SystemStringView pname,
const std::vector<SystemString>& args,
std::string_view gfxApi,
uint32_t samples,
uint32_t anisotropy,
bool deepColor)
2015-05-06 00:50:57 +00:00
: m_callback(callback),
2015-09-02 19:09:13 +00:00
m_uniqueName(uniqueName),
2015-05-06 00:50:57 +00:00
m_friendlyName(friendlyName),
m_pname(pname),
m_args(args) {
m_metalCtx.m_sampleCount = samples;
m_metalCtx.m_anisotropy = anisotropy;
m_metalCtx.m_pixelFormat = deepColor ? MTLPixelFormatRGBA16Float : MTLPixelFormatBGRA8Unorm;
2018-10-16 03:13:57 +00:00
#if 0
m_glCtx.m_sampleCount = samples;
m_glCtx.m_anisotropy = anisotropy;
m_glCtx.m_deepColor = deepColor;
2018-10-16 03:13:57 +00:00
#endif
2018-01-07 05:17:14 +00:00
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
/* Delegate (OS X callbacks) */
m_appDelegate = [[AppDelegate alloc] initWithApp:this];
[[NSApplication sharedApplication] setDelegate:m_appDelegate];
/* App menu */
NSMenu* rootMenu = [[NSMenu alloc] initWithTitle:@"main"];
NSMenu* appMenu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:m_friendlyName.c_str()]];
NSMenuItem* fsItem = [appMenu addItemWithTitle:@"Toggle Full Screen"
action:@selector(toggleFs:)
keyEquivalent:@"f"];
[fsItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem* quitItem = [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %s", m_friendlyName.c_str()]
action:@selector(quitApp:)
keyEquivalent:@"q"];
[quitItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
[[rootMenu addItemWithTitle:[NSString stringWithUTF8String:m_friendlyName.c_str()]
action:nil keyEquivalent:@""] setSubmenu:appMenu];
[[NSApplication sharedApplication] setMainMenu:rootMenu];
m_metalCtx.m_dev = MTLCreateSystemDefaultDevice();
if (!m_metalCtx.m_dev)
2020-04-11 22:46:05 +00:00
Log.report(logvisor::Fatal, FMT_STRING("Unable to create metal device"));
m_metalCtx.m_q = [m_metalCtx.m_dev newCommandQueue];
while (![m_metalCtx.m_dev supportsTextureSampleCount:m_metalCtx.m_sampleCount])
m_metalCtx.m_sampleCount = flp2(m_metalCtx.m_sampleCount - 1);
2020-04-11 22:46:05 +00:00
Log.report(logvisor::Info, FMT_STRING("using Metal renderer"));
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
EPlatformType getPlatformType() const override {
return EPlatformType::Cocoa;
}
std::thread m_clientThread;
int m_clientReturn = 0;
bool m_terminateNow = false;
int run() override {
/* Spawn client thread */
m_clientThread = std::thread([&]() {
std::string thrName = std::string(getFriendlyName()) + " Client Thread";
logvisor::RegisterThreadName(thrName.c_str());
/* Run app */
m_clientReturn = m_callback.appMain(this);
/* Cleanup */
for (auto& w : m_windows)
if (std::shared_ptr<IWindow> window = w.second.lock())
window->closeWindow();
#if COCOA_TERMINATE
/* Continue termination */
dispatch_sync(dispatch_get_main_queue(),
^{
/* Ends modal run loop and continues Cocoa termination */
[NSApp replyToApplicationShouldTerminate:YES];
/* If this is reached, application didn't spawn any windows
* and must be explicitly terminated */
m_terminateNow = true;
[NSApp terminate:nil];
});
#else
/* Return control to main() */
dispatch_sync(dispatch_get_main_queue(),
^{
[[NSApplication sharedApplication] stop:nil];
});
#endif
});
2016-02-24 21:07:48 +00:00
/* Already in Cocoa's event loop; return now */
return 0;
}
2016-02-24 21:07:48 +00:00
void quit() {
[NSApp terminate:nil];
}
2016-02-24 21:07:48 +00:00
SystemStringView getUniqueName() const override {
return m_uniqueName;
}
2016-02-24 21:07:48 +00:00
SystemStringView getFriendlyName() const override {
return m_friendlyName;
}
2016-02-24 21:07:48 +00:00
SystemStringView getProcessName() const override {
return m_pname;
}
2016-02-24 21:07:48 +00:00
const std::vector<SystemString>& getArgs() const override {
return m_args;
}
2016-02-24 21:07:48 +00:00
std::shared_ptr<IWindow> newWindow(std::string_view title) override {
auto newWindow = _WindowCocoaNew(title, &m_metalCtx);
m_windows[newWindow->getPlatformHandle()] = newWindow;
return newWindow;
}
2016-02-24 21:07:48 +00:00
/* Last GL context */
2018-10-16 03:13:57 +00:00
#if 0
NSOpenGLContext* m_lastGLCtx = nullptr;
2018-10-16 03:13:57 +00:00
#endif
2015-05-06 00:50:57 +00:00
};
2016-02-24 21:07:48 +00:00
2018-10-16 03:13:57 +00:00
#if 0
2015-11-01 00:06:56 +00:00
void _CocoaUpdateLastGLCtx(NSOpenGLContext* lastGLCtx)
{
static_cast<ApplicationCocoa*>(APP)->m_lastGLCtx = lastGLCtx;
}
2018-10-16 03:13:57 +00:00
#endif
2015-05-06 00:50:57 +00:00
2015-11-01 00:06:56 +00:00
IApplication* APP = nullptr;
2015-11-01 00:06:56 +00:00
int ApplicationRun(IApplication::EPlatformType platform,
IApplicationCallback& cb,
2017-11-13 06:13:32 +00:00
SystemStringView uniqueName,
SystemStringView friendlyName,
SystemStringView pname,
2015-11-01 00:06:56 +00:00
const std::vector<SystemString>& args,
std::string_view gfxApi,
2018-01-07 05:17:14 +00:00
uint32_t samples,
uint32_t anisotropy,
2018-01-16 06:29:43 +00:00
bool deepColor,
int64_t targetFrameTime,
bool singleInstance) {
std::string thrName = std::string(friendlyName) + " Main Thread";
logvisor::RegisterThreadName(thrName.c_str());
@autoreleasepool {
if (!APP) {
if (platform != IApplication::EPlatformType::Cocoa &&
platform != IApplication::EPlatformType::Auto)
return 1;
/* Never deallocated to ensure window destructors have access */
APP = new ApplicationCocoa(cb, uniqueName, friendlyName, pname, args,
gfxApi, samples, anisotropy, deepColor);
}
2018-06-12 01:13:34 +00:00
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
if ([NSApp respondsToSelector:@selector(setAppearance:)])
[NSApp setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]];
2018-06-12 01:13:34 +00:00
#endif
[NSApp run];
ApplicationCocoa* appCocoa = static_cast<ApplicationCocoa*>(APP);
if (appCocoa->m_clientThread.joinable())
appCocoa->m_clientThread.join();
return appCocoa->m_clientReturn;
}
2015-05-06 00:50:57 +00:00
}
2016-02-24 21:07:48 +00:00
2015-05-06 00:50:57 +00:00
}
2015-11-01 00:06:56 +00:00
@implementation AppDelegate
- (id)initWithApp:(boo::ApplicationCocoa*)app {
self = [super init];
m_app = app;
return self;
2015-11-01 00:06:56 +00:00
}
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
(void) notification;
m_app->run();
2015-11-01 00:06:56 +00:00
}
#if COCOA_TERMINATE
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app {
(void) app;
if (m_app->m_terminateNow)
return NSTerminateNow;
m_app->m_callback.appQuitting(m_app);
return NSTerminateLater;
2015-11-01 00:06:56 +00:00
}
#else
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app
{
(void)app;
m_app->m_callback.appQuitting(m_app);
return NSTerminateCancel;
}
#endif
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename {
std::vector<boo::SystemString> strVec;
strVec.push_back(boo::SystemString([filename UTF8String]));
m_app->m_callback.appFilesOpen(boo::APP, strVec);
return true;
2015-11-01 00:06:56 +00:00
}
- (void)application:(NSApplication*)sender openFiles:(NSArray*)filenames {
std::vector<boo::SystemString> strVec;
strVec.reserve([filenames count]);
for (NSString* str in filenames)
strVec.push_back(boo::SystemString([str UTF8String]));
m_app->m_callback.appFilesOpen(boo::APP, strVec);
2015-11-01 00:06:56 +00:00
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender {
(void) sender;
return YES;
2015-11-01 00:06:56 +00:00
}
- (IBAction)toggleFs:(id)sender {
(void) sender;
[[NSApp keyWindow] toggleFullScreen:nil];
2015-11-01 00:06:56 +00:00
}
- (IBAction)quitApp:(id)sender {
(void) sender;
[NSApp terminate:nil];
2015-11-01 00:06:56 +00:00
}
@end