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
總結
- 上一篇: 状态空间树
- 下一篇: 打印纸张尺寸换算_纸张开本和尺寸对照表!