#桌面应用程序 #Vulkan 图形 #声明式 UI #Vulkan #React #Flutter

narui

一个受 React 启发的 UI 库,用于使用 Rust 和 Vulkan 构建多媒体桌面应用程序

2 个版本

0.1.1 2021 年 12 月 13 日
0.1.0 2021 年 11 月 19 日

#794 in GUI

MIT/Apache

700KB
6K SLoC

narui

一个受 React 启发的 UI 库,用于使用 Rust 和 Vulkan 构建多媒体桌面应用程序。

  • 具有类似 React hooks 的人体工程学的声明式 UI
  • 类似于 JSX 的语法用于组合小部件
  • 干净、易读且熟悉的应用程序代码
  • 类似 Flutter 风格的盒式布局算法

narui node graph demo gif

使用方法

以下是对 narui 基本概念的简要介绍。如果您使用过现代 React 或 Flutter,许多东西可能会听起来很熟悉。

请务必查看 示例,这些示例涵盖了更多高级主题,并包含更复杂的代码。

基础

narui UI 由 小部件 组成。这些构建块可以是简单的框,也可以是复杂的节点图节点,甚至是一个完整的应用程序。应用程序的小部件形成一个树,在需要时部分重新评估。

小部件 是带有 小部件 属性宏注解的函数,返回 Fragment(用于组合小部件)或 FragmentInner(用于原始小部件)。

#[widget]
pub fn square(context: &mut WidgetContext) -> Fragment {
    rsx! {
        <rect fill=Some(color!(#ffffff)) constraint=BoxConstraints::tight(10.0, 10.0)>
    }
}

以这种方式定义的小部件可以用于其他小部件或作为应用程序的顶级元素通过 rsx 宏使用。

fn main() {
    render(
        WindowBuilder::new(),
        rsx_toplevel! {
            <square />
        },
    );
}

组合

narui 遵循组合优于继承的原则:您构建小的可重用组件,然后形成更大的小部件和应用程序。为了实现这一点,narui 小部件可以具有参数和子代。

#[widget(color = color!(#00aaaa))]  // we assign a default value to the color attribute which is used when color is unspecified
pub fn colored_column(children: FragmentChildren, color: Color, context: &mut WidgetContext) -> Fragment {
    rsx! {
        <rect fill=Some(color)>
            <padding padding=EdgeInsets::all(10.0)>
                <column>
                    {children}
                </column>
            </padding>
        </rect>
    }
}

然后我们可以这样使用该小部件

rsx! {
    <colored_container>
        <text>{"Hello, world"}</text>
        <square />
    </colored_container>
}

如果我们以编程方式生成多个小部件(例如显示列表),我们必须手动指定一个 key,以便每个小部件都可以唯一标识

rsx! {
    <colored_container>
        {
            (0..10).map(|i| {
                rsx! { 
                    <text key=&i> // <-- explicit key is given here
                        {format!("{}", i)}
                    </text> 
                }
            })
        }
    </colored_container>
}

状态、Hooks 和输入处理

传递给每个小部件的 上下文 类似于指向小部件树的指针(因此可以廉价复制),并用于将数据关联到特定的小部件。

narui 中,状态管理是通过使用 hooks 来实现的。Hooks 的工作原理类似于 React hooks。最简单的 hook 是 context.listenable,用于存储状态。小部件可以通过 context.listen 方法订阅 Listenable,并在它们所监听的状态变化时重新评估。同样,可以通过使用 context.shout 方法来更新可监听对象的值。

#[widget(initial_value = 1)]
pub fn counter(initial_value: i32, context: &mut WidgetContext) -> Fragment {
    let count = context.listenable(initial_value);
    let on_click = move |context: &CallbackContext| {
        context.shout(count, context.spy(count) + 1)
    };

    rsx! {
        <button on_click=on_click>
            <text>
                {format!("{}", context.listen(count))}
            </text>
        </button>
    }
}

动画

通常,小部件会在外部事件(如鼠标点击或通过 mpsc 通道发送的消息)的触发下改变状态。然而,有时让小部件本身驱动状态变化(例如创建动画)也是很有用的。小部件在评估时不应更新可监听对象,而只应在响应外部事件时更新。这样,可以干净且容易地避免重新渲染循环。

尽管存在这一规则,但可以通过使用 context.after_frame hook 来创建动画,该 hook 允许小部件在渲染每一帧后运行一个闭包。这允许小部件在每一帧中更改 Listenables,从而创建动画。

业务逻辑交互与与其他世界的接口

与非 UI 相关的代码的交互应类似于与 UI 相关的代码的交互方式。

  • Listenables 应从业务逻辑向 UI 传递状态。
  • 事件应从 UI 向业务逻辑传递输入。这可以是简单的回调,就像在您的 UI 代码中做的那样,也可以是更复杂的 mpsc 或类似技术。

与业务逻辑交互的第一步是运行它。这可以通过手动使用 effect hook 或使用 thread hook 作为工具来完成。有关如何实现的简单示例,请参阅 examples/stopwatch.rs

自定义渲染

narui 允许下游应用程序代码中定义的小部件发出完全自定义的 Vulkan API 调用,包括绘制调用。这对于多媒体应用程序尤为重要。可以通过这种方式实现的小部件示例包括 3D 视口、图像/视频视图等。

依赖关系

~57MB
~1M SLoC