Skip to content

Commit 1a6d4d4

Browse files
committed
[win32] Add workaround for missing selection indicator for menu item
with image on Win11 (#501) For menu items of type CHECK and RADIO with an image, it create an second image with a checkmark/circle over the original image.
1 parent f498ca3 commit 1a6d4d4

File tree

2 files changed

+117
-9
lines changed
  • bundles/org.eclipse.swt
    • Eclipse SWT PI/win32/org/eclipse/swt/internal/win32
    • Eclipse SWT/win32/org/eclipse/swt/widgets

2 files changed

+117
-9
lines changed

bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,7 @@ public class OS extends C {
890890
public static final int MF_SEPARATOR = 0x800;
891891
public static final int MF_SYSMENU = 0x2000;
892892
public static final int MF_UNCHECKED = 0x0;
893+
public static final int MIIM_CHECKMARKS = 0x8;
893894
public static final int MIIM_BITMAP = 0x80;
894895
public static final int MIIM_DATA = 0x20;
895896
public static final int MIIM_FTYPE = 0x100;

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.eclipse.swt.graphics.*;
2020
import org.eclipse.swt.internal.*;
2121
import org.eclipse.swt.internal.win32.*;
22+
import org.eclipse.swt.internal.win32.version.*;
2223

2324
/**
2425
* Instances of this class represent a selectable user interface object
@@ -42,6 +43,8 @@
4243
public class MenuItem extends Item {
4344
Menu parent, menu;
4445
long hBitmap;
46+
Image imageSelected;
47+
long hBitmapSelected;
4548
int id, accelerator, userId;
4649
ToolTip itemToolTip;
4750
/* Image margin. */
@@ -53,6 +56,11 @@ public class MenuItem extends Item {
5356
// value in wmMeasureChild is increased by a fixed value (in points) when wmDrawChild is called
5457
// This static is used to mitigate this increase
5558
private final static int WINDOWS_OVERHEAD = 6;
59+
// Workaround for: selection indicator is missing for menu item with image on Win11 (#501)
60+
// 0= off/system behavior; 1= no image if selected; 2= with overlay marker (default)
61+
private final static int CUSTOM_SELECTION_IMAGE = (OsVersion.IS_WIN11_21H2) ?
62+
Integer.getInteger("org.eclipse.swt.internal.win32.menu.customSelectionImage", 2) : 0;
63+
5664
static {
5765
DPIZoomChangeRegistry.registerHandler(MenuItem::handleDPIChange, MenuItem.class);
5866
}
@@ -543,6 +551,12 @@ void releaseWidget () {
543551
super.releaseWidget ();
544552
if (hBitmap != 0) OS.DeleteObject (hBitmap);
545553
hBitmap = 0;
554+
if (hBitmapSelected != 0) OS.DeleteObject (hBitmapSelected);
555+
hBitmapSelected = 0;
556+
if (imageSelected != null) {
557+
imageSelected.dispose();
558+
imageSelected = null;
559+
}
546560
if (accelerator != 0) {
547561
parent.destroyAccelerators ();
548562
}
@@ -774,14 +788,34 @@ public void setImage (Image image) {
774788
if (this.image == image) return;
775789
if ((style & SWT.SEPARATOR) != 0) return;
776790
super.setImage (image);
791+
if (imageSelected != null) {
792+
imageSelected.dispose();
793+
imageSelected = null;
794+
}
795+
if ((style & (SWT.CHECK | SWT.RADIO)) != 0 && CUSTOM_SELECTION_IMAGE > 1
796+
&& image != null && getSelection()) {
797+
initCustomSelectedImage();
798+
}
799+
updateImage();
800+
}
801+
802+
private void updateImage () {
777803
MENUITEMINFO info = new MENUITEMINFO ();
778804
info.cbSize = MENUITEMINFO.sizeof;
779805
info.fMask = OS.MIIM_BITMAP;
780806
if (parent.needsMenuCallback()) {
781807
info.hbmpItem = OS.HBMMENU_CALLBACK;
782808
} else {
783809
if (OS.IsAppThemed ()) {
784-
info.hbmpItem = hBitmap = getMenuItemIconBitmapHandle(image);
810+
hBitmap = getMenuItemIconBitmapHandle(image);
811+
if ((style & (SWT.CHECK | SWT.RADIO)) != 0 && CUSTOM_SELECTION_IMAGE > 0) {
812+
info.fMask |= OS.MIIM_CHECKMARKS;
813+
info.hbmpUnchecked = hBitmap;
814+
info.hbmpChecked = getMenuItemIconSelectedBitmapHandle();
815+
}
816+
else {
817+
info.hbmpItem = hBitmap;
818+
}
785819
} else {
786820
info.hbmpItem = image != null ? OS.HBMMENU_CALLBACK : 0;
787821
}
@@ -791,16 +825,84 @@ public void setImage (Image image) {
791825
parent.redraw ();
792826
}
793827

828+
private void initCustomSelectedImage() {
829+
Image image = this.image;
830+
if (image == null) {
831+
return;
832+
}
833+
Rectangle imageBounds = image.getBounds();
834+
Color foregroundColor = increaseContrast((display.menuBarForegroundPixel != -1) ? Color.win32_new (this.display, display.menuBarForegroundPixel) : parent.getForeground());
835+
Color backgroundColor = increaseContrast((display.menuBarBackgroundPixel != -1) ? Color.win32_new (this.display, display.menuBarBackgroundPixel) : parent.getBackground());
836+
ImageGcDrawer drawer = new ImageGcDrawer() {
837+
@Override
838+
public int getGcStyle() {
839+
return SWT.TRANSPARENT;
840+
}
841+
@Override
842+
public void drawOn(GC gc, int imageWidth, int imageHeight) {
843+
gc.setAdvanced(true);
844+
gc.drawImage(image, imageWidth - imageBounds.width, (imageHeight - imageBounds.height) / 2);
845+
int x0 = imageWidth - 16;
846+
int y0 = imageHeight / 2 - 8;
847+
if (((style & SWT.CHECK) != 0)) {
848+
int[] points = new int[] { x0 + 4, y0 + 10, x0 + 6, y0 + 12, x0 + 12, y0 + 6 };
849+
gc.setAntialias(SWT.ON);
850+
gc.setLineStyle(SWT.LINE_SOLID);
851+
gc.setForeground(backgroundColor);
852+
gc.setLineCap(SWT.CAP_ROUND);
853+
gc.setLineJoin(SWT.JOIN_ROUND);
854+
gc.setAlpha(127);
855+
gc.setLineWidth(6);
856+
gc.drawPolyline(points);
857+
gc.setLineJoin(SWT.JOIN_MITER);
858+
gc.setAlpha(255);
859+
gc.setLineWidth(3);
860+
gc.drawPolyline(points);
861+
gc.setForeground(foregroundColor);
862+
gc.setLineWidth(1);
863+
gc.setLineCap(SWT.CAP_FLAT);
864+
gc.drawPolyline(points);
865+
}
866+
else {
867+
gc.setAntialias(SWT.ON);
868+
gc.setBackground(backgroundColor);
869+
gc.setAlpha(127);
870+
gc.fillOval(x0 + 4, y0 + 5, 8, 8);
871+
gc.setAlpha(255);
872+
gc.fillOval(x0 + 5, y0 + 6, 6, 6);
873+
gc.setBackground(foregroundColor);
874+
gc.fillOval(x0 + 6, y0 + 7, 4, 4);
875+
}
876+
}
877+
};
878+
imageSelected = new Image(image.getDevice(), drawer,
879+
Math.max(imageBounds.width, 16), Math.max(imageBounds.height, 16));
880+
}
881+
882+
private Color increaseContrast(Color color) {
883+
return (color.getRed() + color.getGreen() + color.getBlue() > 127 * 3) ? display.getSystemColor(SWT.COLOR_WHITE) : color;
884+
}
885+
794886
private long getMenuItemIconBitmapHandle(Image image) {
795887
if (image == null) {
796888
return 0;
797889
}
798890
if (hBitmap != 0) OS.DeleteObject (hBitmap);
799-
int zoom = adaptZoomForMenuItem(getZoom());
891+
int zoom = adaptZoomForMenuItem(getZoom(), image);
800892
return Display.create32bitDIB (image, zoom);
801893
}
802894

803-
private int adaptZoomForMenuItem(int currentZoom) {
895+
private long getMenuItemIconSelectedBitmapHandle() {
896+
Image image = imageSelected;
897+
if (image == null) {
898+
return 0;
899+
}
900+
if (hBitmapSelected != 0) OS.DeleteObject (hBitmapSelected);
901+
int zoom = adaptZoomForMenuItem(getZoom(), image);
902+
return hBitmapSelected = Display.create32bitDIB (image, zoom);
903+
}
904+
905+
private int adaptZoomForMenuItem(int currentZoom, Image image) {
804906
int primaryMonitorZoomAtAppStartUp = getPrimaryMonitorZoomAtStartup();
805907
/*
806908
* Windows has inconsistent behavior when setting the size of MenuItem image and
@@ -985,6 +1087,14 @@ public void setSelection (boolean selected) {
9851087
if (!success) error (SWT.ERROR_CANNOT_SET_SELECTION);
9861088
info.fState &= ~OS.MFS_CHECKED;
9871089
if (selected) info.fState |= OS.MFS_CHECKED;
1090+
1091+
if (selected && CUSTOM_SELECTION_IMAGE > 1 && hBitmap != 0 && imageSelected == null) {
1092+
initCustomSelectedImage();
1093+
info.fMask |= OS.MIIM_CHECKMARKS;
1094+
info.hbmpUnchecked = hBitmap;
1095+
info.hbmpChecked = getMenuItemIconSelectedBitmapHandle();
1096+
}
1097+
9881098
success = OS.SetMenuItemInfo (hMenu, id, false, info);
9891099
if (!success) {
9901100
/*
@@ -1350,12 +1460,9 @@ private static void handleDPIChange(Widget widget, int newZoom, float scalingFac
13501460
if (!(widget instanceof MenuItem menuItem)) {
13511461
return;
13521462
}
1353-
// Refresh the image
1354-
Image menuItemImage = menuItem.getImage();
1355-
if (menuItemImage != null) {
1356-
Image currentImage = menuItemImage;
1357-
menuItem.image = null;
1358-
menuItem.setImage (currentImage);
1463+
// Refresh the image(s)
1464+
if (menuItem.getImage() != null) {
1465+
((MenuItem)menuItem).updateImage();
13591466
}
13601467
// Refresh the sub menu
13611468
Menu subMenu = menuItem.getMenu();

0 commit comments

Comments
 (0)