ryanmcgrath / alchemy

An experimental GUI framework for Rust, backed by per-platform native widgets. React, AppKit/UIKit inspired. EXPERIMENTAL, runs on Cocoa right now. ;P
https://alchemy.rs/
MIT License
384 stars 7 forks source link
appkit gtk gui qt react rust uikit uwp

Notice

This project is on indefinite hiatus for right now. I appreciate the Rust community's interest in GUI frameworks, but this project is personal for me - I worked on it extensively during a time when my younger brother was battling Leukemia, and so returning to it brings up a lot of things that I prefer to take time dealing with.

If you're interested in following work I'm doing in the GUI space with regards to Rust, feel free to follow appkit-rs, which would end up being one of the underlying layers of this anyway (much the same way that gtk-rs would need to back, well, Gtk).

Potion

Alchemy - A Rust GUI Framework

Crates.io

HomepageAPI Documentation

Alchemy is an experimental Rust GUI Framework, backed by native widgets on each platform it supports, with an API that's a blend of those found in AppKit, UIKit, and React Native. It aims to provide an API that feels at home in Rust, while striving to provide a visual appearance that's easy to scan and parse. It does not, and will never, require nightly. It's still early stages, but feedback and contributions are welcome.

Supported Platforms

Alchemy will, ideally, support the platforms listed below. At the moment, the Cocoa backend is the most complete, as I develop on a Mac and know the framework more than I'd care to admit. This list will be updated as more frameworks are added.

Support for more platforms is desired - for example, I think an OrbTk or Piston backend could be cool to see. A web backend would be awesome to support. A winapi-rs backend could be cool, too!

What Currently Works...?

At the moment, the following is implemented:

You can clone this repo and cargo run from the root to see the example app.

What's it look like?

use alchemy::{AppDelegate, Error, RSX, rsx, styles, View, Window, WindowDelegate};

struct AppState {
    window: Window
}

impl AppDelegate for AppState {
    fn did_finish_launching(&mut self) {
        self.window.set_title("Test");
        self.window.set_dimensions(10., 10., 600., 600.);
        self.window.show();
    }
}

struct WindowState;

impl WindowDelegate for WindowState {
    fn render(&self) -> Result<RSX, Error> {
        Ok(rsx! {
            <View styles=["box"]>
                <View styles=["innerbox"] />
            </View>
        })
    }
}

fn main() {
    let app = alchemy::shared_app();

    app.register_styles("default", styles! {
        box {
            background-color: #307ace;
            width: 300;
            height: 300;
            margin-top: 10;
            padding-top: 10;
        }

        innerbox {
            background-color: #003366;
            width: 200;
            height: 200;
        }
    });

    app.run(AppState {
        window: Window::new(WindowState {

        })
    });
}

Does it support custom Components?

Yes. Alchemy implements the React component lifecycle - although it does not (currently) implement Hooks, and may or may not implement them in the future. The class-based lifecycle maps fairly well to Rust idioms already, as you really never wanted to subclass in React anyway.

A custom component would look like the following:

use alchemy::{Component, ComponentKey, Error, Props, rsx, RSX};

#[derive(Default)]
pub struct MySpecialWidgetProps;

#[derive(Props)]
pub struct MySpecialWidget {
    props: MySpecialWidgetProps
}

impl Component for MySpecialWidget {
    fn new(key: ComponentKey) -> MySpecialWidget {
        MySpecialWidget {}
    }

    fn component_did_mount(&mut self) {
        // Do whatever you want. Fire a network request or something, I dunno.
    }

    fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> {
        Ok(RSX::None)
    }
}

Rust allows the lifecycle to have a few cool guarantees that you can't really get in JavaScript - for instance, props don't actually belong to you... but it was a weird aspect of class-based components in JavaScript where you'd be able to arbitrarily call this.props.whatever. Function based components actually communicated it better, in that they were passed in - with Rust, it's very clear that you just get a reference.

Alchemy follows this diagram of React's lifecycle methods to a T for the most part. What's cool is that methods that shouldn't have side effects, we can call as straight up borrows... and the ones that are allowed to have mutable side effects, we can call them as &mut self. You can, of course, still incur side effects by doing something else, but being able to imply the intention directly in the API is kind of cool.

License

I'm dual licensing this, due to the licenses that some of the projects it depends on being that. If there's some other (more appropriate) way to do this, please feel free to open an issue.

Contributing

Before contributing, please read the contributors guide for useful information about setting up Alchemy locally, coding style and common abbreviations.

Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work, should be dual-licensed as above, without any additional terms or conditions.

Notes