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,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+
794894private 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