Skip to content

[macOS 26] Add layer-based border rendering for Text and Combo widgets#3302

Open
tobiasmelcher wants to merge 2 commits into
eclipse-platform:masterfrom
tobiasmelcher:fix_nstextfield_bezel_rendering
Open

[macOS 26] Add layer-based border rendering for Text and Combo widgets#3302
tobiasmelcher wants to merge 2 commits into
eclipse-platform:masterfrom
tobiasmelcher:fix_nstextfield_bezel_rendering

Conversation

@tobiasmelcher
Copy link
Copy Markdown
Contributor

@tobiasmelcher tobiasmelcher commented May 13, 2026

On macOS 26 (Tahoe) Liquid Glass, all text fields and combo boxes
require a visible border regardless of SWT.BORDER style. This adds
the necessary Cocoa bindings (NSView.setWantsLayer,
CALayer.setBorderColor/
setBorderWidth/setCornerRadius, NSColor.separatorColor/CGColor) and
draws borders via the layer pipeline. The shared logic is extracted
into Control.configureLayerBorder() and the SDK version is cached in
OS.MACH_O_SDK_VERSION.

The macOS runtime can report incorrect OS versions (10.16 instead of
actual version) when the executable's linked SDK is too old. Add
OS.getMachOSDKVersion() to extract the SDK version from the
LC_BUILD_VERSION load command in the main executable's Mach-O header.

This allows SWT to detect the SDK version the running JVM was built
against, enabling proper diagnostics for version reporting issues.
@tobiasmelcher
Copy link
Copy Markdown
Contributor Author

tobiasmelcher commented May 13, 2026

Reproduce the rendering problem of the SWT text control with following simple code snippet:

package org.eclipse.swt.widgets;

import org.eclipse.swt.*;
import org.eclipse.swt.layout.*;

public class TestTextControl {
	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setText("Text Control Test");
		shell.setLayout(new GridLayout(1, false));

		Text text = new Text(shell, SWT.NONE);
		text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		text.setText("Text 1");

		Text text2 = new Text(shell, SWT.NONE);
		text2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		text2.setText("Text 2");
		shell.setSize(400, 350);
		shell.open();

		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		display.dispose();
	}
}

When running this code with a java vm build against macOS 26 SDK without the changes in this pull request, the UI looks like this:

image

With the changes of this pull request, it looks like this. The corners are now visible for "Text 2".
image

I also tested the NSTextField behavior in Objective-C with following code

    NSView *contentView = [self.window contentView];
    
    NSTextField *textField1 = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 200, 360, 24)];
    [textField1 setStringValue:@"setBezeled:NO setBordered:NO"];
    [textField1 setEditable:YES];
    [textField1 setSelectable:YES];
    [textField1 setBezeled:NO];
    [textField1 setBordered:NO];
    [contentView addSubview:textField1];

    NSTextField *textField2 = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 160, 360, 24)];
    [textField2 setStringValue:@"setBezeled:Yes setBordered:NO"];
    [textField2 setEditable:YES];
    [textField2 setSelectable:YES];
    [textField2 setBezeled:YES];
    [textField2 setBordered:NO];
    [contentView addSubview:textField2];
    
    NSTextField *textField3 = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 120, 360, 24)];
    [textField3 setStringValue:@"setBezeled not called setBordered:NO"];
    [textField3 setEditable:YES];
    [textField3 setSelectable:YES];
    [textField3 setBezeled:YES];
    [textField3 setBordered:NO];
    [contentView addSubview:textField3];
    
    NSTextField *textField4 = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 80, 360, 24)];
    [textField4 setStringValue:@"setBezeled and set Bordered not called"];
    [textField4 setEditable:YES];
    [textField4 setSelectable:YES];
    [contentView addSubview:textField4];

one can see that calling setBordered destroys rendering of the corners. setBezeled doesn't have any effect.

image

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Test Results (macos)

   58 files  ±0     58 suites  ±0   6m 4s ⏱️ - 1m 36s
4 571 tests ±0  4 324 ✅ ±0  247 💤 ±0  0 ❌ ±0 
2 202 runs  ±0  2 138 ✅ ±0   64 💤 ±0  0 ❌ ±0 

Results for commit 1e4df2d. ± Comparison against base commit f33b79e.

♻️ This comment has been updated with latest results.

@tobiasmelcher
Copy link
Copy Markdown
Contributor Author

I think I went into the wrong direction. I got following explanation from Gemini. I will try the wantsLayer approach and upload an updated pull request.


Description
With the introduction of macOS SDK 26, the rendering behavior of NSTextField has changed significantly, especially concerning legacy bezel styles. Specifically, when isBordered is set to false (or setBordered(false) is called), the control no longer renders a visual boundary. This results in a white-on-white situation where the text input field becomes invisible to the user in the default system theme.

This change is part of Apple's move toward layer-backed controls and the "Liquid Glass" design language, where traditional CPU-based drawing of bezels is being phased out in favor of GPU-accelerated layer properties.

Changes
To ensure the NSTextField remains visible and maintains a native look and feel on newer macOS versions, this PR transitions the border rendering from the legacy NSBezelStyle to a Core Animation layer-based approach:

Enables Layer-Backing: Sets wantsLayer = true for the control.

Manual Border Definition: Uses CALayer properties to define a border that mimics the native appearance.

System Color Integration: Uses NSColor.separatorColor to ensure the border automatically adapts to Light Mode, Dark Mode, and high-contrast accessibility settings.

Refined Geometry: Applies a slight cornerRadius to align with modern macOS UI standards.

Technical Snippet (Implementation Logic)
Instead of relying on the disappearing system bezel, we now define the visual boundary as follows:

Swift
textField.isBordered = false
textField.wantsLayer = true
textField.layer?.borderColor = NSColor.separatorColor.cgColor
textField.layer?.borderWidth = 1.0
textField.layer?.cornerRadius = 4.0
textField.drawsBackground = true
textField.backgroundColor = NSColor.controlBackgroundColor
Impact
Visual Consistency: Restores visibility of text fields in Eclipse-based applications running on macOS 15+.

Forward Compatibility: Moves away from deprecated drawing behaviors toward the recommended AppKit/Core Animation architecture.

No Regression: On older macOS versions, this approach remains functionally identical and provides a cleaner, modern aesthetic.

@HeikoKlare
Copy link
Copy Markdown
Contributor

Thank you for taking so much time to investigate this, @tobias-melcher. It's great to see the continuously increasing understanding of what changed/happens. Looking forward to hopefully seeing a solution that keeps SWT adopt the latest macOS look & feel. The screenshots already look very promising.

On macOS 26 (Tahoe) Liquid Glass, all text fields and combo boxes
require a visible border regardless of SWT.BORDER style. This adds
the necessary Cocoa bindings (NSView.setWantsLayer,
CALayer.setBorderColor/
setBorderWidth/setCornerRadius, NSColor.separatorColor/CGColor) and
draws borders via the layer pipeline. The shared logic is extracted
into Control.configureLayerBorder() and the SDK version is cached in
OS.MACH_O_SDK_VERSION.
@tobiasmelcher tobiasmelcher force-pushed the fix_nstextfield_bezel_rendering branch from 5cd8838 to 1e4df2d Compare May 14, 2026 15:05
@tobiasmelcher tobiasmelcher changed the title [macOS 26] Fix NSTextField bezel not rendering on SDK 26 [macOS 26] Add layer-based border rendering for Text and Combo widgets May 14, 2026
@tobiasmelcher
Copy link
Copy Markdown
Contributor Author

Pull request 1e4df2d no longer uses any hack or workaround from my point of view.

Following screenshots are made with -Dorg.eclipse.swt.internal.carbon.smallFonts

  1. Light and dark mode from "Console" preferences page without the changes of this pull request running with the homebrew jvm built with MacOS SDK 26. No borders for text and combo box control rendered. White on white - text and combo box control are not detectable.
macos26_light_before macos26_dark_before
  1. Light and dark mode from "Console" preferences page with the changes of this pull request running with the homebrew jvm. Borders on text and combo box are now visible. The dark mode combo box has still some glitches, it is the best what I could get until now. Any help is highly appreciated.
macos26_light_with_fix macos26_dark_with_fix
  1. Light and dark mode from "Console" preferences page with the changes of this pull request running with a jvm built against an older MacOS SDK. The changes of this pull request do not have any influence because they are not applied because of call if (OS.VERSION_MAJOR(OS.MACH_O_SDK_VERSION) == 26) {
old_sdk_light_with_fix old_sdk_dark_with_fix

What do you think? Is it an improvement compared to the current state? Does it make sense to merge these changes?

@Phillipus
Copy link
Copy Markdown
Contributor

Phillipus commented May 14, 2026

Does it make sense to merge these changes?

I don't think it's a good idea to merge any of these macOS changes so late in 4.40. If there is more time for targeting a later release it allows (at least me) more time to test on macOS 15, 26 and on various combinations. It would be a shame to find serious regressions in 4.40.

@tobiasmelcher
Copy link
Copy Markdown
Contributor Author

Does it make sense to merge these changes?

I don't think it's a good idea to merge any of these macOS changes so late in 4.40. If there is more time for targeting a later release it allows (at least me) more time to test on macOS 15, 26 and on various combinations. It would be a shame to find serious regressions in 4.40.

No worries, I don't want to break any release. It is fine for me. Just wanted to know if the change goes into the right direction.

@Phillipus
Copy link
Copy Markdown
Contributor

No worries, I don't want to break any release. It is fine for me. Just wanted to know if the change goes into the right direction.

Thanks. I want to spend some focussed time testing this and the other changes to Mac for macOS 26 support and also test for possible regressions on earlier macOS versions (I have various Mac setups including Intel). It would be helpful if the changes which are proposed could have longer testing period into 4.41.

Thanks for your work on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants