Files
FlatLaf/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm
Karl Tauber 251198c66d Native Libraries:
- made C methods `static` (similar to `private` in Java) to avoid that they are added (exported) to shared library symbol table
- macOS and Linux: added `-fvisibility=hidden` to compiler options to mark C methods hidden by default
2025-01-08 12:59:38 +01:00

388 lines
13 KiB
Plaintext

/*
* Copyright 2023 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
*/
@interface WindowData : NSObject
// used when window is full screen
@property (nonatomic) int lastWindowButtonAreaWidth;
@property (nonatomic) int lastWindowTitleBarHeight;
// full screen observers
@property (nonatomic) id willEnterFullScreenObserver;
@property (nonatomic) id willExitFullScreenObserver;
@property (nonatomic) id didExitFullScreenObserver;
@end
@implementation WindowData
@end
// declare exported methods
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window );
// declare internal methods
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate );
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden );
static int getWindowButtonAreaWidth( NSWindow* nsWindow );
static int getWindowTitleBarHeight( NSWindow* nsWindow );
static bool isWindowFullScreen( NSWindow* nsWindow );
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
if( window == NULL )
return NULL;
// initialize class IDs (done only once because variables are static)
static jclass lwWindowPeerClass = findClass( env, "sun/lwawt/LWWindowPeer", true );
static jclass cfRetainedResourceClass = findClass( env, "sun/lwawt/macosx/CFRetainedResource", true );
if( lwWindowPeerClass == NULL || cfRetainedResourceClass == NULL )
return NULL;
// initialize field IDs (done only once because variables are static)
static jfieldID peerID = getFieldID( env, findClass( env, "java/awt/Component", false ), "peer", "Ljava/awt/peer/ComponentPeer;", false );
static jfieldID platformWindowID = getFieldID( env, lwWindowPeerClass, "platformWindow", "Lsun/lwawt/PlatformWindow;", false );
static jfieldID ptrID = getFieldID( env, cfRetainedResourceClass, "ptr", "J", false );
if( peerID == NULL || platformWindowID == NULL || ptrID == NULL )
return NULL;
// get field java.awt.Component.peer
jobject peer = env->GetObjectField( window, peerID );
if( peer == NULL || !env->IsInstanceOf( peer, lwWindowPeerClass ) )
return NULL;
// get field sun.lwawt.LWWindowPeer.platformWindow
jobject platformWindow = env->GetObjectField( peer, platformWindowID );
if( platformWindow == NULL || !env->IsInstanceOf( platformWindow, cfRetainedResourceClass ) )
return NULL;
// get field sun.lwawt.macosx.CFRetainedResource.ptr
return (NSWindow *) jlong_to_ptr( env->GetLongField( platformWindow, ptrID ) );
}
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) {
static char key;
WindowData* windowData = objc_getAssociatedObject( nsWindow, &key );
if( windowData == NULL && allocate ) {
windowData = [WindowData new];
objc_setAssociatedObject( nsWindow, &key, windowData, OBJC_ASSOCIATION_RETAIN_NONATOMIC );
}
return windowData;
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder
( JNIEnv* env, jclass cls, jobject window, jfloat radius, jfloat borderWidth, jint borderColor )
{
JNI_COCOA_ENTER()
NSWindow* nsWindow = getNSWindow( env, cls, window );
if( nsWindow == NULL )
return FALSE;
[FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){
nsWindow.hasShadow = YES;
nsWindow.contentView.wantsLayer = YES;
nsWindow.contentView.layer.cornerRadius = radius;
nsWindow.contentView.layer.masksToBounds = YES;
nsWindow.contentView.layer.borderWidth = borderWidth;
if( borderWidth > 0 ) {
CGFloat red = ((borderColor >> 16) & 0xff) / 255.;
CGFloat green = ((borderColor >> 8) & 0xff) / 255.;
CGFloat blue = (borderColor & 0xff) / 255.;
CGFloat alpha = ((borderColor >> 24) & 0xff) / 255.;
nsWindow.contentView.layer.borderColor = [[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] CGColor];
}
nsWindow.backgroundColor = NSColor.clearColor;
nsWindow.opaque = NO;
[nsWindow.contentView.layer removeAllAnimations];
[nsWindow invalidateShadow];
}];
return TRUE;
JNI_COCOA_EXIT()
return FALSE;
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonsSpacing
( JNIEnv* env, jclass cls, jobject window, jint buttonsSpacing )
{
JNI_COCOA_ENTER()
NSWindow* nsWindow = getNSWindow( env, cls, window );
if( nsWindow == NULL )
return FALSE;
#define SPACING_DEFAULT com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_DEFAULT
#define SPACING_MEDIUM com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM
#define SPACING_LARGE com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE
bool isMacOS_11_orLater = @available( macOS 11, * );
if( !isMacOS_11_orLater && buttonsSpacing == SPACING_LARGE )
buttonsSpacing = SPACING_MEDIUM;
int oldButtonsSpacing = (nsWindow.toolbar != NULL)
? ((isMacOS_11_orLater && nsWindow.toolbarStyle == NSWindowToolbarStyleUnified)
? SPACING_LARGE
: SPACING_MEDIUM)
: SPACING_DEFAULT;
if( buttonsSpacing == oldButtonsSpacing )
return TRUE;
WindowData* windowData = getWindowData( nsWindow, true );
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
// NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] );
// add/remove toolbar
NSToolbar* toolbar = NULL;
bool needsToolbar = (buttonsSpacing != SPACING_DEFAULT);
if( needsToolbar ) {
toolbar = [NSToolbar new];
toolbar.showsBaselineSeparator = NO; // necessary for older macOS versions
if( isWindowFullScreen( nsWindow ) )
toolbar.visible = NO;
}
nsWindow.toolbar = toolbar;
if( isMacOS_11_orLater ) {
nsWindow.toolbarStyle = (buttonsSpacing == SPACING_LARGE)
? NSWindowToolbarStyleUnified
: (buttonsSpacing == SPACING_MEDIUM)
? NSWindowToolbarStyleUnifiedCompact
: NSWindowToolbarStyleAutomatic;
}
windowData.lastWindowButtonAreaWidth = 0;
windowData.lastWindowTitleBarHeight = 0;
// NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] );
// when window becomes full screen, it is necessary to hide the toolbar
// because it otherwise is shown non-transparent and hides Swing components
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if( needsToolbar && windowData.willEnterFullScreenObserver == NULL ) {
// NSLog( @"add observers %@", nsWindow );
windowData.willEnterFullScreenObserver = [center addObserverForName:NSWindowWillEnterFullScreenNotification
object:nsWindow queue:nil usingBlock:^(NSNotification *note) {
// NSLog( @"enter full screen %@", nsWindow );
if( nsWindow.toolbar != NULL ) {
// remember button area width, which is used later when window exits full screen
// remembar title bar height so that "main" JToolBar keeps its height in full screen
windowData.lastWindowButtonAreaWidth = getWindowButtonAreaWidth( nsWindow );
windowData.lastWindowTitleBarHeight = getWindowTitleBarHeight( nsWindow );
// NSLog( @"%d %d", windowData.lastWindowButtonAreaWidth, windowData.lastWindowTitleBarHeight );
nsWindow.toolbar.visible = NO;
}
}];
windowData.willExitFullScreenObserver = [center addObserverForName:NSWindowWillExitFullScreenNotification
object:nsWindow queue:nil usingBlock:^(NSNotification *note) {
// NSLog( @"will exit full screen %@", nsWindow );
if( nsWindow.toolbar != NULL )
setWindowButtonsHidden( nsWindow, true );
}];
windowData.didExitFullScreenObserver = [center addObserverForName:NSWindowDidExitFullScreenNotification
object:nsWindow queue:nil usingBlock:^(NSNotification *note) {
// NSLog( @"exit full screen %@", nsWindow );
if( nsWindow.toolbar != NULL ) {
setWindowButtonsHidden( nsWindow, false );
nsWindow.toolbar.visible = YES;
}
windowData.lastWindowButtonAreaWidth = 0;
windowData.lastWindowTitleBarHeight = 0;
}];
} else if( !needsToolbar ) {
// NSLog( @"remove observers %@", nsWindow );
if( windowData.willEnterFullScreenObserver != NULL ) {
[center removeObserver:windowData.willEnterFullScreenObserver];
windowData.willEnterFullScreenObserver = nil;
}
if( windowData.willExitFullScreenObserver != NULL ) {
[center removeObserver:windowData.willExitFullScreenObserver];
windowData.willExitFullScreenObserver = nil;
}
if( windowData.didExitFullScreenObserver != NULL ) {
[center removeObserver:windowData.didExitFullScreenObserver];
windowData.didExitFullScreenObserver = nil;
}
}
}];
return TRUE;
JNI_COCOA_EXIT()
return FALSE;
}
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) {
// get buttons
NSView* buttons[3] = {
[nsWindow standardWindowButton:NSWindowCloseButton],
[nsWindow standardWindowButton:NSWindowMiniaturizeButton],
[nsWindow standardWindowButton:NSWindowZoomButton]
};
for( int i = 0; i < 3; i++ ) {
NSView* button = buttons[i];
if( button != NULL )
button.hidden = hidden;
}
}
extern "C"
JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds
( JNIEnv* env, jclass cls, jobject window )
{
JNI_COCOA_ENTER()
NSWindow* nsWindow = getNSWindow( env, cls, window );
if( nsWindow == NULL )
return NULL;
WindowData* windowData = getWindowData( nsWindow, false );
int width = 0;
int height = 0;
// get width
if( isWindowFullScreen( nsWindow ) ) {
// use zero if window is full screen because close/minimize/zoom buttons are hidden
width = 0;
} else if( windowData != NULL && windowData.lastWindowButtonAreaWidth > 0 ) {
// use remembered value if window is in transition from full screen to non-full screen
// because NSToolbar is not yet visible
width = windowData.lastWindowButtonAreaWidth;
} else
width = getWindowButtonAreaWidth( nsWindow );
// get height
if( windowData != NULL && windowData.lastWindowTitleBarHeight > 0 ) {
// use remembered value if window is full screen because NSToolbar is hidden
height = windowData.lastWindowTitleBarHeight;
} else
height = getWindowTitleBarHeight( nsWindow );
// initialize class and method ID (done only once because variables are static)
static jclass cls = findClass( env, "java/awt/Rectangle", true );
static jmethodID methodID = getMethodID( env, cls, "<init>", "(IIII)V", false );
if( cls == NULL || methodID == NULL )
return NULL;
// create and return Rectangle
return env->NewObject( cls, methodID, 0, 0, width, height );
JNI_COCOA_EXIT()
return NULL;
}
static int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
// get buttons
NSView* buttons[3] = {
[nsWindow standardWindowButton:NSWindowCloseButton],
[nsWindow standardWindowButton:NSWindowMiniaturizeButton],
[nsWindow standardWindowButton:NSWindowZoomButton]
};
// get most left and right coordinates
int left = -1;
int right = -1;
for( int i = 0; i < 3; i++ ) {
NSView* button = buttons[i];
if( button == NULL )
continue;
int x = [button convertRect: [button bounds] toView:button.superview].origin.x;
int width = button.bounds.size.width;
if( left == -1 || x < left )
left = x;
if( right == -1 || x + width > right )
right = x + width;
}
if( left == -1 || right == -1 )
return -1;
// 'right' is the actual button area width (from left window edge)
// adding 'left' to add same empty space on right side as on left side
return right + left;
}
static int getWindowTitleBarHeight( NSWindow* nsWindow ) {
NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton];
if( closeButton == NULL )
return -1;
NSView* titlebar = closeButton.superview;
return titlebar.bounds.size.height;
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWindowFullScreen
( JNIEnv* env, jclass cls, jobject window )
{
JNI_COCOA_ENTER()
NSWindow* nsWindow = getNSWindow( env, cls, window );
if( nsWindow == NULL )
return FALSE;
return (jboolean) isWindowFullScreen( nsWindow );
JNI_COCOA_EXIT()
return FALSE;
}
static bool isWindowFullScreen( NSWindow* nsWindow ) {
return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0);
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen
( JNIEnv* env, jclass cls, jobject window )
{
JNI_COCOA_ENTER()
NSWindow* nsWindow = getNSWindow( env, cls, window );
if( nsWindow == NULL )
return FALSE;
[FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){
[nsWindow toggleFullScreen:nil];
}];
return TRUE;
JNI_COCOA_EXIT()
return FALSE;
}