Trees
树形结构,允许在树中显示项目,使用add方法发添加条目
use fltk::{prelude::*, *};
use fltk::enums::FrameType;fn main() {let a = celet mut win = window::Window::default().with_size(400, 300);let mut tree = tree::Tree::new(5,5,390,290,"");tree.set_root_label("Trceee_try");tree.add("Item 1");tree.add("Item 2");tree.add("Item 3");tree.add("Item 1/1");tree.add("Item 1/2");tree.add("Item 2/3");win.end();win.show();tree.set_callback(|tree| match tree.get_selected_items(){Some(s) => {for item in s{ println!("{} selected", tree.item_pathname(&item).unwrap());}}_ =>{},});a.run().unwrap();
}
我们的树组件初始化设定为仅单选。实际上我们可以通过一些设置设置样式与多选
// 允许多选
tree.set_select_mode(tree::TreeSelect::Multi);
// 实现或者虚线
tree.set_connector_style(tree::TreeConnectorStyle::Dotted);
// 线条颜色
tree.set_connector_color(Color::from_rgb(127,187,135));
Custom widgets
除了上述组件之外(我觉得够用了), fltk-rs还允许我们自定义组件。我们自定义的组件可以是完全自己写的~~(应该不会有人从零开写吧。。)~~, 或者是从前面的组件衍生出来的。我们定义组件结构体的时候要定义两个东西:衍生的组件,以及组件的数据的存储。
eg.
struct MyCustomButton {// 我们的组件inner: widget::Widget,// 我们的数据num_clicks: Rc<RefCell<i32>>,
}
我们这里存储数据使用的是Rc<RefCell<some>>>的结构。这个很重要, 因为我们在回调(callback)的时候我们需要移动这个数据到callback块中。然而,我们访问的时候却又要改变他,所以所有权一直在我们GUI调用线程中。所以使用RC<RefCell的方式定义保证数据的可用性。举个简单的例子:
use std::rc::Rc;
fn main() {let p = Rc::from(RefCell::from(10));// p,q指向同一块地址let q = p.clone();// 拿走了p的所有权let h = (move ||{*p.borrow_mut() = 20;});h();// 但是q仍能访问,而p也可以在其他块中被更改println!("{:?}",q);
}
RefCell { value: 20 }
所以我们的数据通过Rc 包裹 Refcell的方式去存储。
定义好结构体后,我们就需要定义方法了。其中最重要的方法就是初始化
struct MyCustomButton {inner: widget::Widget,num_clicks: Rc<RefCell<i32>>,
}impl MyCustomButton {// 我们的结构体// 我们定义的是一个圆形的按钮,r是半径pub fn new(radius: i32, label: &str) -> Self {// 我们的圆形站的尺寸的xy应该是直径缩影成2,最中间let mut inner = widget::Widget::default().with_size(radius * 2, radius * 2).with_label(label).center_of_parent();// 设定帧样式,后面我会介绍。其实就是一俺家样式,样式是库里的没啥问题inner.set_frame(enums::FrameType::OFlatBox);// 初始化我们的数据let num_clicks = 0;let num_clicks = Rc::from(RefCell::from(num_clicks));let clicks = num_clicks.clone();// 在按钮内部写字,协商按钮是干啥的。这个draw有点意思inner.draw(|i| { // we need a draw implementationdraw::draw_box(i.frame(), i.x(), i.y(), i.w(), i.h(), i.color());draw::set_draw_color(enums::Color::Black); // for the textdraw::set_font(enums::Font::Helvetica, 14);draw::draw_text2("Our button", i.x(), i.y(), i.w(), i.h(), i.align());});// 这个ev 我不太清楚干嘛用的,不过是判断点击用的我倒是清楚inner.handle(move |i, ev| match ev {enums::Event::Push => {// 与我上面演示的变化原理相同*clicks.borrow_mut() += 1;// 这个call_back应该是默认为空i.do_callback(); // do the callback which we'll set using set_callback().true}_ => false,});Self {inner,num_clicks,}}// get the times our button was clicked// 这个功能是鸡肋,应该,,pub fn num_clicks(&self) -> i32 {*self.num_clicks.borrow()}
}
最后我们想使用的话需要使用extren宏将其变成我们可以使用的组件:
widget_extends!(MyCustomButton, widget::Widget, inner);
运行效果:
fn main() {let app = app::App::default().with_scheme(app::Scheme::Gleam);app::background(255, 255, 255); // make the background whitelet mut wind = window::Window::new(100, 100, 400, 300, "Hello from rust");let mut btn = MyCustomButton::new(50, "Click");btn.set_color(enums::Color::Cyan);btn.set_callback(|_| println!("Clicked"));wind.end();wind.show();app.run().unwrap();// print the number our button was clicked on exitprintln!("Our button was clicked {} times", btn.num_clicks());
}
Clicked
Our button was clicked 1 times
至此,常用的组件就介绍完毕了
Dialogs
我之前以为对话框应该算是组件的一个部分,可是没想到官方的数把它单列出来了啊,看来还是有点东西的
对话框可以分为两种类型,原生的(操作系统自带)的文件对话框(对于我Windows用户就是win32对话框) 和FLTK自己的对话框。
关于本机的对话框倒是没什么好说的,直接调用即可:
let mut dialog = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseFile);
dialog.show();
// 返回文件的路径
println!("{:?}", dialog.filename());
这里的对话框有六个接口可供选择,单选文件,文件夹;多选文件,文件夹; 保存文件,文件夹路径。我们还可以加文件类型过滤:
dialog.set_filter("*.{txt,rs,toml,py}");
总之还是很简单的,重点是FLTK自带的对话框:
FLTK提供了几个对话框方便我们构建应用:
-
Help 这个对话框可以显示html文档,当然也可以我们自己输入显示的文字
let mut help = dialog::HelpDialog::new(100, 100, 400, 300); help.set_value("<h2>Hello world</h2>"); // this takes html help.show(); // 在另一个窗口展示的时候挂起主应用 while help.shown() {app::wait(); }
-
Message:
//dialog::message(坐标x,坐标y,"你要写的话”);, 效果和下面一样,只不过自定义了位置。不定义的话就是center默认 dialog::message_default("This is a Message\nA important message");
-
Choice:选择对话框,不过只能由三个选项
// 设置弹窗标题 dialog::message_title_default("Save or Not"); // 选项的显示是从左到右的0,1,2 let choice = dialog::choice2_default("Would you like to save", "No", "Yes", "Cancel"); // 这里的choice返回的是Some类型 println!("{:?}", choice);
cancle 2, Yes 1, No 0
-
Input/ Password:
dialog::message_title_default("Input name"); let choice = dialog::input_default("Pleasr input your name", "None"); // 返回值依然是Some println!("{:?}", choice); //password就是input的铭文编程不可见的密文小点点。
Custom Dialog:
这些对话框并不是不好看,但是怎么说呢,上个时代的风格确实太浓了点,而且没办法更自由地定义一些东西。所以我们需要自定义对话框。对话框,本质是新开的窗口,话句话说我们定义窗口即可。
// 官网的小李子
use fltk::{app, button,enums::{Color, Font, FrameType},frame, group, input,prelude::*,window,
};// 设置button的样式
fn style_button(btn: &mut button::Button) {btn.set_color(Color::Cyan);btn.set_frame(FrameType::RFlatBox);btn.clear_visible_focus();
}pub fn show_dialog() -> MyDialog {MyDialog::default()
}pub struct MyDialog {inp: input::Input,
}impl MyDialog {pub fn default() -> Self {let mut win = window::Window::default().with_size(400, 100).with_label("My Dialog");// 这里原来是由用到布局工具的,但是我把它改了方便大家看win.set_color(Color::from_rgb(240, 240, 240));let mut inp = input::Input::new(150,35, 100, 30,"Input name");inp.set_frame(FrameType::FlatBox);let mut ok = button::Button::new(300,35,80, 30,"Ok");style_button(&mut ok);win.end();// 只允许运行当前窗体达到弹窗的效果,不允许窗体关闭win.make_modal(true);win.show();ok.set_callback({let mut win = win.clone();move |_| {// 窗体关闭,由于我们要让主appwait, 所以我们下面海涌到了win,所以这里用的clone方法win.hide();}});while win.shown() {app::wait();}Self { inp }}pub fn value(&self) -> String {self.inp.value()}
}fn main() {let a = app::App::default();app::set_font(Font::Times);let mut win = window::Window::default().with_size(600, 400);win.set_color(Color::from_rgb(240, 240, 240));let mut btn = button::Button::default().with_size(80, 30).with_label("Click").center_of_parent();style_button(&mut btn);// 这里没想到这个frame布局还可以这么用,挺好玩的let mut frame = frame::Frame::new(btn.x() - 40, btn.y() - 100, btn.w() + 80, 30, None);frame.set_frame(FrameType::BorderBox);frame.set_color(Color::Red.inactive());win.end();win.show();btn.set_callback(move |_| {let d = show_dialog();// 同步更新frame.set_label(&d.value());});a.run().unwrap();
}
Pictures
我们GUI无论样式如何都是要使用图片来美化我们的应用的,FLTK支持一堆图片使用的,请参考官网支持的图片种类:https://fltk-rs.github.io/fltk-book/Images.html
fn main() {let app = app::App::default().with_scheme(app::Scheme::Gleam);let mut wind = window::Window::new(100, 100, 400, 300, "Hello from rust");let mut frame = frame::Frame::new(20,20,90,160,"");let mut image = image::JpegImage::load("。。。1.jpg").unwrap();// 强转大小image.scale(90,160,true,true);frame.set_image(Some(image));wind.make_resizable(true);wind.end();wind.show();// 当图片被点击的时候触发frame.handle(|frame,ev|{match ev{enums::Event::Push => {// 与我上面演示的变化原理相同println!("Clicked");true}_ => false,}});app.run().unwrap();
}
简单代码意思一下。此外除了set_picture, 我们还可以使用draw方法,这样在大部分组件中都可以使用图片了。
use fltk::{prelude::*, *};
use fltk::enums::FrameType;fn main() {let app = app::App::default().with_scheme(app::Scheme::Gleam);let mut wind = window::Window::new(100, 100, 400, 300, "Hello from rust");let mut button = button::Button::new(10,10,200,200,"");let mut image = image::JpegImage::load("C:/Users/40629/Desktop/image/1.jpg").unwrap();button.draw(move |b|{image.scale(b.w(),b.h(),true,true);image.draw(b.x(),b.y(),b.w(),b.h());});wind.make_resizable(true);wind.end();wind.show();button.set_callback(|button|{println!("clicked");});app.run().unwrap();
}
Events
我们前面所接触的大部分不见为我们提供了callback方法进行回调,但是fltk给了我们更多的操作空间,比如我之前使用handle.这一节会讲解这些东西的用法:
-
callback,前面已经讲过很多了,我觉得没必要再说了。callback可以接受闭包,也可以接收函数。闭包只是因为更方便
-
handle: handle 方法接受一个参数为 Event 的闭包,并为已处理的事件返回一个 bool。bool 让 FLTK 知道事件是否被处理。这也是为什么我前面的代码都要返回一个bool的原因。eg.
frame.handle(|frame,ev|{match ev{// Push单机,此外还有一些方法enums::Event::Push => {// 与我上面演示的变化原理相同println!("Clicked");true}_ => false,}});
-
emit: 使用sender/ receiver的消息使用消费者模型,我们在菜单组件中也有介绍过哦:
fn main() {let app = app::App::default();let mut my_window = window::Window::new(100, 100, 400, 300, "My Window");let mut but = button::Button::new(160, 200, 80, 40, "Click me!");my_window.end();my_window.show();// 这里是用app_cahhel定义,而不是我们生产者消费者模型的channel定义// 当然,这里用生产者消费者模型也完全没问题let (s, r) = app::channel();but.emit(s, true);// 循环监听while app.wait() {// 收到消息(没被阻塞)就完成下面的操作if let Some(msg) = r.recv() {match msg {true => println!("Clicked"),false => (), // Here we basically do nothing}}} }
-
自定义事件:讲道理我觉得已有的29个事件够用了,我反正是没啥遗憾啦,如果想看自定义事件参见:https://fltk-rs.github.io/fltk-book/Events.html