Rust Druid 之Selector选择器使用

2021-05-25 10:27:06 浏览数 (1)

概念

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

总体:

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

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

查看 application.rs 源码:

代码语言:javascript复制
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。

代码语言:javascript复制
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 点击时, 启动线程向界面发送信息。

代码语言:javascript复制
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: "Hin".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的使用场景。

0 人点赞