Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encapsulate widgets with rust logic #2031

Open
John0x opened this issue Jan 3, 2023 · 7 comments
Open

Encapsulate widgets with rust logic #2031

John0x opened this issue Jan 3, 2023 · 7 comments
Assignees
Labels
a:language-slint Compiler for the .slint language (mO,bF) enhancement New feature or request

Comments

@John0x
Copy link

John0x commented Jan 3, 2023

Hey, I've read the documentation and examples, but couldn't quite figure out what the proper approach for more complex applications looks like.
Let's say, that I have some application with multiple screens using Rust. From what I understand, if I want to add some logic using rust, I have to do it via callbacks on the window?
Is there no way to create a widget using the slint language, add some logic using rust and then use that Widget, with the rust logic, within another slint language widget/window?

Having a single point to add the rust logic feels extremely inconvenient to me.
I would have imagined something like this: (pseudocode):

pub fn MyLoginScreen() {
    slint::slint! {
        import { Button } from "std-widgets.slint";

        export LoginScreen := Rectangle {
            callback login_clicked <=> btnLogin.clicked;

            HorizontalLayout {
                alignment: center;
                VerticalLayout {
                    alignment: center;
                    btnLogin := Button {
                        text: "Login";
                    }
                }
            }
        }
    }

    let login = LoginScreen::new();
    login.on_login_clicked(|| eprintln!("LOGIN"));
}


pub fn main() {
    slint::slint! {
        export App := Window {
            callback quit();
            login := MyLoginScreen {  }
        }
    }

    let app = App::new();
    app.run();
}

Is adding the logic via callbacks to the window and then forwarding these to the screens the only way?

@tronical
Copy link
Member

tronical commented Jan 4, 2023

Yes, at the moment you need to either forward it like that or you can also direct this through globals - that way you don't need to direct it all the way, but at the same time it's not per-instance.

I hope that the groundwork for #1726 will also allow us to have a better per-instance encapsulation of native (Rust) logic and Slint components. #784 is also required I think for more modular application setups.

@tronical
Copy link
Member

tronical commented Jan 4, 2023

For clarification, I think that this is a valid issue and the acceptance criteria would be to build on the linked issues implement support for encapsulation of elements that come with native code, document it, and perhaps use it in one of our examples.

@tronical tronical added enhancement New feature or request a:language-c++ C++ API, codegen, CMake build system (mS,mO) a:language-rust Rust API and codegen (mO,mS) labels Jan 4, 2023
@John0x
Copy link
Author

John0x commented Jan 4, 2023

Okay, thank you :)
That makes it unusable for complex applications, in my opinion. At least for the time being. I'm excited to see how it will progress :)
I really like the tooling and the slint language, but the whole rust <-> slint interaction model feels too cumbersome. Lot's of potential tho, hope this goes far 👍

@ogoffart
Copy link
Member

ogoffart commented Jan 5, 2023

I think the linked issue by @tronical are orthogonal to this.
Currently, the way to do this is to have a global object and do something like this:

struct LoginScreenData := { /*...*/ }

 export global LoginScreenLogic := {
     callback login_clicked(LoginScreenData);
 }

 export LoginScreen := Rectangle {
            property<LoginScreenData> data;
            HorizontalLayout {
                alignment: center;
                VerticalLayout {
                    alignment: center;
                    btnLogin := Button {
                        text: "Login";
                        clicked => { LoginScreenLogic.login_clicked(data) }
                    }
                }
            }
        }

And then initialize the callbacks on LoginScreenLogic so that the LoginScreen can be used by the rest of the slint code.

But there is room for improvements there

@John0x
Copy link
Author

John0x commented Jan 5, 2023

@ogoffart I think something like that would be fine for small applications but is miles away from being an acceptable solution for complex ones. Compared to other frameworks of course

@tronical
Copy link
Member

tronical commented Sep 7, 2023

Olivier and I keep discussing this topic and iterating - it's a tricky problem that spans from the language syntax, to API, all the way to developr ergonomics (diagnostics when something goes wrong). In our latest iteration we're thinking of experimenting with the following approach. I'll explain by example: Suppose you want to write a VideoPlayer in Slint that needs some native code.

Let's draft that in Slint code:

@native-init()
component VideoPlayer {
    callback play();
    in property<string> url;
    ...
    play-button := PlayButton {}
    time := TimeDisplay {}
    private property <string> current-time <=> time.text;
}

We'd annotate a component with @native-init() to say that when compiling to native code, we need some Rust code associated with it - the developer needs to supply it.

When compiling to Rust, we could provide a trait that the user needs to implement. Let's call it slint::CustomInit:

// The Rust generated code for the internal VideoPlayer component expects `CustomInit` to be implemented, and
// internally would call init in its constructor to invoke the user code:
// ...
//.   <Self as slint::CustomInit>::init(self>;
// ...
//
impl slint::CustomInit for VideoPlayer {
    fn init(&self) {
        self.set_native_data(Box::new(FFmpegVideoPlayer::new());

        // Note how the callback takes a &Self argument, so that it's easily accessible
        self.on_play(|player: &Self| {
            self.native_data().downcast_ref::<FFmpegVideoPlayer>().play();
            self.set_current_time(...);
        });
    }
}

In C++ we would use ADL to let the compiler find the user supplied function:

A catch-all in slint.h:

void slint_custom_init(auto& ) {
    static_assert(`false, "You must declare a slint_custom_init function for the types that have @native-init");
}

and then, before including the Slint generated code

void slint_custom_init(const VideoPlayer &) {
    
}

For the interpreter API it would have to be entirely dynamic, so there'd have to be a set_factory_fn(component_name: &str, init_fn: fn(&slint_interpreter::CustomComponent)) type of function.

Note: This is somewhat orthogonal to the question of how to make the compilation itself modular, i.e. we still want the ability in the future to take a bunch of .slint files along with say Rust code, place it in a crate, compile it, and then use that crate and .slint files in the application.

@tronical tronical self-assigned this Sep 8, 2023
@tronical tronical added this to the 1.3 milestone Sep 8, 2023
@tronical
Copy link
Member

tronical commented Sep 8, 2023

I will experiment with this as part of the 1.3 release cycle. If the experiment succeeds, then I think this can be resolved within the release. If the experiment fails (i.e. requires further research, not happy with the resulting API/approach), then we'll descope it from 1.3.

@tronical tronical modified the milestones: 1.3, 1.4 Oct 25, 2023
@ogoffart ogoffart added a:language-slint Compiler for the .slint language (mO,bF) and removed a:language-c++ C++ API, codegen, CMake build system (mS,mO) a:language-rust Rust API and codegen (mO,mS) labels Jan 16, 2024
@tronical tronical removed this from the 1.4 milestone Jan 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:language-slint Compiler for the .slint language (mO,bF) enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants