1919import org .eclipse .swt .graphics .*;
2020import org .eclipse .swt .internal .*;
2121import 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
4243public 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+
794886private 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