Rust UI开发(五):iced中如何进行页面布局(pick_list的使用)?(串口调试助手)

注:此文适合于对rust有一些了解的朋友

iced是一个跨平台的GUI库,用于为rust语言程序构建UI界面。

这是一个系列博文,本文是第五篇,前四篇链接:

1、Rust UI开发(一):使用iced构建UI时,如何在界面显示中文字符

2、Rust UI开发(二):iced中如何为窗口添加icon图标

3、Rust UI开发(三):iced如何打开图片(对话框)并在窗口显示图片?

4、Rust UI开发(四):iced中如何添加菜单栏(串口调试助手)

本篇是系列第五篇,本篇主要说明如何制作关于"串口调试助手"的界面布局,包括菜单栏的创建、UI主界面picklist的使用、以及如何排布。

实际效果预览:

界面分为两个部分,一个是菜单栏,暂时设置了四个主菜单项:

1、文件:新建、打开、保存、关闭

2、通讯:获取端口、连接、断开、参数

3、工具:CRC16、字符转换

4、关于:帮助、检测更新、层级(用于测试)

虽然菜单的子项功能不一定会用到,但作为一个演示功能会添加以上菜单项,其中一些菜单项会在后续篇章中赋予实际功能。

二是串口通讯的参数设置及收发数据部件:

以上部分主要由文本、按钮、下拉列表框(pic-klist)组合实现。

cargo.toml

依赖部分:

rust 复制代码
[dependencies]
iced.workspace=true
iced.features=["image","svg"]
iced_aw={ workspace=true, features = ["card","menu","quad","icon_text"] }
image.workspace=true
num-complex.workspace=true
serialport.workspace=true

[workspace.dependencies]
iced = "0.10"
iced_aw={ version = "0.7.0", default-features = false }
image="0.24.7"
num-complex="0.4.4"
serialport="4.2.2"

项目文件结构:

页面布局

一 菜单布局

先说菜单,我在上篇博文中已经说明了如何创建菜单栏,是使用iced-aw库,但上篇中只是简单说明了如何创建,并没有针对性的创建实用菜单。所以,本篇将会按照实际布局来创建菜单。

先简要回顾一下菜单的创建,是使用menu_bar和menu_tree!两个方法和menu_tree函数,来实现菜单和子菜单的创建及组合,以及对其样式进行设置(后续篇章说明)。

官方的menu示例其实是非常好的参考,但是在实际使用时,会觉得有所不便,所以,我在上篇博文中也介绍了通过创建自定义函数来创建菜单,好处是函数的参数可以自己调整。

本篇中,我们继续创建自己的函数,综合来说,将会有3个菜单函数,分别是menu_main、menu_sub和menu_sub_sub,其中:

menu_main:用于创建一个主菜单

menu_sub:用于创建一个子菜单,但没有下级菜单

menu_sub_sub:用于创建一个子菜单,可以附加下级菜单

这样做的好处是,可以随意定义菜单项,是否需要子菜单,组合使用即可。
menu_main函数:

rust 复制代码
///创建一个主菜单
fn menu_main<'a>(label:&str,
    msg: Message,
    children: Vec<MenuTree<'a, Message, iced::Renderer>>,
)->MenuTree<'a,Message,iced::Renderer>{
    menu_tree(
        debug_button(label,msg),
        children,
    )
}

menu_main函数设置了3个参数,label是菜单文本,msg是菜单触发后传递的消息,children是包含的子菜单项。

menu_sub函数:

rust 复制代码
///创建一个子菜单(无sub)
fn menu_sub<'a>(label:&str,
    msg:Message,
)->MenuTree<'a,Message,iced::Renderer>{
    menu_tree!(
        base_button(text(label)
        .width(Length::Fill)
        .height(Length::Fill)
        .vertical_alignment(alignment::Vertical::Center),msg))
      }

menu_sub设置了2个参数,label是菜单文本,msg是菜单触发传递的消息值。

menu_sub_sub函数:

rust 复制代码
///创建一个子菜单(带sub)
fn menu_sub_sub<'a>(label:&str,
    msg: Message,
    children: Vec<MenuTree<'a, Message, iced::Renderer>>,
)->MenuTree<'a, Message, iced::Renderer>{
    let handle = svg::Handle::from_path("../iced_test/img/caret-right-fill.svg");
        let arrow = svg(handle)
            .width(Length::Shrink)
            .style(theme::Svg::custom_fn(|theme| svg::Appearance {
                color: Some(theme.extended_palette().background.base.text),
            }));
    
        menu_tree(
            base_button(
                row![
                    text(label)
                        .width(Length::Fill)
                        .height(Length::Fill)
                        .vertical_alignment(alignment::Vertical::Center),
                    arrow
                ]
                .align_items(iced::Alignment::Center),
                msg,
            )
            .width(Length::Fill)
            .height(Length::Fill),
            children,
        )
    }

menu_sub_sub函数设置了3个参数,分别是label、msg、children,这和主菜单函数很像,只是内部代码稍有不同,这里的内部函数,是参考的官方示例,有子菜单的子菜单会有一个箭头图标。

利用以上三个函数,就可以创建自定义的菜单项了。以本篇中文件菜单项为例,看一下其创建代码:

rust 复制代码
//文件菜单
        let menu_wj_sub1=menu_sub("新建",
        Message::MenuXiaoxi(MenuXiaoxi::WjFile)); 
        let menu_wj_sub2=menu_sub("打开",
        Message::MenuXiaoxi(MenuXiaoxi::WjOpen));
        let menu_wj_sub3=menu_sub("保存",
        Message::MenuXiaoxi(MenuXiaoxi::WjSave));
        let menu_wj_sub4=menu_sub("关闭",
        Message::MenuXiaoxi(MenuXiaoxi::WjClose));  
        let menu_wj_main=menu_main("文件", 
        Message::MenuXiaoxi(MenuXiaoxi::WjMain),
        vec![menu_wj_sub1,menu_wj_sub2,menu_wj_sub3,menu_wj_sub4]);  

子菜单的顺序可以调整,然后按照你的顺序加入主菜单函数中即可。

二 picklist布局

一般串口调试助手中,设置波特率、数据位等串口参数,都是用下拉列表框来选择参数。下拉列表框一般是combobox,但iced-aw中的combobox稍有不同,所以这里我们选择pick-list部件来构建参数选择UI。

与创建菜单类似,因为参数设置的UI也是比较整齐的文本框+picklist部件,所以,我们也使用自定义函数,以便于构建单个可重复布局。

针对于波特率、数据位等单个项,我们创建serial_item函数:

rust 复制代码
///用于串口行添加,采用row布局
fn serial_item<'a>(label:&str,
                option:impl Into<Cow<'a,[Baudrate]>>,
                selected:Option<Baudrate>,
                on_selected:impl Fn(Baudrate)->Message+'a,
)->Row<'a,Message>{
    row!(
        text(label).size(16),
        pick_list(option,selected,on_selected)
        .placeholder(label)
        .width(100)
        .text_size(15)
    ).spacing(10)
}

可以看到,其实也很简单,就是包括一个文本框和picklist,然后参数中设置了label、option、selected、on_selected,主要用于针对每个项进行单独设置。这里,label是用于文本的设置,而后三个参数,是pick-list的参数:

option:如波特率的9600、19200这些预设项,用于picklist下拉显示。

selected:当选择一项时,将值传给此参数

on_selected:消息参数,pick-list选择时,触发消息,用于更新函数

然后我们为所有项创建一个serial_group函数:

rust 复制代码
///serial group 布局,
///将同一个布局集中在一个函数中,
fn serial_group<'a>(_app:&Counter)-> Column<'a,Message>{

    column![
            serial_item("端口   :",&Baudrate::PORT[..],_app.selected_port,Message::SelectedPort),       
            serial_item("波特率:",&Baudrate::BAUD[..],_app.selected_baudrate,Message::SelectedBaud),
            serial_item("数据位:",&Baudrate::DATABIT[..],_app.selected_databit,Message::SelectedDatabit),
            serial_item("校验位:",&Baudrate::PABIT[..],_app.selected_pabit,Message::SelectedPabit),
            serial_item("停止位:",&Baudrate::STOPBIT[..],_app.selected_stopbit,Message::SelectedStopbit),                                         
        ]
        .spacing(10)
        .padding(4)
        .align_items(Alignment::Start)
        .into()

}

正如函数的注释所说,serial_group函数只是为了将类似的项集中在一起布局。方便和其他部件在同一个界面上时,使程序结构看起来更加清晰。

基本上,以上两部分就能够实现整个UI的创建了,当然,现在看起来整个界面比较朴素和简单,那是因为并没有对其进行美化,这不是现在的重点。在实现基本的通信功能之前,UI界面将一直保持这种朴素。

将布局设置好后,可以在view函数里使其显示:

rust 复制代码
fn view(&self) -> Element<Message> {    
        //文件菜单
        let menu_wj_sub1=menu_sub("新建",
        Message::MenuXiaoxi(MenuXiaoxi::WjFile)); 
        let menu_wj_sub2=menu_sub("打开",
        Message::MenuXiaoxi(MenuXiaoxi::WjOpen));
        let menu_wj_sub3=menu_sub("保存",
        Message::MenuXiaoxi(MenuXiaoxi::WjSave));
        let menu_wj_sub4=menu_sub("关闭",
        Message::MenuXiaoxi(MenuXiaoxi::WjClose));  
        let menu_wj_main=menu_main("文件", 
        Message::MenuXiaoxi(MenuXiaoxi::WjMain),
        vec![menu_wj_sub1,menu_wj_sub2,menu_wj_sub3,menu_wj_sub4], self);  
        //通讯菜单
        let menu_tx_sub1=menu_sub("参数",
        Message::MenuXiaoxi(MenuXiaoxi::TxParam));
        let menu_tx_sub2=menu_sub("连接",
        Message::MenuXiaoxi(MenuXiaoxi::TxConnect));  
        let menu_tx_sub3=menu_sub("断开",
        Message::MenuXiaoxi(MenuXiaoxi::TxDisconenct));
        let menu_tx_sub4=menu_sub("获取端口",
    Message::MenuXiaoxi(MenuXiaoxi::TxConnect));
        let menu_tx_main=menu_main("通讯", 
        Message::MenuXiaoxi(MenuXiaoxi::TxMain),
        vec![menu_tx_sub4,menu_tx_sub2,menu_tx_sub3,menu_tx_sub1], self);
        //工具菜单
        let menu_gj_sub1=menu_sub("CRC16",
        Message::MenuXiaoxi(MenuXiaoxi::GjCRC));
        let menu_gj_sub2=menu_sub("字符转换",
        Message::MenuXiaoxi(MenuXiaoxi::GjStrConvert));
        let menu_gj_main=menu_main("工具",
        Message::MenuXiaoxi(MenuXiaoxi::GjMain),
         vec![menu_gj_sub1,menu_gj_sub2], self);
        //测试菜单
        let menu_cs_subsub1=menu_sub("层级3",
        Message::Showtext);
        let menu_cs_sub1=menu_sub_sub("层级2",
        Message::Showtext,vec![menu_cs_subsub1]);
        let menu_cs_sub2=menu_sub_sub("层级1",
        Message::Showtext,vec![menu_cs_sub1]);
        //帮助菜单
        let menu_bz_sub1=menu_sub("帮助",
        Message::MenuXiaoxi(MenuXiaoxi::BzHelper));
        let menu_bz_sub2=menu_sub("检查更新",
        Message::MenuXiaoxi(MenuXiaoxi::BzAbout));
        let menu_bz_main=menu_main("关于", 
        Message::MenuXiaoxi(MenuXiaoxi::BzMain),
        vec![menu_bz_sub1,menu_bz_sub2,menu_cs_sub2], self);
        let mb=menu_bar!(menu_wj_main,menu_tx_main,menu_gj_main,
            menu_bz_main);
        let sg= column![
            serial_group(self),
            row![
                button(text("连接").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                button(text("断开").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                //button("获取端口").on_press(Message::Showtext),
                
            ].spacing(20),
            //text(format!("当前所选菜单是:{:?}",self.value)).size(20),
            //text(format!("菜单消息是:{:?}",self.menu_xiaoxi)).size(20), 
                ]
                .spacing(20)
                .padding(6);
        let sg2=  column![
                text("接收数据:").size(20),
                text_input("发送数据:",&self.value3).width(200)
                .on_input(Message::InputChanged)
                .padding(6),
                text("发送数据:").size(20),
                text_input("发送数据:",&self.value3).width(200)
                .on_input(Message::InputChanged)
                .padding(6),
                    ].spacing(20);

        column![         
                    mb,
                    row![
                        sg,      
                        sg2,].spacing(10)
                ].spacing(20)
                .padding(1)
                .into() 
    }

view函数看起来可能代码比较多,是因为增加了菜单项的代码,后续,这些菜单的设置可以再用函数来集中,但目前暂时按照这样来。

到目前为止,我们在程序中所涉及的数据并没有详细说明,这些将在后续一起说明,本篇主要还是说明部件的布局,这其中,可以注意到,在view函数里,用于显示的布局,采用的是column和row两种布局方法,column就纵向布局,row就是横行布局。

以row布局举例,如下,row![...]方法中,添加部件,也可以嵌套布局,即column和row互相嵌套,或者重复嵌套都可以,前提是你先想好自己的UI上,各个部件的大致位置,是纵向还是横向,哪些可以组合在一起设置,哪些需要分开设置,等等,

rust 复制代码
 row![
                button(text("连接").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                button(text("断开").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                //button("获取端口").on_press(Message::Showtext),
                
            ].spacing(20)

除了将部件直接添加其中,还可以是push功能添加部件。比如这样:

rust 复制代码
let c1=column![];
c1.push(text("hello"))

嵌套使用:

rust 复制代码
 column![         
                    mb,
                    row![
                        sg,      
                        sg2,].spacing(10)
                ].spacing(20)
                .padding(1)
                .into() 

要说明的是,iced的布局方面,我个人认为现在版本还不是很方便,这当然是因为iced库本身就是在发展中的,并非是成熟稳定的GUI库。

动态演示:

完整代码:
完整代码里有一些另外的数据和函数,是本篇未提及的,但不影响本篇内容的测试。另外,所有代码都是测试中的程序,所以可能会不够干净清晰。

rust 复制代码
use iced::widget::{button, column,row, text,text_input,combo_box,pick_list,svg, container};
use iced::{Alignment,theme, Element, Color,Sandbox,Length, Settings, alignment};
use iced::widget::{Column,Row};
use iced::Font;
use iced::font::Family;
use iced::window;
use iced::window::icon;
use std::borrow::Cow;

use iced_aw::menu::{menu_tree::MenuTree, CloseCondition, ItemHeight, ItemWidth, PathHighlight};
use iced_aw::quad;
use iced_aw::{helpers::menu_tree, menu_bar, menu_tree};

use serialport::{available_ports, SerialPortType};

extern  crate image;
extern crate num_complex;

pub fn main() -> iced::Result {
    //Counter::run(Settings::default())             //此处为使用默认窗口设置 
    let ff="微软雅黑";                  //设置自定义字体

    //第二种获取rgba图片的方法,利用Image库
    let img2=image::open("../iced_ser/img/dota22.png");
    let img2_path=match  img2 {
        Ok(path)=>path,
        Err(error)=>panic!("error is {}",error),
    };
    let img2_file=img2_path.to_rgba8();
    let ico2=icon::from_rgba(img2_file.to_vec(), 64, 64);
    let ico2_file=match ico2{
        Ok(file)=>file,
        Err(error)=>panic!("error is {}",error),
    };
    Counter::run(Settings { 
        window:window::Settings{                    //设置自定义窗口尺寸
            size:(800,600),
            //icon:Some(ico_file),
            icon:Some(ico2_file),
            ..window::Settings::default()
        },
        default_font:Font{                          //设置自定义字体,用于显示中文字符
            family:Family::Name(ff),
            ..Font::DEFAULT},
        ..Settings::default()
    })
}
//创建结构体struct
struct Counter{
    value: String,
    value2:String,
    value3:String,
    baudrates:combo_box::State<Baudrate>,
    selected_port:Option<Baudrate>,
    selected_baudrate:Option<Baudrate>,
    selected_databit:Option<Baudrate>,
    selected_pabit:Option<Baudrate>,
    selected_stopbit:Option<Baudrate>,
    text:String,
    menu_xiaoxi:MenuXiaoxi,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MenuXiaoxi{
    //文件菜单
    WjMain,
    WjFile,
    WjOpen,
    WjSave,
    WjClose,
    //通讯菜单
    TxMain,
    TxGetPort,
    TxParam,
    TxConnect,
    TxDisconenct,
    //工具菜单
    GjMain,
    GjCRC,
    GjStrConvert,
    //帮助菜单
    BzMain,
    BzHelper,
    BzAbout,
    //无消息
    Nomenuxx,
}

impl MenuXiaoxi {
    const WJ: [MenuXiaoxi; 5] = [
        MenuXiaoxi::WjMain,
        MenuXiaoxi::WjFile,
        MenuXiaoxi::WjOpen,
        MenuXiaoxi::WjSave,
        MenuXiaoxi::WjClose,
    ];
    const TX:[MenuXiaoxi;5]=[
        MenuXiaoxi::TxMain,
        MenuXiaoxi::TxGetPort,
        MenuXiaoxi::TxParam,
        MenuXiaoxi::TxConnect,
        MenuXiaoxi::TxDisconenct,
    ];
    const GJ:[MenuXiaoxi;3]=[
        MenuXiaoxi::GjMain,
        MenuXiaoxi::GjCRC,
        MenuXiaoxi::GjStrConvert,
    ];
    const BZ:[MenuXiaoxi;3]=[
        MenuXiaoxi::BzMain,
        MenuXiaoxi::BzHelper,
        MenuXiaoxi::BzAbout,
    ];
    const NM:[MenuXiaoxi;1]=[
        MenuXiaoxi::Nomenuxx,
    ];
}
impl std::fmt::Display for MenuXiaoxi {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::WjMain=>"wenjian",
                Self::WjFile => "file",
                Self::WjOpen => "open",
                Self::WjSave => "save",
                Self::WjClose=>"close",
                //
                Self::TxMain=>"tongxun",
                Self::TxGetPort=>"getport",
                Self::TxParam=>"parameter",
                Self::TxConnect=>"connect",
                Self::TxDisconenct=>"disconenct",
                //
                Self::GjMain=>"gongjv",
                Self::GjCRC=>"crc16",
                Self::GjStrConvert=>"字符转换",
                //
                Self::BzMain=>"bangzhu",
                Self::BzHelper=>"helper",
                Self::BzAbout=>"about",
                //
                Self::Nomenuxx=>"no"
            }
        )
    }
}


#[derive(Debug, Clone)]           //为下方的enum添加特性trait
enum Message {
    MenuXiaoxi(MenuXiaoxi),
    Showtext,
    InputChanged(String),
    SelectedPort(Baudrate),
    SelectedBaud(Baudrate),
    SelectedDatabit(Baudrate),
    SelectedPabit(Baudrate),
    SelectedStopbit(Baudrate),
    OptionHovered(Baudrate),
    Closed,
}

//sandbox是一个trait
impl Sandbox for Counter {             //impl将sandbox添加给Counter,使Counter具有了sandbox的一些特性
    type Message = Message;
    fn new() -> Self {                  //初始化sandbox,返回初始值
        Self { 
            value: String::new(),
            value2:String::new(),
            value3:String::new(),
            baudrates: combo_box::State::new(Baudrate::BAUD.to_vec()),
            selected_port:None,
            selected_baudrate: None,
            selected_databit:None,
            selected_pabit:None,
            selected_stopbit:None,
            text: String::new(),
            menu_xiaoxi:MenuXiaoxi::Nomenuxx,
        }
    }

    fn title(&self) -> String {         //返回sandbox的标题
        String::from("iced_串口助手")
        //self.value.clone()
    }

    fn update(&mut self, message: Message) {        //此处书写更新逻辑程序,所有UI交互会在这里处理
       
        match message {
            Message::MenuXiaoxi(mxx)=>{
                self.menu_xiaoxi=mxx;
            }
            Message::Showtext=> { 

                let ss=&self.value;         //新建一个value的引用
                self.value2=ss.to_string();          //将变量的引用字符化后传给value2
                    
            }
            Message::InputChanged(value) =>{
                self.value3=value;
                //get_serialport_list();
                let mb=match self.menu_xiaoxi{
                    MenuXiaoxi::BzMain=>{
                        self.value="bangzhu".to_string();
                    }
                    MenuXiaoxi::BzHelper=>{
                        self.value="Bangzhu".to_string();
                    }
                    MenuXiaoxi::BzAbout=>{
                        self.value="guanyu".to_string();
                    }
                    MenuXiaoxi::GjMain=>{
                        self.value="gongjv".to_string();
                    }
                    MenuXiaoxi::GjCRC=>{
                        self.value="crc16".to_string();
                    }
                    MenuXiaoxi::GjStrConvert=>{
                        self.value="zifu".to_string();
                    }
                    MenuXiaoxi::Nomenuxx=>{
                        self.value="no xiaoxi".to_string();
                    }
                    MenuXiaoxi::TxMain=>{
                        self.value="tongxun".to_string();
                    }
                    MenuXiaoxi::TxGetPort=>{
                        self.value="getport".to_string();
                    }
                    MenuXiaoxi::TxParam=>{
                        self.value="param".to_string();
                    }
                    MenuXiaoxi::TxConnect=>{
                        self.value="conn".to_string();
                    }
                    MenuXiaoxi::TxDisconenct=>{
                        self.value="disc".to_string();
                    }
                    MenuXiaoxi::WjMain=>{
                        self.value="wenjian".to_string();
                    }
                    MenuXiaoxi::WjFile=>{
                        self.value="file".to_string();
                    }
                    MenuXiaoxi::WjOpen=>{
                        self.value="open".to_string();
                    }
                    MenuXiaoxi::WjSave=>{
                        self.value="save".to_string();
                    }
                    MenuXiaoxi::WjClose=>{
                        self.value="close".to_string();
                    }
                };
            }
            Message::SelectedPort(port)=>{
                self.selected_port=Some(port);
            }
            Message::SelectedBaud(baudrate)=>{
                self.selected_baudrate=Some(baudrate);
                self.text=baudrate.hello().to_string();
            }
            Message::SelectedDatabit(databit)=>{
                self.selected_databit=Some(databit);
            }
            Message::SelectedPabit(pabit)=>{
                self.selected_pabit=Some(pabit);
            }
            Message::SelectedStopbit(stopbit)=>{
                self.selected_stopbit=Some(stopbit);
            }
            Message::OptionHovered(baudrate)=>{
                self.text=baudrate.hello().to_string();
            }
            Message::Closed=>{
                self.text = self.selected_baudrate.map(|baudrate| baudrate.hello().to_string())
                                                    .unwrap_or_default();
            }
                
        }
    }

    fn view(&self) -> Element<Message> {    
        //文件菜单
        let menu_wj_sub1=menu_sub("新建",
        Message::MenuXiaoxi(MenuXiaoxi::WjFile)); 
        let menu_wj_sub2=menu_sub("打开",
        Message::MenuXiaoxi(MenuXiaoxi::WjOpen));
        let menu_wj_sub3=menu_sub("保存",
        Message::MenuXiaoxi(MenuXiaoxi::WjSave));
        let menu_wj_sub4=menu_sub("关闭",
        Message::MenuXiaoxi(MenuXiaoxi::WjClose));  
        let menu_wj_main=menu_main("文件", 
        Message::MenuXiaoxi(MenuXiaoxi::WjMain),
        vec![menu_wj_sub1,menu_wj_sub2,menu_wj_sub3,menu_wj_sub4], self);  
        //通讯菜单
        let menu_tx_sub1=menu_sub("参数",
        Message::MenuXiaoxi(MenuXiaoxi::TxParam));
        let menu_tx_sub2=menu_sub("连接",
        Message::MenuXiaoxi(MenuXiaoxi::TxConnect));  
        let menu_tx_sub3=menu_sub("断开",
        Message::MenuXiaoxi(MenuXiaoxi::TxDisconenct));
        let menu_tx_sub4=menu_sub("获取端口",
    Message::MenuXiaoxi(MenuXiaoxi::TxConnect));
        let menu_tx_main=menu_main("通讯", 
        Message::MenuXiaoxi(MenuXiaoxi::TxMain),
        vec![menu_tx_sub4,menu_tx_sub2,menu_tx_sub3,menu_tx_sub1], self);
        //工具菜单
        let menu_gj_sub1=menu_sub("CRC16",
        Message::MenuXiaoxi(MenuXiaoxi::GjCRC));
        let menu_gj_sub2=menu_sub("字符转换",
        Message::MenuXiaoxi(MenuXiaoxi::GjStrConvert));
        let menu_gj_main=menu_main("工具",
        Message::MenuXiaoxi(MenuXiaoxi::GjMain),
         vec![menu_gj_sub1,menu_gj_sub2], self);
        //测试菜单
        let menu_cs_subsub1=menu_sub("层级3",
        Message::Showtext);
        let menu_cs_sub1=menu_sub_sub("层级2",
        Message::Showtext,vec![menu_cs_subsub1]);
        let menu_cs_sub2=menu_sub_sub("层级1",
        Message::Showtext,vec![menu_cs_sub1]);
        //帮助菜单
        let menu_bz_sub1=menu_sub("帮助",
        Message::MenuXiaoxi(MenuXiaoxi::BzHelper));
        let menu_bz_sub2=menu_sub("检查更新",
        Message::MenuXiaoxi(MenuXiaoxi::BzAbout));
        let menu_bz_main=menu_main("关于", 
        Message::MenuXiaoxi(MenuXiaoxi::BzMain),
        vec![menu_bz_sub1,menu_bz_sub2,menu_cs_sub2], self);
        let mb=menu_bar!(menu_wj_main,menu_tx_main,menu_gj_main,
            menu_bz_main);
        let sg= column![
            serial_group(self),
            row![
                button(text("连接").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                button(text("断开").horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center))
                .width(80).height(40)
                .on_press(Message::Showtext),
                //button("获取端口").on_press(Message::Showtext),
                
            ].spacing(20),
            //text(format!("当前所选菜单是:{:?}",self.value)).size(20),
            //text(format!("菜单消息是:{:?}",self.menu_xiaoxi)).size(20), 
                ]
                .spacing(20)
                .padding(6);
        let sg2=  column![
                text("接收数据:").size(20),
                text_input("发送数据:",&self.value3).width(200)
                .on_input(Message::InputChanged)
                .padding(6),
                text("发送数据:").size(20),
                text_input("发送数据:",&self.value3).width(200)
                .on_input(Message::InputChanged)
                .padding(6),
                    ].spacing(20);

        column![         
                    mb,
                    row![
                        sg,      
                        sg2,].spacing(10)
                ].spacing(20)
                .padding(1)
                .into() 
    }

}

///用于串口行添加,采用row布局
fn serial_item<'a>(label:&str,
                option:impl Into<Cow<'a,[Baudrate]>>,
                selected:Option<Baudrate>,
                on_selected:impl Fn(Baudrate)->Message+'a,
)->Row<'a,Message>{
    row!(
        text(label).size(16),
        pick_list(option,selected,on_selected)
        .placeholder(label)
        .width(100)
        .text_size(15)
    ).spacing(10)
}
///serial group 布局,
///将同一个布局集中在一个函数中,
fn serial_group<'a>(_app:&Counter)-> Column<'a,Message>{

    column![
            serial_item("端口   :",&Baudrate::PORT[..],_app.selected_port,Message::SelectedPort),       
            serial_item("波特率:",&Baudrate::BAUD[..],_app.selected_baudrate,Message::SelectedBaud),
            serial_item("数据位:",&Baudrate::DATABIT[..],_app.selected_databit,Message::SelectedDatabit),
            serial_item("校验位:",&Baudrate::PABIT[..],_app.selected_pabit,Message::SelectedPabit),
            serial_item("停止位:",&Baudrate::STOPBIT[..],_app.selected_stopbit,Message::SelectedStopbit),                                         
        ]
        .spacing(10)
        .padding(4)
        .align_items(Alignment::Start)
        .into()

}

///自定义按钮样式,用于菜单栏使用
struct ButtonStyle;
//让ButtonStyle实现button的StyleSheet功能
impl button::StyleSheet for ButtonStyle {
    type Style = iced::Theme;

    //生成按钮的激活外观
    fn active(&self, style: &Self::Style) -> button::Appearance {
        button::Appearance {
            text_color: style.extended_palette().background.base.text,
            border_radius: [2.0; 4].into(),
            background: Some(Color::TRANSPARENT.into()),
            ..Default::default()
        }
    }

    //生成按钮的悬停外观
    fn hovered(&self, style: &Self::Style) -> button::Appearance {
        let plt = style.extended_palette();

        button::Appearance {
            background: Some(plt.primary.weak.color.into()),
            text_color: plt.primary.weak.text,
            border_radius:[6.0; 4].into(),                      //border_radius:四角倒圆半径
            ..self.active(style)
        }
    }
}

//基础按钮
fn base_button<'a>(
    content: impl Into<Element<'a, Message, iced::Renderer>>,
    msg: Message,
) -> button::Button<'a, Message, iced::Renderer> {
    button(content)
        .padding([4, 8])
        .style(iced::theme::Button::Custom(Box::new(ButtonStyle {})))
        .on_press(msg)
}
//带标签按钮
fn labeled_button<'a>(label: &str, msg: Message) -> button::Button<'a, Message, iced::Renderer> {
    base_button(
        text(label)
            .width(Length::Fill)
            .height(Length::Fill)
            .vertical_alignment(alignment::Vertical::Center),
        msg,
    )
}

//测试按钮
fn debug_button<'a>(label: &str,msg: Message) -> button::Button<'a, Message, iced::Renderer> {
    labeled_button(label,msg)
}

///创建一个子菜单(无sub)
fn menu_sub<'a>(label:&str,
    msg:Message,
)->MenuTree<'a,Message,iced::Renderer>{
    menu_tree!(
        base_button(text(label)
        .width(Length::Fill)
        .height(Length::Fill)
        .vertical_alignment(alignment::Vertical::Center),msg))
      }
///创建一个子菜单(带sub)
fn menu_sub_sub<'a>(label:&str,
    msg: Message,
    children: Vec<MenuTree<'a, Message, iced::Renderer>>,
)->MenuTree<'a, Message, iced::Renderer>{
    let handle = svg::Handle::from_path("../iced_ser/img/caret-right-fill.svg");
        let arrow = svg(handle)
            .width(Length::Shrink)
            .style(theme::Svg::custom_fn(|theme| svg::Appearance {
                color: Some(theme.extended_palette().background.base.text),
            }));
    
        menu_tree(
            base_button(
                row![
                    text(label)
                        .width(Length::Fill)
                        .height(Length::Fill)
                        .vertical_alignment(alignment::Vertical::Center),
                    arrow
                ]
                .align_items(iced::Alignment::Center),
                msg,
            )
            .width(Length::Fill)
            .height(Length::Fill),
            children,
        )
    }


///创建一个主菜单
fn menu_main<'a>(label:&str,
    msg: Message,
    children: Vec<MenuTree<'a, Message, iced::Renderer>>,
    _app:&Counter
)->MenuTree<'a,Message,iced::Renderer>{
    menu_tree(
        debug_button(label,msg),
        children,
    )
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Baudrate{
    //port
    PortCOM0,
    //baudrate
    Rate9600,
    #[default]
    Rate19200,
    Rate38400,
    Rate57600,
    Rate115200,
    //databit
    Databit8,
    Databit7,
    Databit6,
    Databit5,
    //paritybit
    Pabitnone,
    Pabiteven,
    Pabitodd,
    Pabitspace,
    Pabitmark,
    //stopbit
    Stopbit1,
    Stopbit1dot5,
    Stopbit2,
}
impl Baudrate {
    const PORT:[Baudrate;1]=[
        Baudrate::PortCOM0,
    ];
    const BAUD: [Baudrate; 5] = [
        Baudrate::Rate9600,
        Baudrate::Rate19200,
        Baudrate::Rate38400,
        Baudrate::Rate57600,
        Baudrate::Rate115200,
    ];
    const DATABIT:[Baudrate;4]=[
        Baudrate::Databit8,
        Baudrate::Databit7,
        Baudrate::Databit6,
        Baudrate::Databit5,

    ];
    const PABIT:[Baudrate;5]=[
        Baudrate::Pabitnone,
        Baudrate::Pabiteven,
        Baudrate::Pabitodd,
        Baudrate::Pabitspace,
        Baudrate::Pabitmark,
    ];
    const STOPBIT:[Baudrate;3]=[
        Baudrate::Stopbit1,
        Baudrate::Stopbit1dot5,
        Baudrate::Stopbit2,
    ];

    fn hello(&self) -> &str {
        match self {

            Baudrate::PortCOM0=>"COM0",

            Baudrate::Rate9600 => "9600",
            Baudrate::Rate19200 => "9600",
            Baudrate::Rate38400=> "9600",
            Baudrate::Rate57600 => "9600",
            Baudrate::Rate115200 => "9600",
        
            Baudrate::Databit8=>"8",
            Baudrate::Databit7=>"7",
            Baudrate::Databit6=>"6",
            Baudrate::Databit5=>"5",

            Baudrate::Pabitnone=>"None",
            Baudrate::Pabiteven=>"Even",
            Baudrate::Pabitodd=>"Odd",
            Baudrate::Pabitspace=>"Space",
            Baudrate::Pabitmark=>"Mark",

            Baudrate::Stopbit1=>"1",
            Baudrate::Stopbit1dot5=>"1.5",
            Baudrate::Stopbit2=>"2",

        }
    }
}
impl std::fmt::Display for Baudrate {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {

                Baudrate::PortCOM0=>"COM0",

                Baudrate::Rate9600 => "9600",
                Baudrate::Rate19200 => "19200",
                Baudrate::Rate38400 => "38400",
                Baudrate::Rate57600 => "57600",
                Baudrate::Rate115200=> "115200",
             
                Baudrate::Databit8=>"8",
                Baudrate::Databit7=>"7",
                Baudrate::Databit6=>"6",
                Baudrate::Databit5=>"5",

                Baudrate::Pabitnone=>"None",
                Baudrate::Pabiteven=>"Even",
                Baudrate::Pabitodd=>"Odd",
                Baudrate::Pabitspace=>"Space",
                Baudrate::Pabitmark=>"Mark",

                Baudrate::Stopbit1=>"1",
                Baudrate::Stopbit1dot5=>"1.5",
                Baudrate::Stopbit2=>"2",
            }
        )
    }
}

///获取可用串口
fn get_serialport_list()->String {
    let mut pn=String::new();
    match available_ports() {
        Ok(ports) => {
            match ports.len() {
                0 => println!("No ports found."),
                1 => println!("Found 1 port:"),
                n => println!("Found {} ports:", n),
            };
            for p in ports {
                println!("  {}", p.port_name);
                pn=p.port_name;
                match p.port_type {
                    SerialPortType::UsbPort(info) => {
                        println!("    Type: USB");
                        println!("    VID:{:04x} PID:{:04x}", info.vid, info.pid);
                        println!(
                            "     Serial Number: {}",
                            info.serial_number.as_ref().map_or("", String::as_str)
                        );
                        println!(
                            "      Manufacturer: {}",
                            info.manufacturer.as_ref().map_or("", String::as_str)
                        );
                        println!(
                            "           Product: {}",
                            info.product.as_ref().map_or("", String::as_str)
                        );
                        #[cfg(feature = "usbportinfo-interface")]
                        println!(
                            "         Interface: {}",
                            info.interface
                                .as_ref()
                                .map_or("".to_string(), |x| format!("{:02x}", *x))
                        );
                    }
                    SerialPortType::BluetoothPort => {
                        println!("    Type: Bluetooth");
                    }
                    SerialPortType::PciPort => {
                        println!("    Type: PCI");
                    }
                    SerialPortType::Unknown => {
                        println!("    Type: Unknown");
                    }
                }
            }
        }
        Err(e) => {
            eprintln!("{:?}", e);
            eprintln!("Error listing serial ports");
        }
    
    }
    pn
}
相关推荐
UestcXiye几秒前
Rust 学习笔记:编程练习(一)
rust
新时代苦力工1 小时前
处理对象集合,输出Map<String, Map<String, List<MyObject>>>格式数据,无序组合键处理方法
java·数据结构·list
Themberfue5 小时前
Redis ⑥-string | hash | list
数据库·redis·分布式·缓存·list
编程乐趣6 小时前
推荐一个微软官方开源浏览器自动化工具,可以用于UI自动化测试、爬虫等,具备.Net、Java、Python等多个版本!
microsoft·ui·自动化
高峰君主8 小时前
WebAssembly全栈革命:在Rust与JavaScript之间构建高性能桥梁
javascript·rust·wasm
折纸星空Unity课堂8 小时前
Unity之基于MVC的UI框架-含案例
ui·unity·mvc
长沙火山9 小时前
SwiftUI 8.List介绍和使用
ios·list·swiftui
爱的叹息20 小时前
解决 Dart Sass 的旧 JS API 弃用警告 的详细步骤和解决方案
javascript·rust·sass
只可远观1 天前
Flutter Dart 集合类型List Set Map详解军 以及循环语句 forEaclh map where any every
windows·flutter·list
Python私教1 天前
Rust:安全与性能兼得的现代系统编程语言
java·安全·rust