ryanmcgrath / cacao

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

Can't seem to make Button fire when clicked #102

Closed Isaac-Leonard closed 11 months ago

Isaac-Leonard commented 11 months ago

I just got started with cacao today so am probably missing something but I can't seem to get the button to fire when clicked. Here is my code:

use cacao::appkit::window::Window;
use cacao::appkit::{App, AppDelegate};
use cacao::filesystem::FileSelectPanel;
use cacao::layout::Layout;
use cacao::notification_center::Dispatcher;
use cacao::text::{Font, Label};
use cacao::view::{View, ViewController, ViewDelegate};
use cacao::{button, view};

struct BasicApp {
    window: Window,
    content_view: ViewController<ContentView>,
}

impl Default for BasicApp {
    fn default() -> Self {
        Self {
            window: Window::default(),
            content_view: ViewController::new(ContentView::default()),
        }
    }
}

impl Dispatcher for BasicApp {
    type Message = Message;

    /// Handles a message that came over on the main (UI) thread.
    fn on_ui_message(&self, message: Self::Message) {
        eprint!("Got message");
        if let Some(delegate) = &self.content_view.view.delegate {
            delegate.on_ui_message(message);
        }
    }
}

impl AppDelegate for BasicApp {
    fn did_finish_launching(&self) {
        App::activate();
        self.window.set_minimum_content_size(400., 400.);
        self.window.set_title("Hello World!");
        self.window.set_movable_by_background(true);
        self.window.set_content_view_controller(&self.content_view);
        self.window.show();
        //        FileSelectPanel::new().show(|_| {});
    }

    fn should_terminate_after_last_window_closed(&self) -> bool {
        true
    }
}

#[derive(Default)]
struct ContentView {
    content: view::View,
    label: Label,
}

impl ViewDelegate for ContentView {
    const NAME: &'static str = "SafeAreaView";

    fn did_load(&mut self, view: View) {
        let font = Font::system(30.);
        self.label.set_font(&font);
        self.label.set_text("Hello World");
        self.label
            .set_text_color(cacao::color::Color::rgb(255, 255, 255));

        self.content.add_subview(&self.label);
        let mut btn = button::Button::new("Click me");
        btn.set_action(|| dispatch_ui(Message::ChangeText));
        btn.set_key_equivalent("c");
        self.content.add_subview(&btn);
        view.add_subview(&self.content);

        // Add layout constraints to be 100% excluding the safe area
        // Do last because it will crash because the view needs to be inside the hierarchy
        cacao::layout::LayoutConstraint::activate(&[
            self.content
                .top
                .constraint_equal_to(&view.safe_layout_guide.top),
            self.content
                .leading
                .constraint_equal_to(&view.safe_layout_guide.leading),
            self.content
                .trailing
                .constraint_equal_to(&view.safe_layout_guide.trailing),
            self.content
                .bottom
                .constraint_equal_to(&view.safe_layout_guide.bottom),
        ])
    }
}

impl Dispatcher for ContentView {
    type Message = Message;

    /// Handles a message that came over on the main (UI) thread.
    fn on_ui_message(&self, message: Self::Message) {
        self.label.set_text("Changed text")
    }
}

/// Dispatch a message on a background thread.
fn dispatch_ui(message: Message) {
    println!("Dispatching UI message: {:?}", message);
    App::<BasicApp, Message>::dispatch_main(message);
}

#[derive(Clone, Debug)]
enum Message {
    ChangeText,
}
fn main() {
    App::new("com.hello.world", BasicApp::default()).run();
}

No matter what I do the button refuses to call the action when I click on it, is this a bug or have I missed something? It may be relevant that I am using VoiceOver, the macOS screen reader, but have tested without it and still have the same issue so don't think its related.

Isaac-Leonard commented 11 months ago

Okay, I've fixed it, I wasn't storing the button anywhere so it was getting dropped Is there any way to enforce in the type system that it can't be dropped if its being displayed?

ryanmcgrath commented 11 months ago

Unfortunately no, at least not without some significant creative solution that I've not thought of yet (e.g effectively some form of a virtual DOM that's managed for you). It's due to having to work between Rust's lifetime model and the ObjC model.

I would be open to reviewing and considering a solution for this as a feature of the library, provided it's not invalidating the Rust ownership model - but I unfortunately don't have time to work on it myself.

Going to close this issue but feel free to open other ones as you hit issues, always happy to weigh in.

Isaac-Leonard commented 11 months ago

Makes sense I'd love to try implement some sort of framework on top of all this to manage everything like that if I get time.

ryanmcgrath commented 11 months ago

You're always welcome to pick up and revive Alchemy. ;)