#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include "common.h"
#include "FrnView.h"
#include "menu.h"
#include "automation.h"
#include "cursor.h"
#include "ResizeScope.h"
#include "AutoFitContentView.h"
#import "WindowProtocol.h"
#import "WindowInterfaces.h"
#include "WindowBaseImpl.h"
#include "WindowImpl.h"
#include "FrnTextInputMethod.h"
#include "FrnView.h"
#include <algorithm>
@class AutoFitContentView;
static void ActivateApplication() {
if ([NSApp isRunning]) {
[NSApp activateIgnoringOtherApps:YES];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp activateIgnoringOtherApps:YES];
});
}
}
WindowBaseImpl::~WindowBaseImpl() {
View = nullptr;
Window = nullptr;
}
WindowBaseImpl::WindowBaseImpl(IFrnWindowBaseEvents *events, bool usePanel) : TopLevelImpl(events) {
_children = std::list<ComObjectWeakPtr<WindowBaseImpl>>();
_shown = false;
_inResize = false;
BaseEvents = events;
lastPositionSet = { 0, 0 };
hasPosition = false;
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
lastMenu = nullptr;
CreateNSWindow(usePanel);
StandardContainer = [[AutoFitContentView new] initWithContent:View];
[Window setContentView:StandardContainer];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
}
NSWindow *WindowBaseImpl::GetNSWindow() {
return Window;
}
HRESULT WindowBaseImpl::ObtainNSWindowHandleRetained(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge_retained void *) Window;
return S_OK;
}
HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
[Window setContentSize:lastSize];
if(hasPosition)
{
SetPosition(lastPositionSet);
} else
{
[Window center];
}
auto collectionBehavior = [Window collectionBehavior];
[Window setCollectionBehavior:collectionBehavior & ~NSWindowCollectionBehaviorFullScreenPrimary];
UpdateAppearance();
[Window invalidateShadow];
if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window];
[Window makeKeyAndOrderFront:Window];
[Window makeFirstResponder:View];
ActivateApplication();
} else {
[Window orderFront:Window];
}
_shown = true;
[Window setCollectionBehavior:collectionBehavior];
[View.layer setNeedsDisplay];
return S_OK;
}
}
bool WindowBaseImpl::IsShown ()
{
return _shown;
}
bool WindowBaseImpl::ShouldTakeFocusOnShow() {
return true;
}
HRESULT WindowBaseImpl::ObtainNSWindowHandle(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge void *) Window;
return S_OK;
}
HRESULT WindowBaseImpl::Hide() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
auto attachedSheet = Window.attachedSheet;
if (attachedSheet
&& ([attachedSheet isKindOfClass: [NSOpenPanel class]]
|| [attachedSheet isKindOfClass: [NSSavePanel class]]))
{
[Window endSheet:attachedSheet];
[attachedSheet close];
}
auto frame = [Window frame];
FrnPoint point;
point.X = frame.origin.x;
point.Y = frame.origin.y + frame.size.height;
lastPositionSet = ConvertPointY(point);
hasPosition = true;
[Window orderOut:Window];
}
return S_OK;
}
}
HRESULT WindowBaseImpl::Activate() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
[Window makeKeyAndOrderFront:nil];
ActivateApplication();
}
}
return S_OK;
}
HRESULT WindowBaseImpl::SetTopMost(bool value) {
START_COM_CALL;
@autoreleasepool {
_isTopmost = value;
UpdateWindowLevel();
return S_OK;
}
}
NSWindowLevel WindowBaseImpl::GetBaseWindowLevel() {
return _isTopmost ? NSFloatingWindowLevel : NSNormalWindowLevel;
}
void WindowBaseImpl::UpdateWindowLevel() {
if (Window == nullptr)
return;
auto level = GetBaseWindowLevel();
auto parent = Parent.tryGet();
if (parent != nullptr && parent->Window != nullptr)
level = std::max(level, [parent->Window level]);
[Window setLevel:level];
for (auto iterator = _children.begin(); iterator != _children.end(); iterator++) {
auto child = (*iterator).tryGet();
if (child != nullptr)
child->UpdateWindowLevel();
}
}
HRESULT WindowBaseImpl::Close() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
auto window = Window;
Window = nullptr;
try {
[window close];
}
catch (NSException *) {}
}
return S_OK;
}
}
HRESULT WindowBaseImpl::GetFrameSize(FrnSize *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr)
return E_POINTER;
if(Window != nullptr && _shown){
auto frame = [Window frame];
ret->Width = frame.size.width;
ret->Height = frame.size.height;
}
return S_OK;
}
}
HRESULT WindowBaseImpl::SetMinMaxSize(FrnSize minSize, FrnSize maxSize) {
START_COM_CALL;
@autoreleasepool {
lastMinSize = ToNSSize(minSize);
lastMaxSize = ToNSSize(maxSize);
if(Window != nullptr) {
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
}
return S_OK;
}
}
HRESULT WindowBaseImpl::Resize(double x, double y, FrnPlatformResizeReason reason) {
if (_inResize) {
return S_OK;
}
_inResize = true;
START_COM_CALL;
auto resizeBlock = ResizeScope(View, reason);
@autoreleasepool {
auto maxSize = lastMaxSize;
auto minSize = lastMinSize;
if (x < minSize.width) {
x = minSize.width;
}
if (y < minSize.height) {
y = minSize.height;
}
if (x > maxSize.width) {
x = maxSize.width;
}
if (y > maxSize.height) {
y = maxSize.height;
}
@try {
if(x != lastSize.width || y != lastSize.height)
{
if (!_shown) {
auto screenSize = [Window screen].visibleFrame.size;
if (x > screenSize.width) {
x = screenSize.width;
}
if (y > screenSize.height) {
y = screenSize.height;
}
}
lastSize = NSSize{x, y};
SetClientSize(lastSize);
[Window invalidateShadow];
}
}
@finally {
_inResize = false;
}
return S_OK;
}
}
HRESULT WindowBaseImpl::SetMainMenu(IFrnMenu *menu) {
START_COM_CALL;
auto nativeMenu = dynamic_cast<FrnAppMenu *>(menu);
lastMenu = nativeMenu->GetNative();
if(Window != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
return S_OK;
}
HRESULT WindowBaseImpl::BeginMoveDrag() {
START_COM_CALL;
@autoreleasepool {
auto lastEvent = [View lastMouseDownEvent];
if (lastEvent == nullptr) {
auto currentEvent = [NSApp currentEvent];
if (currentEvent != nullptr && [currentEvent window] == Window &&
([currentEvent type] == NSEventTypeLeftMouseDown ||
[currentEvent type] == NSEventTypeLeftMouseDragged)) {
lastEvent = currentEvent;
}
}
if (lastEvent == nullptr) {
return S_OK;
}
[Window performWindowDragWithEvent:lastEvent];
return S_OK;
}
}
HRESULT WindowBaseImpl::BeginResizeDrag(__attribute__((unused)) FrnWindowEdge edge) {
START_COM_CALL;
return S_OK;
}
HRESULT WindowBaseImpl::GetPosition(FrnPoint *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
if(Window != nullptr) {
auto frame = [Window frame];
ret->X = frame.origin.x;
ret->Y = frame.origin.y + frame.size.height;
*ret = ConvertPointY(*ret);
} else
{
*ret = lastPositionSet;
}
return S_OK;
}
}
HRESULT WindowBaseImpl::SetPosition(FrnPoint point) {
START_COM_CALL;
@autoreleasepool {
lastPositionSet = point;
hasPosition = true;
if(Window != nullptr) {
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
}
return S_OK;
}
}
HRESULT WindowBaseImpl::SetTransparencyMode(FrnWindowTransparencyMode mode) {
START_COM_CALL;
[Window setBackgroundColor: (mode != Transparent ? [NSColor windowBackgroundColor] : [NSColor clearColor])];
[StandardContainer ShowBlur: mode == Blur];
return S_OK;
}
HRESULT WindowBaseImpl::SetFrameThemeVariant(FrnPlatformThemeVariant variant) {
START_COM_CALL;
NSAppearanceName appearanceName;
if (@available(macOS 10.14, *))
{
appearanceName = variant == FrnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua;
}
else
{
appearanceName = variant == FrnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua;
}
[Window setAppearance: [NSAppearance appearanceNamed: appearanceName]];
return S_OK;
}
bool WindowBaseImpl::IsModal() {
return false;
}
void WindowBaseImpl::UpdateAppearance() {
[Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::SetClientSize(NSSize size){
[Window setContentSize:lastSize];
}
void WindowBaseImpl::CleanNSWindow() {
if(Window != nullptr) {
[GetWindowProtocol() disconnectParent];
[Window close];
Window = nullptr;
}
}
void WindowBaseImpl::CreateNSWindow(bool usePanel) {
NSPoint origin = NSZeroPoint;
NSArray<NSScreen*>* screens = [NSScreen screens];
if (screens.count > 0)
origin = screens.firstObject.frame.origin;
NSRect contentRect = NSRect { origin.x, origin.y, lastSize };
if (usePanel) {
Window = [[FrnPanel alloc] initWithParent:this contentRect:contentRect styleMask:NSWindowStyleMaskBorderless];
[Window setHidesOnDeactivate:false];
} else {
Window = [[FrnWindow alloc] initWithParent:this contentRect:contentRect styleMask:NSWindowStyleMaskBorderless];
}
}
id <FrnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
if(Window == nullptr)
{
return nullptr;
}
return (id <FrnWindowProtocol>) Window;
}
void WindowBaseImpl::BringToFront()
{
}
HRESULT WindowBaseImpl::SetParent(IFrnWindowBase *parent) {
START_COM_CALL;
@autoreleasepool {
auto oldParent = Parent.tryGet();
if(oldParent != nullptr)
{
oldParent->_children.remove(this);
}
auto cparent = dynamic_cast<WindowImpl *>(parent);
Parent = cparent;
if(cparent != nullptr && Window != nullptr){
if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
cparent->_children.push_back(this);
UpdateAppearance();
}
UpdateWindowLevel();
return S_OK;
}
}