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 = "*"
最终界面如下: