FLTK-Rs

终于还是到这一步了,可视化,我的超人!

FLTK是一个跨平台的轻量级 gui 库。该库本身是用 C++98 编写的,具有很高的可移植性。fltk crate 是用 Rust 编写的,并使用 FFI 调用 FLTK 包装器cfltk,它是用 C89 和 C++11 编写的。

虽然Rust里面不太兴面向对象编程,但是不可否认的是fltk却有浓浓的面向对象风格,不过这也要学了才知道的:

配置,Helloworld:

这个应该是有教学手册且最简单配置的rust GUI库,没有之一

话不多说直接Hello world:

[dependencies]
fltk = { version = "^1.2", features = ["fltk-bundled"] }
fn main() {
    let a = app::App::default();
    let mut wind = window::Window::new(100, 100, 800, 300, "Hello world");
    wind.end();
    wind.show();
    a.run().unwrap();
}

在这里插入图片描述

我们成功的利用fltk创建出了一个窗口

APP结构

crate在app模块提供一个App结构。初始化App结构会初始化所有内部样式,字体,支持的图像模型,运行的多线程换环境等等

App 结构允许您使用 with_scheme() 初始化程序设置应用程序的全局方案:

let c  = app::App::default().with_scheme(app::Scheme::Plastic);

在这里插入图片描述

官网给出了四种风格:Gtk,Basic、Plastic 和 Gleam等等。App是我们整个应用的承载。

Window

窗口是我们所有组件、图片的容器,FLTK 在它支持的每个平台上调用原生窗口,然后基本上是自己绘制(看来这个轮子不多,要自己画)。这意味着它在 Windows 上调用 HWND

关于窗口的定义请允许我重新展示一边:

let mut wind = window::Window::new(x, y, width, height, name);
  • x, 画出窗口后距离屏幕左侧的水平距离
  • y, 画出窗口后距屏幕顶部的垂直距离
  • width: 窗口的宽度
  • height: 窗口的高度
  • name: 窗口的名字,或者说title标题

窗口时可以嵌套的,我们嵌套窗口的方法:

use fltk::{prelude::*, *};

fn main() {
    let app = app::App::default();
    let mut my_window = window::Window::new(100, 100, 400, 300, "My Window");
    let mut my_window2 = window::Window::new(10, 10, 380, 280, "");
    my_window2.set_color(Color::Black);
    // 关于end我会在下面写注释
    my_window2.end();
    my_window.end();
    my_window.show();
    app.run().unwrap();
}

在这里插入图片描述

窗口本身就是一个容器,容器之间的嵌套分子级父级关系的 。向我们上面的写法,我们只需主窗口调用end前定义,即可证明父子关系,这样对子组件的修饰甚至无需考虑执行顺序!,并且只需要主窗口调用show()即可。

    let mut my_window = window::Window::new(100, 100, 400, 300, "My Window");
    let mut my_window2 = window::Window::new(10, 10, 380, 280, "");
    my_window.show();
    my_window2.set_color(Color::Black);
    app.run().unwrap();

如果完完全全的分开两个窗口,不构建父子关系,那么会创建新的窗口,,

    let mut my_window = window::Window::new(100, 100, 400, 300, "My Window");
    my_window.show();
    let mut my_window2 = window::Window::new(10, 10, 380, 280, "");
    my_window2.set_color(Color::Blue);
    my_window2.show();

在这里插入图片描述

组件

fltk 提供了80多个组件,当然你让我去学完80多个组件不可能的好嘛

按钮:

let mut but = button::Button::new(160, 200, 80, 40, "Click me!");

钮的父级是 my_window,因为它是在隐式 begin() 和 end() 调用之间创建的。添加小部件的另一种方法是使用实现 GroupExt 特征的小部件提供的 add(widget) 方法:

let mut my_window2 = window::Window::new(10, 10, 380, 280, "");
my_window2.set_color(Color::Blue);
let but = button::Button::new(10,10,50,30,"button");
my_window2.add(&but);

按钮的 x 和 y 坐标是相对于包含该按钮的窗口的

此外,组件都可以使用构建器模式构建:

let but1 = Button::default().with_pos(10, 10).with_size(80, 40).with_label("Button 1");

对于这种可点击的组件,我们肯定是要绑定事件的:

let mut but = button::Button::new(10,10,50,30,"button");
    but.set_callback(move |_| {println!("Hello world");});

这样我们每点击一次按钮就会运行此函数

除了最基本的按钮,按钮还包括很多。我将展示其中几个按钮的效果(怎么有种上个世纪GUI的感觉):

  • LightButton:

    let mut but2 = button::LightButton::new(100,100,100,50,"button2");
    

    在这里插入图片描述

  • CheckedButton:

    let mut but2 = button::CheckButton::new(100,100,100,50,"button2");
    

    在这里插入图片描述

  • RoundButton:

    let mut but2 = button::RoundButton::new(100,100,100,50,"button2");
    

    在这里插入图片描述

需要注意的是,对于某些比如我想选的RoundButton或者说CheckedButton, 我们可以使用value查看选中的值:

println!("{}  {}",but2.value(),but3.value());
false  true

所以我们可以构建枚举与数据结构完成整个页面的数据收集:

// 目前只假设两个按钮的数据收集
#[derive(Debug)]
enum button_status{
    clicked(String),
    unclicked(Option<i32>),
}


#[derive(Debug)]
struct checked{
    but1_status:button_status,
    but2_status:button_status,
}
//
impl checked {
    fn new(but1_value: button_status, but2_value:button_status) -> checked {
        checked{but1_status:but1_value, but2_status:but2_value}
    }
}

fn main(){
	let app = app::App::default();
    let mut my_window = window::Window::new(100, 100, 400, 300, "My Window");
    let mut my_window2 = window::Window::new(10, 10, 380, 280, "");
    my_window2.set_color(Color::Blue);
    let mut but1 = button::RoundButton::new(100,100,100,50,"button1");
    let mut but2 = button::RoundButton::new(100,200,100,50,"button2");
    // 清除边框
    but1.clear_visible_focus();
    but2.clear_visible_focus();
    let mut but = button::Button::new(300,200,50,30,"Submit");
    my_window.show();
    but.set_callback(move |but| {
        let mut but1_ = button_status::unclicked(None);
        let mut but2_ = button_status::unclicked(None);
        if but1.value(){
            but1_ = button_status::clicked(but1.label());
        }
        if but2.value(){
            but2_ = button_status::clicked(but2.label());
        }
        let p = checked::new(but1_,but2_);
        println!("Data now: {:?}", p);
    });
    app.run().unwrap();
}
// 输出
Data now: checked { but1_status: unclicked(None), but2_status: clicked("button2") }

在这里插入图片描述

标签

FLTK没有标签部件,标签属于其他组件的自带部分。

FLTK标签非常的有意思,在某种程度上甚至有markdown的感觉,参见网址:https://fltk-rs.github.io/fltk-book/Labels.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CpbsvRPe-1653141672371)(https://www.fltk.org/doc-1.4/symbols.png)]

这个真的害挺好玩的。

菜单

菜单可以分为两种类型:选择型菜单与菜单栏

use fltk::{prelude::*, *};

fn main() {
    let app = app::App::default();
    let mut wind = window::Window::default().with_size(400, 300);
    let mut choice = menu::Choice::default().with_size(80, 30).center_of_parent().with_label("Select item");
    let mut button= button::Button::new(150,200,100,70,"submit");
    choice.add_choice("Choice 1");
    choice.add_choice("Choice 2");
    choice.add_choice("Choice 3");
    // You can also simply type choice.add_choice("Choice 1|Choice 2|Choice 3");
    wind.end();
    wind.show();
	// 这种方法是由我们来处理回调
    button.set_callback(move |c| {
       match choice.choice(){
           Some(a) =>{println!("{}",a)},
           _ =>{println!("No chooesd");},
       }
    });

    app.run().unwrap();
}
Choice1

在这里插入图片描述

在其他的GUI假面中都会有信号机制,其实在fltk中也有。还记得我们学过的send 和 recv么,在这里也可以用起来啊

// 官网示例,跑过可以用
use fltk::{prelude::*, *};

#[derive(Clone)]
enum Message {
    Choice1,
    Choice2,
    Choice3,
}

fn main() {
    let a = app::App::default();
    let (s, r) = app::channel();
    let mut wind = window::Window::default().with_size(400, 300);
    let mut choice = menu::Choice::default()
        .with_size(80, 30)
        .center_of_parent()
        .with_label("Select item");

    choice.add_emit(
        "Choice 1",
        enums::Shortcut::None,
        menu::MenuFlag::Normal,
        s.clone(),
        Message::Choice1,
    );
    choice.add_emit(
        "Choice 2",
        enums::Shortcut::None,
        menu::MenuFlag::Normal,
        s.clone(),
        Message::Choice2,
    );
    choice.add_emit(
        "Choice 3",
        enums::Shortcut::None,
        menu::MenuFlag::Normal,
        s,
        Message::Choice3,
    );

    wind.end();
    wind.show();

    while a.wait() {
        if let Some(msg) = r.recv() {
            match msg {
                Message::Choice1 => println!("choice 1 selected"),
                Message::Choice2 => println!("choice 2 selected"),
                Message::Choice3 => println!("choice 3 selected"),
            }
        }
    }
}

这个add_emit,或者说sender机制目前只在菜单栏这里看到过,有大佬用过其他API欢迎补充。总之菜单差不多这样就OK啦

而菜单栏,就是一堆菜单对单一块的栏目就是菜单栏啦,

use fltk::{prelude::*, *};
use fltk::enums::{FrameType, Shortcut};

fn main() {
    let a = app::App::default();
    let mut win = window::Window::new(300, 300, 800, 600, "New window");
    // 添加我们的菜单栏,这个很好理解我就不说了
    let mut menu = menu::SysMenuBar::default().with_size(800, 35);
    // 要使用frame布局,这是布局的一部分后面会讲到
    menu.set_frame(FrameType::FlatBox);
    // sender和receiver上面有
    let (s, r) = app::channel();
    menu.add_emit(
                 "&test1/test1a\t",
                  Shortcut::Ctrl | 'a',
                  menu::MenuFlag::MenuHorizontal,
                  s.clone(),
                  String::from("this is test1/test1a"),
    );
    menu.add_emit(
        "&test1/test1b\t",
        Shortcut::empty(),
        menu::MenuFlag::Inactive,
        s.clone(),
        String::from("this is test1/test1b"),
    );
    menu.add_emit(
        "&test2/test2a\t",
        Shortcut::empty(),
        menu::MenuFlag::Normal,
        s.clone(),
        String::from("this is test2/test2a"),
    );
    menu.add_emit(
        "&test2/test2b\t",
        Shortcut::empty(),
        menu::MenuFlag::Normal,
        s.clone(),
        String::from("this is test2/test2b"),
    );
    menu.add_emit(
        "&test2/test2c\t",
        Shortcut::empty(),
        menu::MenuFlag::Normal,
        s.clone(),
        String::from("this is test2/test2c"),
    );

    win.show();
    while a.wait(){
        match  r.recv(){
            Some(msg) =>{println!("{}",msg);},
            _ =>{continue}
        }
    }
}

Shoetcut主要是快捷键的设定,比如说:Shortcut::Ctrl | ‘a’, 就是用ctrl + a就能够唤醒的意思。menuflag主要是我们GUI显示的样式定义,比如menu::MenuFlag::MenuHorizontal显示快捷键啥的。这个页面长这个样子哦:

在这里插入图片描述

Input

Input就是文本框,没啥难的(简单示例):

fn main() {
    let a = app::App::default();
    let mut win = window::Window::new(300, 300, 800, 600, "New window");
    let mut input_area = input::Input::default().with_size(100, 30).with_label("input your name").center_of_parent();
    input_area.set_value("None");
    let mut but = Button::new(350,500,100,50,"submit");
    win.show();
    but.set_callback(move |but|{
        println!("Your name is {}", input_area.value());
        // input_area.set_readonly(true);
    });
    a.run().unwrap();
}

在input中有一些好用的方法,比如:output是一个无法编辑的文本展示框,或者使用Input。set_readonly等方法

注意,无论Input的何种其他变体,我们开发者,或者说程序读到的都是字符串类型而不是其他类型

Valuator

说实话,我并不知道这个组件怎么称呼比较合适。Valuator包含滑块,滚动条一类的可以拖拽调节的东西:

use fltk::{prelude::*, *};
use fltk::button::Button;
use fltk::enums::{FrameType, Shortcut};

fn main() {
    let a = app::App::default();
    let mut win = window::Window::new(300, 300, 800, 600, "New window");
    let mut border = valuator::ValueSlider::new(200,200,50,300,"Slider");
    // 设置滚动条的最大与最小值
    border.set_maximum(100.0);
    border.set_minimum(0.0);
    // 滚动条每次变化以1为单位
    border.set_step(1.0,1);
    // 初识位置为20,下同
    border.set_value(20.0);
    let mut c = valuator::Counter::new(400,200,200,50,"Counter");
    c.set_value(0.8);
    let mut d = valuator::Dial::new(400,400,100,100,"Dial");
    d.set_maximum(100.0);
    d.set_minimum(0.0);
    d.set_step(0.5,1);
    let mut e = valuator::Adjuster::new(600,0,200,100,"adjuster");
    let mut f = valuator::FillSlider::new(0,0,200,100,"FillSlider");

    d.set_callback(move |d|{
        println!("{}", d.value());
    });

    e.set_callback(move |e|{
        println!("{}", e.value());
    });
    win.show();
    a.run().unwrap();
}

在这里插入图片描述

讲道理看着玩玩害挺不错,这一节也没有其他特别要注意的点,自己动手试试API挺好玩的

Text

主要目的是显示和编辑文本,需要TextBuffer缓冲区缓冲文本,因为肯定会涉及到文档保存的。

use fltk::{prelude::*, *};

fn main() {
    let a = app::App::default();
    let mut buf = text::TextBuffer::default();

    let mut win = window::Window::default().with_size(800, 700).with_label("Editor");
    let mut txt = text::TextEditor::default().with_size(790, 590).center_of_parent();
    let mut button = button::Button::new(350,650,100,50,"Submit");
    txt.set_buffer(buf.clone());
    win.end();
    win.show();

    buf.set_text("Hello world!\nThis is a text editor!");
    button.set_callback(move |button|{
        println!("Do you want to save {}", buf.text());
    });
    a.run().unwrap();
}

我们可以使用一些DisplayExe提供的方法为我们的文本框添加一些更绚丽的效果。我们TextBuffer的作用不仅是缓冲数据,更可以缓冲样式

不过官网的例子导师有些花里胡哨了,我简单的更改字体与颜色

txt.set_buffer(buf);
txt.set_text_font(Font::Courier);
txt.set_text_size(16);

各种设置可以参见这里:https://docs.rs/fltk/latest/fltk/prelude/trait.DisplayExt.html#tymethod.style_buffer

fn main() {
    let a = app::App::default();
    let mut buf = text::TextBuffer::default();
    let mut sbuf = text::TextBuffer::default();


    let mut win = window::Window::default().with_size(400, 300);
    let mut txt = text::TextEditor::default()
        .with_size(390, 290)
        .center_of_parent();
    // 这里用clone才能追踪到buf的变化。
    txt.set_buffer(buf.clone());
    txt.set_text_font(Font::Courier);
    txt.set_text_size(16);
    win.end();
    win.show();

    let mut p = 0;
    while a.wait(){
        // 每换一行就会改变颜色的小玩具
        let h = buf.text().lines().count() % 2;
        if h == 1{
            txt.set_text_color(Color::Blue);
        }else{
                txt.set_text_color(Color::Black);
        }
    }
}

在这里插入图片描述

Browser

此浏览器非我们上网用的浏览器,更像是任务管理器之流的浏览器,最要是有点类似表格一样的使用。

//官网的代码,演示的代码确实有点潦草可以理解:
use fltk::{prelude::*, *};

fn main() {
    let app = app::App::default();
    let mut win = window::Window::default().with_size(900, 300);
    let mut b = browser::Browser::new(10, 10, 900 - 20, 300 - 20, "");
    let widths = &[50, 50, 50, 70, 70, 40, 40, 70, 70, 50];
    b.set_column_widths(widths);
    b.set_column_char('\t');
    // we can now use the '\t' char in our add method.
    b.add("USER\tPID\t%CPU\t%MEM\tVSZ\tRSS\tTTY\tSTAT\tSTART\tTIME\tCOMMAND");
    b.add("root\t2888\t0.0\t0.0\t1352\t0\ttty3\tSW\tAug15\t0:00\t@b@f/sbin/mingetty tty3");
    b.add("erco\t2889\t0.0\t13.0\t221352\t0\ttty3\tR\tAug15\t1:34\t@b@f/usr/local/bin/render a35 0004");
    b.add("uucp\t2892\t0.0\t0.0\t1352\t0\tttyS0\tSW\tAug15\t0:00\t@b@f/sbin/agetty -h 19200 ttyS0 vt100");
    b.add("root\t13115\t0.0\t0.0\t1352\t0\ttty2\tSW\tAug30\t0:00\t@b@f/sbin/mingetty tty2");
    b.add(
        "root\t13464\t0.0\t0.0\t1352\t0\ttty1\tSW\tAug30\t0:00\t@b@f/sbin/mingetty tty1 --noclear",
    );
    win.end();
    win.show();
    app.run().unwrap();
}

在这里插入图片描述

browser特效格式写法参见:https://fltk-rs.github.io/fltk-book/Browsers.html

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值