mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-5124 Rewrite custom decorations support
JBR API v0.0.8 Added new WindowDecorations API, deprecated old CustomWindowDecoration. JBR-4641 JBR-4630 Fix client area calculation with custom decorations on Windows. - Window insets are rounded up, which causes visible & unusable border in fullscreen on some scales, round down instead. - Clipping in Swing components sometimes cuts what it shouldn't, fixed.
This commit is contained in:
@@ -179,7 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBAWT, \
|
||||
DISABLED_WARNINGS_microsoft_awt_Toolkit.cpp := 4267, \
|
||||
LDFLAGS := $(LDFLAGS_JDKLIB) $(call SET_SHARED_LIBRARY_ORIGIN), \
|
||||
LDFLAGS_macosx := -L$(INSTALL_LIBRARIES_HERE), \
|
||||
LDFLAGS_windows := -delayload:user32.dll -delayload:gdi32.dll \
|
||||
LDFLAGS_windows := -delayload:user32.dll -delayload:gdi32.dll -delayload:gdiplus.dll \
|
||||
-delayload:shell32.dll -delayload:winmm.dll \
|
||||
-delayload:winspool.drv -delayload:imm32.dll \
|
||||
-delayload:ole32.dll -delayload:comdlg32.dll \
|
||||
@@ -194,7 +194,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBAWT, \
|
||||
-framework JavaRuntimeSupport \
|
||||
-framework ApplicationServices \
|
||||
-framework AudioToolbox, \
|
||||
LIBS_windows := kernel32.lib user32.lib gdi32.lib winspool.lib \
|
||||
LIBS_windows := kernel32.lib user32.lib gdi32.lib gdiplus.lib winspool.lib \
|
||||
imm32.lib ole32.lib uuid.lib shell32.lib \
|
||||
comdlg32.lib winmm.lib comctl32.lib shlwapi.lib \
|
||||
delayimp.lib jvm.lib $(WIN_JAVA_LIB) advapi32.lib dwmapi.lib $(A11Y_NVDA_ANNOUNCING_LIBS), \
|
||||
|
||||
@@ -72,7 +72,6 @@ import sun.awt.AWTThreading;
|
||||
import sun.awt.PaintEventDispatcher;
|
||||
import sun.java2d.SunGraphicsEnvironment;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.lwawt.LWComponentPeer;
|
||||
import sun.lwawt.LWLightweightFramePeer;
|
||||
import sun.lwawt.LWToolkit;
|
||||
import sun.lwawt.LWWindowPeer;
|
||||
@@ -82,7 +81,7 @@ import sun.security.action.GetPropertyAction;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
public class CPlatformWindow extends CFRetainedResource implements PlatformWindow {
|
||||
private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h, double transparentTitleBarHeight);
|
||||
private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h);
|
||||
private static native void nativeSetNSWindowStyleBits(long nsWindowPtr, int mask, int data);
|
||||
private static native void nativeSetNSWindowAppearance(long nsWindowPtr, String appearanceName);
|
||||
private static native void nativeSetNSWindowMenuBar(long nsWindowPtr, long menuBarPtr);
|
||||
@@ -109,7 +108,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse();
|
||||
private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive);
|
||||
private static native boolean nativeDelayShowing(long nsWindowPtr);
|
||||
private static native void nativeSetTransparentTitleBarHeight(long nsWindowPtr, float height);
|
||||
private static native void nativeUpdateCustomTitleBar(long nsWindowPtr);
|
||||
private static native void nativeCallDeliverMoveResizeEvent(long nsWindowPtr);
|
||||
private static native void nativeSetRoundedCorners(long nsWindowPrt, float radius);
|
||||
|
||||
@@ -144,7 +143,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
public static final String WINDOW_TRANSPARENT_TITLE_BAR = "apple.awt.transparentTitleBar";
|
||||
public static final String WINDOW_TITLE_VISIBLE = "apple.awt.windowTitleVisible";
|
||||
public static final String WINDOW_APPEARANCE = "apple.awt.windowAppearance";
|
||||
public static final String WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT = "apple.awt.windowTransparentTitleBarHeight";
|
||||
public static final String WINDOW_CORNER_RADIUS = "apple.awt.windowCornerRadius";
|
||||
|
||||
// This system property is named as jdk.* because it is not specific to AWT
|
||||
@@ -295,17 +293,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
}
|
||||
}
|
||||
},
|
||||
new Property<CPlatformWindow>(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT) {
|
||||
public void applyProperty(final CPlatformWindow c, final Object value) {
|
||||
if (value != null && (value instanceof Float)) {
|
||||
boolean enabled = (float) value != 0f;
|
||||
c.setStyleBits(FULL_WINDOW_CONTENT, enabled);
|
||||
c.setStyleBits(TRANSPARENT_TITLE_BAR, enabled);
|
||||
c.setStyleBits(TITLE_VISIBLE, !enabled);
|
||||
c.execute(ptr -> AWTThreading.executeWaitToolkit(wait -> nativeSetTransparentTitleBarHeight(ptr, (float) value)));
|
||||
}
|
||||
}
|
||||
},
|
||||
new Property<CPlatformWindow>(WINDOW_CORNER_RADIUS) {
|
||||
public void applyProperty(final CPlatformWindow c, final Object value) {
|
||||
c.setRoundedCorners(value);
|
||||
@@ -377,8 +364,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
responder = createPlatformResponder();
|
||||
contentView.initialize(peer, responder);
|
||||
|
||||
float transparentTitleBarHeight = getTransparentTitleBarHeight(_target);
|
||||
|
||||
Rectangle bounds;
|
||||
if (!IS(DECORATED, styleBits)) {
|
||||
// For undecorated frames the move/resize event does not come if the frame is centered on the screen
|
||||
@@ -399,7 +384,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
+ ", bounds=" + bounds);
|
||||
}
|
||||
long windowPtr = createNSWindow(viewPtr, ownerPtr, styleBits,
|
||||
bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight);
|
||||
bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("window created: " + Long.toHexString(windowPtr));
|
||||
}
|
||||
@@ -414,7 +399,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
+ ", bounds=" + bounds);
|
||||
}
|
||||
long windowPtr = createNSWindow(viewPtr, 0, styleBits,
|
||||
bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight);
|
||||
bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("window created: " + Long.toHexString(windowPtr));
|
||||
}
|
||||
@@ -578,14 +563,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
if (prop != null) {
|
||||
styleBits = SET(styleBits, TITLE_VISIBLE, Boolean.parseBoolean(prop.toString()));
|
||||
}
|
||||
|
||||
prop = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT);
|
||||
if (prop != null) {
|
||||
boolean enabled = Float.parseFloat(prop.toString()) != 0f;
|
||||
styleBits = SET(styleBits, FULL_WINDOW_CONTENT, enabled);
|
||||
styleBits = SET(styleBits, TRANSPARENT_TITLE_BAR, enabled);
|
||||
styleBits = SET(styleBits, TITLE_VISIBLE, !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDialog) {
|
||||
@@ -1494,10 +1471,9 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
double x,
|
||||
double y,
|
||||
double w,
|
||||
double h,
|
||||
double transparentTitleBarHeight) {
|
||||
double h) {
|
||||
return AWTThreading.executeWaitToolkit(() ->
|
||||
nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h, transparentTitleBarHeight));
|
||||
nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
@@ -1531,24 +1507,11 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
isFullScreenAnimationOn = false;
|
||||
}
|
||||
|
||||
private float getTransparentTitleBarHeight(Window target) {
|
||||
if (target instanceof javax.swing.RootPaneContainer) {
|
||||
final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane();
|
||||
if (rootpane != null) {
|
||||
Object transparentTitleBarHeightProperty = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT);
|
||||
if (transparentTitleBarHeightProperty != null) {
|
||||
return Float.parseFloat(transparentTitleBarHeightProperty.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// JBR API internals
|
||||
private static void setCustomDecorationTitleBarHeight(Window target, ComponentPeer peer, float height) {
|
||||
if (target instanceof javax.swing.RootPaneContainer) {
|
||||
final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane();
|
||||
if (rootpane != null) rootpane.putClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT, height);
|
||||
private static void updateCustomTitleBar(ComponentPeer peer) {
|
||||
if (peer instanceof LWWindowPeer lwwp &&
|
||||
lwwp.getPlatformWindow() instanceof CPlatformWindow cpw) {
|
||||
cpw.execute(CPlatformWindow::nativeUpdateCustomTitleBar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,7 @@
|
||||
BOOL isJustCreated;
|
||||
NSWindowTabbingMode javaWindowTabbingMode;
|
||||
BOOL isEnterFullScreen;
|
||||
CGFloat _transparentTitleBarHeight;
|
||||
NSMutableArray* _transparentTitleBarConstraints;
|
||||
NSLayoutConstraint *_transparentTitleBarHeightConstraint;
|
||||
NSMutableArray *_transparentTitleBarButtonCenterXConstraints;
|
||||
CGFloat _customTitleBarHeight;
|
||||
}
|
||||
|
||||
// An instance of either AWTWindow_Normal or AWTWindow_Panel
|
||||
@@ -72,6 +69,11 @@
|
||||
@property (nonatomic) NSWindowTabbingMode javaWindowTabbingMode;
|
||||
@property (nonatomic) BOOL isEnterFullScreen;
|
||||
@property (nonatomic, retain) NSNumber *currentDisplayID;
|
||||
@property (nonatomic, readonly) CGFloat customTitleBarHeight;
|
||||
@property (nonatomic) BOOL customTitleBarControlsVisible;
|
||||
@property (nonatomic, retain) NSMutableArray *customTitleBarConstraints;
|
||||
@property (nonatomic, retain) NSLayoutConstraint *customTitleBarHeightConstraint;
|
||||
@property (nonatomic, retain) NSMutableArray *customTitleBarButtonCenterXConstraints;
|
||||
|
||||
- (id) initWithPlatformWindow:(jobject)javaPlatformWindow
|
||||
ownerWindow:owner
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <objc/objc-runtime.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <java_awt_Window_CustomWindowDecoration.h>
|
||||
#include <java_awt_Window_CustomTitleBar.h>
|
||||
#import "sun_lwawt_macosx_CPlatformWindow.h"
|
||||
#import "com_apple_eawt_event_GestureHandler.h"
|
||||
#import "com_apple_eawt_FullScreenHandler.h"
|
||||
@@ -406,6 +406,18 @@ AWT_NS_WINDOW_IMPLEMENTATION
|
||||
return type;
|
||||
}
|
||||
|
||||
+ (jint) affectedStyleMaskForCustomTitleBar {
|
||||
return MASK(FULL_WINDOW_CONTENT) | MASK(TRANSPARENT_TITLE_BAR) | MASK(TITLE_VISIBLE);
|
||||
}
|
||||
|
||||
+ (jint) overrideStyleBits:(jint)styleBits customTitleBarEnabled:(BOOL)customTitleBarEnabled fullscreen:(BOOL)fullscreen {
|
||||
if (customTitleBarEnabled) {
|
||||
styleBits |= MASK(FULL_WINDOW_CONTENT) | MASK(TRANSPARENT_TITLE_BAR);
|
||||
if (!fullscreen) styleBits &= ~MASK(TITLE_VISIBLE);
|
||||
}
|
||||
return styleBits;
|
||||
}
|
||||
|
||||
// updates _METHOD_PROP_BITMASK based properties on the window
|
||||
- (void) setPropertiesForStyleBits:(jint)bits mask:(jint)mask {
|
||||
if (IS(mask, RESIZABLE)) {
|
||||
@@ -467,14 +479,26 @@ AWT_NS_WINDOW_IMPLEMENTATION
|
||||
styleBits:(jint)bits
|
||||
frameRect:(NSRect)rect
|
||||
contentView:(NSView *)view
|
||||
transparentTitleBarHeight:(CGFloat)transparentTitleBarHeight
|
||||
{
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
self = [super init];
|
||||
if (self == nil) return nil; // no hope
|
||||
self.javaPlatformWindow = platformWindow;
|
||||
|
||||
NSUInteger newBits = bits;
|
||||
if (IS(bits, SHEET) && owner == nil) {
|
||||
newBits = bits & ~NSWindowStyleMaskDocModalWindow;
|
||||
}
|
||||
|
||||
_customTitleBarHeight = -1.0f; // Negative means uninitialized
|
||||
self.customTitleBarControlsVisible = YES;
|
||||
self.customTitleBarConstraints = nil;
|
||||
self.customTitleBarHeightConstraint = nil;
|
||||
self.customTitleBarButtonCenterXConstraints = nil;
|
||||
// Force properties if custom title bar is enabled, but store original value in self.styleBits.
|
||||
newBits = [AWTWindow overrideStyleBits:newBits customTitleBarEnabled:self.isCustomTitleBarEnabled fullscreen:false];
|
||||
|
||||
NSUInteger styleMask = [AWTWindow styleMaskForStyleBits:newBits];
|
||||
|
||||
NSRect contentRect = rect; //[NSWindow contentRectForFrameRect:rect styleMask:styleMask];
|
||||
@@ -485,10 +509,6 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
contentRect.size.height = 1.0;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self == nil) return nil; // no hope
|
||||
|
||||
if (IS(bits, UTILITY) ||
|
||||
IS(bits, HUD) ||
|
||||
IS(bits, HIDES_ON_DEACTIVATE) ||
|
||||
@@ -513,10 +533,9 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
self.isEnabled = YES;
|
||||
self.isMinimizing = NO;
|
||||
self.javaPlatformWindow = platformWindow;
|
||||
self.styleBits = bits;
|
||||
self.ownerWindow = owner;
|
||||
[self setPropertiesForStyleBits:styleBits mask:MASK(_METHOD_PROP_BITMASK)];
|
||||
[self setPropertiesForStyleBits:newBits mask:MASK(_METHOD_PROP_BITMASK)];
|
||||
|
||||
if (IS(bits, SHEET) && owner != nil) {
|
||||
[self.nsWindow setStyleMask: NSWindowStyleMaskDocModalWindow];
|
||||
@@ -528,9 +547,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
self.nsWindow.collectionBehavior = NSWindowCollectionBehaviorManaged;
|
||||
self.isEnterFullScreen = NO;
|
||||
|
||||
_transparentTitleBarHeight = transparentTitleBarHeight;
|
||||
if (transparentTitleBarHeight != 0.0 && !self.isFullScreen) {
|
||||
[self setUpTransparentTitleBar];
|
||||
if (self.isCustomTitleBarEnabled && !self.isFullScreen) {
|
||||
[self setUpCustomTitleBar];
|
||||
}
|
||||
|
||||
self.currentDisplayID = nil;
|
||||
@@ -651,6 +669,9 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
self.nsWindow = nil;
|
||||
self.ownerWindow = nil;
|
||||
self.currentDisplayID = nil;
|
||||
self.customTitleBarConstraints = nil;
|
||||
self.customTitleBarHeightConstraint = nil;
|
||||
self.customTitleBarButtonCenterXConstraints = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -1203,9 +1224,61 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) isTransparentTitleBarEnabled
|
||||
{
|
||||
return _transparentTitleBarHeight != 0.0;
|
||||
- (CGFloat) customTitleBarHeight {
|
||||
CGFloat h = _customTitleBarHeight;
|
||||
if (h < 0.0f) {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
GET_CPLATFORM_WINDOW_CLASS_RETURN(YES);
|
||||
DECLARE_FIELD_RETURN(jf_target, jc_CPlatformWindow, "target", "Ljava/awt/Window;", 0.0f);
|
||||
DECLARE_CLASS_RETURN(jc_Window, "java/awt/Window", 0.0f);
|
||||
DECLARE_METHOD_RETURN(jm_internalCustomTitleBarHeight, jc_Window, "internalCustomTitleBarHeight", "()F", 0.0f);
|
||||
DECLARE_METHOD_RETURN(jm_internalCustomTitleBarControlsVisible, jc_Window, "internalCustomTitleBarControlsVisible", "()Z", 0.0f);
|
||||
|
||||
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
|
||||
if (!platformWindow) return 0.0f;
|
||||
jobject target = (*env)->GetObjectField(env, platformWindow, jf_target);
|
||||
if (target) {
|
||||
h = (CGFloat) (*env)->CallFloatMethod(env, target, jm_internalCustomTitleBarHeight);
|
||||
self.customTitleBarControlsVisible = (BOOL) (*env)->CallBooleanMethod(env, target, jm_internalCustomTitleBarControlsVisible);
|
||||
(*env)->DeleteLocalRef(env, target);
|
||||
}
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, platformWindow);
|
||||
if (h < 0.0f) h = 0.0f;
|
||||
_customTitleBarHeight = h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
- (BOOL) isCustomTitleBarEnabled {
|
||||
CGFloat h = _customTitleBarHeight;
|
||||
if (h < 0.0f) h = self.customTitleBarHeight;
|
||||
return h > 0.0f;
|
||||
}
|
||||
|
||||
- (void) updateCustomTitleBarInsets:(BOOL)hasControls {
|
||||
CGFloat leftInset;
|
||||
if (hasControls) {
|
||||
CGFloat shrinkingFactor = self.customTitleBarButtonShrinkingFactor;
|
||||
CGFloat horizontalButtonOffset = shrinkingFactor * DefaultHorizontalTitleBarButtonOffset;
|
||||
leftInset = self.customTitleBarHeight + 2.0f * horizontalButtonOffset;
|
||||
} else leftInset = 0.0f;
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
GET_CPLATFORM_WINDOW_CLASS();
|
||||
DECLARE_FIELD(jf_target, jc_CPlatformWindow, "target", "Ljava/awt/Window;");
|
||||
DECLARE_CLASS(jc_Window, "java/awt/Window");
|
||||
DECLARE_METHOD(jm_internalCustomTitleBarUpdateInsets, jc_Window, "internalCustomTitleBarUpdateInsets", "(FF)V");
|
||||
|
||||
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
|
||||
if (!platformWindow) return;
|
||||
jobject target = (*env)->GetObjectField(env, platformWindow, jf_target);
|
||||
if (target) {
|
||||
(*env)->CallVoidMethod(env, target, jm_internalCustomTitleBarUpdateInsets, (jfloat) leftInset, (jfloat) 0.0f);
|
||||
(*env)->DeleteLocalRef(env, target);
|
||||
}
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, platformWindow);
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
@@ -1214,8 +1287,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
self.isEnterFullScreen = YES;
|
||||
|
||||
if ([self isTransparentTitleBarEnabled]) {
|
||||
[self resetTitleBar];
|
||||
if (self.isCustomTitleBarEnabled) {
|
||||
[self resetCustomTitleBar];
|
||||
}
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
@@ -1233,6 +1306,10 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
|
||||
self.isEnterFullScreen = YES;
|
||||
|
||||
if (self.isCustomTitleBarEnabled) {
|
||||
[self forceHideCustomTitleBarTitle:NO];
|
||||
[self updateCustomTitleBarInsets:NO];
|
||||
}
|
||||
[self allowMovingChildrenBetweenSpaces:NO];
|
||||
[self fullScreenTransitionFinished];
|
||||
|
||||
@@ -1254,8 +1331,10 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
[self fullScreenTransitionStarted];
|
||||
|
||||
if ([self isTransparentTitleBarEnabled]) {
|
||||
if (self.isCustomTitleBarEnabled) {
|
||||
[self setWindowControlsHidden:YES];
|
||||
[self updateCustomTitleBarInsets:self.customTitleBarControlsVisible];
|
||||
[self forceHideCustomTitleBarTitle:YES];
|
||||
}
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
@@ -1280,9 +1359,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
[self fullScreenTransitionFinished];
|
||||
|
||||
if ([self isTransparentTitleBarEnabled]) {
|
||||
[self setUpTransparentTitleBar];
|
||||
[self setWindowControlsHidden:NO];
|
||||
if (self.isCustomTitleBarEnabled) {
|
||||
[self setUpCustomTitleBar];
|
||||
}
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
@@ -1379,15 +1457,13 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
|
||||
- (CGFloat) getTransparentTitleBarButtonShrinkingFactor
|
||||
{
|
||||
- (CGFloat) customTitleBarButtonShrinkingFactor {
|
||||
CGFloat minimumHeightWithoutShrinking = 28.0; // This is the smallest macOS title bar availabe with public APIs as of Monterey
|
||||
CGFloat shrinkingFactor = fmin(_transparentTitleBarHeight / minimumHeightWithoutShrinking, 1.0);
|
||||
CGFloat shrinkingFactor = fmin(self.customTitleBarHeight / minimumHeightWithoutShrinking, 1.0);
|
||||
return shrinkingFactor;
|
||||
}
|
||||
|
||||
- (void) setUpTransparentTitleBar
|
||||
{
|
||||
- (void) setUpCustomTitleBar {
|
||||
|
||||
/**
|
||||
* The view hierarchy normally looks as follows:
|
||||
@@ -1410,7 +1486,7 @@ static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
NSView* zoomButtonView = [self.nsWindow standardWindowButton:NSWindowZoomButton];
|
||||
NSView* miniaturizeButtonView = [self.nsWindow standardWindowButton:NSWindowMiniaturizeButton];
|
||||
if (!closeButtonView || !zoomButtonView || !miniaturizeButtonView) {
|
||||
NSLog(@"WARNING: setUpTransparentTitleBar closeButtonView=%@, zoomButtonView=%@, miniaturizeButtonView=%@",
|
||||
NSLog(@"WARNING: setUpCustomTitleBar closeButtonView=%@, zoomButtonView=%@, miniaturizeButtonView=%@",
|
||||
closeButtonView, zoomButtonView, miniaturizeButtonView);
|
||||
return;
|
||||
}
|
||||
@@ -1418,49 +1494,45 @@ static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
NSView* titlebarContainer = titlebar.superview;
|
||||
NSView* themeFrame = titlebarContainer.superview;
|
||||
if (!themeFrame) {
|
||||
NSLog(@"WARNING: setUpTransparentTitleBar titlebar=%@, titlebarContainer=%@, themeFrame=%@",
|
||||
NSLog(@"WARNING: setUpCustomTitleBar titlebar=%@, titlebarContainer=%@, themeFrame=%@",
|
||||
titlebar, titlebarContainer, themeFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
_transparentTitleBarConstraints = [[NSMutableArray alloc] init];
|
||||
self.customTitleBarConstraints = [[NSMutableArray alloc] init];
|
||||
titlebarContainer.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_transparentTitleBarHeightConstraint = [titlebarContainer.heightAnchor constraintEqualToConstant:_transparentTitleBarHeight];
|
||||
[_transparentTitleBarConstraints addObjectsFromArray:@[
|
||||
self.customTitleBarHeightConstraint = [titlebarContainer.heightAnchor constraintEqualToConstant:self.customTitleBarHeight];
|
||||
[self.customTitleBarConstraints addObjectsFromArray:@[
|
||||
[titlebarContainer.leftAnchor constraintEqualToAnchor:themeFrame.leftAnchor],
|
||||
[titlebarContainer.widthAnchor constraintEqualToAnchor:themeFrame.widthAnchor],
|
||||
[titlebarContainer.topAnchor constraintEqualToAnchor:themeFrame.topAnchor],
|
||||
_transparentTitleBarHeightConstraint,
|
||||
self.customTitleBarHeightConstraint,
|
||||
]];
|
||||
|
||||
AWTWindowDragView* windowDragView = [[AWTWindowDragView alloc] initWithPlatformWindow:self.javaPlatformWindow];
|
||||
[titlebar addSubview:windowDragView positioned:NSWindowBelow relativeTo:closeButtonView];
|
||||
|
||||
NSArray* viewsToStretch = [titlebarContainer.subviews arrayByAddingObject:windowDragView];
|
||||
for (NSView* view in viewsToStretch)
|
||||
[@[titlebar, windowDragView] enumerateObjectsUsingBlock:^(NSView* view, NSUInteger index, BOOL* stop)
|
||||
{
|
||||
view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_transparentTitleBarConstraints addObjectsFromArray:@[
|
||||
[view.leftAnchor constraintEqualToAnchor:titlebarContainer.leftAnchor],
|
||||
[view.rightAnchor constraintEqualToAnchor:titlebarContainer.rightAnchor],
|
||||
[view.topAnchor constraintEqualToAnchor:titlebarContainer.topAnchor],
|
||||
[view.bottomAnchor constraintEqualToAnchor:titlebarContainer.bottomAnchor],
|
||||
[self.customTitleBarConstraints addObjectsFromArray:@[
|
||||
[view.leftAnchor constraintEqualToAnchor:titlebarContainer.leftAnchor],
|
||||
[view.rightAnchor constraintEqualToAnchor:titlebarContainer.rightAnchor],
|
||||
[view.topAnchor constraintEqualToAnchor:titlebarContainer.topAnchor],
|
||||
[view.bottomAnchor constraintEqualToAnchor:titlebarContainer.bottomAnchor],
|
||||
]];
|
||||
}
|
||||
}];
|
||||
|
||||
for(NSView* view in titlebar.subviews)
|
||||
{
|
||||
view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
}
|
||||
|
||||
CGFloat shrinkingFactor = [self getTransparentTitleBarButtonShrinkingFactor];
|
||||
CGFloat shrinkingFactor = self.customTitleBarButtonShrinkingFactor;
|
||||
CGFloat horizontalButtonOffset = shrinkingFactor * DefaultHorizontalTitleBarButtonOffset;
|
||||
_transparentTitleBarButtonCenterXConstraints = [[NSMutableArray alloc] initWithCapacity:3];
|
||||
self.customTitleBarButtonCenterXConstraints = [[NSMutableArray alloc] initWithCapacity:3];
|
||||
[@[closeButtonView, miniaturizeButtonView, zoomButtonView] enumerateObjectsUsingBlock:^(NSView* button, NSUInteger index, BOOL* stop)
|
||||
{
|
||||
NSLayoutConstraint* buttonCenterXConstraint = [button.centerXAnchor constraintEqualToAnchor:titlebarContainer.leftAnchor constant:(_transparentTitleBarHeight/2.0 + (index * horizontalButtonOffset))];
|
||||
[_transparentTitleBarButtonCenterXConstraints addObject:buttonCenterXConstraint];
|
||||
[_transparentTitleBarConstraints addObjectsFromArray:@[
|
||||
button.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint* buttonCenterXConstraint = [button.centerXAnchor constraintEqualToAnchor:titlebarContainer.leftAnchor
|
||||
constant:(self.customTitleBarHeight / 2.0 + (index * horizontalButtonOffset))];
|
||||
[self.customTitleBarButtonCenterXConstraints addObject:buttonCenterXConstraint];
|
||||
[self.customTitleBarConstraints addObjectsFromArray:@[
|
||||
[button.widthAnchor constraintLessThanOrEqualToAnchor:titlebarContainer.heightAnchor multiplier:0.5],
|
||||
// Those corrections are required to keep the icons perfectly round because macOS adds a constant 2 px in resulting height to their frame
|
||||
[button.heightAnchor constraintEqualToAnchor: button.widthAnchor multiplier:14.0/12.0 constant:-2.0],
|
||||
@@ -1469,44 +1541,45 @@ static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
]];
|
||||
}];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:_transparentTitleBarConstraints];
|
||||
[NSLayoutConstraint activateConstraints:self.customTitleBarConstraints];
|
||||
// These properties are already retained, release them so that retainCount = 1
|
||||
[self.customTitleBarConstraints release];
|
||||
[self.customTitleBarButtonCenterXConstraints release];
|
||||
|
||||
[self setWindowControlsHidden:!self.customTitleBarControlsVisible];
|
||||
[self updateCustomTitleBarInsets:self.customTitleBarControlsVisible];
|
||||
}
|
||||
|
||||
- (void) updateTransparentTitleBarConstraints
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
_transparentTitleBarHeightConstraint.constant = _transparentTitleBarHeight;
|
||||
CGFloat shrinkingFactor = [self getTransparentTitleBarButtonShrinkingFactor];
|
||||
CGFloat horizontalButtonOffset = shrinkingFactor * DefaultHorizontalTitleBarButtonOffset;
|
||||
[_transparentTitleBarButtonCenterXConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint* buttonConstraint, NSUInteger index, BOOL *stop)
|
||||
{
|
||||
buttonConstraint.constant = (_transparentTitleBarHeight/2.0 + (index * horizontalButtonOffset));
|
||||
}];
|
||||
});
|
||||
- (void) updateCustomTitleBarConstraints {
|
||||
self.customTitleBarHeightConstraint.constant = self.customTitleBarHeight;
|
||||
CGFloat shrinkingFactor = self.customTitleBarButtonShrinkingFactor;
|
||||
CGFloat horizontalButtonOffset = shrinkingFactor * DefaultHorizontalTitleBarButtonOffset;
|
||||
[self.customTitleBarButtonCenterXConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint* buttonConstraint, NSUInteger index, BOOL *stop)
|
||||
{
|
||||
buttonConstraint.constant = (self.customTitleBarHeight / 2.0 + (index * horizontalButtonOffset));
|
||||
}];
|
||||
[self setWindowControlsHidden:!self.customTitleBarControlsVisible];
|
||||
[self updateCustomTitleBarInsets:self.customTitleBarControlsVisible];
|
||||
}
|
||||
|
||||
- (void) resetTitleBar
|
||||
{
|
||||
// See [setUpTransparentTitleBar] for the view hierarchy we're working with
|
||||
- (void) resetCustomTitleBar {
|
||||
// See [setUpCustomTitleBar] for the view hierarchy we're working with
|
||||
NSView* closeButtonView = [self.nsWindow standardWindowButton:NSWindowCloseButton];
|
||||
NSView* titlebar = closeButtonView.superview;
|
||||
NSView* titlebarContainer = titlebar.superview;
|
||||
if (!titlebarContainer) {
|
||||
NSLog(@"WARNING: resetTitleBar closeButtonView=%@, titlebar=%@, titlebarContainer=%@",
|
||||
NSLog(@"WARNING: resetCustomTitleBar closeButtonView=%@, titlebar=%@, titlebarContainer=%@",
|
||||
closeButtonView, titlebar, titlebarContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:_transparentTitleBarConstraints];
|
||||
[NSLayoutConstraint deactivateConstraints:self.customTitleBarConstraints];
|
||||
|
||||
AWTWindowDragView* windowDragView = nil;
|
||||
for (NSView* view in [titlebar.subviews arrayByAddingObjectsFromArray:titlebarContainer.subviews]) {
|
||||
for (NSView* view in titlebar.subviews) {
|
||||
if ([view isMemberOfClass:[AWTWindowDragView class]]) {
|
||||
windowDragView = view;
|
||||
}
|
||||
if (view.translatesAutoresizingMaskIntoConstraints == NO) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (windowDragView != nil) {
|
||||
@@ -1516,56 +1589,75 @@ static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
titlebarContainer.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
titlebar.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
|
||||
_transparentTitleBarConstraints = nil;
|
||||
_transparentTitleBarHeightConstraint = nil;
|
||||
_transparentTitleBarButtonCenterXConstraints = nil;
|
||||
self.customTitleBarConstraints = nil;
|
||||
self.customTitleBarHeightConstraint = nil;
|
||||
self.customTitleBarButtonCenterXConstraints = nil;
|
||||
|
||||
[self setWindowControlsHidden:NO];
|
||||
[self updateCustomTitleBarInsets:NO];
|
||||
}
|
||||
|
||||
- (void) setWindowControlsHidden: (BOOL) hidden
|
||||
{
|
||||
[self.nsWindow standardWindowButton:NSWindowCloseButton].superview.hidden = hidden;
|
||||
- (void) setWindowControlsHidden: (BOOL) hidden {
|
||||
[self.nsWindow standardWindowButton:NSWindowCloseButton].hidden = hidden;
|
||||
[self.nsWindow standardWindowButton:NSWindowZoomButton].hidden = hidden;
|
||||
[self.nsWindow standardWindowButton:NSWindowMiniaturizeButton].hidden = hidden;
|
||||
}
|
||||
|
||||
- (BOOL) isFullScreen
|
||||
{
|
||||
- (BOOL) isFullScreen {
|
||||
NSUInteger masks = [self.nsWindow styleMask];
|
||||
return (masks & NSWindowStyleMaskFullScreen) != 0;
|
||||
}
|
||||
|
||||
- (void) setTransparentTitleBarHeight: (CGFloat) transparentTitleBarHeight
|
||||
{
|
||||
if (_transparentTitleBarHeight == transparentTitleBarHeight) return;
|
||||
- (void) forceHideCustomTitleBarTitle: (BOOL) hide {
|
||||
jint bits = self.styleBits;
|
||||
if (hide) bits &= ~MASK(TITLE_VISIBLE);
|
||||
[self setPropertiesForStyleBits:bits mask:MASK(TITLE_VISIBLE)];
|
||||
}
|
||||
|
||||
if (_transparentTitleBarHeight != 0.0f) {
|
||||
_transparentTitleBarHeight = transparentTitleBarHeight;
|
||||
if (transparentTitleBarHeight == 0.0f) {
|
||||
if (!self.isFullScreen) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[self resetTitleBar];
|
||||
});
|
||||
- (void) updateCustomTitleBar {
|
||||
_customTitleBarHeight = -1.0f; // Reset for lazy init
|
||||
BOOL enabled = self.isCustomTitleBarEnabled;
|
||||
BOOL fullscreen = self.isFullScreen;
|
||||
|
||||
jint mask = [AWTWindow affectedStyleMaskForCustomTitleBar];
|
||||
jint newBits = [AWTWindow overrideStyleBits:self.styleBits customTitleBarEnabled:enabled fullscreen:fullscreen];
|
||||
// Copied from nativeSetNSWindowStyleBits:
|
||||
// The content view must be resized first, otherwise the window will be resized to fit the existing
|
||||
// content view.
|
||||
NSUInteger styleMask = [AWTWindow styleMaskForStyleBits:newBits];
|
||||
if (!fullscreen) {
|
||||
NSRect frame = [nsWindow frame];
|
||||
NSRect screenContentRect = [NSWindow contentRectForFrameRect:frame styleMask:styleMask];
|
||||
NSRect contentFrame = NSMakeRect(screenContentRect.origin.x - frame.origin.x,
|
||||
screenContentRect.origin.y - frame.origin.y,
|
||||
screenContentRect.size.width,
|
||||
screenContentRect.size.height);
|
||||
nsWindow.contentView.frame = contentFrame;
|
||||
}
|
||||
// NSWindowStyleMaskFullScreen bit shouldn't be updated directly
|
||||
[nsWindow setStyleMask:(((NSWindowStyleMask) styleMask) & ~NSWindowStyleMaskFullScreen |
|
||||
nsWindow.styleMask & NSWindowStyleMaskFullScreen)];
|
||||
// calls methods on NSWindow to change other properties, based on the mask
|
||||
[self setPropertiesForStyleBits:newBits mask:mask];
|
||||
if (!fullscreen) [self _deliverMoveResizeEvent];
|
||||
|
||||
if (enabled != (self.customTitleBarConstraints != nil)) {
|
||||
if (!fullscreen) {
|
||||
if (self.isCustomTitleBarEnabled) {
|
||||
[self setUpCustomTitleBar];
|
||||
} else {
|
||||
[self resetCustomTitleBar];
|
||||
}
|
||||
} else if (_transparentTitleBarHeightConstraint != nil || _transparentTitleBarButtonCenterXConstraints != nil) {
|
||||
[self updateTransparentTitleBarConstraints];
|
||||
}
|
||||
} else {
|
||||
_transparentTitleBarHeight = transparentTitleBarHeight;
|
||||
if (!self.isFullScreen) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[self setUpTransparentTitleBar];
|
||||
});
|
||||
}
|
||||
} else if (enabled) {
|
||||
[self updateCustomTitleBarConstraints];
|
||||
}
|
||||
}
|
||||
|
||||
@end // AWTWindow
|
||||
|
||||
@implementation AWTWindowDragView {
|
||||
CGFloat _accumulatedDragDelta;
|
||||
enum WindowDragState {
|
||||
NO_DRAG, // Mouse not dragging
|
||||
SKIP_DRAG, // Mouse dragging in non-draggable area
|
||||
DRAG, // Mouse is dragging window
|
||||
} _draggingWindow;
|
||||
BOOL _dragging;
|
||||
}
|
||||
|
||||
- (id) initWithPlatformWindow:(jobject)javaPlatformWindow {
|
||||
@@ -1576,77 +1668,100 @@ static const CGFloat DefaultHorizontalTitleBarButtonOffset = 20.0;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)mouseDownCanMoveWindow
|
||||
{
|
||||
- (BOOL) areCustomTitleBarNativeActionsAllowed {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
GET_CPLATFORM_WINDOW_CLASS_RETURN(YES);
|
||||
DECLARE_FIELD_RETURN(jf_target, jc_CPlatformWindow, "target", "Ljava/awt/Window;", YES);
|
||||
DECLARE_CLASS_RETURN(jc_Window, "java/awt/Window", YES);
|
||||
DECLARE_FIELD_RETURN(jf_customTitleBarHitTest, jc_Window, "customTitleBarHitTest", "I", YES);
|
||||
|
||||
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
|
||||
if (!platformWindow) return YES;
|
||||
jint hitTest = java_awt_Window_CustomTitleBar_HIT_UNDEFINED;
|
||||
jobject target = (*env)->GetObjectField(env, platformWindow, jf_target);
|
||||
if (target) {
|
||||
hitTest = (jint) (*env)->GetIntField(env, target, jf_customTitleBarHitTest);
|
||||
(*env)->DeleteLocalRef(env, target);
|
||||
}
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, platformWindow);
|
||||
return hitTest <= java_awt_Window_CustomTitleBar_HIT_TITLEBAR;
|
||||
}
|
||||
|
||||
- (BOOL) mouseDownCanMoveWindow {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (jint)hitTestCustomDecoration:(NSPoint)point
|
||||
{
|
||||
jint returnValue = java_awt_Window_CustomWindowDecoration_NO_HIT_SPOT;
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
|
||||
if (platformWindow != NULL) {
|
||||
GET_CPLATFORM_WINDOW_CLASS_RETURN(YES);
|
||||
DECLARE_FIELD_RETURN(jf_target, jc_CPlatformWindow, "target", "Ljava/awt/Window;", YES);
|
||||
DECLARE_CLASS_RETURN(jc_Window, "java/awt/Window", YES);
|
||||
DECLARE_METHOD_RETURN(jm_hitTestCustomDecoration, jc_Window, "hitTestCustomDecoration", "(II)I", YES);
|
||||
jobject awtWindow = (*env)->GetObjectField(env, platformWindow, jf_target);
|
||||
if (awtWindow != NULL) {
|
||||
NSRect frame = [self.window frame];
|
||||
float windowHeight = frame.size.height;
|
||||
returnValue = (*env)->CallIntMethod(env, awtWindow, jm_hitTestCustomDecoration, (jint) point.x, (jint) (windowHeight - point.y));
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, awtWindow);
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, platformWindow);
|
||||
}
|
||||
return returnValue;
|
||||
- (BOOL) acceptsFirstMouse:(NSEvent *)event {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
_draggingWindow = NO_DRAG;
|
||||
_accumulatedDragDelta = 0.0;
|
||||
// We don't follow the regular responder chain here since the native window swallows events in some cases
|
||||
[[self.window contentView] deliverJavaMouseEvent:event];
|
||||
- (BOOL) shouldDelayWindowOrderingForEvent:(NSEvent *)event {
|
||||
return [[self.window contentView] shouldDelayWindowOrderingForEvent:event];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)event
|
||||
{
|
||||
if (_draggingWindow == NO_DRAG) {
|
||||
jint hitSpot = [self hitTestCustomDecoration:event.locationInWindow];
|
||||
switch (hitSpot) {
|
||||
case java_awt_Window_CustomWindowDecoration_DRAGGABLE_AREA:
|
||||
// Start drag only after 4px threshold inside DRAGGABLE_AREA
|
||||
if ((_accumulatedDragDelta += fabs(event.deltaX) + fabs(event.deltaY)) <= 4.0) break;
|
||||
case java_awt_Window_CustomWindowDecoration_NO_HIT_SPOT:
|
||||
[self.window performWindowDragWithEvent:event];
|
||||
_draggingWindow = DRAG;
|
||||
break;
|
||||
default:
|
||||
_draggingWindow = SKIP_DRAG;
|
||||
- (void) mouseDown: (NSEvent *)event {
|
||||
_dragging = NO;
|
||||
[[self.window contentView] mouseDown:event];
|
||||
}
|
||||
- (void) mouseUp: (NSEvent *)event {
|
||||
[[self.window contentView] mouseUp:event];
|
||||
if (event.clickCount == 2 && [self areCustomTitleBarNativeActionsAllowed]) {
|
||||
NSString *action = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleActionOnDoubleClick"];
|
||||
if (action != nil && [action caseInsensitiveCompare:@"Minimize"] == NSOrderedSame) {
|
||||
[self.window performMiniaturize:nil];
|
||||
} else if (action == nil || [action caseInsensitiveCompare:@"None"] != NSOrderedSame) { // action == "Maximize" (default)
|
||||
[self.window performZoom:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event
|
||||
{
|
||||
if (_draggingWindow == DRAG) {
|
||||
_draggingWindow = NO_DRAG;
|
||||
} else {
|
||||
jint hitSpot = [self hitTestCustomDecoration:event.locationInWindow];
|
||||
if (event.clickCount == 2 && hitSpot == java_awt_Window_CustomWindowDecoration_NO_HIT_SPOT) {
|
||||
if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleActionOnDoubleClick"] isEqualToString:@"Maximize"]) {
|
||||
[self.window performZoom:nil];
|
||||
} else {
|
||||
[self.window performMiniaturize:nil];
|
||||
}
|
||||
- (void) rightMouseDown: (NSEvent *)event {
|
||||
[[self.window contentView] rightMouseDown:event];
|
||||
}
|
||||
- (void) rightMouseUp: (NSEvent *)event {
|
||||
[[self.window contentView] rightMouseUp:event];
|
||||
}
|
||||
- (void) otherMouseDown: (NSEvent *)event {
|
||||
[[self.window contentView] otherMouseDown:event];
|
||||
}
|
||||
- (void) otherMouseUp: (NSEvent *)event {
|
||||
[[self.window contentView] otherMouseUp:event];
|
||||
}
|
||||
- (void) mouseMoved: (NSEvent *)event {
|
||||
[[self.window contentView] mouseMoved:event];
|
||||
}
|
||||
- (void) mouseDragged: (NSEvent *)event {
|
||||
if (!_dragging) {
|
||||
_dragging = YES;
|
||||
if ([self areCustomTitleBarNativeActionsAllowed]) {
|
||||
[self.window performWindowDragWithEvent:event];
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't follow the regular responder chain here since the native window swallows events in some cases
|
||||
[[self.window contentView] deliverJavaMouseEvent:event];
|
||||
}
|
||||
[[self.window contentView] mouseDragged:event];
|
||||
}
|
||||
- (void) rightMouseDragged: (NSEvent *)event {
|
||||
[[self.window contentView] rightMouseDragged:event];
|
||||
}
|
||||
- (void) otherMouseDragged: (NSEvent *)event {
|
||||
[[self.window contentView] otherMouseDragged:event];
|
||||
}
|
||||
- (void) mouseEntered: (NSEvent *)event {
|
||||
[[self.window contentView] mouseEntered:event];
|
||||
}
|
||||
- (void) mouseExited: (NSEvent *)event {
|
||||
[[self.window contentView] mouseExited:event];
|
||||
}
|
||||
- (void) scrollWheel: (NSEvent*) event {
|
||||
[[self.window contentView] scrollWheel:event];
|
||||
}
|
||||
- (void) keyDown: (NSEvent *)event {
|
||||
[[self.window contentView] keyDown:event];
|
||||
}
|
||||
- (void) keyUp: (NSEvent *)event {
|
||||
[[self.window contentView] keyUp:event];
|
||||
}
|
||||
- (void) flagsChanged: (NSEvent *)event {
|
||||
[[self.window contentView] flagsChanged:event];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1673,10 +1788,10 @@ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeSetAllowAutom
|
||||
/*
|
||||
* Class: sun_lwawt_macosx_CPlatformWindow
|
||||
* Method: nativeCreateNSWindow
|
||||
* Signature: (JJIDDDDD)J
|
||||
* Signature: (JJIDDDD)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeCreateNSWindow
|
||||
(JNIEnv *env, jobject obj, jlong contentViewPtr, jlong ownerPtr, jlong styleBits, jdouble x, jdouble y, jdouble w, jdouble h, jdouble transparentTitleBarHeight)
|
||||
(JNIEnv *env, jobject obj, jlong contentViewPtr, jlong ownerPtr, jlong styleBits, jdouble x, jdouble y, jdouble w, jdouble h)
|
||||
{
|
||||
__block AWTWindow *window = nil;
|
||||
|
||||
@@ -1703,8 +1818,7 @@ JNI_COCOA_ENTER(env);
|
||||
ownerWindow:owner
|
||||
styleBits:styleBits
|
||||
frameRect:frameRect
|
||||
contentView:contentView
|
||||
transparentTitleBarHeight:(CGFloat)transparentTitleBarHeight];
|
||||
contentView:contentView];
|
||||
// the window is released is CPlatformWindow.nativeDispose()
|
||||
|
||||
if (window) {
|
||||
@@ -1736,9 +1850,13 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
|
||||
|
||||
BOOL customTitleBarEnabled = window.isCustomTitleBarEnabled;
|
||||
BOOL fullscreen = window.isFullScreen;
|
||||
// scans the bit field, and only updates the values requested by the mask
|
||||
// (this implicitly handles the _CALLBACK_PROP_BITMASK case, since those are passive reads)
|
||||
jint newBits = window.styleBits & ~mask | bits & mask;
|
||||
jint actualBits = window.styleBits & ~mask | bits & mask;
|
||||
// Force properties if custom title bar is enabled, but store original value in self.styleBits.
|
||||
jint newBits = [AWTWindow overrideStyleBits:actualBits customTitleBarEnabled:customTitleBarEnabled fullscreen:fullscreen];
|
||||
|
||||
BOOL resized = NO;
|
||||
|
||||
@@ -1746,7 +1864,8 @@ JNI_COCOA_ENTER(env);
|
||||
// The content view must be resized first, otherwise the window will be resized to fit the existing
|
||||
// content view.
|
||||
if (IS(mask, FULL_WINDOW_CONTENT)) {
|
||||
if (IS(newBits, FULL_WINDOW_CONTENT) != IS(window.styleBits, FULL_WINDOW_CONTENT)) {
|
||||
if ((IS(newBits, FULL_WINDOW_CONTENT) != IS(window.styleBits, FULL_WINDOW_CONTENT) ||
|
||||
customTitleBarEnabled) && !fullscreen) {
|
||||
NSRect frame = [nsWindow frame];
|
||||
NSUInteger styleMask = [AWTWindow styleMaskForStyleBits:newBits];
|
||||
NSRect screenContentRect = [NSWindow contentRectForFrameRect:frame styleMask:styleMask];
|
||||
@@ -1777,9 +1896,7 @@ JNI_COCOA_ENTER(env);
|
||||
[window setPropertiesForStyleBits:newBits mask:mask];
|
||||
}
|
||||
|
||||
window.styleBits = newBits;
|
||||
|
||||
NSString *uiStyle = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
|
||||
window.styleBits = actualBits;
|
||||
|
||||
if (resized) {
|
||||
[window _deliverMoveResizeEvent];
|
||||
@@ -2450,14 +2567,16 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeDelayShow
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeSetTransparentTitleBarHeight
|
||||
(JNIEnv *env, jclass clazz, jlong windowPtr, jfloat transparentTitleBarHeight)
|
||||
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeUpdateCustomTitleBar
|
||||
(JNIEnv *env, jclass clazz, jlong windowPtr)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
NSWindow *nsWindow = (NSWindow *)jlong_to_ptr(windowPtr);
|
||||
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
|
||||
[window setTransparentTitleBarHeight:((CGFloat) transparentTitleBarHeight)];
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
|
||||
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
|
||||
[window updateCustomTitleBar];
|
||||
}];
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ public class JBRApiModule {
|
||||
.withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer")
|
||||
.service("com.jetbrains.GraphicsUtils")
|
||||
.withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate")
|
||||
.clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D");
|
||||
.clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D")
|
||||
.service("com.jetbrains.WindowDecorations", "java.awt.Window$WindowDecorations")
|
||||
.proxy("com.jetbrains.WindowDecorations$CustomTitleBar", "java.awt.Window$CustomTitleBar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4926,6 +4926,10 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Custom title bar hit test is performed by all mouse events except MOUSE_EXITED and MOUSE_WHEEL
|
||||
Window customTitleBarWindow = e.getID() >= MouseEvent.MOUSE_FIRST && e.getID() <= MouseEvent.MOUSE_LAST &&
|
||||
e.getID() != MouseEvent.MOUSE_EXITED && e.getID() != MouseEvent.MOUSE_WHEEL ?
|
||||
updateCustomTitleBarHitTest(true) : null;
|
||||
|
||||
/*
|
||||
* 2. Allow the Toolkit to pass this to AWTEventListeners.
|
||||
@@ -5090,6 +5094,10 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
}
|
||||
}
|
||||
|
||||
if (customTitleBarWindow != null) {
|
||||
customTitleBarWindow.applyCustomTitleBarHitTest();
|
||||
}
|
||||
|
||||
if (SunToolkit.isTouchKeyboardAutoShowEnabled() &&
|
||||
(toolkit instanceof SunToolkit) &&
|
||||
((e instanceof MouseEvent) || (e instanceof FocusEvent))) {
|
||||
@@ -10580,4 +10588,14 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
public static void disableInputMethodSupport() {
|
||||
INPUT_METHODS_DISABLED = true;
|
||||
}
|
||||
|
||||
Window updateCustomTitleBarHitTest(boolean allowNativeActions) {
|
||||
Container p = parent;
|
||||
if (p == null) return null;
|
||||
if ((eventMask & (AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK)) != 0 ||
|
||||
mouseListener != null || mouseMotionListener != null || mouseWheelListener != null || cursor != null) {
|
||||
allowNativeActions = false;
|
||||
}
|
||||
return p.updateCustomTitleBarHitTest(allowNativeActions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,12 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.AccessController;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
@@ -4016,28 +4018,194 @@ public class Window extends Container implements Accessible {
|
||||
}
|
||||
}
|
||||
|
||||
private transient volatile boolean hasCustomDecoration;
|
||||
private transient volatile List<Map.Entry<Shape, Integer>> customDecorHitTestSpots;
|
||||
private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected
|
||||
// ************************** Custom title bar support *******************************
|
||||
|
||||
// called from native
|
||||
private int hitTestCustomDecoration(int x, int y) {
|
||||
var spots = customDecorHitTestSpots;
|
||||
if (spots == null) return CustomWindowDecoration.NO_HIT_SPOT;
|
||||
for (var spot : spots) {
|
||||
if (spot.getKey().contains(x, y)) return spot.getValue();
|
||||
}
|
||||
return CustomWindowDecoration.NO_HIT_SPOT;
|
||||
private static class WindowDecorations {
|
||||
WindowDecorations() { CustomTitleBar.assertSupported(); }
|
||||
void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar) { ((Window) frame).setCustomTitleBar(customTitleBar); }
|
||||
void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar) { ((Window) dialog).setCustomTitleBar(customTitleBar); }
|
||||
CustomTitleBar createCustomTitleBar() { return new CustomTitleBar(); }
|
||||
}
|
||||
|
||||
private static class CustomWindowDecoration {
|
||||
|
||||
CustomWindowDecoration() {
|
||||
if (Win.INSTANCE == null && MacOS.INSTANCE == null) {
|
||||
private static class CustomTitleBar implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -2330620200902241173L;
|
||||
|
||||
@Native
|
||||
private static final int
|
||||
HIT_UNDEFINED = 0,
|
||||
HIT_TITLEBAR = 1,
|
||||
HIT_CLIENT = 2,
|
||||
HIT_MINIMIZE_BUTTON = 3,
|
||||
HIT_MAXIMIZE_BUTTON = 4,
|
||||
HIT_CLOSE_BUTTON = 5;
|
||||
|
||||
private static void assertSupported() {
|
||||
if (CustomTitleBarPeer.INSTANCE == null) {
|
||||
throw new JBRApi.ServiceNotAvailableException("Only supported on Windows and macOS");
|
||||
}
|
||||
}
|
||||
|
||||
private volatile Window window;
|
||||
private float height;
|
||||
private HashMap<String, Object> properties;
|
||||
private final float[] insets = new float[2];
|
||||
|
||||
private float getHeight() { return height; }
|
||||
private void setHeight(float height) {
|
||||
if (height <= 0.0f) throw new IllegalArgumentException("TitleBar height must be positive");
|
||||
this.height = height;
|
||||
notifyUpdate();
|
||||
}
|
||||
private Map<String, Object> getProperties() {
|
||||
return properties != null ? Collections.unmodifiableMap(properties) : Collections.emptyMap();
|
||||
}
|
||||
private void putProperties(Map<String, ?> m) {
|
||||
if (properties == null) properties = new HashMap<>();
|
||||
properties.putAll(m);
|
||||
notifyUpdate();
|
||||
}
|
||||
private void putProperty(String key, Object value) {
|
||||
if (properties == null) properties = new HashMap<>();
|
||||
properties.put(key, value);
|
||||
notifyUpdate();
|
||||
}
|
||||
private float getLeftInset() { return insets[0]; }
|
||||
private float getRightInset() { return insets[1]; }
|
||||
private void forceHitTest(boolean client) {
|
||||
Window w = window;
|
||||
if (w != null) {
|
||||
w.pendingCustomTitleBarHitTest = client ? CustomTitleBar.HIT_CLIENT : CustomTitleBar.HIT_TITLEBAR;
|
||||
w.applyCustomTitleBarHitTest();
|
||||
}
|
||||
}
|
||||
private Window getContainingWindow() { return window; }
|
||||
|
||||
private void notifyUpdate() {
|
||||
// Do not synchronize on itself to prevent possible deadlocks, synchronize on parent window instead.
|
||||
Window w = window;
|
||||
if (w != null) {
|
||||
synchronized (w) {
|
||||
if (w == window) w.setCustomTitleBar(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CustomTitleBar customTitleBar;
|
||||
|
||||
/**
|
||||
* Convenience method for JNI access.
|
||||
* @return 0 if there's no custom title bar, >0 otherwise.
|
||||
*/
|
||||
private float internalCustomTitleBarHeight() {
|
||||
CustomTitleBar t = customTitleBar;
|
||||
return t != null ? t.getHeight() : 0.0f;
|
||||
}
|
||||
/**
|
||||
* Convenience method for JNI access.
|
||||
* @return true if custom title bar controls are visible.
|
||||
*/
|
||||
private boolean internalCustomTitleBarControlsVisible() {
|
||||
CustomTitleBar t = customTitleBar;
|
||||
return t == null || Boolean.TRUE.equals(t.getProperties().getOrDefault("controls.visible", Boolean.TRUE));
|
||||
}
|
||||
/**
|
||||
* Convenience method for JNI access.
|
||||
*/
|
||||
private void internalCustomTitleBarUpdateInsets(float left, float right) {
|
||||
CustomTitleBar t = customTitleBar;
|
||||
if (t != null) {
|
||||
t.insets[0] = left;
|
||||
t.insets[1] = right;
|
||||
}
|
||||
}
|
||||
|
||||
private interface CustomTitleBarPeer {
|
||||
CustomTitleBarPeer INSTANCE = (CustomTitleBarPeer) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("update", "updateCustomTitleBar", "sun.awt.windows.WFramePeer", "sun.lwawt.macosx.CPlatformWindow").build();
|
||||
void update(ComponentPeer peer);
|
||||
}
|
||||
|
||||
private synchronized void setCustomTitleBar(CustomTitleBar t) {
|
||||
if (t != null && t.getHeight() <= 0.0f) throw new IllegalArgumentException("TitleBar height must be positive");
|
||||
if (customTitleBar != null && customTitleBar != t) {
|
||||
customTitleBar.window = null;
|
||||
customTitleBar.insets[0] = customTitleBar.insets[1] = 0;
|
||||
}
|
||||
customTitleBar = t;
|
||||
if (t != null) t.window = this;
|
||||
if (CustomTitleBarPeer.INSTANCE != null) {
|
||||
CustomTitleBarPeer.INSTANCE.update(peer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to allow/prevent native title bar actions: window drag and double-click maximization.
|
||||
*/
|
||||
private transient volatile int customTitleBarHitTest = CustomTitleBar.HIT_UNDEFINED;
|
||||
/**
|
||||
* Temporary value which will eventually substitute {@code customTitleBarHitTest}.
|
||||
* @see #applyCustomTitleBarHitTest()
|
||||
*/
|
||||
private transient int pendingCustomTitleBarHitTest = CustomTitleBar.HIT_UNDEFINED;
|
||||
/**
|
||||
* Unlike {@code customTitleBarHitTest}, {@code customTitleBarHitTestQuery} is not updated on each mouse event.
|
||||
* Reset this to {@code CustomTitleBar.HIT_UNDEFINED} and it will be set and fixed on the next hit test update.
|
||||
*/
|
||||
private transient volatile int customTitleBarHitTestQuery = CustomTitleBar.HIT_UNDEFINED;
|
||||
|
||||
@Override
|
||||
Window updateCustomTitleBarHitTest(boolean allowNativeActions) {
|
||||
if (customTitleBar == null) return null;
|
||||
pendingCustomTitleBarHitTest = allowNativeActions ? CustomTitleBar.HIT_TITLEBAR : CustomTitleBar.HIT_CLIENT;
|
||||
if (customDecorHitTestSpots != null) { // Compatibility bridge, to be removed with old API
|
||||
Point p = getMousePosition(true);
|
||||
if (p == null) return this;
|
||||
// Perform old-style hit test
|
||||
int result = CustomWindowDecoration.NO_HIT_SPOT;
|
||||
for (var spot : customDecorHitTestSpots) {
|
||||
if (spot.getKey().contains(p.x, p.y)) {
|
||||
result = spot.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Convert old hit test value to new one
|
||||
pendingCustomTitleBarHitTest = switch (result) {
|
||||
case CustomWindowDecoration.MINIMIZE_BUTTON -> CustomTitleBar.HIT_MINIMIZE_BUTTON;
|
||||
case CustomWindowDecoration.MAXIMIZE_BUTTON -> CustomTitleBar.HIT_MAXIMIZE_BUTTON;
|
||||
case CustomWindowDecoration.CLOSE_BUTTON -> CustomTitleBar.HIT_CLOSE_BUTTON;
|
||||
case CustomWindowDecoration.NO_HIT_SPOT, CustomWindowDecoration.DRAGGABLE_AREA -> CustomTitleBar.HIT_TITLEBAR;
|
||||
default -> CustomTitleBar.HIT_CLIENT;
|
||||
};
|
||||
// Overwrite hit test value
|
||||
applyCustomTitleBarHitTest();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void applyCustomTitleBarHitTest() {
|
||||
// Normally this will only be updated on EDT, so we don't care about non-atomic update.
|
||||
if (customTitleBarHitTestQuery == CustomTitleBar.HIT_UNDEFINED) {
|
||||
customTitleBarHitTestQuery = pendingCustomTitleBarHitTest;
|
||||
}
|
||||
customTitleBarHitTest = pendingCustomTitleBarHitTest;
|
||||
}
|
||||
|
||||
// *** Following custom decorations code is kept for backward compatibility and will be removed soon. ***
|
||||
|
||||
@Deprecated
|
||||
private transient volatile boolean hasCustomDecoration;
|
||||
@Deprecated
|
||||
private transient volatile List<Map.Entry<Shape, Integer>> customDecorHitTestSpots;
|
||||
@Deprecated
|
||||
private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected
|
||||
@Deprecated
|
||||
private static class CustomWindowDecoration {
|
||||
|
||||
CustomWindowDecoration() { CustomTitleBar.assertSupported(); }
|
||||
|
||||
@Native public static final int
|
||||
NO_HIT_SPOT = 0,
|
||||
OTHER_HIT_SPOT = 1,
|
||||
@@ -4049,11 +4217,7 @@ public class Window extends Container implements Accessible {
|
||||
|
||||
void setCustomDecorationEnabled(Window window, boolean enabled) {
|
||||
window.hasCustomDecoration = enabled;
|
||||
if (Win.INSTANCE != null) {
|
||||
Win.INSTANCE.updateCustomDecoration(window.peer);
|
||||
} else if (MacOS.INSTANCE != null && window.customDecorTitleBarHeight > 0f) {
|
||||
MacOS.INSTANCE.setTitleBarHeight(window, window.peer, enabled ? window.customDecorTitleBarHeight : 0);
|
||||
}
|
||||
setTitleBar(window, enabled ? Math.max(window.customDecorTitleBarHeight, 0.01f) : 0);
|
||||
}
|
||||
boolean isCustomDecorationEnabled(Window window) {
|
||||
return window.hasCustomDecoration;
|
||||
@@ -4067,42 +4231,32 @@ public class Window extends Container implements Accessible {
|
||||
}
|
||||
|
||||
void setCustomDecorationTitleBarHeight(Window window, int height) {
|
||||
if (height >= 0) {
|
||||
window.customDecorTitleBarHeight = height;
|
||||
if (MacOS.INSTANCE != null && window.hasCustomDecoration) {
|
||||
MacOS.INSTANCE.setTitleBarHeight(window, window.peer, height);
|
||||
}
|
||||
}
|
||||
window.customDecorTitleBarHeight = height;
|
||||
setTitleBar(window, window.hasCustomDecoration ? Math.max(height, 0.01f) : 0);
|
||||
}
|
||||
int getCustomDecorationTitleBarHeight(Window window) {
|
||||
return window.customDecorTitleBarHeight;
|
||||
}
|
||||
|
||||
private interface Win {
|
||||
Win INSTANCE = (Win) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("updateCustomDecoration", "updateCustomDecoration", "sun.awt.windows.WFramePeer").build();
|
||||
void updateCustomDecoration(ComponentPeer peer);
|
||||
}
|
||||
|
||||
private interface MacOS {
|
||||
MacOS INSTANCE = (MacOS) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("setTitleBarHeight", "setCustomDecorationTitleBarHeight", "sun.lwawt.macosx.CPlatformWindow").build();
|
||||
void setTitleBarHeight(Window target, ComponentPeer peer, float height);
|
||||
// Bridge from old to new API
|
||||
private static void setTitleBar(Window window, float height) {
|
||||
if (height <= 0.0f) window.setCustomTitleBar(null);
|
||||
else {
|
||||
CustomTitleBar t = new CustomTitleBar();
|
||||
// Old API accepts title bar height with insets, subtract it for new API.
|
||||
// We use bottom insets here because top insets may change when toggling custom title bar, they are usually equal.
|
||||
if (window instanceof Frame f && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) {
|
||||
height -= window.getInsets().bottom;
|
||||
}
|
||||
t.setHeight(Math.max(height, 0.01f));
|
||||
// In old API versions there were no control buttons on Windows.
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) t.putProperty("controls.visible", false);
|
||||
window.setCustomTitleBar(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
boolean hasCustomDecoration() {
|
||||
return hasCustomDecoration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set via reflection (JB JdkEx API).
|
||||
*/
|
||||
@Deprecated
|
||||
void setHasCustomDecoration() {
|
||||
hasCustomDecoration = true;
|
||||
}
|
||||
// ************************** JBR stuff *******************************
|
||||
|
||||
private volatile boolean ignoreMouseEvents;
|
||||
|
||||
|
||||
@@ -1094,13 +1094,6 @@ public abstract class JComponent extends Container implements Serializable,
|
||||
clipH = clipRect.height;
|
||||
}
|
||||
|
||||
if(clipW > getWidth()) {
|
||||
clipW = getWidth();
|
||||
}
|
||||
if(clipH > getHeight()) {
|
||||
clipH = getHeight();
|
||||
}
|
||||
|
||||
if(getParent() != null && !(getParent() instanceof JComponent)) {
|
||||
adjustPaintFlags();
|
||||
shouldClearPaintFlags = true;
|
||||
|
||||
@@ -256,8 +256,30 @@ class WFramePeer extends WWindowPeer implements FramePeer {
|
||||
private native void synthesizeWmActivate(boolean activate);
|
||||
|
||||
// JBR API internals
|
||||
private static void updateCustomDecoration(ComponentPeer peer) {
|
||||
if (peer instanceof WFramePeer) ((WFramePeer) peer).updateCustomDecoration();
|
||||
private static void updateCustomTitleBar(ComponentPeer peer) {
|
||||
// In native code AwtDialog is actually a descendant of AwtFrame,
|
||||
// so we don't distinguish between WFramePeer and WDialogPeer here,
|
||||
// just treat WFramePeer like a base class.
|
||||
if (peer instanceof WFramePeer || peer instanceof WDialogPeer) {
|
||||
updateCustomTitleBar((WWindowPeer) peer);
|
||||
}
|
||||
}
|
||||
private native void updateCustomDecoration();
|
||||
private static native void updateCustomTitleBar(WWindowPeer peer);
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static boolean isWin11OrNewer() {
|
||||
String osName = AccessController.doPrivileged(new GetPropertyAction("os.name"));
|
||||
String osVersion = AccessController.doPrivileged(new GetPropertyAction("os.version"));
|
||||
if ("Windows 10".equals(osName)) {
|
||||
return false;
|
||||
} else {
|
||||
int version = 10;
|
||||
try {
|
||||
version = (int) Double.parseDouble(osVersion);
|
||||
} catch (NullPointerException | NumberFormatException ignored) {}
|
||||
return version >= 10;
|
||||
}
|
||||
}
|
||||
// Used from native
|
||||
private static final boolean WIN11_OR_NEWER = isWin11OrNewer();
|
||||
}
|
||||
|
||||
@@ -55,12 +55,8 @@ import java.awt.image.DataBufferInt;
|
||||
import java.awt.peer.WindowPeer;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.RootPaneContainer;
|
||||
@@ -1038,30 +1034,4 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer,
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// called from client via reflection
|
||||
@Deprecated
|
||||
private void setCustomDecorationHitTestSpots(List<Rectangle> hitTestSpots) {
|
||||
List<Map.Entry<Shape, Integer>> spots = new ArrayList<>();
|
||||
for (Rectangle spot : hitTestSpots) spots.add(Map.entry(spot, 1));
|
||||
try {
|
||||
Field f = Window.class.getDeclaredField("customDecorHitTestSpots");
|
||||
f.setAccessible(true);
|
||||
f.set(target, spots);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// called from client via reflection
|
||||
@Deprecated
|
||||
private void setCustomDecorationTitleBarHeight(int height) {
|
||||
try {
|
||||
Field f = Window.class.getDeclaredField("customDecorTitleBarHeight");
|
||||
f.setAccessible(true);
|
||||
f.set(target, height);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ typedef enum _PROCESS_DPI_AWARENESS {
|
||||
#endif
|
||||
|
||||
typedef BOOL(WINAPI AdjustWindowRectExForDpiFunc)(LPRECT, DWORD, BOOL, DWORD, UINT);
|
||||
typedef UINT(WINAPI GetDpiForWindowFunc)(HWND);
|
||||
|
||||
class AwtObject;
|
||||
typedef AwtObject* PDATA;
|
||||
|
||||
@@ -767,6 +767,18 @@ jobject AwtComponent::FindHeavyweightUnderCursor(BOOL useCache) {
|
||||
if (comp != NULL) {
|
||||
INT nHittest = (INT)::SendMessage(hit, WM_NCHITTEST,
|
||||
0, MAKELPARAM(p.x, p.y));
|
||||
|
||||
if (AwtFrame::IsTitleBarHitTest(nHittest)) {
|
||||
AwtWindow* window = comp->GetContainer();
|
||||
if (window != NULL && !window->IsSimpleWindow() &&
|
||||
((AwtFrame*) window)->HasCustomTitleBar()) {
|
||||
// In case of custom title bar, WindowFromPoint will return root frame, so search further
|
||||
ScreenToBottommostChild(hit, p.x, p.y);
|
||||
comp = AwtComponent::GetComponent(hit);
|
||||
if (comp != NULL) nHittest = HTCLIENT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix for BugTraq ID 4304024.
|
||||
* Allow a non-default cursor only for the client area.
|
||||
@@ -1338,11 +1350,6 @@ void SpyWinMessage(HWND hwnd, UINT message, LPCTSTR szComment) {
|
||||
|
||||
#endif /* SPY_MESSAGES */
|
||||
|
||||
static BOOL IsMouseEventFromTouch()
|
||||
{
|
||||
return (::GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH_MASK) == MOUSEEVENTF_FROMTOUCH;
|
||||
}
|
||||
|
||||
// consider making general function
|
||||
// T getClassStaticField(cls, field, default_val)
|
||||
static BOOL IsDefaultTouch()
|
||||
@@ -1651,11 +1658,39 @@ LRESULT AwtComponent::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
||||
mr = WmNcMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), LEFT_BUTTON);
|
||||
break;
|
||||
case WM_NCRBUTTONDOWN:
|
||||
mr = WmNcMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), RIGHT_BUTTON);
|
||||
break;
|
||||
case WM_NCMOUSEMOVE:
|
||||
mr = WmNcMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
break;
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
mr = WmNcMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), RIGHT_BUTTON);
|
||||
break;
|
||||
case WM_NCRBUTTONUP:
|
||||
mr = WmNcMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), RIGHT_BUTTON);
|
||||
break;
|
||||
case WM_NCMBUTTONDOWN:
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
mr = WmNcMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MIDDLE_BUTTON);
|
||||
break;
|
||||
case WM_NCMBUTTONUP:
|
||||
mr = WmNcMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MIDDLE_BUTTON);
|
||||
break;
|
||||
case WM_NCXBUTTONDOWN:
|
||||
case WM_NCXBUTTONDBLCLK:
|
||||
if (AwtToolkit::GetInstance().areExtraMouseButtonsEnabled()) {
|
||||
int b = 0;
|
||||
if (HIWORD(wParam) == 1) b = X1_BUTTON;
|
||||
else if (HIWORD(wParam) == 2) b = X2_BUTTON;
|
||||
if (b != 0) mr = WmNcMouseDown(LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), b);
|
||||
}
|
||||
break;
|
||||
case WM_NCXBUTTONUP:
|
||||
if (AwtToolkit::GetInstance().areExtraMouseButtonsEnabled()) {
|
||||
int b = 0;
|
||||
if (HIWORD(wParam) == 1) b = X1_BUTTON;
|
||||
else if (HIWORD(wParam) == 2) b = X2_BUTTON;
|
||||
if (b != 0) mr = WmNcMouseUp(LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), b);
|
||||
}
|
||||
break;
|
||||
case WM_NCMOUSEMOVE:
|
||||
mr = WmNcMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
if (ignoreNextLBTNUP) {
|
||||
ignoreNextLBTNUP = FALSE;
|
||||
@@ -1766,9 +1801,21 @@ LRESULT AwtComponent::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
||||
}
|
||||
case WM_SETCURSOR:
|
||||
mr = mrDoDefault;
|
||||
if (LOWORD(lParam) == HTCLIENT) {
|
||||
if (LOWORD(lParam) == HTCLIENT || AwtFrame::IsTitleBarHitTest(LOWORD(lParam))) {
|
||||
if (AwtComponent* comp =
|
||||
AwtComponent::GetComponent((HWND)wParam)) {
|
||||
if (AwtFrame::IsTitleBarHitTest(LOWORD(lParam))) {
|
||||
AwtWindow* window = comp->GetContainer();
|
||||
if (window == NULL || window->IsSimpleWindow() ||
|
||||
!((AwtFrame*) window)->HasCustomTitleBar()) break;
|
||||
// When custom title bar is enabled, WM_SETCURSOR is sent to root Frame, so find actual component under cursor
|
||||
HWND hwnd = (HWND) wParam;
|
||||
POINT p;
|
||||
::GetCursorPos(&p);
|
||||
ScreenToBottommostChild(hwnd, p.x, p.y);
|
||||
comp = AwtComponent::GetComponent(hwnd);
|
||||
if (!comp) break;
|
||||
}
|
||||
AwtCursor::UpdateCursor(comp);
|
||||
mr = mrConsume;
|
||||
}
|
||||
@@ -2523,7 +2570,7 @@ MsgRouting AwtComponent::WmMouseDown(UINT flags, int x, int y, int button)
|
||||
* SetCapture() sends WM_CAPTURECHANGED and breaks that
|
||||
* assumption.
|
||||
*/
|
||||
SetDragCapture(flags);
|
||||
if (!(flags & MK_NOCAPTURE)) SetDragCapture(flags);
|
||||
|
||||
AwtWindow * owner = (AwtWindow*)GetComponent(GetTopLevelParentForWindow(GetHWnd()));
|
||||
if (AwtWindow::GetGrabbedWindow() != NULL && owner != NULL) {
|
||||
@@ -4759,7 +4806,7 @@ MsgRouting AwtComponent::WmNcHitTest(int x, int y, LRESULT &retVal)
|
||||
AwtWindow* window = GetContainer();
|
||||
if (window == NULL || window->IsSimpleWindow()) return mrDoDefault;
|
||||
AwtFrame* frame = (AwtFrame*)window;
|
||||
if (frame->HasCustomDecoration() &&
|
||||
if (frame->HasCustomTitleBar() &&
|
||||
frame->WmNcHitTest(x, y, retVal) == mrConsume) {
|
||||
retVal = HTTRANSPARENT;
|
||||
return mrConsume;
|
||||
|
||||
@@ -66,6 +66,9 @@ const UINT MAX_ACP_STR_LEN = 7; // ANSI CP identifiers are no longer than this
|
||||
// combination of standard mouse button flags
|
||||
const int ALL_MK_BUTTONS = MK_LBUTTON|MK_MBUTTON|MK_RBUTTON;
|
||||
const int X_BUTTONS = MK_XBUTTON1|MK_XBUTTON2;
|
||||
// Do not set drag capture when dispatching mouse event.
|
||||
// This is useful for non-client events in custom title bar area for preserving native behavior.
|
||||
#define MK_NOCAPTURE 0x80000000
|
||||
|
||||
// Whether to check for embedded frame and adjust location
|
||||
#define CHECK_EMBEDDED 0
|
||||
|
||||
@@ -49,6 +49,8 @@ jmethodID AwtDesktopProperties::setColorPropertyID = 0;
|
||||
jmethodID AwtDesktopProperties::setFontPropertyID = 0;
|
||||
jmethodID AwtDesktopProperties::setSoundPropertyID = 0;
|
||||
|
||||
BOOL AppsUseLightThemeCached = TRUE;
|
||||
|
||||
AwtDesktopProperties::AwtDesktopProperties(jobject self) {
|
||||
this->self = GetEnv()->NewGlobalRef(self);
|
||||
GetEnv()->SetLongField( self, AwtDesktopProperties::pDataID,
|
||||
@@ -699,15 +701,15 @@ void AwtDesktopProperties::GetOtherParameters() {
|
||||
// Add property for light/dark theme detection
|
||||
value = getWindowsPropFromReg(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
|
||||
TEXT("AppsUseLightTheme"), &valueType);
|
||||
BOOL lightTheme = TRUE;
|
||||
if (value != NULL) {
|
||||
if (valueType == REG_DWORD) {
|
||||
SetBooleanProperty(TEXT("win.lightTheme.on"), (BOOL)((int)*value == 1));
|
||||
lightTheme = (BOOL)((int)*value == 1);
|
||||
}
|
||||
free(value);
|
||||
}
|
||||
else {
|
||||
SetBooleanProperty(TEXT("win.lightTheme.on"), TRUE);
|
||||
}
|
||||
SetBooleanProperty(TEXT("win.lightTheme.on"), lightTheme);
|
||||
AppsUseLightThemeCached = lightTheme;
|
||||
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
|
||||
@@ -172,6 +172,8 @@ AwtDialog* AwtDialog::Create(jobject peer, jobject parent)
|
||||
dialog->RecalcNonClient();
|
||||
dialog->UpdateSystemMenu();
|
||||
|
||||
CustomTitleBarControls::Refresh(dialog->customTitleBarControls, dialog->GetHWnd(), target, env);
|
||||
|
||||
/*
|
||||
* Initialize icon as inherited from parent if it exists
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <java_lang_Integer.h>
|
||||
#include <java_awt_Window_CustomWindowDecoration.h>
|
||||
#include <java_awt_Window_CustomTitleBar.h>
|
||||
#include <sun_awt_windows_WEmbeddedFrame.h>
|
||||
#include <sun_awt_windows_WEmbeddedFramePeer.h>
|
||||
|
||||
@@ -95,6 +95,35 @@ struct BlockedThreadStruct {
|
||||
|
||||
static bool SetFocusToPluginControl(HWND hwndPlugin);
|
||||
|
||||
struct WmMouseMessage {
|
||||
UINT flags, mouseUp, mouseDown;
|
||||
};
|
||||
static WmMouseMessage MouseButtonToWm(int button) {
|
||||
button &= ~DBL_CLICK; // Delete double click modifier
|
||||
WmMouseMessage msg {AwtComponent::GetButtonMK(button), 0, 0};
|
||||
switch (button) {
|
||||
case LEFT_BUTTON:
|
||||
msg.mouseDown = WM_LBUTTONDOWN;
|
||||
msg.mouseUp = WM_LBUTTONUP;
|
||||
break;
|
||||
case MIDDLE_BUTTON:
|
||||
msg.mouseDown = WM_MBUTTONDOWN;
|
||||
msg.mouseUp = WM_MBUTTONUP;
|
||||
break;
|
||||
case RIGHT_BUTTON:
|
||||
msg.mouseDown = WM_RBUTTONDOWN;
|
||||
msg.mouseUp = WM_RBUTTONUP;
|
||||
break;
|
||||
case X1_BUTTON:
|
||||
case X2_BUTTON:
|
||||
msg.mouseDown = WM_XBUTTONDOWN;
|
||||
msg.mouseUp = WM_XBUTTONUP;
|
||||
msg.flags = MAKEWPARAM(msg.flags, button == X1_BUTTON ? 1 : 2);
|
||||
break;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* AwtFrame fields
|
||||
*/
|
||||
@@ -129,10 +158,12 @@ AwtFrame::AwtFrame() {
|
||||
m_zoomed = FALSE;
|
||||
m_maxBoundsSet = FALSE;
|
||||
m_forceResetZoomed = FALSE;
|
||||
m_pHasCustomDecoration = NULL;
|
||||
|
||||
isInManualMoveOrSize = FALSE;
|
||||
grabbedHitTest = 0;
|
||||
customTitleBarHeight = -1.0f; // Negative means uninitialized
|
||||
customTitleBarTouchDragPosition = (LPARAM) -1;
|
||||
customTitleBarControls = NULL;
|
||||
}
|
||||
|
||||
AwtFrame::~AwtFrame()
|
||||
@@ -141,6 +172,10 @@ AwtFrame::~AwtFrame()
|
||||
|
||||
void AwtFrame::Dispose()
|
||||
{
|
||||
if (customTitleBarControls) {
|
||||
delete customTitleBarControls;
|
||||
customTitleBarControls = NULL;
|
||||
}
|
||||
AwtWindow::Dispose();
|
||||
}
|
||||
|
||||
@@ -341,6 +376,7 @@ AwtFrame* AwtFrame::Create(jobject self, jobject parent)
|
||||
self);
|
||||
frame->RecalcNonClient();
|
||||
}
|
||||
CustomTitleBarControls::Refresh(frame->customTitleBarControls, frame->GetHWnd(), target, env);
|
||||
}
|
||||
} catch (...) {
|
||||
env->DeleteLocalRef(target);
|
||||
@@ -379,6 +415,11 @@ LRESULT AwtFrame::ProxyWindowProc(UINT message, WPARAM wParam, LPARAM lParam, Ms
|
||||
AwtComponent *focusOwner = NULL;
|
||||
AwtComponent *imeTargetComponent = NULL;
|
||||
|
||||
if (customTitleBarControls) {
|
||||
if (message == WM_NCMOUSELEAVE) customTitleBarControls->Hit(CustomTitleBarControls::HitType::RESET, 0, 0);
|
||||
if (message == WM_DWMCOLORIZATIONCOLORCHANGED || message == WM_THEMECHANGED) customTitleBarControls->Update();
|
||||
}
|
||||
|
||||
// IME and input language related messages need to be sent to a window
|
||||
// which has the Java input focus
|
||||
switch (message) {
|
||||
@@ -515,15 +556,7 @@ MsgRouting AwtFrame::WmMouseMove(UINT flags, int x, int y) {
|
||||
* If this Frame is non-focusable then we should implement move and size operation for it by
|
||||
* ourselfves because we don't dispatch appropriate mouse messages to default window procedure.
|
||||
*/
|
||||
if (isInManualMoveOrSize) {
|
||||
if (grabbedHitTest == HTCAPTION) {
|
||||
WINDOWPLACEMENT placement;
|
||||
::GetWindowPlacement(GetHWnd(), &placement);
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED) {
|
||||
placement.showCmd = SW_SHOWNORMAL;
|
||||
::SetWindowPlacement(GetHWnd(), &placement);
|
||||
}
|
||||
}
|
||||
if (!IsFocusableWindow() && isInManualMoveOrSize) {
|
||||
DWORD curPos = ::GetMessagePos();
|
||||
x = GET_X_LPARAM(curPos);
|
||||
y = GET_Y_LPARAM(curPos);
|
||||
@@ -583,6 +616,48 @@ MsgRouting AwtFrame::WmMouseMove(UINT flags, int x, int y) {
|
||||
}
|
||||
|
||||
MsgRouting AwtFrame::WmNcMouseUp(WPARAM hitTest, int x, int y, int button) {
|
||||
customTitleBarTouchDragPosition = (LPARAM) -1;
|
||||
if (IsTitleBarHitTest(hitTest) && HasCustomTitleBar()) {
|
||||
WmMouseMessage msg = MouseButtonToWm(button);
|
||||
if (IsMouseEventFromTouch()) {
|
||||
if (button & LEFT_BUTTON) {
|
||||
// We didn't send MouseDown event in NcMouseDown, so send it here.
|
||||
if (customTitleBarControls) {
|
||||
customTitleBarControls->Hit(CustomTitleBarControls::HitType::PRESS, x, y);
|
||||
}
|
||||
msg.flags |= MK_NOCAPTURE;
|
||||
SendMessageAtPoint(msg.mouseDown, msg.flags, x, y);
|
||||
} else {
|
||||
// We shouldn't get there, but just in case...
|
||||
// We have already sent MouseDown and MouseUp events in NcMouseDown, so nothing to do here.
|
||||
return mrConsume;
|
||||
}
|
||||
}
|
||||
SendMessageAtPoint(msg.mouseUp, msg.flags, x, y);
|
||||
if (button == LEFT_BUTTON) {
|
||||
if (customTitleBarControls) {
|
||||
LRESULT ht = customTitleBarControls->Hit(CustomTitleBarControls::HitType::RELEASE, x, y);
|
||||
if (ht != hitTest) hitTest = HTNOWHERE;
|
||||
}
|
||||
HWND hwnd = GetHWnd();
|
||||
switch (hitTest) {
|
||||
case HTCLOSE:
|
||||
::SendMessage(hwnd, WM_CLOSE, 0, 0);
|
||||
break;
|
||||
case HTMINBUTTON:
|
||||
if (GetStyle() & WS_MINIMIZEBOX) {
|
||||
::ShowWindow(hwnd, SW_SHOWMINIMIZED);
|
||||
}
|
||||
break;
|
||||
case HTMAXBUTTON:
|
||||
if (GetStyle() & WS_MAXIMIZEBOX) {
|
||||
::ShowWindow(hwnd, ::IsZoomed(hwnd) ? SW_SHOWNORMAL : SW_SHOWMAXIMIZED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mrConsume;
|
||||
}
|
||||
if (!IsFocusableWindow() && (button & LEFT_BUTTON)) {
|
||||
/*
|
||||
* Fix for 6399659.
|
||||
@@ -631,43 +706,45 @@ MsgRouting AwtFrame::WmNcMouseDown(WPARAM hitTest, int x, int y, int button) {
|
||||
if (m_grabbedWindow != NULL/* && !m_grabbedWindow->IsOneOfOwnersOf(this)*/) {
|
||||
m_grabbedWindow->Ungrab();
|
||||
}
|
||||
// For windows with custom decorations, handle caption-related mouse events
|
||||
// Do not handle events from caption itself to preserve native drag behavior
|
||||
if (HasCustomDecoration()) {
|
||||
switch (hitTest) {
|
||||
case HTCAPTION:
|
||||
case HTMINBUTTON:
|
||||
case HTMAXBUTTON:
|
||||
case HTCLOSE:
|
||||
case HTMENU:
|
||||
RECT rcWindow;
|
||||
GetWindowRect(GetHWnd(), &rcWindow);
|
||||
if (hitTest == HTCAPTION) {
|
||||
customTitleBarTouchDragPosition = (LPARAM) -1;
|
||||
if (IsTitleBarHitTest(hitTest) && HasCustomTitleBar()) {
|
||||
// When double-clicking title bar of native Windows apps, they respond to second mouse press, not release
|
||||
const int LEFT_DBLCLCK = LEFT_BUTTON | DBL_CLICK;
|
||||
BOOL maximize = (button & LEFT_DBLCLCK) == LEFT_DBLCLCK && IsResizable();
|
||||
BOOL lpress = button == LEFT_BUTTON;
|
||||
WmMouseMessage msg = MouseButtonToWm(button);
|
||||
if (IsMouseEventFromTouch()) {
|
||||
msg.flags |= MK_NOCAPTURE;
|
||||
if (button & LEFT_BUTTON) {
|
||||
// Don't send mouse down events for left button touch for now, wait for NcMouseUp.
|
||||
// In case of window drag we will not receive a NcMouseUp.
|
||||
if (maximize) return AreCustomTitleBarNativeActionsAllowed() ? mrDoDefault : mrConsume;
|
||||
if (lpress) {
|
||||
customTitleBarTouchDragPosition = MAKELPARAM(x, y);
|
||||
// Reset hit test query, we will check it in NcMouseMove
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
jint customSpot = JNU_CallMethodByName(env, NULL, GetTarget(env),
|
||||
"hitTestCustomDecoration", "(II)I",
|
||||
ScaleDownX(x - rcWindow.left),
|
||||
ScaleDownY(y - rcWindow.top)).i;
|
||||
if (customSpot == java_awt_Window_CustomWindowDecoration_DRAGGABLE_AREA) {
|
||||
if (button & LEFT_BUTTON) {
|
||||
savedMousePos.x = x;
|
||||
savedMousePos.y = y;
|
||||
::SetCapture(GetHWnd());
|
||||
isInManualMoveOrSize = TRUE;
|
||||
grabbedHitTest = hitTest;
|
||||
}
|
||||
} else break;
|
||||
jobject target = GetTarget(env);
|
||||
if (target) {
|
||||
env->SetIntField(target, AwtWindow::customTitleBarHitTestQueryID, java_awt_Window_CustomTitleBar_HIT_UNDEFINED);
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
}
|
||||
POINT myPos;
|
||||
myPos.x = x;
|
||||
myPos.y = y;
|
||||
::ScreenToClient(GetHWnd(), &myPos);
|
||||
WmMouseDown(GetButtonMK(button),
|
||||
myPos.x,
|
||||
myPos.y,
|
||||
button);
|
||||
return mrConsume;
|
||||
} else {
|
||||
// For all buttons except left originated from touch, only NcMouseDown is sent, so treat this as click.
|
||||
SendMessageAtPoint(msg.mouseDown, msg.flags, x, y);
|
||||
SendMessageAtPoint(msg.mouseUp, msg.flags, x, y);
|
||||
}
|
||||
return mrConsume;
|
||||
}
|
||||
if (customTitleBarControls) {
|
||||
LRESULT ht = customTitleBarControls->Hit(CustomTitleBarControls::HitType::PRESS, x, y);
|
||||
if (ht != HTNOWHERE) hitTest = ht;
|
||||
}
|
||||
BOOL defaultControl = lpress && hitTest != HTCAPTION; // Press on min/max/close button
|
||||
BOOL defaultAction = (maximize || lpress) && AreCustomTitleBarNativeActionsAllowed() && !defaultControl;
|
||||
if (defaultAction || defaultControl) msg.flags |= MK_NOCAPTURE;
|
||||
SendMessageAtPoint(msg.mouseDown, msg.flags, x, y);
|
||||
return defaultAction ? mrDoDefault : mrConsume;
|
||||
}
|
||||
if (!IsFocusableWindow() && (button & LEFT_BUTTON)) {
|
||||
switch (hitTest) {
|
||||
@@ -701,21 +778,25 @@ MsgRouting AwtFrame::WmNcMouseDown(WPARAM hitTest, int x, int y, int button) {
|
||||
}
|
||||
|
||||
MsgRouting AwtFrame::WmNcMouseMove(WPARAM hitTest, int x, int y) {
|
||||
// For windows with custom decorations, handle caption-related mouse events
|
||||
if (HasCustomDecoration()) {
|
||||
switch (hitTest) {
|
||||
case HTMINBUTTON:
|
||||
case HTMAXBUTTON:
|
||||
case HTCLOSE:
|
||||
case HTMENU:
|
||||
case HTCAPTION:
|
||||
POINT myPos;
|
||||
myPos.x = x;
|
||||
myPos.y = y;
|
||||
::ScreenToClient(GetHWnd(), &myPos);
|
||||
WmMouseMove(0, myPos.x, myPos.y);
|
||||
if (hitTest != HTCAPTION) return mrConsume; // Preserve default window drag for HTCAPTION
|
||||
if (customTitleBarControls) customTitleBarControls->Hit(CustomTitleBarControls::HitType::MOVE, x, y);
|
||||
if (IsTitleBarHitTest(hitTest) && HasCustomTitleBar()) {
|
||||
if (customTitleBarTouchDragPosition != (LPARAM) -1 && IsMouseEventFromTouch()) {
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
jobject target = GetTarget(env);
|
||||
if (target) {
|
||||
switch (env->GetIntField(target, AwtWindow::customTitleBarHitTestQueryID)) {
|
||||
case java_awt_Window_CustomTitleBar_HIT_UNDEFINED: break; // Hit test query is not ready yet, skip.
|
||||
case java_awt_Window_CustomTitleBar_HIT_TITLEBAR: // Apply drag behavior
|
||||
if (hitTest != HTCAPTION) break; // Native hit test didn't update yet, skip.
|
||||
DefWindowProc(WM_NCLBUTTONDOWN, hitTest, customTitleBarTouchDragPosition);
|
||||
default: // Reset drag-by-touch flag
|
||||
customTitleBarTouchDragPosition = (LPARAM) -1;
|
||||
}
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
}
|
||||
SendMessageAtPoint(WM_MOUSEMOVE, 0, x, y);
|
||||
return mrConsume;
|
||||
}
|
||||
return AwtWindow::WmNcMouseMove(hitTest, x, y);
|
||||
}
|
||||
@@ -980,6 +1061,7 @@ MsgRouting AwtFrame::WmWindowPosChanging(LPARAM windowPos) {
|
||||
|
||||
MsgRouting AwtFrame::WmSize(UINT type, int w, int h)
|
||||
{
|
||||
if (customTitleBarControls) customTitleBarControls->Update();
|
||||
currentWmSizeState = type;
|
||||
if (currentWmSizeState == SIZE_MINIMIZED) {
|
||||
UpdateSecurityWarningVisibility();
|
||||
@@ -1052,6 +1134,11 @@ MsgRouting AwtFrame::WmSize(UINT type, int w, int h)
|
||||
|
||||
MsgRouting AwtFrame::WmActivate(UINT nState, BOOL fMinimized, HWND opposite)
|
||||
{
|
||||
if (customTitleBarControls) {
|
||||
customTitleBarControls->Update(nState == WA_INACTIVE ?
|
||||
CustomTitleBarControls::State::INACTIVE :
|
||||
CustomTitleBarControls::State::NORMAL);
|
||||
}
|
||||
jint type;
|
||||
|
||||
if (nState != WA_INACTIVE) {
|
||||
@@ -1730,104 +1817,103 @@ ret:
|
||||
delete nmbs;
|
||||
}
|
||||
|
||||
// {start} Custom Decoration Support
|
||||
// {start} Custom title bar support
|
||||
|
||||
BOOL AwtFrame::HasCustomDecoration()
|
||||
{
|
||||
if (!m_pHasCustomDecoration) {
|
||||
m_pHasCustomDecoration = new BOOL;
|
||||
BOOL AwtFrame::HasCustomTitleBar() {
|
||||
float h = customTitleBarHeight;
|
||||
if (h < 0.0f) h = GetCustomTitleBarHeight();
|
||||
return h > 0.0f;
|
||||
}
|
||||
|
||||
float AwtFrame::GetCustomTitleBarHeight() {
|
||||
float h = customTitleBarHeight;
|
||||
if (h < 0.0f) {
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
*m_pHasCustomDecoration = JNU_GetFieldByName(env, NULL, GetTarget(env), "hasCustomDecoration", "Z").z;
|
||||
}
|
||||
return *m_pHasCustomDecoration;
|
||||
}
|
||||
|
||||
void _UpdateCustomDecoration(void* p) {
|
||||
JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
|
||||
jobject self = reinterpret_cast<jobject>(p);
|
||||
PDATA pData;
|
||||
JNI_CHECK_PEER_GOTO(self, ret);
|
||||
|
||||
AwtFrame* frame = (AwtFrame*)pData;
|
||||
if (!frame->m_pHasCustomDecoration) frame->m_pHasCustomDecoration = new BOOL;
|
||||
*frame->m_pHasCustomDecoration = JNU_GetFieldByName(env, NULL, frame->GetTarget(env), "hasCustomDecoration", "Z").z;
|
||||
frame->RedrawNonClient();
|
||||
ret:
|
||||
env->DeleteGlobalRef(self);
|
||||
}
|
||||
|
||||
void GetSysInsets(RECT* insets, AwtFrame* pFrame) {
|
||||
if (pFrame->IsUndecorated()) {
|
||||
::SetRectEmpty(insets);
|
||||
return;
|
||||
jobject target = GetTarget(env);
|
||||
if (target) {
|
||||
h = env->CallFloatMethod(target, AwtWindow::internalCustomTitleBarHeightMID);
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
if (h < 0.0f) h = 0.0f;
|
||||
customTitleBarHeight = h;
|
||||
}
|
||||
// Copied from AwtComponent::ScaleUpY, but without rounding
|
||||
int screen = GetScreenImOn();
|
||||
Devices::InstanceAccess devices;
|
||||
HMONITOR hmon;
|
||||
if (::IsZoomed(pFrame->GetHWnd())) {
|
||||
WINDOWPLACEMENT wp;
|
||||
::GetWindowPlacement(pFrame->GetHWnd(), &wp);
|
||||
hmon = ::MonitorFromRect(&wp.rcNormalPosition, MONITOR_DEFAULTTONEAREST);
|
||||
} else {
|
||||
// this method can return wrong monitor in a zoomed state in multi-dpi env
|
||||
hmon = ::MonitorFromWindow(pFrame->GetHWnd(), MONITOR_DEFAULTTONEAREST);
|
||||
}
|
||||
AwtWin32GraphicsDevice* device = devices->GetDevice(AwtWin32GraphicsDevice::GetScreenFromHMONITOR(hmon));
|
||||
int dpi = device ? device->GetScaleX() * 96 : 96;
|
||||
|
||||
// GetSystemMetricsForDpi gives incorrect values, use AdjustWindowRectExForDpi for border metrics instead
|
||||
RECT rect = {};
|
||||
DWORD style = pFrame->IsResizable() ? WS_OVERLAPPEDWINDOW : WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME;
|
||||
AwtToolkit::AdjustWindowRectExForDpi(&rect, style, FALSE, NULL, dpi);
|
||||
::SetRect(insets, -rect.left, -rect.top, rect.right, rect.bottom);
|
||||
AwtWin32GraphicsDevice* device = devices->GetDevice(screen);
|
||||
return device == NULL ? h : h * device->GetScaleY();
|
||||
}
|
||||
|
||||
LRESULT HitTestNCA(AwtFrame* frame, int x, int y) {
|
||||
RECT rcWindow;
|
||||
RECT insets;
|
||||
|
||||
GetSysInsets(&insets, frame);
|
||||
GetWindowRect(frame->GetHWnd(), &rcWindow);
|
||||
|
||||
// Get the frame rectangle, adjusted for the style without a caption.
|
||||
RECT rcFrame = {};
|
||||
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
|
||||
|
||||
jint AwtFrame::GetCustomTitleBarHitTest() {
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
int titleHeight = (int)JNU_GetFieldByName(env, NULL, frame->GetTarget(env),
|
||||
"customDecorTitleBarHeight", "I").i;
|
||||
if (titleHeight >= 0) {
|
||||
titleHeight = frame->ScaleUpY(titleHeight);
|
||||
insets.top = titleHeight; // otherwise leave default
|
||||
jobject target = GetTarget(env);
|
||||
jint result = java_awt_Window_CustomTitleBar_HIT_UNDEFINED;
|
||||
if (target) {
|
||||
result = env->GetIntField(target, AwtWindow::customTitleBarHitTestID);
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BOOL AwtFrame::AreCustomTitleBarNativeActionsAllowed() {
|
||||
return GetCustomTitleBarHitTest() <= java_awt_Window_CustomTitleBar_HIT_TITLEBAR;
|
||||
}
|
||||
|
||||
void AwtFrame::SendMessageAtPoint(UINT msg, WPARAM wparam, int ncx, int ncy) {
|
||||
HWND w = GetHWnd();
|
||||
POINT p = ScreenToBottommostChild(w, ncx, ncy);
|
||||
::SetMessageExtraInfo((LPARAM) 0); // Extra info may contain MOUSEEVENTF_FROMTOUCH, clear it.
|
||||
::SendMessage(w, msg, wparam, MAKELPARAM(p.x, p.y));
|
||||
}
|
||||
|
||||
// Returns frame border insets (without title bar).
|
||||
// Note that in non-maximized state there's 0px between top of the frame and top of the client area.
|
||||
// This method, however, still returns non-zero top inset in that case - it represents height of the resize border.
|
||||
RECT AwtFrame::GetSysInsets() {
|
||||
// GetSystemMetricsForDpi gives incorrect values, use AdjustWindowRectExForDpi for border metrics instead
|
||||
HWND hwnd = GetHWnd();
|
||||
LONG style = GetStyle(), exStyle = GetStyleEx();
|
||||
style &= ~WS_CAPTION | WS_BORDER; // Remove caption but leave border
|
||||
UINT dpi = AwtToolkit::GetDpiForWindow(hwnd);
|
||||
RECT rect = {};
|
||||
AwtToolkit::AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
|
||||
RECT insets;
|
||||
::SetRect(&insets, -rect.left, -rect.top, rect.right, rect.bottom);
|
||||
return insets;
|
||||
}
|
||||
|
||||
LRESULT AwtFrame::HitTestNCA(int x, int y) {
|
||||
RECT rcWindow;
|
||||
HWND hwnd = GetHWnd();
|
||||
|
||||
RECT insets = GetSysInsets();
|
||||
GetWindowRect(hwnd, &rcWindow);
|
||||
|
||||
float titleBarHeight = GetCustomTitleBarHeight();
|
||||
if (::IsZoomed(hwnd)) titleBarHeight += insets.top;
|
||||
|
||||
USHORT uRow = 1;
|
||||
USHORT uCol = 1;
|
||||
BOOL fOnResizeBorder = FALSE;
|
||||
LRESULT captionVariant;
|
||||
|
||||
if (y >= rcWindow.top &&
|
||||
y < rcWindow.top + insets.top)
|
||||
if (y >= rcWindow.top && y < rcWindow.top + titleBarHeight)
|
||||
{
|
||||
jint customSpot = JNU_CallMethodByName(env, NULL, frame->GetTarget(env),
|
||||
"hitTestCustomDecoration", "(II)I",
|
||||
frame->ScaleDownX(x - rcWindow.left),
|
||||
frame->ScaleDownY(y - rcWindow.top)).i;
|
||||
switch (customSpot) {
|
||||
case java_awt_Window_CustomWindowDecoration_NO_HIT_SPOT:
|
||||
case java_awt_Window_CustomWindowDecoration_DRAGGABLE_AREA:
|
||||
break; // Nothing
|
||||
case java_awt_Window_CustomWindowDecoration_MINIMIZE_BUTTON:
|
||||
return HTMINBUTTON;
|
||||
case java_awt_Window_CustomWindowDecoration_MAXIMIZE_BUTTON:
|
||||
return HTMAXBUTTON;
|
||||
case java_awt_Window_CustomWindowDecoration_CLOSE_BUTTON:
|
||||
return HTCLOSE;
|
||||
case java_awt_Window_CustomWindowDecoration_MENU_BAR:
|
||||
return HTMENU;
|
||||
default:
|
||||
return HTNOWHERE;
|
||||
if (y < rcWindow.top + insets.top) {
|
||||
captionVariant = HTTOP;
|
||||
} else {
|
||||
captionVariant = HTNOWHERE;
|
||||
if (customTitleBarControls) {
|
||||
captionVariant = customTitleBarControls->Hit(CustomTitleBarControls::HitType::TEST, x, y);
|
||||
}
|
||||
if (captionVariant == HTNOWHERE) {
|
||||
switch (GetCustomTitleBarHitTest()) {
|
||||
case java_awt_Window_CustomTitleBar_HIT_MINIMIZE_BUTTON: captionVariant = HTMINBUTTON; break;
|
||||
case java_awt_Window_CustomTitleBar_HIT_MAXIMIZE_BUTTON: captionVariant = HTMAXBUTTON; break;
|
||||
case java_awt_Window_CustomTitleBar_HIT_CLOSE_BUTTON: captionVariant = HTCLOSE; break;
|
||||
default: captionVariant = HTCAPTION; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fOnResizeBorder = (y < (rcWindow.top - rcFrame.top));
|
||||
uRow = 0;
|
||||
} else if (y < rcWindow.bottom &&
|
||||
y >= rcWindow.bottom - insets.bottom) {
|
||||
@@ -1845,7 +1931,7 @@ LRESULT HitTestNCA(AwtFrame* frame, int x, int y) {
|
||||
}
|
||||
|
||||
LRESULT hitTests[3][3] = {
|
||||
{HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT},
|
||||
{HTTOPLEFT, captionVariant, HTTOPRIGHT},
|
||||
{HTLEFT, HTNOWHERE, HTRIGHT},
|
||||
{HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT},
|
||||
};
|
||||
@@ -1855,19 +1941,17 @@ LRESULT HitTestNCA(AwtFrame* frame, int x, int y) {
|
||||
|
||||
MsgRouting AwtFrame::WmNcCalcSize(BOOL wParam, LPNCCALCSIZE_PARAMS lpncsp, LRESULT& retVal)
|
||||
{
|
||||
if (!wParam || !HasCustomDecoration()) {
|
||||
if (!wParam || !HasCustomTitleBar()) {
|
||||
return AwtWindow::WmNcCalcSize(wParam, lpncsp, retVal);
|
||||
}
|
||||
RECT insets;
|
||||
GetSysInsets(&insets, this);
|
||||
RECT* rect = &lpncsp->rgrc[0];
|
||||
|
||||
rect->left += insets.left;
|
||||
rect->right -= insets.right;
|
||||
rect->bottom -= insets.bottom;
|
||||
RECT* rect = &lpncsp->rgrc[0];
|
||||
LONG frameTop = rect->top;
|
||||
DefWindowProc(WM_NCCALCSIZE, (WPARAM) wParam, (LPARAM) lpncsp);
|
||||
rect->top = frameTop; // DefWindowProc takes into account caption height, revert this
|
||||
|
||||
if (::IsZoomed(GetHWnd())) {
|
||||
rect->top += insets.bottom;
|
||||
rect->top += GetSysInsets().top; // We need to add top inset in maximized case
|
||||
// [moklev] Workaround for RIDER-27069, IDEA-211327
|
||||
if (!this->IsUndecorated()) {
|
||||
APPBARDATA abData;
|
||||
@@ -1892,34 +1976,58 @@ MsgRouting AwtFrame::WmNcCalcSize(BOOL wParam, LPNCCALCSIZE_PARAMS lpncsp, LRESU
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (abData.uEdge != ABE_RIGHT) {
|
||||
rect->right += this->ScaleUpX(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// this makes the native caption go uncovered
|
||||
// int yBorder = ::GetSystemMetrics(SM_CYBORDER);
|
||||
// rect->top += yBorder;
|
||||
}
|
||||
retVal = 0L;
|
||||
return mrConsume;
|
||||
}
|
||||
|
||||
MsgRouting AwtFrame::WmNcHitTest(int x, int y, LRESULT& retVal)
|
||||
{
|
||||
if (!HasCustomDecoration()) {
|
||||
if (!HasCustomTitleBar()) {
|
||||
return AwtWindow::WmNcHitTest(x, y, retVal);
|
||||
}
|
||||
if (::IsWindow(GetModalBlocker(GetHWnd()))) {
|
||||
retVal = HTCLIENT;
|
||||
return mrConsume;
|
||||
}
|
||||
retVal = HitTestNCA(this, x, y);
|
||||
retVal = HitTestNCA(x, y);
|
||||
return retVal == HTNOWHERE ? mrDoDefault : mrConsume;
|
||||
}
|
||||
|
||||
// {end} Custom Decoration Support
|
||||
void AwtFrame::RedrawNonClient()
|
||||
{
|
||||
UINT flags = SwpFrameChangeFlags;
|
||||
if (!HasCustomTitleBar()) {
|
||||
// With custom title bar enabled, SetWindowPos call below can cause WM_SIZE message being sent.
|
||||
// If we're coming here from WFramePeer.initialize (as part of 'setResizable' call),
|
||||
// WM_SIZE message processing can happen concurrently with window flags update done as part of
|
||||
// 'setState' call), and lead to inconsistent state.
|
||||
// So, we disable asynchronous processing in case we have custom title bar to avoid the race condition.
|
||||
flags |= SWP_ASYNCWINDOWPOS;
|
||||
}
|
||||
::SetWindowPos(GetHWnd(), (HWND) NULL, 0, 0, 0, 0, flags);
|
||||
}
|
||||
|
||||
void AwtFrame::_UpdateCustomTitleBar(void* p) {
|
||||
JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
|
||||
jobject self = reinterpret_cast<jobject>(p);
|
||||
PDATA pData;
|
||||
JNI_CHECK_PEER_GOTO(self, ret);
|
||||
|
||||
AwtFrame* frame = (AwtFrame*)pData;
|
||||
BOOL old = frame->HasCustomTitleBar();
|
||||
frame->customTitleBarHeight = -1.0f; // Reset to uninitialized
|
||||
if (frame->HasCustomTitleBar() != old) frame->RedrawNonClient();
|
||||
jobject target = frame->GetTarget(env);
|
||||
CustomTitleBarControls::Refresh(frame->customTitleBarControls, frame->GetHWnd(), target, env);
|
||||
env->DeleteLocalRef(target);
|
||||
ret:
|
||||
env->DeleteGlobalRef(self);
|
||||
}
|
||||
|
||||
// {end} Custom title bar support
|
||||
|
||||
/************************************************************************
|
||||
* WFramePeer native methods
|
||||
@@ -2253,12 +2361,12 @@ Java_sun_awt_windows_WFramePeer_updateIcon(JNIEnv *env, jobject self)
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_windows_WFramePeer_updateCustomDecoration(JNIEnv *env, jobject self)
|
||||
Java_sun_awt_windows_WFramePeer_updateCustomTitleBar(JNIEnv *env, jclass cls, jobject peer)
|
||||
{
|
||||
TRY;
|
||||
|
||||
AwtToolkit::GetInstance().InvokeFunction(_UpdateCustomDecoration, env->NewGlobalRef(self));
|
||||
// global ref is deleted in _UpdateCustomDecoration()
|
||||
AwtToolkit::GetInstance().InvokeFunction(AwtFrame::_UpdateCustomTitleBar, env->NewGlobalRef(peer));
|
||||
// global ref is deleted in _UpdateCustomTitleBar()
|
||||
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "awt_Window.h"
|
||||
#include "awt_MenuBar.h" //add for multifont
|
||||
#include "awt_Toolkit.h"
|
||||
#include "jbr_CustomTitleBarControls.h"
|
||||
#include "Hashtable.h"
|
||||
|
||||
#include "java_awt_Frame.h"
|
||||
@@ -148,6 +149,7 @@ public:
|
||||
static void _SetIMMOption(void *param);
|
||||
static void _SynthesizeWmActivate(void *param);
|
||||
static void _NotifyModalBlocked(void *param);
|
||||
static void _UpdateCustomTitleBar(void *param);
|
||||
|
||||
virtual void Reshape(int x, int y, int width, int height);
|
||||
|
||||
@@ -159,13 +161,18 @@ public:
|
||||
INLINE HWND GetImeTargetComponent() { return m_imeTargetComponent; }
|
||||
INLINE void SetImeTargetComponent(HWND hwnd) { m_imeTargetComponent = hwnd; }
|
||||
|
||||
BOOL* m_pHasCustomDecoration;
|
||||
BOOL HasCustomDecoration();
|
||||
void RedrawNonClient();
|
||||
BOOL HasCustomTitleBar();
|
||||
static inline BOOL IsTitleBarHitTest(UINT_PTR hitTest) {
|
||||
return hitTest == HTCAPTION || hitTest == HTMINBUTTON || hitTest == HTMAXBUTTON || hitTest == HTCLOSE;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* The frame is undecorated. */
|
||||
BOOL m_isUndecorated;
|
||||
|
||||
CustomTitleBarControls* customTitleBarControls;
|
||||
|
||||
private:
|
||||
LRESULT ProxyWindowProc(UINT message, WPARAM wParam, LPARAM lParam, MsgRouting &mr);
|
||||
|
||||
@@ -225,6 +232,16 @@ private:
|
||||
WPARAM grabbedHitTest;
|
||||
POINT savedMousePos;
|
||||
|
||||
float customTitleBarHeight;
|
||||
LPARAM customTitleBarTouchDragPosition;
|
||||
|
||||
float GetCustomTitleBarHeight();
|
||||
jint GetCustomTitleBarHitTest();
|
||||
BOOL AreCustomTitleBarNativeActionsAllowed();
|
||||
void SendMessageAtPoint(UINT msg, WPARAM w, int x, int y);
|
||||
RECT GetSysInsets();
|
||||
LRESULT HitTestNCA(int x, int y);
|
||||
|
||||
/*
|
||||
* Hashtable<Thread, BlockedThreadStruct> - a table that contains all the
|
||||
* information about non-toolkit threads with modal blocked embedded
|
||||
|
||||
@@ -141,6 +141,7 @@ extern "C" JNIEXPORT jboolean JNICALL AWTIsHeadless() {
|
||||
#define IDT_AWT_MOUSECHECK 0x101
|
||||
|
||||
AdjustWindowRectExForDpiFunc* AwtToolkit::lpAdjustWindowRectExForDpi = NULL;
|
||||
GetDpiForWindowFunc* AwtToolkit::lpGetDpiForWindow = NULL;
|
||||
|
||||
static LPCTSTR szAwtToolkitClassName = TEXT("SunAwtToolkit");
|
||||
|
||||
@@ -669,6 +670,7 @@ BOOL AwtToolkit::Initialize() {
|
||||
HMODULE hLibUser32Dll = JDK_LoadSystemLibrary("User32.dll");
|
||||
if (hLibUser32Dll != NULL) {
|
||||
lpAdjustWindowRectExForDpi = (AdjustWindowRectExForDpiFunc*)GetProcAddress(hLibUser32Dll, "AdjustWindowRectExForDpi");
|
||||
lpGetDpiForWindow = (GetDpiForWindowFunc*)GetProcAddress(hLibUser32Dll, "GetDpiForWindow");
|
||||
::FreeLibrary(hLibUser32Dll);
|
||||
}
|
||||
|
||||
@@ -1758,6 +1760,8 @@ BOOL AwtToolkit::PreProcessMouseMsg(AwtComponent* p, MSG& msg)
|
||||
curPos.x = GET_X_LPARAM(dwCurPos);
|
||||
curPos.y = GET_Y_LPARAM(dwCurPos);
|
||||
HWND hWndFromPoint = ::WindowFromPoint(curPos);
|
||||
// In case of custom title bar, WindowFromPoint will return root frame, so search further just in case
|
||||
if (hWndFromPoint) ScreenToBottommostChild(hWndFromPoint, curPos.x, curPos.y);
|
||||
// hWndFromPoint == 0 if mouse is over a scrollbar
|
||||
AwtComponent* mouseComp =
|
||||
AwtComponent::GetComponent(hWndFromPoint);
|
||||
@@ -3196,3 +3200,15 @@ LRESULT AwtToolkit::InvokeInputMethodFunction(UINT msg, WPARAM wParam, LPARAM lP
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
POINT ScreenToBottommostChild(HWND& w, LONG ncx, LONG ncy) {
|
||||
POINT p;
|
||||
for (;;) {
|
||||
p = {ncx, ncy};
|
||||
::ScreenToClient(w, &p);
|
||||
HWND t = ::ChildWindowFromPointEx(w, p, CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT);
|
||||
if (t == NULL || t == w) return p;
|
||||
w = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ class CriticalSection {
|
||||
#ifndef MOUSEEVENTF_FROMTOUCH
|
||||
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
|
||||
#endif
|
||||
|
||||
inline BOOL IsMouseEventFromTouch() {
|
||||
return (::GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH_MASK) == MOUSEEVENTF_FROMTOUCH;
|
||||
}
|
||||
/************************************************************************
|
||||
* AwtToolkit class
|
||||
*/
|
||||
@@ -444,6 +448,9 @@ public:
|
||||
return lpAdjustWindowRectExForDpi != NULL ?
|
||||
lpAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, dpi) : ::AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle);
|
||||
}
|
||||
static INLINE UINT GetDpiForWindow(HWND hwnd) {
|
||||
return lpGetDpiForWindow != NULL ? lpGetDpiForWindow(hwnd) : 96;
|
||||
}
|
||||
|
||||
HANDLE m_waitEvent;
|
||||
volatile DWORD eventNumber;
|
||||
@@ -513,6 +520,7 @@ private:
|
||||
LRESULT m_inputMethodData;
|
||||
|
||||
static AdjustWindowRectExForDpiFunc *lpAdjustWindowRectExForDpi;
|
||||
static GetDpiForWindowFunc *lpGetDpiForWindow;
|
||||
|
||||
/* track display changes - used by palette-updating code.
|
||||
This is a workaround for a windows bug that prevents
|
||||
@@ -737,4 +745,6 @@ template<typename T> inline T* SafeCreate(T* &pArg) {
|
||||
}
|
||||
}
|
||||
|
||||
POINT ScreenToBottommostChild(HWND& w, LONG ncx, LONG ncy);
|
||||
|
||||
#endif /* AWT_TOOLKIT_H */
|
||||
|
||||
@@ -171,6 +171,8 @@ jfieldID AwtWindow::locationByPlatformID;
|
||||
jfieldID AwtWindow::autoRequestFocusID;
|
||||
jfieldID AwtWindow::securityWarningWidthID;
|
||||
jfieldID AwtWindow::securityWarningHeightID;
|
||||
jfieldID AwtWindow::customTitleBarHitTestID;
|
||||
jfieldID AwtWindow::customTitleBarHitTestQueryID;
|
||||
|
||||
jfieldID AwtWindow::windowTypeID;
|
||||
jmethodID AwtWindow::notifyWindowStateChangedMID;
|
||||
@@ -179,6 +181,7 @@ jfieldID AwtWindow::sysInsetsID;
|
||||
jmethodID AwtWindow::getWarningStringMID;
|
||||
jmethodID AwtWindow::calculateSecurityWarningPositionMID;
|
||||
jmethodID AwtWindow::windowTypeNameMID;
|
||||
jmethodID AwtWindow::internalCustomTitleBarHeightMID;
|
||||
|
||||
int AwtWindow::ms_instanceCounter = 0;
|
||||
HHOOK AwtWindow::ms_hCBTFilter;
|
||||
@@ -1532,11 +1535,23 @@ BOOL AwtWindow::UpdateInsets(jobject insets)
|
||||
jobject peerSysInsets = (env)->GetObjectField(peer, AwtWindow::sysInsetsID);
|
||||
DASSERT(!safe_ExceptionOccurred(env));
|
||||
|
||||
// Floor resulting insets
|
||||
int screen = GetScreenImOn();
|
||||
Devices::InstanceAccess devices;
|
||||
AwtWin32GraphicsDevice* device = devices->GetDevice(screen);
|
||||
float scaleX = device == NULL ? 1.0f : device->GetScaleX();
|
||||
float scaleY = device == NULL ? 1.0f : device->GetScaleY();
|
||||
RECT result;
|
||||
result.top = (LONG) floor(m_insets.top / scaleY);
|
||||
result.bottom = (LONG) floor(m_insets.bottom / scaleY);
|
||||
result.left = (LONG) floor(m_insets.left / scaleX);
|
||||
result.right = (LONG) floor(m_insets.right / scaleX);
|
||||
|
||||
if (peerInsets != NULL) { // may have been called during creation
|
||||
(env)->SetIntField(peerInsets, AwtInsets::topID, ScaleDownY(m_insets.top));
|
||||
(env)->SetIntField(peerInsets, AwtInsets::bottomID, ScaleDownY(m_insets.bottom));
|
||||
(env)->SetIntField(peerInsets, AwtInsets::leftID, ScaleDownX(m_insets.left));
|
||||
(env)->SetIntField(peerInsets, AwtInsets::rightID, ScaleDownX(m_insets.right));
|
||||
(env)->SetIntField(peerInsets, AwtInsets::topID, result.top);
|
||||
(env)->SetIntField(peerInsets, AwtInsets::bottomID, result.bottom);
|
||||
(env)->SetIntField(peerInsets, AwtInsets::leftID, result.left);
|
||||
(env)->SetIntField(peerInsets, AwtInsets::rightID, result.right);
|
||||
}
|
||||
if (peerSysInsets != NULL) {
|
||||
(env)->SetIntField(peerSysInsets, AwtInsets::topID, m_insets.top);
|
||||
@@ -1546,10 +1561,10 @@ BOOL AwtWindow::UpdateInsets(jobject insets)
|
||||
}
|
||||
/* Get insets into the Inset object (if any) that was passed */
|
||||
if (insets != NULL) {
|
||||
(env)->SetIntField(insets, AwtInsets::topID, ScaleDownY(m_insets.top));
|
||||
(env)->SetIntField(insets, AwtInsets::bottomID, ScaleDownY(m_insets.bottom));
|
||||
(env)->SetIntField(insets, AwtInsets::leftID, ScaleDownX(m_insets.left));
|
||||
(env)->SetIntField(insets, AwtInsets::rightID, ScaleDownX(m_insets.right));
|
||||
(env)->SetIntField(insets, AwtInsets::topID, result.top);
|
||||
(env)->SetIntField(insets, AwtInsets::bottomID, result.bottom);
|
||||
(env)->SetIntField(insets, AwtInsets::leftID, result.left);
|
||||
(env)->SetIntField(insets, AwtInsets::rightID, result.right);
|
||||
}
|
||||
env->DeleteLocalRef(peerInsets);
|
||||
|
||||
@@ -2251,13 +2266,6 @@ void AwtWindow::SetResizable(BOOL isResizable)
|
||||
RedrawNonClient();
|
||||
}
|
||||
|
||||
// SetWindowPos flags to cause frame edge to be recalculated
|
||||
static const UINT SwpFrameChangeFlags =
|
||||
SWP_FRAMECHANGED | /* causes WM_NCCALCSIZE to be called */
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
||||
SWP_NOACTIVATE | SWP_NOCOPYBITS |
|
||||
SWP_NOREPOSITION | SWP_NOSENDCHANGING;
|
||||
|
||||
//
|
||||
// Forces WM_NCCALCSIZE to be called to recalculate
|
||||
// window border (updates insets) without redrawing it
|
||||
@@ -2273,16 +2281,7 @@ void AwtWindow::RecalcNonClient()
|
||||
//
|
||||
void AwtWindow::RedrawNonClient()
|
||||
{
|
||||
UINT flags = SwpFrameChangeFlags;
|
||||
if (!HasCustomDecoration()) {
|
||||
// With custom decorations enabled, SetWindowPos call below can cause WM_SIZE message being sent.
|
||||
// If we're coming here from WFramePeer.initialize (as part of 'setResizable' call),
|
||||
// WM_SIZE message processing can happen concurrently with window flags update done as part of
|
||||
// 'setState' call), and lead to inconsistent state.
|
||||
// So, we disable asynchronous processing in case we have custom decorations to avoid the race condition.
|
||||
flags |= SWP_ASYNCWINDOWPOS;
|
||||
}
|
||||
::SetWindowPos(GetHWnd(), (HWND) NULL, 0, 0, 0, 0, flags);
|
||||
::SetWindowPos(GetHWnd(), (HWND) NULL, 0, 0, 0, 0, SwpFrameChangeFlags|SWP_ASYNCWINDOWPOS);
|
||||
}
|
||||
|
||||
int AwtWindow::GetScreenImOn() {
|
||||
@@ -3487,12 +3486,18 @@ Java_java_awt_Window_initIDs(JNIEnv *env, jclass cls)
|
||||
env->GetFieldID(cls, "securityWarningWidth", "I"));
|
||||
CHECK_NULL(AwtWindow::securityWarningHeightID =
|
||||
env->GetFieldID(cls, "securityWarningHeight", "I"));
|
||||
CHECK_NULL(AwtWindow::customTitleBarHitTestID =
|
||||
env->GetFieldID(cls, "customTitleBarHitTest", "I"));
|
||||
CHECK_NULL(AwtWindow::customTitleBarHitTestQueryID =
|
||||
env->GetFieldID(cls, "customTitleBarHitTestQuery", "I"));
|
||||
CHECK_NULL(AwtWindow::getWarningStringMID =
|
||||
env->GetMethodID(cls, "getWarningString", "()Ljava/lang/String;"));
|
||||
CHECK_NULL(AwtWindow::autoRequestFocusID =
|
||||
env->GetFieldID(cls, "autoRequestFocus", "Z"));
|
||||
CHECK_NULL(AwtWindow::calculateSecurityWarningPositionMID =
|
||||
env->GetMethodID(cls, "calculateSecurityWarningPosition", "(DDDD)Ljava/awt/geom/Point2D;"));
|
||||
CHECK_NULL(AwtWindow::internalCustomTitleBarHeightMID =
|
||||
env->GetMethodID(cls, "internalCustomTitleBarHeight", "()F"));
|
||||
|
||||
jclass windowTypeClass = env->FindClass("java/awt/Window$Type");
|
||||
CHECK_NULL(windowTypeClass);
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
static jfieldID autoRequestFocusID;
|
||||
static jfieldID securityWarningWidthID;
|
||||
static jfieldID securityWarningHeightID;
|
||||
static jfieldID customTitleBarHitTestID;
|
||||
static jfieldID customTitleBarHitTestQueryID;
|
||||
|
||||
/* sun.awt.windows.WWindowPeer field and method IDs */
|
||||
static jfieldID windowTypeID;
|
||||
@@ -65,6 +67,7 @@ public:
|
||||
static jmethodID getWarningStringMID;
|
||||
static jmethodID calculateSecurityWarningPositionMID;
|
||||
static jmethodID windowTypeNameMID;
|
||||
static jmethodID internalCustomTitleBarHeightMID;
|
||||
|
||||
static jfieldID sysInsetsID;
|
||||
|
||||
@@ -269,8 +272,6 @@ public:
|
||||
inline HWND GetOverriddenHWnd() { return m_overriddenHwnd; }
|
||||
inline void OverrideHWnd(HWND hwnd) { m_overriddenHwnd = hwnd; }
|
||||
|
||||
virtual BOOL HasCustomDecoration() { return FALSE; }
|
||||
|
||||
private:
|
||||
static int ms_instanceCounter;
|
||||
static HHOOK ms_hCBTFilter;
|
||||
@@ -399,6 +400,13 @@ protected:
|
||||
|
||||
inline Type GetType() { return m_windowType; }
|
||||
|
||||
// SetWindowPos flags to cause frame edge to be recalculated
|
||||
static const UINT SwpFrameChangeFlags =
|
||||
SWP_FRAMECHANGED | /* causes WM_NCCALCSIZE to be called */
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
||||
SWP_NOACTIVATE | SWP_NOCOPYBITS |
|
||||
SWP_NOREPOSITION | SWP_NOSENDCHANGING;
|
||||
|
||||
private:
|
||||
int m_screenNum;
|
||||
|
||||
|
||||
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright 2023 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
// Created by nikita.gubarkov on 26.01.2023.
|
||||
|
||||
#define GDIPVER 0x0110
|
||||
#include <windows.h>
|
||||
#include <objidl.h>
|
||||
#include <gdiplus.h>
|
||||
using namespace Gdiplus;
|
||||
|
||||
#include "jbr_CustomTitleBarControls.h"
|
||||
|
||||
namespace CustomTitleBarControlsSupport {
|
||||
static CriticalSection criticalSection;
|
||||
|
||||
using State = CustomTitleBarControls::State;
|
||||
using Type = CustomTitleBarControls::Type;
|
||||
using ButtonColors = ARGB[2][(int)State::UNKNOWN]; // [Background/Foreground][State]
|
||||
static constexpr ARGB BC_INHERIT = 0x00ffffff; // Transparent white means inherit
|
||||
|
||||
static constexpr ButtonColors DEFAULT_COLORS_WIN11[3] = { // Light/Dark/Close
|
||||
// NORMAL // HOVERED // PRESSED // DISABLED // INACTIVE //
|
||||
{{BC_INHERIT, 0x0A000000, 0x06000000, BC_INHERIT, BC_INHERIT}, // Light background
|
||||
{0xFF000000, 0xFF000000, 0xFF000000, 0x33000000, 0x60000000}}, // Light foreground
|
||||
{{BC_INHERIT, 0x0FFFFFFF, 0x0BFEFEFE, BC_INHERIT, BC_INHERIT}, // Dark background
|
||||
{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x33FFFFFF, 0x60FFFFFF}}, // Dark foreground
|
||||
{{BC_INHERIT, 0xFFC42B1C, 0xE5C32B1B, BC_INHERIT, BC_INHERIT}, // Close background
|
||||
{BC_INHERIT, 0xFFFFFFFF, 0xFFFFFFFF, BC_INHERIT, BC_INHERIT}}, // Close foreground
|
||||
};
|
||||
|
||||
static constexpr ButtonColors DEFAULT_COLORS_WIN10[3] = { // Light/Dark/Close
|
||||
// NORMAL // HOVERED // PRESSED // DISABLED // INACTIVE //
|
||||
{{BC_INHERIT, 0x1A000000, 0x33000000, BC_INHERIT, BC_INHERIT}, // Light background
|
||||
{0xFF000000, 0xFF000000, 0xFF000000, 0x33000000, 0x60000000}}, // Light foreground
|
||||
{{BC_INHERIT, 0x1AFEFEFE, 0x33FFFFFF, BC_INHERIT, BC_INHERIT}, // Dark background
|
||||
{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x33FFFFFF, 0x60FFFFFF}}, // Dark foreground
|
||||
{{BC_INHERIT, 0xFFE81123, 0x99E71022, BC_INHERIT, BC_INHERIT}, // Close background
|
||||
{BC_INHERIT, 0xFFFFFFFF, 0xFFFFFFFF, BC_INHERIT, BC_INHERIT}}, // Close foreground
|
||||
};
|
||||
|
||||
static void PaintIconWin11(Type type, Graphics& g, float scale, SolidBrush* mask) {
|
||||
float size = 10.0f * scale;
|
||||
GraphicsPath p {};
|
||||
switch (type) {
|
||||
case Type::CLOSE:{
|
||||
float o = 0.3f;
|
||||
Pen pen(mask, 1.04f * scale);
|
||||
p.AddLine(o, o, size-o, size-o);
|
||||
p.CloseFigure();
|
||||
p.AddLine(size-o, o, o, size-o);
|
||||
g.DrawPath(&pen, &p);
|
||||
if (scale < 1.5f) {
|
||||
g.SetCompositingMode(CompositingModeSourceOver);
|
||||
g.DrawPath(&pen, &p);
|
||||
}
|
||||
} return;
|
||||
case Type::MINIMIZE:{
|
||||
float t = (int) (4.0f * scale);
|
||||
if (scale > 2 && ((int) (2.0f * scale)) % 2 == 1) t += 0.5f;
|
||||
p.AddArc(0.0f, t, scale, scale, 90, 180);
|
||||
p.AddArc(size-scale, t, scale, scale, 270, 180);
|
||||
} break;
|
||||
case Type::RESTORE:{
|
||||
float r = 6.0f * scale, d = 3.0f * scale, o = 2.0f * scale;
|
||||
float a = 19.4712206f; // asin(1/3) in degrees
|
||||
p.AddArc(o, 0.0f, d, d, 180+a, 90-a);
|
||||
p.AddArc(size-r, 0.0f, r, r, 270, 90);
|
||||
p.AddArc(size-d, size-d-o, d, d, 0, 90-a);
|
||||
d = 4.0f * scale;
|
||||
p.AddArc(size-(r+d)/2.0f, (r-d)/2.0f, d, d, 0, -90);
|
||||
p.CloseFigure();
|
||||
}{
|
||||
size = (int) (8.0f * scale);
|
||||
float r = 3.0f * scale, d = 1.0f * scale, t = (r-d)/2.0f, o = (r+d)/2.0f, y = 10.0f * scale - size;
|
||||
p.AddArc(0.0f, y, r, r, 180, 90);
|
||||
p.AddArc(size-r, y, r, r, 270, 90);
|
||||
p.AddArc(size-r, size-r+y, r, r, 0, 90);
|
||||
p.AddArc(0.0f, size-r+y, r, r, 90, 90);
|
||||
p.CloseFigure();
|
||||
p.AddArc(t, t+y, d, d, 180, 90);
|
||||
p.AddArc(size-o, t+y, d, d, 270, 90);
|
||||
p.AddArc(size-o, size-o+y, d, d, 0, 90);
|
||||
p.AddArc(t, size-o+y, d, d, 90, 90);
|
||||
p.CloseFigure();
|
||||
} break;
|
||||
case Type::MAXIMIZE:{
|
||||
float r = 3.0f * scale, d = 1.0f * scale, t = (r-d)/2.0f, o = (r+d)/2.0f;
|
||||
p.AddArc(0.0f, 0.0f, r, r, 180, 90);
|
||||
p.AddArc(size-r, 0.0f, r, r, 270, 90);
|
||||
p.AddArc(size-r, size-r, r, r, 0, 90);
|
||||
p.AddArc(0.0f, size-r, r, r, 90, 90);
|
||||
p.CloseFigure();
|
||||
p.AddArc(t, t, d, d, 180, 90);
|
||||
p.AddArc(size-o, t, d, d, 270, 90);
|
||||
p.AddArc(size-o, size-o, d, d, 0, 90);
|
||||
p.AddArc(t, size-o, d, d, 90, 90);
|
||||
p.CloseFigure();
|
||||
} break;
|
||||
}
|
||||
g.FillPath(mask, &p);
|
||||
}
|
||||
|
||||
static void PaintIconWin10(Type type, Graphics& g, float scale, SolidBrush* mask) {
|
||||
SolidBrush clear(0xff000000);
|
||||
g.SetSmoothingMode(SmoothingModeNone);
|
||||
float size = 10.0f * scale;
|
||||
switch (type) {
|
||||
case Type::CLOSE:{
|
||||
float o = scale * 0.35f;
|
||||
Pen pen(mask, scale);
|
||||
g.DrawLine(&pen, o, o, size-o, size-o);
|
||||
g.DrawLine(&pen, size-o, o, o, size-o);
|
||||
} break;
|
||||
case Type::MINIMIZE:{
|
||||
float t = (int) (4.0f * scale);
|
||||
g.FillRectangle(mask, 0.0f, t, size, scale);
|
||||
} break;
|
||||
case Type::RESTORE:{
|
||||
float r = (int) (8.0f * scale), t = (int) scale;
|
||||
g.FillRectangle(mask, size-r, 0.0f, r, r);
|
||||
g.FillRectangle(&clear, size-r+t, t, r-t*2.0f, r-t*2.0f);
|
||||
g.FillRectangle(mask, 0.0f, size-r, r, r);
|
||||
g.FillRectangle(&clear, t, size-r+t, r-t*2.0f, r-t*2.0f);
|
||||
} break;
|
||||
case Type::MAXIMIZE:{
|
||||
float t = (int) scale;
|
||||
g.FillRectangle(mask, 0.0f, 0.0f, size, size);
|
||||
g.FillRectangle(&clear, t, t, size-t*2.0f, size-t*2.0f);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static void (*PaintIcon)(Type type, Graphics& g, float scale, SolidBrush* mask);
|
||||
static const ButtonColors* DEFAULT_COLORS;
|
||||
|
||||
static Color GetColor(Type type, State state, bool foreground, bool dark, const ButtonColors& override) {
|
||||
if (type == Type::CLOSE) {
|
||||
ARGB result = DEFAULT_COLORS[2][(int)foreground][(int)state];
|
||||
if (result != BC_INHERIT) return result;
|
||||
}
|
||||
ARGB result = override[(int)foreground][(int)state];
|
||||
if (result != BC_INHERIT) return result;
|
||||
return DEFAULT_COLORS[(int)dark][(int)foreground][(int)state];
|
||||
}
|
||||
|
||||
static Bitmap* CreateIcon(Type type, float scale) {
|
||||
int size = (int) (10.0f * scale + 0.5f); // All icons are 10x10px at 100% scale
|
||||
int stride = ((size * 3 + 3) / 4) * 4;
|
||||
BYTE* bitmapData = new BYTE[size * stride];
|
||||
Bitmap* bitmap = ::new Bitmap(size, size, stride, PixelFormat24bppRGB, bitmapData);
|
||||
SolidBrush mask(0xffffffff);
|
||||
Graphics g(bitmap);
|
||||
g.SetCompositingMode(CompositingModeSourceCopy);
|
||||
g.SetSmoothingMode(SmoothingModeAntiAlias8x8);
|
||||
g.SetPixelOffsetMode(PixelOffsetModeHalf);
|
||||
g.Clear(0xff000000);
|
||||
PaintIcon(type, g, scale, &mask);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
static constexpr int ICON_SCALES = 7;
|
||||
static constexpr float ICON_SCALE_MAP[ICON_SCALES][2] = {
|
||||
{1.0f, 1.0f},
|
||||
{1.25f, 1.2f},
|
||||
{1.5f, 1.5f},
|
||||
{2.0f, 2.0f},
|
||||
{2.5f, 2.4f},
|
||||
{3.0f, 3.0f},
|
||||
{4.0f, 4.0f},
|
||||
};
|
||||
static Bitmap* ICON_CACHE[(int) Type::UNKNOWN][ICON_SCALES] {};
|
||||
|
||||
static Bitmap* GetIcon(Type type, float scale, int scaleId) {
|
||||
CriticalSection::Lock lock(criticalSection);
|
||||
Bitmap*& cached = ICON_CACHE[(int) type][scaleId];
|
||||
if (!cached) cached = CreateIcon(type, scale);
|
||||
return cached;
|
||||
}
|
||||
|
||||
static Bitmap* GetIcon(Type type, float scale) {
|
||||
int i = ICON_SCALES-1;
|
||||
while (i > 0) {
|
||||
if (scale >= ICON_SCALE_MAP[i][0]) break;
|
||||
else i--;
|
||||
}
|
||||
scale = ICON_SCALE_MAP[i][1];
|
||||
return GetIcon(type, scale, i);
|
||||
}
|
||||
|
||||
enum class Availability {
|
||||
UNKNOWN,
|
||||
AVAILABLE,
|
||||
UNAVAILABLE
|
||||
};
|
||||
static volatile Availability availability = Availability::UNKNOWN;
|
||||
static constexpr LPCTSTR CLASS = L"JBRCustomTitleBarControls";
|
||||
static jmethodID jmUpdateInsets = nullptr;
|
||||
|
||||
static bool IsAvailable() {
|
||||
if (availability != Availability::UNKNOWN) {
|
||||
return availability == Availability::AVAILABLE;
|
||||
}
|
||||
CriticalSection::Lock lock(criticalSection);
|
||||
if (availability != Availability::UNKNOWN) {
|
||||
return availability == Availability::AVAILABLE;
|
||||
}
|
||||
|
||||
// Init GDI+
|
||||
ULONG_PTR startupToken;
|
||||
GdiplusStartupInput input;
|
||||
if (GdiplusStartup(&startupToken, &input, nullptr) != Ok) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Choose Win10/11 style
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
bool win11OrNewer = (bool) JNU_GetStaticFieldByName(env, nullptr, "sun/awt/windows/WFramePeer", "WIN11_OR_NEWER", "Z").z;
|
||||
if (win11OrNewer) {
|
||||
PaintIcon = PaintIconWin11;
|
||||
DEFAULT_COLORS = DEFAULT_COLORS_WIN11;
|
||||
} else {
|
||||
PaintIcon = PaintIconWin10;
|
||||
DEFAULT_COLORS = DEFAULT_COLORS_WIN10;
|
||||
}
|
||||
|
||||
// Find internalCustomTitleBarUpdateInsets java method
|
||||
jclass jcWindow = env->FindClass("java/awt/Window");
|
||||
if (!jcWindow) goto fail;
|
||||
jmUpdateInsets = env->GetMethodID(jcWindow, "internalCustomTitleBarUpdateInsets", "(FF)V");
|
||||
env->DeleteLocalRef(jcWindow);
|
||||
if (!jmUpdateInsets) goto fail;
|
||||
|
||||
// Register class
|
||||
WNDCLASSEX wc;
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.style = 0L;
|
||||
wc.lpfnWndProc = (WNDPROC)DefWindowProc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = AwtToolkit::GetInstance().GetModuleHandle();
|
||||
wc.hIcon = nullptr;
|
||||
wc.hCursor = nullptr;
|
||||
wc.hbrBackground = nullptr;
|
||||
wc.lpszMenuName = nullptr;
|
||||
wc.lpszClassName = CLASS;
|
||||
wc.hIconSm = nullptr;
|
||||
RegisterClassEx(&wc);
|
||||
|
||||
availability = Availability::AVAILABLE;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
availability = Availability::UNAVAILABLE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
using namespace CustomTitleBarControlsSupport;
|
||||
extern BOOL AppsUseLightThemeCached;
|
||||
|
||||
// === CustomTitleBarControls implementation ===
|
||||
|
||||
class CustomTitleBarControls::Resources {
|
||||
public:
|
||||
|
||||
const SIZE size;
|
||||
HDC hdc;
|
||||
BYTE* bitmapData;
|
||||
Bitmap* bitmap;
|
||||
HBITMAP hbitmap;
|
||||
Graphics* graphics;
|
||||
|
||||
Resources(SIZE s, HDC hdcComp) :
|
||||
size(s) {
|
||||
bitmapData = new BYTE[s.cx * s.cy * 4];
|
||||
bitmap = ::new Bitmap(s.cx, s.cy, s.cx * 4, PixelFormat32bppPARGB, bitmapData);
|
||||
bitmap->GetHBITMAP(Color(0), &hbitmap);
|
||||
hdc = CreateCompatibleDC(hdcComp);
|
||||
SelectObject(hdc, hbitmap);
|
||||
graphics = Graphics::FromHDC(hdc);
|
||||
}
|
||||
|
||||
~Resources() {
|
||||
delete graphics;
|
||||
DeleteDC(hdc);
|
||||
DeleteObject(hbitmap);
|
||||
::delete bitmap;
|
||||
delete bitmapData;
|
||||
}
|
||||
};
|
||||
|
||||
class CustomTitleBarControls::Style {
|
||||
|
||||
static jobject GetProperty(JNIEnv* env, jobject properties, LPCWSTR key) {
|
||||
jstring jkey = JNU_NewStringPlatform(env, key);
|
||||
jobject value = JNU_CallMethodByName(env, nullptr, properties, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", jkey).l;
|
||||
env->DeleteLocalRef(jkey);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void UnwrapProperty(JNIEnv* env, jobject properties, LPCWSTR key, const char* instanceof,
|
||||
const char* unwrapMethod, const char* unwrapSignature, jvalue& result) {
|
||||
jobject value = GetProperty(env, properties, key);
|
||||
if (value) {
|
||||
if (JNU_IsInstanceOfByName(env, value, instanceof) == 1) {
|
||||
result = JNU_CallMethodByName(env, nullptr, value, unwrapMethod, unwrapSignature);
|
||||
}
|
||||
env->DeleteLocalRef(value);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetBooleanProperty(JNIEnv* env, jobject properties, LPCWSTR key) { // null -> -1
|
||||
jvalue result;
|
||||
result.i = -1;
|
||||
UnwrapProperty(env, properties, key, "java/lang/Boolean", "booleanValue", "()Z", result);
|
||||
return (int) result.i;
|
||||
}
|
||||
|
||||
static float GetNumberProperty(JNIEnv* env, jobject properties, LPCWSTR key) { // null -> -1
|
||||
jvalue result;
|
||||
result.f = -1;
|
||||
UnwrapProperty(env, properties, key, "java/lang/Number", "floatValue", "()F", result);
|
||||
return (float) result.f;
|
||||
}
|
||||
|
||||
static ARGB GetColorProperty(JNIEnv* env, jobject properties, LPCWSTR key) { // null -> BC_INHERIT
|
||||
jvalue result;
|
||||
result.i = BC_INHERIT;
|
||||
UnwrapProperty(env, properties, key, "java/awt/Color", "getRGB", "()I", result);
|
||||
return (ARGB) result.i;
|
||||
}
|
||||
|
||||
public:
|
||||
float height {0}, width {0};
|
||||
int dark {0};
|
||||
ButtonColors colors {};
|
||||
|
||||
bool Update(jobject target, JNIEnv* env) {
|
||||
jobject titleBar = JNU_GetFieldByName(env, nullptr, target, "customTitleBar", "Ljava/awt/Window$CustomTitleBar;").l;
|
||||
if (!titleBar) return false;
|
||||
jobject properties = JNU_CallMethodByName(env, nullptr, titleBar, "getProperties", "()Ljava/util/Map;").l;
|
||||
bool visible = true;
|
||||
if (properties) {
|
||||
if (GetBooleanProperty(env, properties, L"controls.visible") == 0) {
|
||||
visible = false;
|
||||
} else {
|
||||
height = JNU_CallMethodByName(env, nullptr, titleBar, "getHeight", "()F").f;
|
||||
width = GetNumberProperty(env, properties, L"controls.width");
|
||||
dark = GetBooleanProperty(env, properties, L"controls.dark");
|
||||
// Get colors
|
||||
#define GET_STATE_COLOR(NAME, PROPERTY) \
|
||||
colors[0][(int)State::NAME] = GetColorProperty(env, properties, L"controls.background." PROPERTY); \
|
||||
colors[1][(int)State::NAME] = GetColorProperty(env, properties, L"controls.foreground." PROPERTY)
|
||||
GET_STATE_COLOR(NORMAL, L"normal");
|
||||
GET_STATE_COLOR(HOVERED, L"hovered");
|
||||
GET_STATE_COLOR(PRESSED, L"pressed");
|
||||
GET_STATE_COLOR(DISABLED, L"disabled");
|
||||
GET_STATE_COLOR(INACTIVE, L"inactive");
|
||||
}
|
||||
env->DeleteLocalRef(properties);
|
||||
}
|
||||
env->DeleteLocalRef(titleBar);
|
||||
return visible;
|
||||
}
|
||||
};
|
||||
|
||||
void CustomTitleBarControls::Refresh(CustomTitleBarControls*& controls, HWND parent, jobject target, JNIEnv* env) {
|
||||
Style style;
|
||||
if (IsAvailable() && style.Update(target, env)) {
|
||||
if (controls) *controls->style = style;
|
||||
else controls = new CustomTitleBarControls(parent, env->NewWeakGlobalRef(target), style);
|
||||
controls->Update();
|
||||
} else if (controls) {
|
||||
delete controls;
|
||||
controls = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
CustomTitleBarControls::CustomTitleBarControls(HWND parent, jweak target, const Style& style) {
|
||||
this->parent = parent;
|
||||
this->target = target;
|
||||
hwnd = CreateWindowExW(WS_EX_LAYERED | WS_EX_TRANSPARENT, CLASS, L"",
|
||||
WS_CHILD | WS_VISIBLE,
|
||||
0, 0, 0, 0,
|
||||
parent, nullptr, AwtToolkit::GetInstance().GetModuleHandle(), nullptr);
|
||||
hit = HTNOWHERE;
|
||||
pressed = false;
|
||||
windowState = State::NORMAL;
|
||||
resources = nullptr;
|
||||
this->style = new Style(style);
|
||||
}
|
||||
|
||||
CustomTitleBarControls::~CustomTitleBarControls() {
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
jobject target = env->NewLocalRef(this->target);
|
||||
if (target) {
|
||||
env->CallVoidMethod(target, jmUpdateInsets, 0.0f, 0.0f);
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
env->DeleteWeakGlobalRef(this->target);
|
||||
DestroyWindow(hwnd);
|
||||
delete resources;
|
||||
delete style;
|
||||
}
|
||||
|
||||
void CustomTitleBarControls::PaintButton(Type type, State state, int x, int width, float scale, bool dark) {
|
||||
Graphics& g = *resources->graphics;
|
||||
|
||||
// Paint background
|
||||
Color background = GetColor(type, state, false, dark, style->colors);
|
||||
if (background.GetA() > 0) {
|
||||
SolidBrush brush(background);
|
||||
g.FillRectangle(&brush, x, 0, width, resources->size.cy);
|
||||
}
|
||||
|
||||
// Paint icon
|
||||
Color foreground = GetColor(type, state, true, dark, style->colors);
|
||||
float c[4] = {(float)foreground.GetA() / 255.0f,
|
||||
(float)foreground.GetR() / 255.0f,
|
||||
(float)foreground.GetG() / 255.0f,
|
||||
(float)foreground.GetB() / 255.0f};
|
||||
ColorMatrix colorMatrix = {
|
||||
0.0f, 0.0f, 0.0f, c[0], 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
c[1], c[2], c[3], 0.0f, 1.0f};
|
||||
Bitmap* i = GetIcon(type, scale);
|
||||
int w = (int) i->GetWidth(), h = (int) i->GetHeight();
|
||||
ImageAttributes imageAttributes;
|
||||
imageAttributes.SetColorMatrix(&colorMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);
|
||||
g.DrawImage(i, Rect(x + (width - w) / 2, (resources->size.cy - h) / 2, w, h), 0, 0, w, h, UnitPixel, &imageAttributes);
|
||||
}
|
||||
|
||||
#define LOAD_STYLE_BITS() \
|
||||
DWORD styleBits = (DWORD) GetWindowLong(parent, GWL_STYLE); \
|
||||
DWORD exStyleBits = (DWORD) GetWindowLong(parent, GWL_EXSTYLE); \
|
||||
bool allButtons = styleBits & (WS_MINIMIZEBOX | WS_MAXIMIZEBOX); \
|
||||
bool ltr = !(exStyleBits & WS_EX_LAYOUTRTL)
|
||||
|
||||
void CustomTitleBarControls::Update(State windowState) {
|
||||
LOAD_STYLE_BITS();
|
||||
|
||||
// Calculate size
|
||||
float userWidth;
|
||||
if (style->width > 0.0f) {
|
||||
userWidth = style->width;
|
||||
} else {
|
||||
userWidth = allButtons ? 141 : 32;
|
||||
}
|
||||
UINT dpi = AwtToolkit::GetDpiForWindow(hwnd);
|
||||
float scale = (float) dpi / 96.0f;
|
||||
SIZE newSize {(int) (userWidth * scale), (int) (style->height * scale)};
|
||||
|
||||
// Recreate resources if size has changed
|
||||
if (!resources || resources->size.cx != newSize.cx || resources->size.cy != newSize.cy) {
|
||||
delete resources;
|
||||
HDC hdcComp = GetDC(hwnd);
|
||||
resources = new Resources(newSize, hdcComp);
|
||||
ReleaseDC(hwnd, hdcComp);
|
||||
}
|
||||
|
||||
// Calculate states
|
||||
if (windowState != State::UNKNOWN) this->windowState = windowState;
|
||||
State minState = this->windowState, maxState = minState, closeState = minState;
|
||||
if (hit != HTNOWHERE) {
|
||||
State& s = hit == HTMINBUTTON ? minState : hit == HTMAXBUTTON ? maxState : closeState;
|
||||
s = pressed ? State::PRESSED : State::HOVERED;
|
||||
}
|
||||
if (!(styleBits & WS_MINIMIZEBOX)) minState = State::DISABLED;
|
||||
if (!(styleBits & WS_MAXIMIZEBOX)) maxState = State::DISABLED;
|
||||
|
||||
bool dark = style->dark != -1 ? (bool) style->dark : !AppsUseLightThemeCached;
|
||||
|
||||
// Paint buttons
|
||||
resources->graphics->Clear(0);
|
||||
if (allButtons) {
|
||||
int w = newSize.cx / 3;
|
||||
Type maxType = IsZoomed(parent) ? Type::RESTORE : Type::MAXIMIZE;
|
||||
if (ltr) {
|
||||
PaintButton(Type::MINIMIZE, minState, 0, w, scale, dark);
|
||||
PaintButton(maxType, maxState, w, w, scale, dark);
|
||||
PaintButton(Type::CLOSE, closeState, w*2, newSize.cx-w*2, scale, dark);
|
||||
} else {
|
||||
PaintButton(Type::CLOSE, closeState, 0, newSize.cx-w*2, scale, dark);
|
||||
PaintButton(maxType, maxState, newSize.cx-w*2, w, scale, dark);
|
||||
PaintButton(Type::MINIMIZE, minState, newSize.cx-w, w, scale, dark);
|
||||
}
|
||||
} else {
|
||||
PaintButton(Type::CLOSE, closeState, 0, newSize.cx, scale, dark);
|
||||
}
|
||||
|
||||
// Update window
|
||||
POINT position {0, 0}, ptSrc {0, 0};
|
||||
if (ltr) {
|
||||
RECT parentRect;
|
||||
GetClientRect(parent, &parentRect);
|
||||
position.x = parentRect.right - newSize.cx;
|
||||
}
|
||||
|
||||
BLENDFUNCTION blend;
|
||||
blend.SourceConstantAlpha = 255;
|
||||
blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.BlendFlags = 0;
|
||||
|
||||
HDC hdcDst = GetDC(nullptr);
|
||||
SetWindowPos(hwnd, HWND_TOP, position.x, position.y, newSize.cx, newSize.cy, 0);
|
||||
UpdateLayeredWindow(hwnd, hdcDst, &position, &newSize, resources->hdc, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
ReleaseDC(nullptr, hdcDst);
|
||||
|
||||
// Update insets
|
||||
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||
jobject target = env->NewLocalRef(this->target);
|
||||
if (target) {
|
||||
env->CallVoidMethod(target, jmUpdateInsets, !ltr ? userWidth : 0.0f, ltr ? userWidth : 0.0f);
|
||||
env->DeleteLocalRef(target);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CustomTitleBarControls::Hit(HitType type, int ncx, int ncy) {
|
||||
LRESULT newHit = HTNOWHERE;
|
||||
if (type != HitType::RESET) {
|
||||
RECT rect;
|
||||
GetWindowRect(hwnd, &rect);
|
||||
if (ncx >= rect.left && ncx <= rect.right && ncy >= rect.top && ncy <= rect.bottom) {
|
||||
LOAD_STYLE_BITS();
|
||||
newHit = HTCLOSE;
|
||||
if (allButtons) {
|
||||
int w = (rect.right - rect.left) / 3;
|
||||
ncx -= rect.left;
|
||||
if (!ltr) ncx = rect.right - rect.left - ncx;
|
||||
if (ncx < w) newHit = HTMINBUTTON;
|
||||
else if (ncx < w*2) newHit = HTMAXBUTTON;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == HitType::TEST) return newHit;
|
||||
if (newHit != hit || type == HitType::PRESS || type == HitType::RELEASE) {
|
||||
LRESULT oldHit = hit;
|
||||
hit = newHit;
|
||||
if (type == HitType::PRESS) pressed = true;
|
||||
else if (type == HitType::RELEASE || newHit != oldHit) {
|
||||
if (!pressed && type == HitType::RELEASE) newHit = HTNOWHERE; // Cancel action
|
||||
pressed = false;
|
||||
}
|
||||
Update();
|
||||
|
||||
TRACKMOUSEEVENT track;
|
||||
track.cbSize = sizeof(TRACKMOUSEEVENT);
|
||||
track.dwFlags = TME_LEAVE | TME_NONCLIENT;
|
||||
track.hwndTrack = parent;
|
||||
TrackMouseEvent(&track);
|
||||
}
|
||||
return newHit;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2023 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
// Created by nikita.gubarkov on 26.01.2023.
|
||||
|
||||
#ifndef JBR_CUSTOMTITLEBARCONTROLS_H
|
||||
#define JBR_CUSTOMTITLEBARCONTROLS_H
|
||||
|
||||
#include "awt_Toolkit.h"
|
||||
|
||||
class CustomTitleBarControls {
|
||||
public:
|
||||
|
||||
enum class State {
|
||||
NORMAL,
|
||||
HOVERED, // "Hot" in Windows theme terminology
|
||||
PRESSED, // "Pushed" in Windows theme terminology
|
||||
DISABLED,
|
||||
INACTIVE, // Didn't find this state in Windows, it represents button in inactive window
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
MINIMIZE,
|
||||
MAXIMIZE,
|
||||
RESTORE,
|
||||
CLOSE,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
enum class HitType {
|
||||
RESET,
|
||||
TEST,
|
||||
MOVE,
|
||||
PRESS,
|
||||
RELEASE
|
||||
};
|
||||
|
||||
private:
|
||||
class Resources;
|
||||
class Style;
|
||||
|
||||
jweak target;
|
||||
HWND parent, hwnd;
|
||||
Resources* resources;
|
||||
Style* style;
|
||||
LRESULT hit;
|
||||
bool pressed;
|
||||
State windowState;
|
||||
|
||||
CustomTitleBarControls(HWND parent, jweak target, const Style& style);
|
||||
void PaintButton(Type type, State state, int x, int width, float scale, bool dark);
|
||||
|
||||
public:
|
||||
static void Refresh(CustomTitleBarControls*& controls, HWND parent, jobject target, JNIEnv* env);
|
||||
void Update(State windowState = State::UNKNOWN);
|
||||
LRESULT Hit(HitType type, int ncx, int ncy); // HTNOWHERE / HTMINBUTTON / HTMAXBUTTON / HTCLOSE
|
||||
~CustomTitleBarControls();
|
||||
|
||||
};
|
||||
|
||||
#endif //JBR_CUSTOMTITLEBARCONTROLS_H
|
||||
@@ -33,6 +33,7 @@ import java.util.Map;
|
||||
* special behavior like dragging or maximizing on double click.
|
||||
* @implNote Behavior is platform-dependent, only macOS and Windows are supported.
|
||||
*/
|
||||
@Deprecated(forRemoval=true)
|
||||
public interface CustomWindowDecoration {
|
||||
|
||||
/*CONST java.awt.Window.*_HIT_SPOT*/
|
||||
|
||||
177
src/jetbrains.api/src/com/jetbrains/WindowDecorations.java
Normal file
177
src/jetbrains.api/src/com/jetbrains/WindowDecorations.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2023 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.jetbrains;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Window decorations consist of title bar, window controls and border.
|
||||
* @see WindowDecorations.CustomTitleBar
|
||||
*/
|
||||
public interface WindowDecorations {
|
||||
|
||||
/**
|
||||
* If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the
|
||||
* top of the frame with window controls painted over the client area.
|
||||
* {@code customTitleBar=null} resets to the default appearance with system-provided title bar.
|
||||
* @see CustomTitleBar
|
||||
* @see #createCustomTitleBar()
|
||||
*/
|
||||
void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar);
|
||||
|
||||
/**
|
||||
* If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the
|
||||
* top of the dialog with window controls painted over the client area.
|
||||
* {@code customTitleBar=null} resets to the default appearance with system-provided title bar.
|
||||
* @see CustomTitleBar
|
||||
* @see #createCustomTitleBar()
|
||||
*/
|
||||
void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar);
|
||||
|
||||
/**
|
||||
* You must {@linkplain CustomTitleBar#setHeight(float) set title bar height} before adding it to a window.
|
||||
* @see CustomTitleBar
|
||||
* @see #setCustomTitleBar(Frame, CustomTitleBar)
|
||||
* @see #setCustomTitleBar(Dialog, CustomTitleBar)
|
||||
*/
|
||||
CustomTitleBar createCustomTitleBar();
|
||||
|
||||
/**
|
||||
* Custom title bar allows merging of window content with native title bar,
|
||||
* which is done by treating title bar as part of client area, but with some
|
||||
* special behavior like dragging or maximizing on double click.
|
||||
* Custom title bar has {@linkplain CustomTitleBar#getHeight() height} and controls.
|
||||
* @implNote Behavior is platform-dependent, only macOS and Windows are supported.
|
||||
* @see #setCustomTitleBar(Frame, CustomTitleBar)
|
||||
*/
|
||||
interface CustomTitleBar {
|
||||
|
||||
/**
|
||||
* @return title bar height, measured in pixels from the top of client area, i.e. excluding top frame border.
|
||||
*/
|
||||
float getHeight();
|
||||
|
||||
/**
|
||||
* @param height title bar height, measured in pixels from the top of client area,
|
||||
* i.e. excluding top frame border. Must be > 0.
|
||||
*/
|
||||
void setHeight(float height);
|
||||
|
||||
/**
|
||||
* @see #putProperty(String, Object)
|
||||
*/
|
||||
Map<String, Object> getProperties();
|
||||
|
||||
/**
|
||||
* @see #putProperty(String, Object)
|
||||
*/
|
||||
void putProperties(Map<String, ?> m);
|
||||
|
||||
/**
|
||||
* Windows & macOS properties:
|
||||
* <ul>
|
||||
* <li>{@code controls.visible} : {@link Boolean} - whether title bar controls
|
||||
* (minimize/maximize/close buttons) are visible, default = true.</li>
|
||||
* </ul>
|
||||
* Windows properties:
|
||||
* <ul>
|
||||
* <li>{@code controls.width} : {@link Number} - width of block of buttons (not individual buttons).
|
||||
* Note that dialogs have only one button, while frames usually have 3 of them.</li>
|
||||
* <li>{@code controls.dark} : {@link Boolean} - whether to use dark or light color theme
|
||||
* (light or dark icons respectively).</li>
|
||||
* <li>{@code controls.<layer>.<state>} : {@link Color} - precise control over button colors,
|
||||
* where {@code <layer>} is one of:
|
||||
* <ul><li>{@code foreground}</li><li>{@code background}</li></ul>
|
||||
* and {@code <state>} is one of:
|
||||
* <ul>
|
||||
* <li>{@code normal}</li>
|
||||
* <li>{@code hovered}</li>
|
||||
* <li>{@code pressed}</li>
|
||||
* <li>{@code disabled}</li>
|
||||
* <li>{@code inactive}</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
void putProperty(String key, Object value);
|
||||
|
||||
/**
|
||||
* @return space occupied by title bar controls on the left (px)
|
||||
*/
|
||||
float getLeftInset();
|
||||
/**
|
||||
* @return space occupied by title bar controls on the right (px)
|
||||
*/
|
||||
float getRightInset();
|
||||
|
||||
/**
|
||||
* By default, any component which has no cursor or mouse event listeners set is considered transparent for
|
||||
* native title bar actions. That is, dragging simple JPanel in title bar area will drag the
|
||||
* window, but dragging a JButton will not. Adding mouse listener to a component will prevent any native actions
|
||||
* inside bounds of that component.
|
||||
* <p>
|
||||
* This method gives you precise control of whether to allow native title bar actions or not.
|
||||
* <ul>
|
||||
* <li>{@code client=true} means that mouse is currently over a client area. Native title bar behavior is disabled.</li>
|
||||
* <li>{@code client=false} means that mouse is currently over a non-client area. Native title bar behavior is enabled.</li>
|
||||
* </ul>
|
||||
* <em>Intended usage:
|
||||
* <ul>
|
||||
* <li>This method must be called in response to all {@linkplain java.awt.event.MouseEvent mouse events}
|
||||
* except {@link java.awt.event.MouseEvent#MOUSE_EXITED} and {@link java.awt.event.MouseEvent#MOUSE_WHEEL}.</li>
|
||||
* <li>This method is called per-event, i.e. when component has multiple listeners, you only need to call it once.</li>
|
||||
* <li>If this method hadn't been called, title bar behavior is reverted back to default upon processing the event.</li>
|
||||
* </ul></em>
|
||||
* Note that hit test value is relevant only for title bar area, e.g. calling
|
||||
* {@code forceHitTest(false)} will not make window draggable via non-title bar area.
|
||||
*
|
||||
* <h2>Example:</h2>
|
||||
* Suppose you have a {@code JPanel} in the title bar area. You want it to respond to right-click for
|
||||
* some popup menu, but also retain native drag and double-click behavior.
|
||||
* <pre>
|
||||
* CustomTitleBar titlebar = ...;
|
||||
* JPanel panel = ...;
|
||||
* MouseAdapter adapter = new MouseAdapter() {
|
||||
* private void hit() { titlebar.forceHitTest(false); }
|
||||
* public void mouseClicked(MouseEvent e) {
|
||||
* hit();
|
||||
* if (e.getButton() == MouseEvent.BUTTON3) ...;
|
||||
* }
|
||||
* public void mousePressed(MouseEvent e) { hit(); }
|
||||
* public void mouseReleased(MouseEvent e) { hit(); }
|
||||
* public void mouseEntered(MouseEvent e) { hit(); }
|
||||
* public void mouseDragged(MouseEvent e) { hit(); }
|
||||
* public void mouseMoved(MouseEvent e) { hit(); }
|
||||
* };
|
||||
* panel.addMouseListener(adapter);
|
||||
* panel.addMouseMotionListener(adapter);
|
||||
* </pre>
|
||||
*/
|
||||
void forceHitTest(boolean client);
|
||||
|
||||
Window getContainingWindow();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
# 2. When only new API is added, or some existing API was @Deprecated - increment MINOR, reset PATCH to 0
|
||||
# 3. For major backwards incompatible API changes - increment MAJOR, reset MINOR and PATCH to 0
|
||||
|
||||
VERSION = 0.0.9
|
||||
VERSION = 0.0.10
|
||||
|
||||
# Hash is used to track changes to jetbrains.api, so you would not forget to update version when needed.
|
||||
# When you make any changes, "make jbr-api" will fail and ask you to update hash and version number here.
|
||||
|
||||
HASH = F7F494F37B77B846C7EF8F93EBE9E397
|
||||
HASH = D331895B9FBDB29379EC6B45FAB51E
|
||||
|
||||
Reference in New Issue
Block a user