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{
//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
}