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:
Nikita Gubarkov
2022-12-22 16:05:12 +01:00
committed by jbrbot
parent b61238b359
commit 14b6fe9b2a
26 changed files with 1833 additions and 531 deletions

View File

@@ -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), \

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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&) {

View File

@@ -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
*/

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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*/

View 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();
}
}

View File

@@ -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