duckstation/src/duckstation-nogui/cocoa_nogui_platform.mm

302 lines
8.7 KiB
Plaintext

// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "cocoa_nogui_platform.h"
#include "cocoa_key_names.h"
#include "nogui_host.h"
#include "core/host.h"
#include "util/cocoa_tools.h"
#include "util/imgui_manager.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string_util.h"
#include "common/threading.h"
Log_SetChannel(CocoaNoGUIPlatform);
constexpr NSWindowStyleMask WINDOWED_STYLE = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
@implementation CocoaNoGUIView
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)canBecomeKeyView {
return YES;
}
- (void)mouseDown:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(0, true);
}
- (void)rightMouseDown:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(1, true);
}
- (void)otherMouseDown:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(static_cast<s32>(event.buttonNumber), true);
}
- (void)mouseUp:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(0, false);
}
- (void)rightMouseUp:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(1, false);
}
- (void)otherMouseUp:(NSEvent *)event {
NoGUIHost::ProcessPlatformMouseButtonEvent(static_cast<s32>(event.buttonNumber), false);
}
- (void)mouseMoved:(NSEvent *)event {
// Flip for lower-left origin.
const NSView* contentView = self;
const NSPoint pt = [contentView convertPointToBacking:[event locationInWindow]];
const NSSize size = [contentView convertSizeToBacking:contentView.frame.size];
const float local_x = pt.x;
const float local_y = size.height - pt.y;
NoGUIHost::ProcessPlatformMouseMoveEvent(local_x, local_y);
}
- (void)keyDown:(NSEvent *)event {
[super keyDown:event];
if (ImGuiManager::WantsTextInput() && event.characters && event.characters.length > 0)
{
ImGuiManager::AddTextInput([event.characters UTF8String]);
}
if (!event.isARepeat)
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(event.keyCode), true);
}
- (void)keyUp:(NSEvent *)event {
[super keyUp:event];
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(event.keyCode), false);
}
- (void)windowDidEndLiveResize:(NSNotification *)notif
{
const NSSize size = [self convertSizeToBacking:self.frame.size];
NoGUIHost::ProcessPlatformWindowResize(static_cast<s32>(size.width), static_cast<s32>(size.height), 1.0f);
}
@end
CocoaNoGUIPlatform::CocoaNoGUIPlatform() = default;
CocoaNoGUIPlatform::~CocoaNoGUIPlatform()
{
if (m_window)
{
[m_window release];
m_window = nil;
}
}
bool CocoaNoGUIPlatform::Initialize()
{
[NSApplication sharedApplication];
// Needed for keyboard in put.
const ProcessSerialNumber psn = {0, kCurrentProcess};
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
return true;
}
void CocoaNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
{
if (![NSThread isMainThread])
{
dispatch_sync(dispatch_get_main_queue(), [this, &title, &message]() { ReportError(title, message); });
return;
}
@autoreleasepool {
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: CocoaTools::StringViewToNSString(title)];
[alert setInformativeText: CocoaTools::StringViewToNSString(message)];
[alert runModal];
}
}
bool CocoaNoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
{
if (![NSThread isMainThread])
{
bool result = false;
dispatch_sync(dispatch_get_main_queue(), [this, &title, &message, &result]() { result = ConfirmMessage(title, message); });
return result;
}
@autoreleasepool {
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: CocoaTools::StringViewToNSString(title)];
[alert setInformativeText: CocoaTools::StringViewToNSString(message)];
[alert addButtonWithTitle:@"Yes"];
[alert addButtonWithTitle:@"No"];
return ([alert runModal] == 0);
}
}
void CocoaNoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
{
// noop
}
bool CocoaNoGUIPlatform::CreatePlatformWindow(std::string title)
{
@autoreleasepool {
s32 window_x, window_y, window_width, window_height;
const bool has_window_geom = NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height);
if (!has_window_geom)
{
window_width = DEFAULT_WINDOW_WIDTH;
window_height = DEFAULT_WINDOW_HEIGHT;
}
m_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0.0f, 0.0f, static_cast<CGFloat>(window_width), static_cast<CGFloat>(window_height))
styleMask:WINDOWED_STYLE
backing:NSBackingStoreBuffered defer:YES];
CocoaNoGUIView* view = [[[CocoaNoGUIView alloc] init] autorelease];
[m_window setDelegate:view];
[m_window setContentView:view];
if (!has_window_geom)
[m_window center];
else
[m_window setFrameOrigin:NSMakePoint(static_cast<CGFloat>(window_x), static_cast<CGFloat>(window_y))];
[m_window setTitle: [NSString stringWithUTF8String:title.c_str()]];
[m_window setAcceptsMouseMovedEvents:YES];
[m_window setReleasedWhenClosed:NO];
[m_window setIsVisible:TRUE];
[m_window makeKeyAndOrderFront:nil];
}
if (m_fullscreen.load(std::memory_order_acquire))
SetFullscreen(true);
return true;
}
bool CocoaNoGUIPlatform::HasPlatformWindow() const
{
return (m_window != NULL);
}
void CocoaNoGUIPlatform::DestroyPlatformWindow()
{
if (m_window == nil)
return;
const CGPoint frame_origin = m_window.frame.origin;
const CGSize content_size = m_window.contentView.frame.size;
if (!m_fullscreen.load(std::memory_order_acquire))
{
NoGUIHost::SavePlatformWindowGeometry(static_cast<s32>(frame_origin.x), static_cast<s32>(frame_origin.y),
static_cast<s32>(content_size.width), static_cast<s32>(content_size.height));
}
[m_window close];
[m_window release];
m_window = nil;
}
std::optional<WindowInfo> CocoaNoGUIPlatform::GetPlatformWindowInfo()
{
if (m_window == nil)
return std::nullopt;
NSView* contentView = [m_window contentView];
const NSSize size = [contentView convertSizeToBacking:contentView.frame.size];
WindowInfo wi;
wi.surface_width = static_cast<u32>(size.width);
wi.surface_height = static_cast<u32>(size.height);
wi.surface_scale = m_window_scale;
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = static_cast<void*>(m_window.contentView);
return wi;
}
void CocoaNoGUIPlatform::SetPlatformWindowTitle(std::string title)
{
dispatch_async(dispatch_get_main_queue(), [this, title = std::move(title)]() {
if (!m_window)
return;
@autoreleasepool {
[m_window setTitle: [NSString stringWithUTF8String:title.c_str()]];
}
});
}
std::optional<u32> CocoaNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
{
std::optional<unsigned short> converted(CocoaKeyNames::GetKeyCodeForName(str));
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
}
std::optional<std::string> CocoaNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
{
const char* converted = CocoaKeyNames::GetKeyName(static_cast<unsigned short>(code));
return converted ? std::optional<std::string>(converted) : std::nullopt;
}
void CocoaNoGUIPlatform::RunMessageLoop()
{
[NSApp run];
}
void CocoaNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
{
dispatch_async(dispatch_get_main_queue(), [func = std::move(func)]() {
func();
});
}
void CocoaNoGUIPlatform::QuitMessageLoop()
{
[NSApp stop:nil];
}
void CocoaNoGUIPlatform::SetFullscreen(bool enabled)
{
Log_ErrorPrint("SetFullscreen() not implemented.");
}
bool CocoaNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
{
dispatch_async(dispatch_get_main_queue(), [this, new_window_width, new_window_height]() {
if (!m_window)
return;
@autoreleasepool {
[m_window setContentSize:NSMakeSize(static_cast<CGFloat>(new_window_width), static_cast<CGFloat>(new_window_height))];
}
});
return true;
}
bool CocoaNoGUIPlatform::OpenURL(const std::string_view& url)
{
Log_ErrorPrint("OpenURL() not implemented.");
return false;
}
bool CocoaNoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
{
Log_ErrorPrint("CopyTextToClipboard() not implemented.");
return false;
}
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateCocoaPlatform()
{
std::unique_ptr<CocoaNoGUIPlatform> ret(new CocoaNoGUIPlatform());
if (!ret->Initialize())
return {};
return ret;
}