ryanmcgrath / cacao

Rust bindings for AppKit (macOS) and UIKit (iOS/tvOS). Experimental, but working!
MIT License
1.85k stars 69 forks source link

wrapping an existing NSView? #58

Closed httnn closed 2 years ago

httnn commented 2 years ago

i'm getting an NSView as a *mut core::ffi::c_void from the outside which i'd like to add a cacao::webview::WebView. is this possible? tried:

let mut webview_config = WebViewConfig::default();
let webview = WebView::with(webview_config, WebViewInstance::default());
webview.load_html("<strong>hello world</strong>");
let mut view = View::new();
view.objc = ObjcProperty::retain(raw_window_handle.ns_view as id);
view.add_subview(&webview);

but that didn't seem to work. any ideas?

madsmtm commented 2 years ago

cacao uses a custom subclass of NSView, so overriding the one it holds on to won't work.

Rather, you should (probably, though @ryanmcgrath knows more about the design of cacao than I do) do something like (untested):

// Create webview
let mut webview_config = WebViewConfig::default();
let webview = WebView::with(webview_config, WebViewInstance::default());
webview.load_html("<strong>hello world</strong>");

// Add webview as subview of window handle's view
let root_view = raw_window_handle.ns_view as id; // TODO: Check for NULL
webview.get(|obj| {
    let _: () = unsafe { objc::msg_send![root_view, addSubview: obj] };
});
ryanmcgrath commented 2 years ago

@madsmtm has the right idea, yeah - you basically want to hold your view on your side and just manually add it via the ObjC call. You could maybe wrap your NSView in View, but cacao's View makes a lot of assumptions to duck things that just feel annoying in AppKit in 2022, so it'd be a bit of swimming upstream.

This issue also makes me rethink having that objc handle - implementing message passing for widgets directly might honestly be the better play to avoid confusion of "can I set this property". Need to tinker with it.

httnn commented 2 years ago

thank you! this indeed compiles and runs without errors:

let mut webview_config = WebViewConfig::default();
let webview = WebView::with(webview_config, WebViewInstance::default());
webview.load_html("<strong>hello world</strong>");
let root_view = handle.ns_view as id;
webview.objc.get(|obj| {
  let _: () = unsafe { objc::msg_send![root_view, addSubview: obj] };
});

still not seeing the webview though but that might be a frame size problem..

ryanmcgrath commented 2 years ago

Are you using AutoLayout or wanting to use the frame size directly?

On Sat, Sep 24, 2022 at 00:45, Max @.***> wrote:

thank you! this indeed compiles and runs without errors:

let

mut

webview_config =

WebViewConfig

::

default

(

)

;

let

webview =

WebView

::

with

(

webview_config

,

WebViewInstance

::

default

(

)

)

;

webview

.

load_html

(

"hello world"

)

;

let

root_view = handle

.

ns_view

as

id

;

webview

.

objc

.

get

(

|obj|

{

let

_

:

(

)

=

unsafe

{

objc

::

msg_send!

[

root_view, addSubview: obj

]

}

;

}

)

;

still not seeing the webview though but that might be a frame size problem..

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

httnn commented 2 years ago

this is what i tried it with:

let mut webview_config = WebViewConfig::default();
let webview = WebView::with(webview_config, WebViewInstance::default());

webview.set_translates_autoresizing_mask_into_constraints(true);
let frame = CGRect::new(&CGPoint::new(0.0, 0.0), &CGSize::new(400.0, 600.0));
webview.set_frame(frame);

let root_view = handle.ns_view as id;
webview.objc.get(|obj| {
  let _: () = unsafe { objc::msg_send![root_view, addSubview: obj] };
  let _: () = unsafe { objc::msg_send![root_view, setNeedsDisplay: true] };
});

webview.set_needs_display(true);
webview.load_html("<strong>hello world</strong>");

in the delegate, did_load gets called but did_appear doesn't.

httnn commented 2 years ago

ok so the problem with my previous attempt was really dumb: i didn't persist the webview variable so it got dropped immediately 🤦 but got it to work now, this is the minimum amount of code needed:

let mut webview_config = WebViewConfig::default();
let view = WebView::with(webview_config, WebViewInstance::default());

view.set_translates_autoresizing_mask_into_constraints(true);
let frame = CGRect::new(&CGPoint::new(0.0, 0.0), &CGSize::new(400.0, 600.0));
view.set_frame(frame);

let root_view = handle.ns_view as id;
view.objc.get(|obj| {
  unsafe {
    let _: () = objc::msg_send![root_view, addSubview: obj];
  };
});
view.load_html("<strong>hello world</strong>");
state.ns_view = Some(view);

thanks for your help!

ryanmcgrath commented 2 years ago

As a quick aside, you can also use cacao::geometry::Rect rather than CGRect directly - it has a From/Into defined for CGRect. :)

e.g

view.set_frame(Rect {
    top: 0.0,
    left: 0.0,
    width: 400.0,
    height: 600.0
});