From 938c08664752a4b70e3b8b175bf8668ac9acbf24 Mon Sep 17 00:00:00 2001 From: Shahzaib Ibrahim Date: Fri, 20 Sep 2024 11:18:44 +0200 Subject: [PATCH] When switched to Dark Theme, using gc to draw menu item, moving the responsibility from OS due to wrong scaling of menu bar vertically. Also changing the text color from a grayish tone to white. Also when ALT key is pressed mnemonics are underlined and work as a toggle, the behavior which was missing from dark theme previously. --- .../org/eclipse/swt/internal/win32/OS.java | 3 + .../win32/org/eclipse/swt/widgets/Menu.java | 2 +- .../org/eclipse/swt/widgets/MenuItem.java | 118 +++++++++++++++--- .../org/eclipse/swt/snippets/Snippet373.java | 60 ++++++++- 4 files changed, 166 insertions(+), 17 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java index c7b704ddcab..bad23442f90 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java @@ -897,6 +897,7 @@ public class OS extends C { public static final int MFS_CHECKED = 0x8; public static final int MFS_DISABLED = 0x3; public static final int MFS_GRAYED = 0x3; + public static final int MFT_OWNERDRAW = 0x100; public static final int MFT_RADIOCHECK = 0x200; public static final int MFT_RIGHTJUSTIFY = 0x4000; public static final int MFT_RIGHTORDER = 0x2000; @@ -1019,6 +1020,8 @@ public class OS extends C { public static final int OBJ_PEN = 0x1; public static final int OBM_CHECKBOXES = 0x7ff7; public static final int ODS_SELECTED = 0x1; + public static final int ODS_NOACCEL = 0x0100; + public static final int ODS_INACTIVE = 0x80; public static final int ODT_MENU = 0x1; public static final int OIC_BANG = 0x7F03; public static final int OIC_HAND = 0x7F01; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java index 03f18f19925..b35939ef3d9 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java @@ -358,7 +358,7 @@ void createItem (MenuItem item, int index) { info.fMask = OS.MIIM_ID | OS.MIIM_TYPE | OS.MIIM_DATA; info.wID = item.id; info.dwItemData = item.id; - info.fType = item.widgetStyle (); + info.fType = (style & SWT.BAR) != 0 && needsMenuCallback() ? OS.MFT_OWNERDRAW : item.widgetStyle (); info.dwTypeData = pszText; boolean success = OS.InsertMenuItem (handle, index, true, info); if (pszText != 0) OS.HeapFree (hHeap, 0, pszText); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java index 1105dea32c7..046f2ed93c2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java @@ -47,7 +47,8 @@ public class MenuItem extends Item { /* Image margin. */ final static int MARGIN_WIDTH = 1; final static int MARGIN_HEIGHT = 1; - + final static int LEFT_TEXT_MARGIN = 5; + final static int IMAGE_TEXT_GAP = 3; static { DPIZoomChangeRegistry.registerHandler(MenuItem::handleDPIChange, MenuItem.class); } @@ -1121,10 +1122,32 @@ LRESULT wmCommandChild (long wParam, long lParam) { return null; } -LRESULT wmDrawChild (long wParam, long lParam) { - DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT (); - OS.MoveMemory (struct, lParam, DRAWITEMSTRUCT.sizeof); - if (image != null) { +@Override +GC createNewGC(long hDC, GCData data) { + if (getDisplay().isRescalingAtRuntime()) { + return super.createNewGC(hDC, data); + } else { + data.nativeZoom = getMonitorZoom(); + return GC.win32_new(hDC, data); + } +} + +private int getMonitorZoom() { + return getMenu().getShell().getMonitor().zoom; +} + +private int getMenuZoom() { + if (getDisplay().isRescalingAtRuntime()) { + return super.getZoom(); + } else { + return DPIUtil.getZoomForAutoscaleProperty(getMonitorZoom()); + } +} + +LRESULT wmDrawChild(long wParam, long lParam) { + DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT(); + OS.MoveMemory(struct, lParam, DRAWITEMSTRUCT.sizeof); + if ((text != null || image != null)) { GCData data = new GCData(); data.device = display; GC gc = createNewGC(struct.hDC, data); @@ -1134,14 +1157,50 @@ LRESULT wmDrawChild (long wParam, long lParam) { * coordinate. The fix is to ignore this value when * the item is in a menu bar. */ - int x = (parent.style & SWT.BAR) != 0 ? MARGIN_WIDTH * 2 : struct.left; - Image image = getEnabled () ? this.image : new Image (display, this.image, SWT.IMAGE_DISABLE); - int zoom = getZoom(); - gc.drawImage (image, DPIUtil.scaleDown(x, zoom), DPIUtil.scaleDown(struct.top + MARGIN_HEIGHT, zoom)); - if (this.image != image) image.dispose (); - gc.dispose (); + int x = (parent.style & SWT.BAR) == 0 ? MARGIN_WIDTH * 2 : struct.left; + int zoom = getMenuZoom(); + Rectangle menuItemArea = null; + if (text != null) { + this.getParent().redraw(); + int flags = SWT.DRAW_DELIMITER; + boolean isInactive = ((struct.itemState & OS.ODS_INACTIVE) != 0); + boolean isSelected = ((struct.itemState & OS.ODS_SELECTED) != 0); + boolean isNoAccel = ((struct.itemState & OS.ODS_NOACCEL) != 0); + + String drawnText = ""; + if(isNoAccel) { + drawnText = this.text.replace("&", ""); + } else { + drawnText = this.text; + flags |= SWT.DRAW_MNEMONIC; + } + Rectangle menuItemBounds = this.getBounds(); + + int fillMenuWidth = DPIUtil.scaleDown(menuItemBounds.width, zoom); + int fillMenuHeight = DPIUtil.scaleDown(menuItemBounds.height, zoom); + menuItemArea = new Rectangle(DPIUtil.scaleDown(x, zoom), DPIUtil.scaleDown(struct.top, zoom), fillMenuWidth, fillMenuHeight); + + gc.setForeground(isInactive ? display.getSystemColor(SWT.COLOR_GRAY) : display.getSystemColor(SWT.COLOR_WHITE)); + gc.setBackground(isSelected ? display.getSystemColor(SWT.COLOR_DARK_GRAY) : parent.getBackground()); + gc.fillRectangle(menuItemArea); + + int xPositionText = LEFT_TEXT_MARGIN + DPIUtil.scaleDown(x, zoom) + (this.image != null ? this.image.getBounds().width + IMAGE_TEXT_GAP : 0); + int yPositionText = DPIUtil.scaleDown(struct.top , zoom); + gc.drawText(drawnText, xPositionText, yPositionText, flags); + } + if (image != null) { + Image image = getEnabled() ? this.image : new Image(display, this.image, SWT.IMAGE_DISABLE); + int gap = (menuItemArea.height - image.getBounds().height)/2; + gc.drawImage(image, LEFT_TEXT_MARGIN + DPIUtil.scaleDown(x, zoom), gap + DPIUtil.scaleDown(struct.top, zoom)); + if (this.image != image) { + image.dispose(); + } + } + gc.dispose(); + } + if (parent.foreground != -1) { + OS.SetTextColor(struct.hDC, parent.foreground); } - if (parent.foreground != -1) OS.SetTextColor (struct.hDC, parent.foreground); return null; } @@ -1158,10 +1217,11 @@ LRESULT wmMeasureChild (long wParam, long lParam) { * if menu item has a mnemonic, it's always drawn at a fixed * position. I have tested on Win7, Win8.1, Win10 and found * that value of 5 works well in matching text to mnemonic. - * NOTE: autoScaleUpUsingNativeDPI() is used to avoid problems - * with applications that disable automatic scaling. */ - struct.itemWidth = DPIUtil.scaleUp(5, nativeZoom); + Point point = calculateRenderedTextSize(); + int menuZoom = getDisplay().isRescalingAtRuntime()? super.getZoom(): getMonitorZoom(); + struct.itemHeight = DPIUtil.scaleUp(point.y, menuZoom); + struct.itemWidth = DPIUtil.scaleUp(LEFT_TEXT_MARGIN + point.x + (this.image != null ? this.image.getBounds().width + IMAGE_TEXT_GAP : 0), menuZoom); OS.MoveMemory (lParam, struct, MEASUREITEMSTRUCT.sizeof); return null; } @@ -1205,6 +1265,34 @@ LRESULT wmMeasureChild (long wParam, long lParam) { return null; } +private Point calculateRenderedTextSize() { + GC gc = new GC(this.getMenu().getShell()); + String textWithoutMnemonicCharacter = getText().replace("&", ""); + Point points = gc.textExtent(textWithoutMnemonicCharacter); + gc.dispose(); + + if (getDisplay().isRescalingAtRuntime()) { + return points; + } else { + int primaryMonitorZoom = this.getDisplay().getDeviceZoom(); + int adjustedPrimaryMonitorZoom = DPIUtil.getZoomForAutoscaleProperty(primaryMonitorZoom); + if (primaryMonitorZoom != adjustedPrimaryMonitorZoom) { + // Windows will use a font matching the native primary monitor zoom for calculating the size in pixels, + // GC will use the native primary monitor zoom to scale down from pixels to points in this scenario + // Therefore we need to make sure adjust the points as if it would have been scaled down by the + // native primary monitor zoom. + // Example: + // Primary monitor on 150% with int200: native zoom 150%, adjusted zoom 100% + // Pixel height of font in this example is 15px + // GC calculated height of 15px, scales down with adjusted zoom of 100% and returns 15pt -> should be 10pt + // this calculation is corrected by the following line + // This is the only place, where the GC needs to use the native zoom to do that, therefore it is fixed only here + points = DPIUtil.scaleDown(DPIUtil.scaleUp(points, adjustedPrimaryMonitorZoom), primaryMonitorZoom); + } + return points; + } +} + private static final class MenuItemToolTip extends ToolTip { public MenuItemToolTip(Shell parent) { diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet373.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet373.java index 8ed82b1d82f..c9fdc16cb7e 100644 --- a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet373.java +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet373.java @@ -49,6 +49,9 @@ public class Snippet373 { static final ImageFileNameProvider filenameProvider = zoom -> { String path = null; switch (zoom) { + case 100: + path = IMAGE_PATH_100; + break; case 150: path = IMAGE_PATH_150; break; @@ -56,7 +59,7 @@ public class Snippet373 { path = IMAGE_PATH_200; break; default: - path = IMAGE_PATH_100; + path = null; } return path; }; @@ -113,6 +116,61 @@ public void handleEvent(Event e) { subItem.setAccelerator(SWT.MOD1 + 'A'); subItem.setImage(eclipse); + fileItem = new MenuItem(bar, SWT.CASCADE); + fileItem.setText("&File"); + fileItem.setImage(eclipse); + submenu = new Menu(shell, SWT.DROP_DOWN); + fileItem.setMenu(submenu); + subItem = new MenuItem(submenu, SWT.PUSH); + subItem.addListener(SWT.Selection, e -> System.out.println("Item 1")); + subItem.setText("Select &All\tCtrl+A"); + subItem.setAccelerator(SWT.MOD1 + 'A'); + subItem.setImage(eclipse); + + fileItem = new MenuItem(bar, SWT.CASCADE); + fileItem.setText("&File"); + fileItem.setImage(eclipse); + submenu = new Menu(shell, SWT.DROP_DOWN); + fileItem.setMenu(submenu); + subItem = new MenuItem(submenu, SWT.PUSH); + subItem.addListener(SWT.Selection, e -> System.out.println("Item 2")); + subItem.setText("Select &All\tCtrl+A"); + subItem.setAccelerator(SWT.MOD1 + 'A'); + subItem.setImage(eclipse); + + fileItem = new MenuItem(bar, SWT.CASCADE); + fileItem.setText("&File"); + fileItem.setImage(eclipse); + submenu = new Menu(shell, SWT.DROP_DOWN); + fileItem.setMenu(submenu); + subItem = new MenuItem(submenu, SWT.PUSH); + subItem.addListener(SWT.Selection, e -> System.out.println("Item 3")); + subItem.setText("Select &All\tCtrl+A"); + subItem.setAccelerator(SWT.MOD1 + 'A'); + subItem.setImage(eclipse); + + fileItem = new MenuItem(bar, SWT.CASCADE); + fileItem.setText("&File"); + fileItem.setImage(eclipse); + submenu = new Menu(shell, SWT.DROP_DOWN); + fileItem.setMenu(submenu); + subItem = new MenuItem(submenu, SWT.PUSH); + subItem.addListener(SWT.Selection, e -> System.out.println("Item 4")); + subItem.setText("Select &All\tCtrl+A"); + subItem.setAccelerator(SWT.MOD1 + 'A'); + subItem.setImage(eclipse); + + fileItem = new MenuItem(bar, SWT.CASCADE); + fileItem.setText("&File"); + fileItem.setImage(eclipse); + submenu = new Menu(shell, SWT.DROP_DOWN); + fileItem.setMenu(submenu); + subItem = new MenuItem(submenu, SWT.PUSH); + subItem.addListener(SWT.Selection, e -> System.out.println("Item 5")); + subItem.setText("Select &All\tCtrl+A"); + subItem.setAccelerator(SWT.MOD1 + 'A'); + subItem.setImage(eclipse); + // CTabFolder CTabFolder folder = new CTabFolder(shell, SWT.BORDER); for (int i = 0; i < 2; i++) {