rust 性能出色,但其gui库并不是很多,成熟的就更少了,而fltk-rs作为fltk rust语言的绑定,基本是够用的。本文描述了一个fltk-rs 编程的一个小例子,代码如下:
main.rs
#![windows_subsystem = "windows"]
#[allow(dead_code)]
#[warn(unreachable_patterns)]
use fltk::{
App,
enums::{FrameType, *},
frame::Frame,
image::{SharedImage, SvgImage},
prelude::*,
window::Window,
*,
};
use fltk::{
button::Button,
group::Flex,
text::{TextBuffer, TextEditor},
};
use reqwest::Error;
// use opener;
use std::time::Duration;
use std::{process::Command, thread};
fn main() {
let app = app::App::default().with_scheme(app::Scheme::Gtk);
app::background(230, 230, 230);
let (width, _height) = app::screen_size();
let mut wind = Window::new(100, 100, 800, 600, "fltk test 04").center_screen();
//窗口关闭事件
wind.set_callback(|_| {
let (width, height) = app::screen_size();
if app::event() == enums::Event::Close {
let dialog = dialog::choice2(
((width / 2.0) as i32) - 200,
((height / 2.0) as i32) - 100,
"确定退出吗?",
"否",
"是",
"取消",
)
.unwrap();
println!("dialog: {}", dialog);
if dialog == 1 {
app::quit();
std::process::exit(0);
}
}
});
//图标
wind.set_icon(Some(SvgImage::load("screenshots/logo.svg").unwrap()));
let mut menu = menu::MenuBar::default().with_size(800, 35);
menu.set_frame(FrameType::FlatBox);
menu.add(
"文件/新建\t",
Shortcut::Ctrl | 'n',
menu::MenuFlag::Normal,
menu_cb,
);
menu.add(
"文件/保存\t",
Shortcut::Ctrl | 'w',
menu::MenuFlag::Normal,
menu_cb,
);
menu.add(
"文件/退出\t",
Shortcut::Ctrl | 'x',
menu::MenuFlag::Normal,
menu_cb,
);
menu.add(
"编辑/改写\t",
Shortcut::None,
menu::MenuFlag::Normal,
menu_cb,
);
menu.add(
"编辑/字句\t",
Shortcut::None,
menu::MenuFlag::Normal,
menu_cb,
);
menu.add(
"编辑/规则\t",
Shortcut::None,
menu::MenuFlag::Normal,
menu_cb,
);
let mut frame = Frame::new(10, 50, 780, 60, "");
frame.set_frame(FrameType::EngravedBox);
let mut btn1 = Button::new(20, 70, 60, 30, "@< 开始");
let mut btn2 = Button::new(90, 70, 60, 30, "@>| 结束");
btn2.set_frame(FrameType::GleamUpBox);
let flex = Flex::new(220, 70, 120, 30, "").column();
let mut choice_thread = menu::Choice::default().with_label("多线程");
for i in 1..10 {
choice_thread.add_choice(&i.to_string());
}
//设置默认值
choice_thread.set_value(6);
//获取默认值
println!("{}", choice_thread.value() + 1);
//监听
choice_thread.set_callback(|c| {
let v = c.value();
match v {
v => println!("{} is selected!", v + 1),
}
});
flex.end();
let mut btn_img = Button::new(350, 60, 340, 40, None);
btn_img.set_frame(FrameType::NoBox);
// 设置程序图标
let mut image = SharedImage::load("screenshots/logo.png").unwrap();
image.scale(240, 50, true, true);
btn_img.set_image(Some(image));
// button tooltip message
btn_img.set_tooltip("点击访问官方网页");
//打开网址
btn_img.set_callback(|_| {
let url = "https://www.lanhong-vip.com";
Command::new("cmd.exe")
.args(["/C", "start", url])
.output()
.expect("调用失败");
});
// let mut btn3 = Button::new(710,70,60,30,"@<- 退出");
let mut frame2 = Frame::new(10, 120, 780, 320, "");
frame2.set_frame(FrameType::EngravedBox);
let flex2 = Flex::new(20, 140, 760, 280, "").column();
let mut text1 = TextEditor::default().center_of(&flex2);
let buffer = TextBuffer::default();
let buffer2: TextBuffer = buffer.clone();
text1.set_buffer(buffer.clone());
flex2.end();
let width = (width / 1.0) as i32;
let mut frame3 = Frame::new(50, 450, 700, 60, "");
frame3.set_frame(FrameType::EngravedBox);
let input_url = input::Input::new(100, 465, 400, 30, "网址:");
let mut choice_encode = menu::Choice::new(520, 465, 100, 30, "");
choice_encode.add_choice("选择编码");
choice_encode.add_choice("utf-8");
choice_encode.add_choice("gbk");
choice_encode.set_value(0);
let mut btn_submit = button::Button::new(630, 465, 60, 30, "@> 提交");
btn_submit.set_callback(move |btn_submit| {
let mut btn_sumbit_clone = btn_submit.clone();
let mut value = input_url.value().trim().to_string();
if value.len() == 0 {
value = "是空的".to_string();
dialog::message_title_default("提示");
dialog::alert(
width / 2 - 200,
width / 4 - 100,
&format!("你输入的是:\t{}", value),
);
} else {
let mut buffer2 = buffer2.clone();
btn_sumbit_clone.deactivate();
let charset_index = choice_encode.value();
let mut charset = "utf-8";
if charset_index == 0 || charset_index == 1 {
charset = "utf-8";
} else if charset_index == 2 {
charset = "gbk";
}
thread::spawn(move || {
let res = get_content_by_url(&value, &charset);
match res {
Ok(r) => buffer2.set_text(&r),
Err(e) => buffer2.set_text(&e.to_string()),
}
btn_sumbit_clone.activate();
});
}
});
let flex3 = Flex::default().column().center_of_parent();
flex3.end();
let mut frame4 = Frame::new(0, 580, width, 320, "");
frame4.set_frame(FrameType::EngravedBox);
let mut label1 = Button::new(0, 580, 40, 20, "状态栏");
label1.set_frame(FrameType::NoBox);
let mut status_bar_text = Button::new(150, 580, 450, 20, None);
status_bar_text.set_frame(FrameType::NoBox);
status_bar_text.set_align(Align::Left);
status_bar_text.set_label_color(Color::Blue);
//必须克隆一个btn2,否则会涉及到多次借用,编译不通过
let mut bt2 = btn2.clone();
let mut bt1 = btn1.clone();
btn1.set_callback(move |btn1| match btn1.value() {
false => {
let mut b = buffer.clone();
let mut status = status_bar_text.clone();
let mut text_area = text1.clone();
let c = btn1.clone();
let mut btn1_1 = btn1.clone();
let mut btn2_2 = bt2.clone();
bt2.activate();
btn1.deactivate();
//多线程
thread::spawn(move || {
for i in 1..10000 {
//停止标志
if c.active() {
break;
}
let st = "第".to_owned() + &(i.to_string()) + "个";
status.set_label(&st);
b.append(&(i.to_string() + "-添加生成的内容\n"));
thread::sleep(Duration::from_nanos(0));
}
//一直往下滚动
let line = b.text().split("\n").count().try_into().unwrap();
text_area.scroll(line, 1);
btn1_1.activate();
btn2_2.deactivate();
});
}
true => {
bt2.deactivate();
bt1.activate();
}
});
btn2.set_callback(move |btn2| match btn2.value() {
false => {
btn1.activate();
btn2.deactivate();
}
true => {
btn1.deactivate();
btn2.activate();
}
});
let flex4 = Flex::new(0, 580, width, 40, "").column();
flex4.end();
wind.make_resizable(true);
wind.end();
wind.show();
app.run().unwrap();
}
/**
* 菜单事件监听
*/
fn menu_cb(m: &mut impl MenuExt) {
dialog::message_title_default("退出程序?");
if let Some(choice) = m.choice() {
//匹配每个菜单项
let (width, height) = app::screen_size();
match choice.as_str() {
"新建\t" => println!("New"),
"退出\t" => {
//居中显示x,y参数是关键
let dialog = dialog::choice2(
(width / 2.0) as i32 - 200,
(height / 2.0) as i32 - 100,
"确定退出吗?",
"否",
"是",
"取消",
)
.unwrap();
if dialog == 1 {
app::quit();
}
}
_ => println!("{}", choice),
}
}
}
fn get_content_by_url(url: &str, charset: &str) -> Result<String, Error> {
let r = reqwest::blocking::get(url);
//异常处理
match r {
Ok(re) => {
let res = re.text_with_charset(charset).unwrap();
let document = scraper::Html::parse_document(&res);
let title_select = document.root_element();
Ok(title_select.html())
}
Err(e) => Err(e),
}
}
cargo.toml 内容
[package]
name = "test_04"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fltk = "*"
reqwest = {version ="0.11",features = ["blocking",'json']}
scraper = "*"
最终界面如下:

