ryanmcgrath / cacao

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

SplitViewController doesn't implement Layout (or super-trait ObjcAccess) - example code? #112

Closed mgraves closed 8 months ago

mgraves commented 8 months ago

I'm impressed with what I've been able to do with cacao, thank you for the hard work you've put into this:

I'm trying to scaffold an app with the SplitViewController three column setup as a candidate for using this for one of our internal apps, and have gotten stuck on instantiating a SuperViewController in a simple test app.

If there is an example of a SplitViewController being instantiated and presented in a cacao example (or other) app. I've searched thoroughly, to no avail, but would be happy to get a referral and if it is working, I'll thank you and consider this closed and will focus on analyzing what the example did that I'm missing.

Without an example, though, here is what I tried:

mod views;

use cacao::appkit::{App, AppDelegate};
use cacao::button::Button;
use cacao::appkit::window::{Window, WindowConfig, WindowStyle};
use cacao::geometry::Rect;
use cacao::appkit::window::WindowToolbarStyle;
use cacao::foundation::NSUInteger;
use cacao::view::{SplitViewController, View};
use crate::views::sidebar_view::SidebarView;
use crate::views::content_view::ContentView;
use crate::views::detail_view::DetailView;

struct CacaoTest2 {
  window: Window,
}

impl AppDelegate for CacaoTest2 {
  fn did_finish_launching(&self) {
    self.window.show();
  }
}

fn main() {
  let button = Button::new("Click me!");
  // let main_view = MainView::default();
  let window_config = WindowConfig {
    style: (WindowStyle::Titled as NSUInteger
      | WindowStyle::Closable as NSUInteger
      | WindowStyle::Resizable as NSUInteger) as NSUInteger,
    initial_dimensions: Rect::new(0.0, 0.0, 800.0, 600.0),
    defer: false,
    toolbar_style: WindowToolbarStyle::Automatic,
  };

  let window = Window::new(window_config);
  let split_view_controller = SplitViewController::new(SidebarView::default(),
                                                       ContentView::default(),
                                                       Some(DetailView::default()));
  window.set_content_view(&split_view_controller);

  App::new("net.xdix.cacao_test_2", CacaoTest2 { window }).run();
}

I have the SidebarView, ContentView and DetailView views defined and usable as simple (just a Label) views that work on their own just fine.

I've tried embedding the SplitControllerView in an enclosing MainView, and calling add_subview(splitViewController) in the did_load() override for that MainView -- same error:

error[E0277]: the trait bound `SplitViewController<SidebarView, ContentView, DetailView>: cacao::layout::Layout` is not satisfied
   --> src/main.rs:25:36
    |
25  |       self.window.set_content_view(&split_view_controller);
    |                   ---------------- ^^^^^^^^^^^^^^^^^^^^^^ the trait `cacao::layout::Layout` is not implemented for `SplitViewController<SidebarView, ContentView, DetailView>`
    |                   |
    |                   required by a bound introduced by this call

I can upload a zip of the entire (small) project if it helps, but I'm guessing I just have a conceptual problem here in terms of the specific recipe needed for getting a SplitViewController working.

ryanmcgrath commented 8 months ago

It's been a minute since I've touched that, but at a glance - you want window.set_content_view_controller, not window.set_content_view.

If that doesn't work, let me know and we can debug further. :)

mgraves commented 8 months ago

It's been a minute since I've touched that, but at a glance - you want window.set_content_view_controller, not window.set_content_view.

If that doesn't work, let me know and we can debug further. :)

Thank you, will try that!

mgraves commented 8 months ago

It's been a minute since I've touched that, but at a glance - you want window.set_content_view_controller, not window.set_content_view.

If that doesn't work, let me know and we can debug further. :)

That worked, thanks for the tip. Actually, it wasn't just window.set_content_view_controller(), got a hang when just had it inserted after the window creation in main().

What made it work is creating a WindowDelegate and using did_load() to crate the SplitViewController. Here's the WindowDelegate and the main sources for any who may want to see how it works:

main.rs

mod main_window;
use cacao::appkit::{App, AppDelegate};
use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowStyle};
use crate::main_window::MainWindow;

struct MyApp {
  window: WindowController<MainWindow>,
}

impl AppDelegate for MyApp {
  fn did_finish_launching(&self) {
    App::activate();
    self.window.show();
  }
  fn should_terminate_after_last_window_closed(&self) -> bool {
    true
  }
}

fn main() {
  App::new("net.groupmg.cacao-test", MyApp {
    window: WindowController::with(WindowConfig::default(), MainWindow::default())
  })
    .run();
}

main_window.rs

use cacao::appkit::window::{Window, WindowDelegate};
use cacao::view::SplitViewController;
use crate::views::content_view::ContentView;
use crate::views::detail_view::DetailView;
use crate::views::sidebar_view::SidebarView;

#[derive(Default)]
pub struct MainWindow {
  controller: Option<SplitViewController<SidebarView, ContentView, DetailView>>
}

impl WindowDelegate for MainWindow {
  const NAME: &'static str = "MainWindow";

  fn did_load(&mut self, window: Window) {
    window.set_minimum_content_size(400., 400.);
    window.set_title("MyApp");
    let sidebar = SidebarView::default();
    let content = ContentView::default();
    let detail = DetailView::default();

    let split_view_controller = SplitViewController::new(sidebar, content, Some(detail));
    window.set_content_view_controller(&split_view_controller);
    self.controller = Some(split_view_controller);
  }

  fn will_close(&self) {
    println!("Closing now!");
  }
}

Your comment was all I needed to get pointed in the right direction. Much appreciated!

ryanmcgrath commented 8 months ago

Ah yeah - you def want to be creating things after did_finish_launching since that ensures all the macOS-isms are set up in the background. :)

Glad it works! Should probably add an example to the repo at some point... closing this now but feel free to reopen if you hit further issues.