当前位置: 首页 > news >正文

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
依赖部分:

[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{//portPortCOM0,//baudrateRate9600,#[default]Rate19200,Rate38400,Rate57600,Rate115200,//databitDatabit8,Databit7,Databit6,Databit5,//paritybitPabitnone,Pabiteven,Pabitodd,Pabitspace,Pabitmark,//stopbitStopbit1,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
}

相关文章:

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

注&#xff1a;此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库&#xff0c;用于为rust语言程序构建UI界面。 这是一个系列博文&#xff0c;本文是第五篇&#xff0c;前四篇链接&#xff1a; 1、Rust UI开发&#xff08;一&#xff09;&#xff1a;使用iced构建UI时…...

Linux学习笔记2

web服务器部署&#xff1a; 1.装包&#xff1a; [rootlocalhost ~]# yum -y install httpd 2.配置一个首页&#xff1a; [rootlocalhost ~]# echo i love yy > /var/www/html/index.html 启动服务&#xff1a;[rootlocalhost ~]# systemctl start httpd Ctrl W以空格为界…...

数据结构算法-插入排序算法

引言 玩纸牌 的时候。往往 需要将牌从乱序排列变成有序排列 这就是插入排序 插入排序算法思想 先看图 首先第一个元素 我默认已有序 那我们从第二个元素开始&#xff0c;依次插入到前面已有序的部分中。具体来说&#xff0c;我们将第二个元素与第一个元素比较&#xff0c;…...

安装Kuboard管理K8S集群

目录 第一章.安装Kuboard管理K8S集群 1.安装kuboard 2.绑定K8S集群&#xff0c;完成信息设定 3.内网安装 第二章.kuboard-spray安装K8S 2.1.先拉镜像下来 2.2.之后打开后&#xff0c;先熟悉功能&#xff0c;注意版本 2.3.打开资源包管理&#xff0c;选择符合自己服务器…...

网络安全行业大模型调研总结

随着人工智能技术的发展&#xff0c;安全行业大模型SecLLM&#xff08;security Large Language Model&#xff09;应运而生&#xff0c;可应用于代码漏洞挖掘、安全智能问答、多源情报整合、勒索情报挖掘、安全评估、安全事件研判等场景。 参考&#xff1a; 1、安全行业大模…...

Linux AMH服务器管理面板本地安装与远程访问

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. Linux 安装AMH 面板2. 本地访问AMH 面板3. Linux安装…...

Sharding-Jdbc(3):Sharding-Jdbc分表

1 分表分库 LogicTable 数据分片的逻辑表&#xff0c;对于水平拆分的数据库(表)&#xff0c;同一类表的总称。 订单信息表拆分为2张表,分别是t_order_0、t_order_1&#xff0c;他们的逻辑表名为t_order。 ActualTable 在分片的数据库中真实存在的物理表。即上个示例中的t_…...

zookeeper集群 +kafka集群

1.zookeeper kafka3.0之前依赖于zookeeper zookeeper是一个开源&#xff0c;分布式的架构&#xff0c;提供协调服务&#xff08;Apache项目&#xff09; 基于观察者模式涉及的分布式服务管理架构 存储和管理数据&#xff0c;分布式节点上的服务接受观察者的注册&#xff0c…...

2022年全国大学生数据分析大赛医药电商销售数据分析求解全过程论文及程序

2022年全国大学生数据分析大赛 医药电商销售数据分析 原题再现&#xff1a; 问题背景   20 世纪 90 年代是电子数据交换时代&#xff0c;中国电子商务开始起步并初见雏形&#xff0c;随后 Web 技术爆炸式成长使电子商务处于蓬勃发展阶段&#xff0c;目前互联网信息碎片化以…...

Python版本与opencv版本的对应关系

python版本要和opencv版本相对应&#xff0c;否则安装的时候会报错。 可以到Links for opencv-python上面查看python版本和opencv版本的对应关系&#xff0c;如图&#xff0c;红框内是python版本&#xff0c;绿框内是opencv版本。 查看自己的python版本后&#xff0c;使用下面…...

【开源视频联动物联网平台】LiteFlow

LiteFlow是一个轻量且强大的国产规则引擎框架&#xff0c;可用于复杂的组件化业务的编排领域。它基于规则文件来编排流程&#xff0c;支持xml、json、yml三种规则文件写法方式&#xff0c;再复杂的逻辑过程都能轻易实现。LiteFlow于2020年正式开源&#xff0c;2021年获得开源中…...

家用智能门锁——智能指纹锁方案

智能指纹锁产品功能&#xff1a; 1&#xff1a;指纹识别技术&#xff1a;光学传感器、半导体传感器或超声波传感器等。 2&#xff1a;指纹容量&#xff1a;智能指纹锁可以存储的指纹数量&#xff0c;通常在几十到几百个指纹之间。 3&#xff1a;解锁时间&#xff1a;指纹识别和…...

Qt6 QRibbon 一键美化Qt界面

强烈推荐一个 github 项目&#xff1a; https://github.com/gnibuoz/QRibbon 作用&#xff1a; 在几乎不修改任何你自己代码的情况下&#xff0c;一键美化你的 UI 界面。 代码环境&#xff1a;使用 VS2019 编译 Qt6 GUI 程序&#xff0c;继承 QMainWindow 窗口类 一、使用方法 …...

JAVA IO:NIO

1.阻塞 IO 模型 ​ 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线程交出 CPU。当…...

Python 在控制台打印带颜色的信息

#格式&#xff1a;  设置颜色开始 &#xff1a;\033[显示方式;前景色;背景色m #说明&#xff1a; 前景色 背景色 颜色 --------------------------------------- 30 40 黑色 31 41 红色 32 …...

SQL Server 数据库,创建触发器避免数据被更改

5.4触发器 触发器是一种特殊类型的存储过程&#xff0c;当表中的数据发生更新时将自动调用&#xff0c;以响应INSERT、 UPDATE 或DELETE 语句。 5.4.1什么是触发器 1.触发器的概念 触发器是在对表进行插入、更新或删除操作时自动执行的存储过程&#xff0c;触发器通常用于强…...

C语言实现植物大战僵尸(完整版)

实现这个游戏需要Easy_X 这个在我前面一篇C之番外篇爱心代码有程序教你怎么下载&#xff0c;大家可自行查看 然后就是需要植物大战僵尸的素材和音乐&#xff0c;需要的可以在评论区 首先是main.cpp //开发日志 //1导入素材 //2实现最开始的游戏场景 //3实现游戏顶部的工具栏…...

基于YOLOv8深度学习的火焰烟雾检测系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…...

【C++】手撕string思路梳理

目录 基本思路 代码实现 1.构建框架&#xff1a; 2.构建函数重载 3.迭代器&#xff1a; 4.遍历string 5.resetve 开空间&#xff0c;insert任意位置插入push_back,append,(按顺序依次实现) 6.erase删除&#xff0c;clear清除&#xff0c;resize缩容 7.流插入&#xff0…...

【数据结构和算法】确定两个字符串是否接近

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1操作 1 的本质&#xff1a;字符可以任意排列 2.2操作 2 的本质&#xff1a;出现次数是可以交换的 2.…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么&#xff1f;它的作用是什么&#xff1f; Spring框架的核心容器是IoC&#xff08;控制反转&#xff09;容器。它的主要作用是管理对…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...