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

news2024/12/24 10:07:31

注:此文适合于对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
依赖部分:

[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函数:

///创建一个主菜单
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函数:

///创建一个子菜单(无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函数:

///创建一个子菜单(带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,这和主菜单函数很像,只是内部代码稍有不同,这里的内部函数,是参考的官方示例,有子菜单的子菜单会有一个箭头图标。
在这里插入图片描述

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

//文件菜单
        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函数:

///用于串口行添加,采用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函数:

///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函数里使其显示:

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上,各个部件的大致位置,是纵向还是横向,哪些可以组合在一起设置,哪些需要分开设置,等等,

 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功能添加部件。比如这样:

let c1=column![];
c1.push(text("hello"))

嵌套使用:

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

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

动态演示:
在这里插入图片描述

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

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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1274233.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深度学习】KMeans中自动K值的确认方法

1 前言 聚类常用于数据探索或挖掘前期&#xff0c;在没有做先验经验的背景下做的探索性分析&#xff0c;也适用于样本量较大情况下的数据预处理等方面工作。例如针对企业整体用户特征&#xff0c;在未得到相关知识或经验之前先根据数据本身特点进行用户分群&#xff0c;然后再…

Apache DolphinScheduler 开源之夏采访:苏国伟的开源之旅

个人介绍 大家好&#xff0c;我是苏国伟&#xff0c;来自西安电子科技大学软件工程专业。我在实验室中主要从事数据集成等方面的工作。除了编程&#xff0c;我还热衷于踢足球、观看球赛和健身&#xff0c;这些爱好让我的生活更加丰富多彩。 开源之路 我最初是在本科的分布式…

鸿蒙系统开发手册 - HarmonyOS内核驱动层源码分析

众所周知系统定义HarmonyOS是一款“面向未来”、面向全场景&#xff08;移动办公、运动健康、社交通信、媒体娱乐等&#xff09;的分布式操作系统。在传统的单设备系统能力的基础上&#xff0c;HarmonyOS提出了基于同一套系统能力、适配多种终端形态的分布式理念&#xff0c;能…

博文小调研

感谢信 很高兴认识各位盆友&#xff0c;天南地北一家人&#xff01; 无论身在行业差异&#xff0c;所处职位高低&#xff0c;工作年限长短&#xff0c;这个平台都为爱好学习的人们提供了很好的机会和进步的源动力。 博主今年自11月份开启了新的系列文章&#xff0c;每周发表6…

【Linux】第二十二站:文件(二)深入理解重定向

文章目录 一、重定向1.文件描述符对应的分配规则2.重定向的接口 二、再次实现myshell1.实现细节2.盘点文件与进程替换的一个细节3.代码 三、1号文件和2号文件的区别四、如何理解“一切皆文件&#xff1f;” 一、重定向 1.文件描述符对应的分配规则 我们先看如下代码 #includ…

【Qt开发流程】之打印文档

描述 Qt为打印提供了广泛的跨平台支持。使用每个平台上的打印系统&#xff0c;Qt应用程序可以打印到连接的打印机上&#xff0c;也可以通过网络打印到远程打印机上。Qt的打印系统还支持PDF文件生成&#xff0c;为基本报表生成工具提供了基础。 支持打印的类 下面的类支持选择…

【c++|SDL】三、画图类抽象

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 1. 画图类抽象 之前将画图&#xff08;显示图像&#xff09;放在Game.cpp中,现在将其单独放在一个类中, 编译运行为&#xff1a; g m14main.cpp Game.…

Course1-Week3-分类问题

Course1-Week3-分类问题 文章目录 Course1-Week3-分类问题1. 逻辑回归1.1 线性回归不适用于分类问题1.2 逻辑回归模型1.3 决策边界 2. 逻辑回归的代价函数3. 实现梯度下降4. 过拟合与正则化4.1 线性回归和逻辑回归中的过拟合4.2 解决过拟合的三种方法4.3 正则化4.4 用于线性回归…

C语言之结构体详解

C语言之结构体详解 文章目录 C语言之结构体详解1. 结构体类型的声明2. 结构体变量的创建和初始化3. 结构体的特殊声明4. 结构体的自引用结构体的自引用匿名结构体的自引用 5. 结构体内存对齐5.1 练习一5.2 练习三 6. 为什么存在内存对⻬? 1. 结构体类型的声明 struct tag {me…

【Web】UUCTF 2022 新生赛 个人复现

目录 ①websign ②ez_rce ③ez_upload ④ez_unser ⑤ezsql ⑥ezpop ⑦funmd5 ⑧phonecode ⑨ezrce ①websign 右键打不开&#xff0c;直接抓包发包看源码 ②ez_rce “反引号” 在PHP中会被当作SHELL命令执行 ?codeprintf(l\s /); ?codeprintf(ta\c /ffffffffffl…

leetCode 131.分割回文串 + 动态规划 + 回溯算法 + 优化 + 图解 + 笔记

我的往期文章&#xff1a; leetCode 647.回文子串 动态规划 优化空间 / 中心扩展法 双指针-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/133883091?spm1001.2014.3001.5501leetCode 131.分割回文串 回溯算法 图解 笔记-CSDN博客https://blog.csdn.n…

有什么值得推荐的node. js练手项目吗?

前言 可以参考一下下面的nodejs相关的项目&#xff0c;希望对你的学习有所帮助&#xff0c;废话少说&#xff0c;让我们直接进入正题 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数据库&…

在Windows中加密文件或文件夹不需要太大的努力就可以实现,主要有两种加密方法

如果你正在寻找一种在Windows计算机上保持文件和文件夹隐私的简单方法,你有几个选择。 得益于Microsoft Office Suite,你可以使用内置的加密功能对Office文件(如Word文档或PowerPoint演示文稿)进行密码保护。 一些Windows操作系统还配备了加密文件系统(EFS),可以对任何…

Set集合的特点

Set系列集合特点&#xff1a; 无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff1b;无索引&#xff1b; HashSet&#xff1a;无序&#xff0c;不重复&#xff0c;无索引 LinkedHashSet&#xff1a;有序&#xff0c;不重复&#xff0c;无索引…

python进阶技巧

1.闭包 通过函数嵌套&#xff0c;可以让内部函数依赖外部变量&#xff0c;可以避免全局变量的污染问题 闭包注意事项&#xff1a; 总结&#xff1a; 2.装饰器 2.1装饰器的一般写法 2.2 装饰器的语法糖写法 def outer(func):def inner():print(睡了)func()print(起床)retur…

Zookeeper从零入门笔记

Zookeeper从零入门笔记 一、入门1. 概述2. 特点3. 数据结构4. 应用场景 二、本地1.安装2. 参数解读 三、集群操作3.1.1 集群安装3.2 选举机制1. 第一次启动2. 非第一次启动 3.3 ZK集群启动停止脚本3.4 客户端命令行操作3.2.1 命令行语法3.2.2 节点类型&#xff08;持久/短暂/有…

飞致云开源社区月度动态报告(2023年11月)

自2023年6月起&#xff0c;中国领先的开源软件公司FIT2CLOUD飞致云以月度为单位发布《飞致云开源社区月度动态报告》&#xff0c;旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况&#xff0c;以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源大屏…

传统算法:使用 Pygame 实现选择排序

使用 Pygame 模块实现了选择排序的动画演示。首先,它生成一个包含随机整数的数组,并通过 Pygame 在屏幕上绘制这个数组的条形图。接着,通过选择排序算法对数组进行排序,动画效果可视化每一步的排序过程。在排序的过程中,程序找到未排序部分的最小元素,并将其与未排序部分…

如何获取阿里巴巴中国站按图搜索1688商品(拍立淘) API接口(item_search_img-按图搜索1688商品(拍立淘))

一、背景介绍 阿里巴巴中国站作为中国领先的B2B电子商务平台&#xff0c;提供了大量的商品信息和交易服务。其中&#xff0c;按图搜索1688商品&#xff08;拍立淘&#xff09;是阿里巴巴中国站特有的功能之一&#xff0c;它可以通过上传图片来搜索与图片相似的商品&#xff0c…

Js页面录屏切片存储数据上传后端

前端 screenShot(){// 获取屏幕共享流navigator.mediaDevices.getDisplayMedia({video: true,audio: true,//preferCurrentTab 共享本次操作的页面preferCurrentTab: true}).then(async stream > {const mediaRecorder new MediaRecorder(stream, { mimeType: video/webm; …