Repository: servo/core-foundation-rs (language: rust)

Issue:
Missing UI component NSPanel
Hey folks, 

## Problem
At the moment, it is not possible to create UI elements of type `NSPanel`. NSPanel in macOS subclasses `NSWindow`, overwrites a set of methods and adds a small number of additional methods. As the result, the UX of an NSPanel is slightly different from an NSWindow. 

## Context / Motivation
In a project of mine, I happen to need the NSPanel behavior. I am building the app with [Tauri](https://github.com/tauri-apps/tauri), which uses [Tao](https://github.com/tauri-apps/tao) to control views across OS's. Tao, in turn, relies on `core-foundation-rs` for controlling macOS views. 

## Solution
I investigated how it could be done and I think I could make the needed contribution. But since there are multiple ways to solve it, I want to consult with you, the maintainers, on how to do it for the contribution to be merged asap. 😊 

**Affected library:** `cocoa`

- [x] Introduce _trait_ `NSPanel` and _impl_ for type `id` in `appkit.rs`
- [x] As `NSPanel` is subclassing `NSWindow`, it shares all the methods and enums with it. ⚠️ Here I need your advice - see further down. 
- [x] Add trait/impl for methods [`setBecomesKeyOnlyIfNeeded` & `becomesKeyOnlyIfNeeded`](https://developer.apple.com/documentation/appkit/nspanel/1528836-becomeskeyonlyifneeded)
- [x] Add trait/impl for methods [`setFloatingPanel`& `isFloatingPanel`](https://developer.apple.com/documentation/appkit/nspanel/1531901-isfloatingpanel)
- [x] Add trait/impl for methods [`setWorksWhenModal`& `worksWhenModal`](https://developer.apple.com/documentation/appkit/nspanel/1525549-workswhenmodal)


## Issues seen
One straightforward approach for implementing it would be to copy the whole `trait` and `impl` code block of `NSWindow` and add the additional methods. But this duplication leads to disambiguate methods calls: 
![image](https://user-images.githubusercontent.com/6577198/171816676-7ccf88d0-8290-4229-8fb9-ba3e6ba29c0d.png)

This requires developers to adapt the syntax for calling methods on either `NSWindow` or `NSPanel`: writing `NSPanel::center(window);` instead of `window.center();`

‼️⁉️ **Need your opinion: is this acceptable and should I contribute as described above?** ‼️⁉️

## Resources
* ["How Panels Work"](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingPanels.html#//apple_ref/doc/uid/20000224-BBCBEADB) from the Apple docs; explaining the differences between `NSWindow` and `NSPanel`
* [NSPanel docs](https://developer.apple.com/documentation/appkit/nspanel)
* Not directly relevant but part of NSPanel UX: ["How Modal Windows Work"](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html#//apple_ref/doc/uid/20000223-CJBEADBA)


Discussion / hints:
Note that it doesn't matter from Rust's perspective whether you write `NSWindow::center(obj)` or `NSPanel::center(obj)` - they both end up calling the same selector.

Therefore, I think it would probably make the most sense to simply add `NSPanel` trait that only has the methods that are unique to `NSPanel`s
Thank you for your reply. And thank you for that pointer regarding that Rust is calling the same selector on both variations. ... I am still new to Rust. 

I am giving it a go, maybe I can even finish it this afternoon. 
Okay, as you said, it is probably the best idea to just add the `NSPanel` trait. 

This code block does the job and works as intended:

```Rust
pub trait NSPanel: Sized {
    unsafe fn alloc(_: Self) -> id {
        msg_send![class!(NSPanel), alloc]
    }

    // NSPanel subclasses NSWindow, hence we only add the added methods
    // https://developer.apple.com/documentation/appkit/nspanel
    unsafe fn setBecomesKeyOnlyIfNeeded(self, becomesKeyOnlyIfNeeded: BOOL);
    unsafe fn becomesKeyOnlyIfNeeded(self) -> BOOL;
    unsafe fn setFloatingPanel(self, floatingPanel: BOOL);
    unsafe fn floatingPanel(self) -> BOOL;
}

impl NSPanel for id {
    // NSPanel subclasses NSWindow, hence we only add the added methods
    // https://developer.apple.com/documentation/appkit/nspanel
    unsafe fn setBecomesKeyOnlyIfNeeded(self, becomesKeyOnlyIfNeeded: BOOL) {
        msg_send![self, setBecomesKeyOnlyIfNeeded: becomesKeyOnlyIfNeeded]
    }

    unsafe fn becomesKeyOnlyIfNeeded(self) -> BOOL {
        msg_send![self, becomesKeyOnlyIfNeeded]
    }

    unsafe fn setFloatingPanel(self, floatingPanel: BOOL) {
        msg_send![self, setFloatingPanel: floatingPanel]
    }

    unsafe fn floatingPanel(self) -> BOOL {
        msg_send![self, isFloatingPanel]
    }
}
```


Fix patch:
diff --git a/cocoa/src/appkit.rs b/cocoa/src/appkit.rs
index d282fd1c1..6d00d522e 100644
--- a/cocoa/src/appkit.rs
+++ b/cocoa/src/appkit.rs
@@ -1802,6 +1802,44 @@ impl NSWindow for id {
     // TODO: Constraint-Based Layouts
 }
 
+pub trait NSPanel: Sized {
+    unsafe fn alloc(_: Self) -> id {
+        msg_send![class!(NSPanel), alloc]
+    }
+
+    // NSPanel subclasses NSWindow, hence we only add the added methods
+    // https://developer.apple.com/documentation/appkit/nspanel
+    unsafe fn setBecomesKeyOnlyIfNeeded(self, becomesKeyOnlyIfNeeded: BOOL);
+    unsafe fn becomesKeyOnlyIfNeeded(self) -> BOOL;
+    unsafe fn setFloatingPanel(self, floatingPanel: BOOL);
+    unsafe fn floatingPanel(self) -> BOOL;
+    unsafe fn setWorksWhenModal(self, worksWithPanel: BOOL);
+}
+
+impl NSPanel for id {
+    // NSPanel subclasses NSWindow, hence we only add the added methods
+    // https://developer.apple.com/documentation/appkit/nspanel
+    unsafe fn setBecomesKeyOnlyIfNeeded(self, becomesKeyOnlyIfNeeded: BOOL) {
+        msg_send![self, setBecomesKeyOnlyIfNeeded: becomesKeyOnlyIfNeeded]
+    }
+
+    unsafe fn becomesKeyOnlyIfNeeded(self) -> BOOL {
+        msg_send![self, becomesKeyOnlyIfNeeded]
+    }
+
+    unsafe fn setFloatingPanel(self, floatingPanel: BOOL) {
+        msg_send![self, setFloatingPanel: floatingPanel]
+    }
+
+    unsafe fn floatingPanel(self) -> BOOL {
+        msg_send![self, isFloatingPanel]
+    }
+
+    unsafe fn setWorksWhenModal(self, worksWhenModal: BOOL) {
+        msg_send![self, setWorksWhenModal: worksWhenModal]
+    }
+}
+
 #[repr(i64)]
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum NSModalResponse {


Test patch: