Crate finestra

source ·
Expand description

§Finestra

CI Crates.io Version GitHub License

Finestra is a simple and practical desktop UI framework for Rust. It maintains the authentic look and feel of each platform by integrating with their native UI backends. With Finestra, you can write an application that targets both Windows and macOS.

§Installation

Finestra provides a crate which contains all the tools you need to start developing desktop applications:

[dependencies]
finestra = "0.1.0"

§Example

The following example demonstrates the basic usage of Finestra, by providing a button that get its text updated each time it is clicked.

use finestra::*;

struct MyApplication;

impl AppDelegate<AppState> for Application {
    fn make_content_view(&mut self, state: &mut AppState, _: Window) -> impl finestra::View<Self, AppState>  {
        state.label.set("Count: 0");

        Button::new(&state.label)
            .with_on_click(|state: &mut AppState, window: Window| {
                state.count += 1;
                state.label.set(format!("Count: {}", state.count));

                if state.count % 10 == 0 {
                    window.create_dialog(format!("You clicked {} times!", state.count))
                        .show();
                }
            })
    }
}

#[derive(Debug, Default)]
struct AppState {
    count: usize,
    label: TextValue,
}

fn main() {
    App::with_state(MyApplication, AppState::default())
        .run();
}

§Usage

The crate provides a single entrypoint, the App structure.

App::new(MyApplication::default()).run()

To react to common events (such as launching) and to configure and populate the window, you must provide an AppDelegate implementation.

struct MyApplication;

impl AppDelegate for MyApplication {
    fn did_launch(&mut self, _: &mut ()) {
        println!("Taking Off 🚀");
    }

    fn configure_main_window(&mut self, _: &mut ()) -> WindowConfiguration {
        WindowConfiguration::new()
            .with_title("Exciting Window Title 🤩")
    }

    fn will_show_window(&mut self, _: Window, _: &mut ()) {
        println!("About to show the window, be prepared! 👀");
    }

    fn make_content_view(&mut self, _: &mut (), _: Window) -> impl View<Self> {
        Label::new("Welcome to Finestra!")
    }
}

§State

A powerful tool for application development is the State<T> object, which is a shared and subscribed object that you can use to write once, update everywhere. It is akin to WPF’s Binding and SwiftUI’s Binding. It is also deeply integrated in the library, for example by allowing you to pass a State<String> everywhere you pass a String/&str.

In the following example, the user can modify the title of the window by changing the contents of a TextField:

struct Application;

impl AppDelegate<AppState> for Application {
    fn configure_main_window(&mut self, state: &mut AppState) -> WindowConfiguration {
        state.title.set("My Application");

        WindowConfiguration::new()
            .with_title(state.title.clone())
    }

    fn make_content_view(&mut self, state: &mut AppState, _: Window) -> impl finestra::View<Self, AppState>  {
        Stack::horizontal()
            .with(Label::new("Choose a title:"))
            .with(TextField::new(state.title.clone()))
    }
}

#[derive(Debug, Default)]
struct AppState {
    title: TextValue,
}

fn main() {
    App::with_state(Application, AppState::default())
        .run();
}

Click here for the full example.

§Stacking

To place multiple items in the same row or column, use the Stack view. This can be horizontal (row-like) or vertical (column-like).

fn make_content_view(&mut self, _: &mut (), _: Window) -> impl finestra::View<Self, ()> {
    Stack::horizontal()
        .with(Label::new("Hello, world!"))
        .with(Button::new("Click Me"))
        .with(Button::new("Goodbye, world!"))
}

To see how these can be combined to create a powerful interface, see the Calculator App example.

§Dialogs

When a specific event occurs that requires attention from the user, you can use dialog boxes.

use rand::seq::SliceRandom;

const FRUITS: &[&str] = &[
    "Apple", "Banana",
    "Strawberry", "Orange",
    "Kiwi", "Pear",
    "Berries", "Lemon",
    // "Tomato",
];

Button::new("Random Fruit")
    .with_on_click(|_, window| {
        window.create_dialog(FRUITS.choose(&mut rand::thread_rng()))
                .title("Fruit")
                .kind(DialogKind::Informational)
                .show();
    })

For further information, consult the documentation of DialogBuilder and DialogKind.

§Colors

To use colors that are consistent with every platform Finestra supports, you can use the SystemColor enumeration:

Label::new("BlueExampleSoftware")
    .with_color(Color::system(SystemColor::Blue))

These will ensure that you always use the correct color, harmonizing with the system applications.

If you need to use a specific color, you can of use the Color::rgb() and Color::rgba() functions:

Label::new("Maroon Balloon 🎈")
    .with_color(Color::rgb(155, 68, 68))

You can naturally use the State<Color> pattern for changing colors dynamically. See the Disco Button example to see how it’s implemented.

§Component Overview

The following components are supported by Finestra at the moment:

  • Button can be used to invoke a specific action.
  • ImageView can display images.
  • Label contains a single line of text.
  • Stack places items horizontally or vertically.
  • TextBlock can contain multiple lines of text, and allows for specific alignment.
  • TextField can be used to request a specific string from the user.

§Rationale

Operating Systems often specify their own design language, e.g. Apple’s Human Interface Guidelines and Microsoft’s Windows 11 Design Principles. These guidelines are provided to let users experience a consistent and familiar user interface, and honoring them is almost always appreciated by the users of your applications, just like Arc for Windows was praised on X/Twitter.

TODO: Update with blogpost

§Further Reading

Copyright (C) 2024 Tristan Gerritsen

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Re-exports§

Structs§

  • This is the main entrypoint to the framework. You have to implement the AppDelegate to get notified of specific app lifecycle events.
  • A View that displays text and is clickable.
  • A View that displays text and is clickable.
  • A platform-independent color type.
  • The cursor is the pointer that the user can move around with the mouse.
  • Builds and presents dialog boxes. To create a dialog box, use window.create_dialog().
  • An image that can be used in an ImageView.
  • A view that displays an image.
  • A View that displays text.
  • A menu is a (drop-down) menu in an MenuBar. It contains zero or more MenuItems.
  • A menu bar is a bar containing Menus.
  • An item in a Menu, such as “Open” and “Save” in the “File” menu.
  • A stack can be used to place multiple items after each other, e.g. horizontally, or vertically.
  • The State primitive is the tool to modify the characteristics of Views, by e.g. modifying a Label when a Button is clicked:
  • A text block is a view that can display multiple lines of text.
  • A text field is a view that the user can enter text into.
  • Use this timer to delay a certain action.
  • A reference to a Window. Use this to perform certain actions at runtime. If you want to modify the look and feel, use] WindowConfiguration.
  • Use this to configure the look and feel of the Window.

Enums§

  • Specifies the kind of dialog that is shown. This helps accessibility by providing more context, and is used by Finestra to show a certain icon.
  • Set the direction the items inside a Stack should be aligned in.
  • A convenient wrapper for State or the “raw” value. A bunch of APIs let you call them with either of them, and this wrapper provides easy Into implementations.
  • A platform-dependent color type. As each platform has its own default colors, this enumeration can be used to conform to the UI Guidelines of the respective platform.
  • These cursor are guaranteed to be available on all platforms, or appropriate alternatives that carry the meaning of the cursor exist.
  • Set the alignment/justification of e.g. a TextBlock.
  • The theme of the window.
  • This enumeration specifies the UI backend for Finestra.
  • The following cursors are available, that might be most appropriate for some platforms, but don’t have proper alternatives on other platforms.

Traits§

  • A platform-agnostic delegation trait, which will be used as a facade for these native frameworks.
  • Extensions for views that are quite common, such as setting the tooltip.
  • A generic graphical component.

Type Aliases§