前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust Druid 之Selector选择器使用

Rust Druid 之Selector选择器使用

原创
作者头像
8菠萝
修改2021-05-25 10:27:06
9470
修改2021-05-25 10:27:06
举报

概念

Command(Selector)的用处主要是用于窗体之间的消息传递。

总体:

  1. 定义一个Selector 并绑定消息体
  2. 在Send端使用EventCtx.submit_command()方法发送消息。
  3. 接收端使用Event::Command() 来获取消息。

Druid 内部也是基于事件循环的,当程序调用 AppLauncher::launch() 方法时,程序进入事件循环。在事件循环中,窗体间的消息传递是使用Selector来进行。

查看 application.rs 源码:

pub fn run(self, _handler: Option<Box<dyn AppHandler>>) {
        unsafe {
           
            // 事件循环 win32
            loop {
                let mut msg = mem::MaybeUninit::uninit();
                PeekMessageW(
                    msg.as_mut_ptr(),
                    ptr::null_mut(),
                    WM_TIMER,
                    WM_TIMER,
                    PM_NOREMOVE,
                );

                let res = GetMessageW(msg.as_mut_ptr(), ptr::null_mut(), 0, 0);
                if res <= 0 {
                    if res == -1 {
                        tracing::error!(
                            "GetMessageW failed: {}",
                            Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
                        );
                    }
                    break;
                }
                let mut msg: MSG = msg.assume_init();
                let accels = accels::find_accels(GetAncestor(msg.hwnd, GA_ROOT));
                let translated = accels.map_or(false, |it| {
                    TranslateAcceleratorW(msg.hwnd, it.handle(), &mut msg) != 0
                });
                if !translated {
                    TranslateMessage(&msg);
                    DispatchMessageW(&msg);
                }
            }
        }
    }

1. 当主线线程进入事件循环时,其他线程向主线程发消息时, 可用使用Command。

2. 各个Widget之间的消息传递,也可以使用Command。

实践

Cargo.toml

[dependencies]

druid = { git = "https://github.com/linebender/druid.git", features=["image", "png"]}

窗体之间通信

有两个TextBox 一个Button, 当Button触发点击事件时候, 将一个TextBox中的数据发送到另外一个TextBox。

use druid::WidgetExt;
use druid::{
    lens, text,
    widget::{Button, Controller, SizedBox},
    Event, Selector,
};
use druid::{
    widget::{Flex, TextBox},
    AppLauncher, Data, Size, Widget, WindowDesc,
};

#[derive(Data, Clone, Debug)]
pub struct DataText {
    pub data: String,
    pub data_rev: String,
}

const REV_CHAR: Selector<DataText> = Selector::new("com.text.rev");

struct TextBoxController;

impl<W: Widget<DataText>> Controller<DataText, W> for TextBoxController {
    fn event(
        &mut self,
        child: &mut W,
        ctx: &mut druid::EventCtx,
        event: &druid::Event,
        data: &mut DataText,
        env: &druid::Env,
    ) {
        if let Event::Command(e) = event {
            println!("Command Event");
            data.data_rev += e.get_unchecked(REV_CHAR).data.as_ref();
        }
        child.event(ctx, event, data, env)
    }

    fn lifecycle(
        &mut self,
        child: &mut W,
        ctx: &mut druid::LifeCycleCtx,
        event: &druid::LifeCycle,
        data: &DataText,
        env: &druid::Env,
    ) {
        child.lifecycle(ctx, event, data, env)
    }

    fn update(
        &mut self,
        child: &mut W,
        ctx: &mut druid::UpdateCtx,
        old_data: &DataText,
        data: &DataText,
        env: &druid::Env,
    ) {
        child.update(ctx, old_data, data, env)
    }
}

fn buid_root() -> impl Widget<DataText> {
    let s1 = lens!(DataText, data);
    let s2 = lens!(DataText, data_rev);
    let text_box_up = TextBox::multiline()
        .lens(s1)
        .controller(TextBoxController {});
    let text_box_down = TextBox::multiline().lens(s2);

    // 发送
    let clear_btn = Button::new("Send").on_click(|_ctx, data: &mut DataText, _env| {
        _ctx.submit_command(REV_CHAR.with(data.clone()));
        data.data = "".to_string();
    });

    Flex::column()
        .with_flex_child(text_box_up.expand(), 1.0)
        .with_flex_child(text_box_down.expand(), 1.0)
        .with_child(
            Flex::row()
                .with_flex_child(SizedBox::empty().fix_height(20.0).expand_width(), 1.0)
                .with_child(clear_btn),
        )
}

fn main() {
    let m = DataText {
        data: "Hello".to_string(),
        data_rev: "".to_string(),
    };
    let w = WindowDesc::new(buid_root()).window_size(Size::new(400.0, 400.0));
    AppLauncher::with_window(w)
        .log_to_console()
        .launch(m)
        .unwrap();
}

跨线程通信

定义一个TextBox和一个Button, 当Button 点击时, 启动线程向界面发送信息。

use std::{sync::{Arc, mpsc::{self, Receiver, Sender}}, thread::{self, Thread}, time::{Duration, SystemTime}};

use druid::{Command, ExtEventSink, Target, WidgetExt, image::imageops::FilterType::Lanczos3};
use druid::{
    lens, text,
    widget::{Button, Controller, SizedBox},
    Event, Selector,
};
use druid::{
    widget::{Flex, TextBox},
    AppLauncher, Data, Size, Widget, WindowDesc,
};

#[derive(Data, Clone, Debug)]
pub struct DataText {
    pub data: String,
}

// 定义消息类型
const ADD_MSG : Selector<DataText> = Selector::new("com.text.add");

struct TextBoxController;

// 定义控制器, 在控制器中处理Command
impl<W: Widget<DataText>> Controller<DataText, W> for TextBoxController {
    fn event(
        &mut self,
        child: &mut W,
        ctx: &mut druid::EventCtx,
        event: &druid::Event,
        data: &mut DataText,
        env: &druid::Env,
    ) {
        if let Event::Command(e) = event {
            println!("Command Event");
            data.data += e.get_unchecked(ADD_MSG).data.as_ref();
        }
        child.event(ctx, event, data, env)
    }

    fn lifecycle(
        &mut self,
        child: &mut W,
        ctx: &mut druid::LifeCycleCtx,
        event: &druid::LifeCycle,
        data: &DataText,
        env: &druid::Env,
    ) {
        child.lifecycle(ctx, event, data, env)
    }

    fn update(
        &mut self,
        child: &mut W,
        ctx: &mut druid::UpdateCtx,
        old_data: &DataText,
        data: &DataText,
        env: &druid::Env,
    ) {
        child.update(ctx, old_data, data, env)
    }
}

// 外部线程调用函数
fn event_thread(sink: ExtEventSink, rx: mpsc::Receiver<bool>){
    let mut runing = false;
    loop {
        match rx.try_recv() {
            Ok(true) | Err(mpsc::TryRecvError::Disconnected) =>{
                runing = !runing;
            }
            _=> {}
        }
        thread::sleep(Duration::from_millis(500));
        if runing == false {
            continue;
        }
        let d = DataText {
            data: "Hi\n".to_string()
        };
        // 向窗体线程发送消息
        sink.submit_command(ADD_MSG, d, Target::Auto).expect("Sumbmit Error");
        println!("Send Msg");
    }
}

fn buid_root (tx:Arc<Sender<bool>>) -> impl Widget<DataText> {
    let s1 = lens!(DataText, data);
    let text_box = TextBox::multiline()
        .lens(s1)
        .controller(TextBoxController {});
    // 发送消息
    let clear_btn = Button::new("Send")
    .on_click(move |_ctx, data: &mut DataText, _env| {
        tx.send(true).expect("Send Fail!");
    });

    Flex::column()
        .with_flex_child(text_box.expand(), 1.0)
        .with_child(
            Flex::row()
                .with_flex_child(SizedBox::empty().fix_height(20.0).expand_width(), 1.0)
                .with_child(clear_btn),
        )
}

fn main() {
    let m = DataText {
        data: "".to_string(),
    };

    let (tx , rx) : (Sender<bool>, Receiver<bool>) = mpsc::channel();
    let w = WindowDesc::new(buid_root(Arc::new(tx))).window_size(Size::new(400.0, 400.0));
    let launcher = AppLauncher::with_window(w)
        .log_to_console();
    let handler = launcher.get_external_handle();
	
    thread::spawn(move || {
        event_thread(handler, rx);
    });

    launcher.launch(m).unwrap();
}

Ps:Druid是数据驱动的GUI框架, 所以上述的例子完全可以使用修改Data来实现。 例子的目的主要关注Selector的使用场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 实践
    • 窗体之间通信
      • 跨线程通信
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档