0
votes

I've been trying to solve this very frustrating issue and I'm hoping someone can help. I am trying to create an NSWindow which has multiple views. The views can overlap each other. These are the requirements.

  • The blue NSView as seen in the image below is what can move the window.
  • The yellow NSView cannot move the window. This is another NSView instance which is underneath the blue NSView.
  • The movement needs to feel like any other window movement.
  • Things like dragging the window all the way to the top should activate Expose (Sierra and High Sierra).

enter image description here

First Attempt

I have attempted this by trying the following. Unfortunately this simply doesn't work.

In NSWindow

override func awakeFromNib() {
    self.refreshElements()
    self.titlebarAppearsTransparent = true
    self.isMovableByWindowBackground = true
}

In my custom views

class Blue: NSView {
    public override var mouseDownCanMoveWindow: Bool { return true }
}

class Yellow: NSView {
    public override var mouseDownCanMoveWindow: Bool { return false }
}

Second Attempt

Then I have tried implementing mouseDown mouseDragged and mouseUp in Blue. I would calculate the mouse movement and self.setFrame(frameRect:display:animate:) on the window. This worked OK in that I could control moving the window but it had a couple of issues. One no matter what the window movement is always slower. I think it's animating even when I set the animate property to false. Also it does not active Expose as other windows do.


Third Attempt

This is as close as I can get to the behavior I want with one major issue. The initial mouseDown does not register. So you can't click on the blue view and drag and move the window. You have mouseDown, mouseUp, then mouseDown again in order for dragging to move the window.

public class MyWindow: NSWindow {

    @IBOutlet var blue: NSView!
    @IBOutlet var yellow: NSView!

    public override func awakeFromNib() {
        self.titlebarAppearsTransparent = true
        self.isMovable = false
        self.isMovableByWindowBackground = true
    }

    public override func mouseDown(with event: NSEvent) {
        let point = event.locationInWindow

        let move = NSPointInRect(point, self.blue.frame)
        self.isMovableByWindowBackground = move
        self.isMovable = move

        super.mouseDown(with: event)
    }
    public override func mouseDragged(with event: NSEvent) {
        super.mouseDragged(with: event)
    }

}

My preferred method is method 3. It is the cleanest code and outside of the unusual behavior of having to click then click again in order for things to work everything else is perfect. My question is how can I force the initial click to also register with the window after it is dynamically set to allow movement?

1

1 Answers

3
votes

Since macOS 10.11, NSWindow has had the method performDrag(with:) for this purpose. It initiates window dragging just like what's done when the user clicks and drags the title bar. Your blue view can simply call that in its override of mouseDown().

The thing with mouseDownCanMoveWindow is that it simply passes the question along to the superview. Unless all ancestor views up to the window's content view all return true, it won't matter.