gpui step by step 2. 状态保存与点击事件处理

写在前面

在上一篇的基础上,本节演示在 gpui 中组件的状态保存方式,与如何处理点击事件,实现一个简单的按钮

本次项目规划改动如下

我们将上一篇的 MyView 该写成 BaseView,用于构建基础的布局,同时创建 components 用来承载自定义的组件,本节实现一个简单的 button

注:可通过

bash 复制代码
cargo fix --allow-dirty

快速修复各种警告信息

main.rs

rust 复制代码
mod base_view;
mod components;

use gpui::{AppContext, Application, Bounds, Point, Size, WindowBounds, WindowOptions, px};

use crate::base_view::BaseView;

fn main() {
    let app = Application::new();
    app.run(|cx| {
        cx.open_window(
            WindowOptions {
                window_bounds: Some(WindowBounds::Windowed(Bounds {
                    origin: Point::default(),
                    size: Size::new(px(600.), px(480.)),
                })),
                ..Default::default()
            },
            // MyView 改名为 BaseView,并单独放在一个 mod 中
            |_window, cx| cx.new(|_cx| BaseView {}), 
        )
        .ok();
    });
}

components/button.rs

rust 复制代码
use gpui::{
    Context, CursorStyle, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
    StatefulInteractiveElement, Styled, Window, div, rgb,
};

pub struct Button {
    // 在 gpui 中,推荐使用 SharedString,这是一个不可变的字符串类型,在 gpui 中可以低成本复制
    // 并且更贴合日常使用,如,使用 String 会缺少作为某些场景下入参的 trait 实现
    text: SharedString,
}

impl Button {
    pub fn new(text: &str) -> Button {
        Button {
            text: SharedString::from(text.to_owned()),
        }
    }
}

impl Render for Button {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            // ⚠️注意:如果想使用第 34 行的 on_click,需要调用 id 这个方法将 Div 转换成 Stateful<Div>
            .id(self.text.clone()) 
            .flex()
            .justify_center()
            .items_center()
            .w_16()
            .h_8()
            .bg(rgb(0x8F98BD))
            .text_color(rgb(0xffffff))
            .rounded_sm()
            .hover(|style| style.cursor(CursorStyle::PointingHand).bg(rgb(0x615F73))) // 这里表示当鼠标放上来时需要进行的操作,这个回调函数会修改鼠标指针的样式和当前 button 的背景色
            .child(self.text.clone())
            .on_click(|_, _window, _cx| { // 这里需要一个回调函数,简单起见只在控制台打印一条信息
                println!("I have been clicked!");
            })
    }
}

接下来将 Button 添加在 BaseView 中,这里我先不放出代码,各位可根据如下信息按照自己的方式组合:

render 函数在组件视图发生变化 时会重新调用一次

接下来展示几种可能遇到的情况

一、直接在 child 中构建 Button

base_view.rs

rust 复制代码
use gpui::{Context, IntoElement, ParentElement, Render, Styled, Window, div, rgb};

use crate::components::button::Button;

pub struct BaseView {}

impl Render for BaseView {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .flex_col()
            .gap_2()
            .size_full()
            .items_center()
            .justify_center()
            .bg(rgb(0xffffff))
            .child("Hello, world!")
            .child(Button::new("click me"))
    }
}

你会发现报错:the trait bound `Button: IntoElement` is not satisfied

不必担心,你不需要想办法去实现 IntoElement,只需要将你的组件托付给 gpui,让它来管理其生命周期即可

diff 复制代码
use gpui::{AppContext, Context, IntoElement, ParentElement, Render, Styled, Window, div, rgb};

use crate::components::button::Button;

pub struct BaseView {}

impl Render for BaseView {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+       let button = cx.new(|_| Button::new("click me"));
        div()
            .flex()
            .flex_col()
            .gap_2()
            .size_full()
            .items_center()
            .justify_center()
            .bg(rgb(0xffffff))
            .child("Hello, world!")
+           .child(button)
    }
}

这下可以正常编译了,而且你会看到如下内容

但当你点击这个按钮时就会发现,控制台并没有输出内容,这就是另外一种情况

二、回调函数没生效

记得一开始说的:render 函数在组件视图发生变化 时会重新调用一次

也就是我们通过 cx.new 创建的 button,实际上已经被释放了,那么相应的闭包也就没有了效果,所以我们需要将 button 保存起来,就和在 Button 中保存 text 一样

base_view.rs

rust 复制代码
use gpui::{
    App, AppContext, Context, Entity, IntoElement, ParentElement, Render, Styled, Window, div, rgb,
};

use crate::components::button::Button;

pub struct BaseView {
    button: Entity<Button>, // 直接保存在 BaseView 中,Entity 的复制也很廉价,相当于指针计数+1
}
impl BaseView {
    pub fn new(cx: &mut App) -> BaseView {
        BaseView {
            button: cx.new(|_| Button::new("click me")),
        }
    }
}

impl Render for BaseView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .flex_col()
            .gap_2()
            .size_full()
            .items_center()
            .justify_center()
            .bg(rgb(0xffffff))
            .child("Hello, world!")
            .child(self.button.clone())
    }
}

main.rs

rust 复制代码
mod base_view;
mod components;

use gpui::{AppContext, Application, Bounds, Point, Size, WindowBounds, WindowOptions, px};

use crate::base_view::BaseView;

fn main() {
    let app = Application::new();
    app.run(|cx| {
        cx.open_window(
            WindowOptions {
                window_bounds: Some(WindowBounds::Windowed(Bounds {
                    origin: Point::default(),
                    size: Size::new(px(600.), px(480.)),
                })),
                ..Default::default()
            },
            |_window, cx| cx.new(|cx| BaseView::new(cx)), // 这里同步修改
        )
        .ok();
    });
}

如此,再次点击就能发现终端已经输出了我们希望看到的内容

相关推荐
EterNity_TiMe_2 小时前
[鸿蒙PC命令行移植适配]移植rust三方库lsd到鸿蒙PC的完整实践
华为·rust·harmonyos
星栈独行3 小时前
Makepad、egui、Dioxus、Tauri:Rust GUI 到底怎么选
开发语言·后端·程序人生·ui·rust
yangyongdehao303 小时前
桌面宠物开发记:从Rust到Tauri的探索之旅
开发语言·rust·宠物
禁默3 小时前
[鸿蒙PC命令行移植适配]移植rust三方库tealdeer到鸿蒙PC的完整实践
华为·rust·harmonyos
不爱学英文的码字机器3 小时前
[鸿蒙PC命令行移植适配]移植rust三方库xh到鸿蒙PC的完整实
华为·rust·harmonyos
Pocker_Spades_A5 小时前
[鸿蒙PC命令行移植适配]移植rust三方库erdtree到鸿蒙PC的完整实践
华为·rust·harmonyos
禁默5 小时前
[鸿蒙PC命令行移植适配]移植rust三方库starship到鸿蒙PC的完整实践
华为·rust·harmonyos
何忆清风19 小时前
Tauri2实现圆角窗口
rust
Snasph20 小时前
Rust 编程语言中文手册
rust