221 条记录
22 私有链接
22 私有链接
https://www.fltk.org/doc-1.5/index.html
https://www.fltk.org/
FLTK
fltk = { version = "^1.5", features = ["fltk-bundled"] }
CMAKE
https://cmake.org/download/
https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-windows-x86_64.msi
FLTK-FLUID
cargo install fltk-fluid
cargo uninstall fltk-fluid
fluid &
FL2RUST
cargo install fl2rust
fl2rust <fl file>.fl > <output file>.rs
fltk-fluid 只负责“画界面(布局/控件)”;
程序逻辑要在你自己的 Rust 代码里写。fl2rust ui.fl > src/ui.rs
只把界面蓝图翻译成 Rust 结构体和一个构造函数,不要在生成的 ui.rs
里写业务逻辑(下次重新生成会被覆盖)。
下面给你一套常用做法 + 可运行示例 👇
推荐做法(总结)
- 用 FLUID 画界面,给重要控件起名字(如
btn_inc
,lbl_count
)。 - 用 fl2rust 生成
ui.rs
(包含UserInterface
等结构和make_window()
)。 - 在
main.rs
(或你自己的模块)里:
- 创建
App
和ui
; - 给控件绑定回调(
set_callback
); - 更新控件状态(
set_label
、set_value
…); - 用
app::channel
做更复杂的消息/状态管理。
- 不要改
ui.rs
,只把它当“视图层”。逻辑放main.rs
或其他模块,必要时给UserInterface
写扩展impl
(单独文件即可)。
最小示例:按钮计数器
1) 在 FLUID 里
- 新建窗口类名(Class)设为:
UserInterface
(默认就行)。 - 放一个
Button
,名字(Name)设为:btn_inc
,标签(Label)设为:点击 +1
。 - 放一个
Box
(或Frame
),名字设为:lbl_count
,初始文本设为:0
。 - 保存为
ui.fl
。
2) 生成 Rust 界面代码
fl2rust ui.fl > src/ui.rs
src/ui.rs
会导出一个类似:
pub struct UserInterface {
pub window: fltk::window::Window,
pub btn_inc: fltk::button::Button,
pub lbl_count: fltk::frame::Frame,
// ...
}
impl UserInterface {
pub fn make_window() -> Self { /* ... */ }
}
3) 在 Cargo.toml 加依赖
[dependencies]
fltk = "1.5"
4) 在 src/main.rs 写逻辑
方案 A:用闭包直接改 UI(最直观)
use fltk::{prelude::*, *};
mod ui; // 就是生成的 src/ui.rs
fn main() {
let app = app::App::default();
let mut ui = ui::UserInterface::make_window();
ui.window.make_resizable(true);
// 计数状态放在闭包里;要更新标签,需要 clone 一份引用
let mut count = 0;
let mut lbl = ui.lbl_count.clone();
ui.btn_inc.set_callback(move |_| {
count += 1;
lbl.set_label(&count.to_string());
});
app.run().unwrap();
}
方案 B:用 channel 做解耦(适合复杂逻辑)
use fltk::{prelude::*, *};
mod ui;
#[derive(Clone, Copy)]
enum Msg { Inc }
fn main() {
let app = app::App::default();
let (s, r) = app::channel::();
let mut ui = ui::UserInterface::make_window();
ui.window.make_resizable(true);
// UI → 发送消息
ui.btn_inc.set_callback(move |_| s.send(Msg::Inc));
// 应用状态
let mut count = 0;
// 事件循环:接收消息并更新 UI
while app.wait() {
if let Some(msg) = r.recv() {
match msg {
Msg::Inc => {
count += 1;
ui.lbl_count.set_label(&count.to_string());
}
}
}
}
}
为什么不建议改 ui.rs?
- 这是生成文件,你每次改图再生成都会覆盖;
- 把逻辑放到
main.rs
或单独模块,界面变了也不影响业务逻辑; - 如需给
UserInterface
增加方法,可在另一个文件写扩展impl
:
// src/ui_ext.rs
use crate::ui::UserInterface;
impl UserInterface {
pub fn init_logic(&mut self) {
// 绑回调、初始化状态…
}
}
然后在 main.rs
:
mod ui;
mod ui_ext;
// ...
let mut ui = ui::UserInterface::make_window();
ui.init_logic();
小技巧
- 在 FLUID 里给控件起 Name(不是 Label),
fl2rust
才会把它们作为pub
字段导出,便于在 Rust 里访问。 - 业务状态尽量放在你自己的 struct 或闭包/通道里,不放生成文件里。
- 需要跨回调共享控件就
clone()
(FLTK 控件是轻量句柄,可安全克隆)。 - 如果你不想每次手动运行
fl2rust
,可以用我之前给你的 build.rs 集成 或 proc-macro 宏方案,编译时自动生成。
如果你愿意,我可以根据你现在的 ui.fl
帮你把 main.rs
的逻辑搭好(比如输入框 + 按钮 + 校验 + 弹窗),你只要贴一下界面里控件的名字(Name)即可。
后面弹出控制台的解决办法
// main.rs 最开头插入:
// 发布版隐藏控制台,调试版保留控制台
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
// 无论调试还是发布都隐藏控制台
// #![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
eg.:
// main.rs
// 发布版隐藏控制台,调试版保留控制台
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
// 无论调试还是发布都隐藏控制台
// #![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
use fltk::{prelude::*, *};
mod ui;
fn main() {
let app = app::App::default();
// 现在会有 struct UserInterface 和 make_window()
let mut ui = ui::UserInterface::make_window();
// 给 Text_editor 绑定一个 TextBuffer(关键!)
let buf = text::TextBuffer::default();
ui.text_editor.set_buffer(Some(buf)); // 先把所有权交给 editor
// 之后通过 editor 拿 buffer 来改内容,避免 clone 带来的困惑
if let Some(mut b) = ui.text_editor.buffer() {
b.set_text("在这里编辑…");
}
// 如果生成代码里没 show,这里补一下:
ui.window.show();
app.run().unwrap();
}
// ui.rs
// Automatically generated by fl2rust
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(unused_imports)]
#![allow(dead_code)]
#![allow(clippy::needless_update)]
use fltk::browser::*;
use fltk::button::*;
use fltk::dialog::*;
use fltk::enums::*;
use fltk::frame::*;
use fltk::group::*;
use fltk::image::*;
use fltk::input::*;
use fltk::menu::*;
use fltk::misc::*;
use fltk::output::*;
use fltk::prelude::*;
use fltk::table::*;
use fltk::text::*;
use fltk::tree::*;
use fltk::valuator::*;
use fltk::widget::*;
use fltk::window::*;
#[derive(Debug, Clone)]
pub struct UserInterface {
pub window: Window,
pub text_editor: TextEditor,
}
impl UserInterface {
pub fn make_window() -> Self {
let mut window = Window::new(397, 259, 969, 573, None);
window.set_label(r#"TEST"#);
window.set_type(WindowType::Double);
window.make_resizable(true);
window.hide();
let mut fl2rust_widget_0 = MenuBar::new(0, 0, 969, 22, None);
fl2rust_widget_0.set_label(r#"mb1"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu1/item1"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu1/item2"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu1/item3"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu1/item4"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu2/item1"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu2/item2"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu2/item3"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu2/item4"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu3/item1"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu3/item2"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu3/item3"#);
let idx = fl2rust_widget_0.add_choice(r#"submenu3/item4"#);
fl2rust_widget_0.end();
let mut fl2rust_widget_1 = CheckBrowser::new(10, 30, 144, 517, None);
fl2rust_widget_1.set_label(r#"cb1"#);
let mut text_editor = TextEditor::new(157, 48, 801, 353, None);
text_editor.set_label(r#"TE1"#);
window.resizable(&text_editor);
text_editor.set_tooltip(r#"Please Input"#);
window.end();
Self {
window,
text_editor,
}
}
}
// cargo.toml
[package]
name = "fltk_widgets_showcase"
version = "0.1.0"
edition = "2024"
[dependencies]
fltk = { version = "^1.5", features = ["fltk-bundled"] }