FLTK-Rs 2

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值