Trees
树形结构,允许在树中显示项目,使用add方法发添加条目
use fltk::{prelude::*, *};
use fltk::enums::FrameType;
fn main() {
let a = ce
let 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 implementation
draw::draw_box(i.frame(), i.x(), i.y(), i.w(), i.h(), i.color());
draw::set_draw_color(enums::Color::Black); // for the text
draw::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 white
let 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 exit
println!("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