0
votes

I need to create non-opaque transparent window, with opaque subviews (call them surfaceView) inside. Each subview can contain thousands of subview (call them controlView). So structure must be something like this:

NSWindow (non-opaque)
- NSView (non-opaque) (Window contentview)
-- NSView (opaque) (SurfaceView)
--- NSView (opaque) (ControlView)

Illustration

The problem is, that WindowServer become overload, when there are thousands of controlViews inside. It looks like any NSView in window become non-opaque. I can't understand what i have to do.

There is no overload of WindowServer if the NSWindow is not opaque. But i need non-opaque window. This case proves that opaque NSView can contain lot of subviews with well performance.

There is no overload if the NSWindow has stylemask [.titled, .resizable], window become transparent (It proves that the transparent window with good performance is possible), but mouse not works through transparent parts of window. Also NSWindow has rounded corners from private class NSThemeframe. This solution is very dirty, because it needs to reimplement mouse events and replace some methods from private class.

There is no overload if the surface are childWindows instead subviews. But in this case, surfaces are not clip by main window, miniaturize animation not works, and in fact the window is not actualy window.

I have tried different combination and manipulations with CAlayer, opaque and other, but it looks like a bug. It is definitely possible to get good performance, but i can't understand how. Any ideas?

1

1 Answers

1
votes

Maybe this will help somebody. The problem was in combination of two unexpected facts.

  1. NSWindow property ignoresMouseEvents has very strange behavior. If this ignoresMouseEvents is true - the window become fully transparent for mouse, if false - window become fully opaque for mouse, even if some parts of window are transparent. The default value for window ignoresMouseEvents is false, but the trick is that if you didn't change this property, the real behavior of windows ignoresMouseEvents will has a third option - window become transparent for mouse in its transparent parts and this is a default state of window.

So ignoresMouseEvents is a boolean value, that actually has three options: true, false, and the default state of Schrödinger if you didn't change it.

  1. This unintuitive third option of ignoresMouseEvents cause unexpected cpu overload of windowServer, if window has transparent background and there are lot of CALayer inside window. So windowServer transparency check for mouse is incredible slow in some circumstances.

So, by default this code will make your windowServer cry on any mouse movement:

    let window = NSWindow(contentRect: windowrect, styleMask: [.borderless], backing: .buffered, defer: false);
    window.setIsVisible(true);

    window.contentView?.wantsLayer = true;
    window.contentView?.layer?.borderWidth = 1;
    window.backgroundColor = NSColor.clear;

    for _ in 0...5000 {
        let view =  NSView(frame: viewrect);
        view.wantsLayer = true;
        view.layer?.backgroundColor = CGColor(gray: 1.0, alpha: 1.0);
        window.contentView?.addSubview(view);
    }

And it is doesn't matter are your view or layer opaque or not, are they subviews of other views or not. But if you add window.ignoresMouseEvents = false or true your windowServer will calm down, but the window become fully opaque or fully transparent for mouse.

The solution is implementing your own windows transparency check for mouse on mouse movements (using trackingArea or GlobalMonitor), and set ignoresmouseevents to true, if mouse on transparent part of window, and false if not. Fortunately, it is hard to write the implementation of mouse checking, that will be slower than the default windowServer way.

P.S. Sorry for bad language.