ryanmcgrath / cacao

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

[Question] Rendering a pixel buffer to a window? #21

Closed jjant closed 2 years ago

jjant commented 2 years ago

Hey there, I'm pretty new to mac/cocoa programming, I'm trying to basically write a renderer, ending up with some kind of array of pixel data, and display that to a window.

It seems like I could use Bitmap contexts/Image::draw to achieve this somehow, but so far I haven't been able to.

I tried stuff like this, which doesn't render anything to the screen:

struct BasicApp {
    window: Window,
}

impl AppDelegate for BasicApp {
    fn did_finish_launching(&self) {
        self.window.set_minimum_content_size(400., 400.);
        self.window.set_title("Hello World!");
        self.window.show();
        self.window.make_key_window();
        self.window.make_key_and_order_front();

        let config = DrawConfig {
            source: (0., 100.),
            target: (0., 100.),
            resize: cacao::image::ResizeBehavior::Stretch,
        };
        let image = Image::draw(config, |cg_rect, cg_context_ref| {
            cg_context_ref.set_fill_color(&CGColor::rgb(255., 255., 255., 255.));
            cg_context_ref.fill_rect(cg_rect);
            cg_context_ref.flush();

            true
        });
        let image_view = ImageView::new();
        image_view.set_background_color(cacao::color::Color::SystemBlue);
        image_view.set_image(&image);

        let view = View::new();
        view.add_subview(&image_view);

        LayoutConstraint::activate(&[
            image_view.top.constraint_equal_to(&view.top),
            image_view.leading.constraint_equal_to(&view.leading),
            image_view.trailing.constraint_equal_to(&view.trailing),
            image_view.bottom.constraint_equal_to(&view.bottom),
        ]);

        self.window.set_content_view(&view);
    }
}

fn main() {
    let app_delegate = BasicApp {
        window: Window::default(),
    };
    let app = App::new("com.hello.world", app_delegate);

    app.run()
}

And many variations of it (without really achieving anything), any ideas?

ryanmcgrath commented 2 years ago

Heya, cool to see someone using the custom drawing code... I don't think anyone has dug that deep yet!

I went ahead and made an example in the repo: https://github.com/ryanmcgrath/cacao/blob/trunk/examples/custom_image_drawing.rs

Basically, you were hitting a few things here:

Feel free to grab the example in the repo and go crazy - it just renders a generic file icon, but hopefully it's helpful!

ryanmcgrath commented 2 years ago

(This is also probably a great candidate project for custom drawing code on a CALayer or something, so if you wind up looking into that... pull requests welcome!)

jjant commented 2 years ago

Hey man, thank you so much for the detailed answer!

Unfortunately I'm not smart enough to get what I want to do to work... I noticed that CGContext has a .data() function that I think lets me access the pixel data directly, but on the Image::draw callback I get a CGContextRef (which I assume is a C CGContext*? Apple docs are not the clearest thing...). I can get a CGContext from the ref with to_owned(), but modifying that doesn't seem to do anything (I assume I'm getting a copy, rather than the actual original thing).

I saw there's also CGContext::from_existing_context_ptr() but it takes a different CGContextRef (which appears to come from some sys crate and not core-foundation-rs).

I can also build a context by itself, with CGContext::create_bitmap_context, but I can't seem to understand how to turn that into a cacao image.


A different question altogether, but I don't understand what the bool returned from the Image::draw callback is, should it always be true?

ryanmcgrath commented 2 years ago

Re: the returned boolean value, the Image::draw method is mostly a mapping over NSImage draw handlers - which return a BOOL in their handler method. I don't exactly think it's the greatest API ever, but I wanted to keep it more in line with it from the Rust side for clarity's sake.

See: https://developer.apple.com/documentation/appkit/nsimage/1519860-imagewithsize?language=objc

Regarding understanding the CGContextRef pieces, you probably wanna scope out image/image.rs line ~179ish (the draw method). It grabs the current graphics context and does some basic calls before passing it to you - hopefully this helps with whatever you're trying to do?

jjant commented 2 years ago

Fantastic, thanks for all the help and pointers, @ryanmcgrath!