rust编程-Druid UI框架-Widget trait

Widget trait 表示 UI 的组件。Druid 包含一组内置的小部件,您也可以编写自己的小部件。将内置小部件和自定义小部件组合起来创建一个小部件树; 您将从一些单根小部件开始,这些小部件(通常)有子部件,它们本身可能有子部件,等等。Widget 有一个通用参数 T,表示该小部件处理的数据。有些小部件(比如布局小部件)可能完全不知道它们遇到的数据类型,而其他小部件(比如滑块)可能期望只有一种类型(比如 f64)。

widgets

由所有小部件实现的 trait。

小部件的所有外观和行为都封装在实现此特性的对象中。

Trait 由关联数据的类型(T)参数化。所有 trait 方法都可以访问这些数据,并且在事件的情况下,引用是可变的,因此事件可以直接更新数据。

只要应用程序数据发生变化,框架就会使用 update 方法遍历小部件层次结构。框架需要知道数据实际上是否已经更改,这就是为什么 T 有一个 Data 绑定。

所有的特征方法都提供了相应的上下文。小部件可以通过调用上下文中的方法来请求事物并导致操作。

此外,所有 trait 方法都提供了一个环境(Env)。

容器小部件通常不会直接在它们的子小部件上调用 Widget 方法,而是拥有封装在 WidgetPod 中的小部件,并在其上调用相应的方法。WidgetPod 包含这些遍历的状态和逻辑。另一方面,特别是轻量级容器可能直接包含它们的子 Widget (当不需要布局或事件流逻辑时) ,并且在这些情况下将调用这些方法。

作为一般模式,容器小部件将在其所有子级上调用相应的 WidgetPod 方法。WidgetPod 根据需要应用逻辑来确定是否递归。

pub fn event(
    &mut self,
    ctx: &mut EventCtx<'_, '_>,
    event: &Event,
    data: &mut T,
    env: &Env
)

处理事件

此方法调用处理许多不同的事件(在 Event 枚举中)。小部件可以通过多种方式处理这些事件: 从 EventCtx 请求事件、变更数据或提交命令

pub fn lifecycle(
    &mut self,
    ctx: &mut LifeCycleCtx<'_, '_>,
    event: &LifeCycle,
    data: &T,
    env: &Env
)
处理生命周期通知。

调用此方法是为了通知小部件某些特殊事件(可在 LifeCycle 枚举中获得) ,这些事件通常与小部件图中的变化或特定小部件的状态有关。
小部件不会根据这些事件变更应用程序状态,而只是根据需要更新它自己的内部状态; 如果小部件需要变更数据,它可以提交一个将在下一次机会执行的 Command。
pub fn update(
    &mut self,
    ctx: &mut UpdateCtx<'_, '_>,
    old_data: &T,
    data: &T,
    env: &Env
)

更新小部件的外观以响应应用程序的 Data 或 Env 的更改。

每当数据或环境发生更改时调用此方法。当需要更新小部件的外观以响应这些更改时,您可以在提供的 UpdateCtx 上调用 request _ aint 或 request _ layup,以根据需要安排绘制和布局的调用。 如果小部件希望计算细粒度的增量,则提供数据的前一个值; 如果实际需要,则应尝试仅请求新的布局或绘制通过。 要确定 Env 是否已经更改,可以在提供的 UpdateCtx 上调用 Env _ change; 然后可以使用小部件中使用的任何键调用 Env _ key _ change,查看它们是否已经更改; 然后可以根据需要请求布局或绘制。

pub fn layout(
    &mut self,
    ctx: &mut LayoutCtx<'_, '_>,
    bc: &BoxConstraints,
    data: &T,
    env: &Env
) -> Size

计算布局

叶子小部件应该确定它的大小(受提供的约束的限制)并返回它。 容器小部件将在其子小部件上递归地调用 WidgetPod: : 布局,为每个小部件提供适当的框约束、计算布局,然后在其每个子小部件上调用 set _ source。最后,它应该返回容器的大小。容器可以以任何顺序递归,这对于首先计算非 flex 小部件的大小来确定可用于 flex 小部件的空间量是很有帮助的。 为了提高效率,容器应该只调用一次子窗口小部件的布局,尽管没有任何强制措施。 布局策略受到 Flutter 的强烈启发。

pub fn paint(&mut self, ctx: &mut PaintCtx<'_, '_, '_>, data: &T, env: &Env)

绘制小部件外观。 PaintCtx 对实现 RenderContext 特性的内容进行了定义,该特性公开了小部件可以用来绘制其外观的各种方法。 容器小部件可以在递归到它们的子元素之前绘制背景,或者在之后绘制注释(例如,滚动条)。此外,它们还可以对呈现上下文应用掩码和转换,这对滚动特别有用。

下面让我们手动实现一个自己的小部件

首先创建我们的rust项目

cargo new druid-widget

创建完成项目后添加包

druid = { git = "https://github.com/linebender/druid.git"}

在main.rs引入我们需要用到的引用

use druid::{widget::{Label, LabelText, ControllerHost, Click, Flex},RoundedRectRadii,Affine,UnitPoint,LinearGradient,Insets,theme, Data, Size, Env, WindowDesc, AppLauncher, Color, Lens};
use druid::widget::prelude::*;

我们先创建我们的小部件结构体

struct MyButton<T>{
    //按钮上面的文字使用Label
    label:Label<T>,
    //尺寸
    label_size:Size
}

给MyButton实现new 方法和form_label方法,on_click点击事件

impl<T: Data> MyButton<T> {

    pub fn new(text: impl Into<LabelText<T>>) -> MyButton<T>{
        //根据传入文字参数,创建按钮
        MyButton::form_label(Label::new(text))
    }

    pub fn form_label(label:Label<T>) -> MyButton<T>{
        //根据传入label部件创建按钮
        MyButton 
        { 
            label,
            label_size: Size::ZERO,
        }
    }

    //按钮点击事件(也可以实现其余的事件,鼠标悬浮,鼠标移除等)
    pub fn on_click(
        self,
        f: impl Fn(&mut EventCtx,&mut T,&Env)+ 'static,
    ) -> ControllerHost<Self,Click<T>> {
        //controller事件处理,后面会详细讲解,Click是druid里面的部件可以直接使用,点击事件
        ControllerHost::new(self, Click::new(f))
    }

}

下面让我们的MyButton实现Widget,实现部件显示(里边的代码和方法我都添加了注释)

impl<T:Data> Widget<T> for MyButton<T> {

    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
        match event {
            Event::MouseDown(_) => {
                if !ctx.is_disabled() {
                    ctx.set_active(true);
                    //请求重画
                    ctx.request_paint();
                }
            },
            Event::MouseUp(_) => {
                if ctx.is_active() && ctx.is_disabled() {
                    //请求重画
                    ctx.request_paint();
                    //trace!("Button {:?} released",ctx.widget_id());
                }
                ctx.set_active(false);
            },
            _ => {}
        }
    }

    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
        if let LifeCycle::HotChanged(_) | LifeCycle::DisabledChanged(_) = event {
            ctx.request_paint();
        }
        self.label.lifecycle(ctx, event, data, env)
    }

    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
        //调用label的update方法
        self.label.update(ctx, old_data, data, env)
    }

    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
        //设置padding的大小
        let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
        //shrink 按照传入的padding设置label的边距,(给定的大小也是取整数,四舍五入)
        //loosen 复制一份相同大小的尺寸
        let label_bc = bc.shrink(padding).loosen();
        //调用label的layout方法
        self.label_size = self.label.layout(ctx, &label_bc, data, env);
        //获取部件的最小宽度
        let min_height = env.get(theme::BORDERED_WIDGET_HEIGHT);
        //返回偏移量
        let baseline = self.label.baseline_offset();
        //设置偏移量
        ctx.set_baseline_offset(baseline + LABEL_INSETS.y1);
        //按钮的大小
        let button_size = bc.constrain(Size::new(
            self.label_size.width + padding.width,
            (self.label_size.height + padding.height).max(min_height),
        ));
        println!("{:?}",button_size);
        button_size
    }

    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {

        //active 表示是否鼠标按下
        //disabled 表示是否禁用
        let is_active = ctx.is_active() && !ctx.is_disabled();
        //hot 鼠标是否悬停在按钮上面
        let is_hot = ctx.is_hot();
        //size 布局大小
        let size = ctx.size();
        //获取按钮边框的宽度
        let stroke_width = env.get(theme::BUTTON_BORDER_WIDTH);

        let rounded_rect = size
            //转换成原点格式 Rect::new(0., 0., self.width, self.height)
            .to_rect()
            //计算尺寸
            .inset(-stroke_width / 2.0)
            //拐角的半径 env.get(theme::BUTTON_BORDER_RADIUS)
            .to_rounded_rect(RoundedRectRadii {
                top_left:size.width/2.0,
                top_right:size.width/2.0,
                bottom_right:size.width/2.0,
                bottom_left:size.width/2.0,
            });

        //
        let bg_gradient = if ctx.is_disabled() {
            //禁用的效果
            LinearGradient::new(
                UnitPoint::TOP,
                UnitPoint::BOTTOM,
                (
                    env.get(theme::DISABLED_BUTTON_LIGHT),
                    env.get(theme::DISABLED_BUTTON_DARK),
                ),
            )
            //鼠标按下的效果
        } else if is_active {
            LinearGradient::new(
                UnitPoint::TOP,
                UnitPoint::BOTTOM,
                (Color::YELLOW,Color::WHITE)
                //(env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
            )
        } else {
            //从某个位置到某个位置,可以渐变
            LinearGradient::new(
                //从顶部到底部  由红色渐变到白色
                UnitPoint::TOP,
                UnitPoint::BOTTOM,
                (Color::PURPLE,Color::WHITE)
                //(env.get(theme::BUTTON_LIGHT), env.get(theme::BUTTON_DARK)),
            )
        };

        //边框的颜色
        let border_color = if is_hot && !ctx.is_disabled() {
            //env.get(theme::BORDER_LIGHT)
            Color::RED
        } else {
            //env.get(theme::BORDER_DARK)
            Color::WHITE
        };

        //
        ctx.stroke(rounded_rect, &border_color, stroke_width);

        ctx.fill(rounded_rect, &bg_gradient);

        let label_offset = (size.to_vec2() - self.label_size.to_vec2()) / 2.0;

        ctx.with_save(|ctx| {
            ctx.transform(Affine::translate(label_offset));
            //调用label的paint
            self.label.paint(ctx, data, env);
        });
    }
}

下面我们创建我们的Data数据结构体

#[derive(Clone,Data,Lens)]
struct State{
    //label显示的文字
    text:String,
}

编写窗体部分

fn builder_ui() -> impl Widget<State> {

    //MyButton创建,这里的Label使用了Data模型,MyButton添加了点击事件
    let my_button = MyButton::form_label(Label::new(|state: &State,_env: &Env|{ format!("{}",state.text)}))
        .on_click(|_etc,state,_env|{
            println!("点击了按钮");
            state.text = "你点击了".to_string();
        });

    //Flex布局,这个后面也会讲解一下
    Flex::row()
        //把我们的组件添加到布局
        .with_child(my_button)
}

编写启动方法

fn main() {
    let win = WindowDesc::new(builder_ui()).title("自定义按钮").window_size((300.0,300.0));

    let _app = AppLauncher::with_window(win)
        .log_to_console()
        .launch(State { text: "按钮".to_string() });
}

运行实现的样式

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若梦网络编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值