Skip to content

Commit ecf3096

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 95b46b6 commit ecf3096

File tree

2 files changed

+125
-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

+125
-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: 124 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,92 @@ 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+
842+
@Override
843+
public void drawOn(GC gc, int imageWidth, int imageHeight) {
844+
gc.setAdvanced(true);
845+
gc.drawImage(image, imageWidth - imageBounds.width, (imageHeight - imageBounds.height) / 2);
846+
gc.setAntialias(SWT.ON);
847+
int x = imageWidth - 16;
848+
int y = imageHeight / 2 - 8;
849+
if ((style & SWT.CHECK) != 0) {
850+
drawCheck(gc, foregroundColor, backgroundColor, x, y);
851+
}
852+
else {
853+
drawRadio(gc, foregroundColor, backgroundColor, x, y);
854+
}
855+
}
856+
};
857+
imageSelected = new Image(image.getDevice(), drawer,
858+
Math.max(imageBounds.width, 16), Math.max(imageBounds.height, 16));
859+
}
860+
861+
private void drawCheck(GC gc, Color foregroundColor, Color backgroundColor, int x, int y) {
862+
int[] points = new int[] { x + 4, y + 10, x + 6, y + 12, x + 12, y + 6 };
863+
gc.setLineStyle(SWT.LINE_SOLID);
864+
gc.setForeground(backgroundColor);
865+
gc.setLineCap(SWT.CAP_ROUND);
866+
gc.setLineJoin(SWT.JOIN_ROUND);
867+
gc.setAlpha(127);
868+
gc.setLineWidth(6);
869+
gc.drawPolyline(points);
870+
gc.setLineJoin(SWT.JOIN_MITER);
871+
gc.setAlpha(255);
872+
gc.setLineWidth(3);
873+
gc.drawPolyline(points);
874+
gc.setForeground(foregroundColor);
875+
gc.setLineWidth(1);
876+
gc.setLineCap(SWT.CAP_FLAT);
877+
gc.drawPolyline(points);
878+
}
879+
880+
private void drawRadio(GC gc, Color foregroundColor, Color backgroundColor, int x, int y) {
881+
gc.setBackground(backgroundColor);
882+
gc.setAlpha(127);
883+
gc.fillOval(x + 4, y + 5, 8, 8);
884+
gc.setAlpha(255);
885+
gc.fillOval(x + 5, y + 6, 6, 6);
886+
gc.setBackground(foregroundColor);
887+
gc.fillOval(x + 6, y + 7, 4, 4);
888+
}
889+
890+
private Color increaseContrast(Color color) {
891+
return (color.getRed() + color.getGreen() + color.getBlue() > 127 * 3) ? display.getSystemColor(SWT.COLOR_WHITE) : color;
892+
}
893+
794894
private long getMenuItemIconBitmapHandle(Image image) {
795895
if (image == null) {
796896
return 0;
797897
}
798898
if (hBitmap != 0) OS.DeleteObject (hBitmap);
799-
int zoom = adaptZoomForMenuItem(getZoom());
899+
int zoom = adaptZoomForMenuItem(getZoom(), image);
800900
return Display.create32bitDIB (image, zoom);
801901
}
802902

803-
private int adaptZoomForMenuItem(int currentZoom) {
903+
private long getMenuItemIconSelectedBitmapHandle() {
904+
Image image = imageSelected;
905+
if (image == null) {
906+
return 0;
907+
}
908+
if (hBitmapSelected != 0) OS.DeleteObject (hBitmapSelected);
909+
int zoom = adaptZoomForMenuItem(getZoom(), image);
910+
return hBitmapSelected = Display.create32bitDIB (image, zoom);
911+
}
912+
913+
private int adaptZoomForMenuItem(int currentZoom, Image image) {
804914
int primaryMonitorZoomAtAppStartUp = getPrimaryMonitorZoomAtStartup();
805915
/*
806916
* Windows has inconsistent behavior when setting the size of MenuItem image and
@@ -985,6 +1095,14 @@ public void setSelection (boolean selected) {
9851095
if (!success) error (SWT.ERROR_CANNOT_SET_SELECTION);
9861096
info.fState &= ~OS.MFS_CHECKED;
9871097
if (selected) info.fState |= OS.MFS_CHECKED;
1098+
1099+
if (selected && CUSTOM_SELECTION_IMAGE > 1 && hBitmap != 0 && imageSelected == null) {
1100+
initCustomSelectedImage();
1101+
info.fMask |= OS.MIIM_CHECKMARKS;
1102+
info.hbmpUnchecked = hBitmap;
1103+
info.hbmpChecked = getMenuItemIconSelectedBitmapHandle();
1104+
}
1105+
9881106
success = OS.SetMenuItemInfo (hMenu, id, false, info);
9891107
if (!success) {
9901108
/*
@@ -1350,12 +1468,9 @@ private static void handleDPIChange(Widget widget, int newZoom, float scalingFac
13501468
if (!(widget instanceof MenuItem menuItem)) {
13511469
return;
13521470
}
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);
1471+
// Refresh the image(s)
1472+
if (menuItem.getImage() != null) {
1473+
((MenuItem)menuItem).updateImage();
13591474
}
13601475
// Refresh the sub menu
13611476
Menu subMenu = menuItem.getMenu();

0 commit comments

Comments
 (0)