2 个版本
0.1.1 | 2022年11月10日 |
---|---|
0.1.0 | 2022年11月10日 |
#395 in GUI
515KB
11K SLoC
用于创建终端用户界面 (TUI) 的库
目标是通过提供足够的文档和许多精简的示例,使 TUI 创建变得快速简单,这样开发者就可以专注于应用程序的逻辑,而不是过多地考虑表示层的烦恼。
原因
受到 这篇帖子 的启发,我决定尝试创建一个用于终端用户界面管理的通用库,这是我用 Rust 编写的第一个更严肃的项目。
通过使您的程序更易于使用,您有更大的机会扩大您创建的实用程序的快乐用户群。
允许用户通过社区驱动的皮肤/主题和快捷键自定义这些解决方案的外观和感觉,这会使人们更有可能继续使用这些应用程序。
如何使用它
此库使用 termios 作为其唯一依赖项,这使得您可以在多个操作系统上使用它。
图形对象可以作为纯文本文件定义和加载。图形的构建块 - 框架也是文本文件,包含可选的 ANSI 转义码,允许使用颜色和不同的样式。
内部渲染由单独的线程提供服务,因此等待按键不会对动画播放产生影响。
附带的 studio 应用程序是此库有效工作的一个工作证明。它可以由非程序员使用,以便更注重设计的人可以帮助用户界面的外观和感觉。
设置
为了使用此库,您需要创建一个 Manager
use animaterm::prelude::*;
use std::time::Duration;
let capture_keyboard = true;
let cols = Some(40);
let rows = None; // use all rows available
let glyph = Some(Glyph::default()); // initially fill the screen with this
// You can crank refresh_timeout down, but anything below 1ms won't make a difference,
// other than high CPU usage.
// With default 30ms you get as high as 33 FPS, probably enough for a terminal application.
let refresh_timeout = Some(Duration::from_milis(10));
let mut mgr = Manager::new(capture_keyboard, cols, rows, glyph, refresh_timeout);
请注意,为了看到您使用此库所取得的进展,您需要保持您的程序运行,因为 animaterm 使用备用终端缓冲区。当您的程序完成时,您将回到原始缓冲区,清除迄今为止放置在屏幕上的所有图形。
为了保持您的程序运行,您可以使用以下循环
let mut keep_running = true;
while keep_running {
if let Some(key) = mgr.read_key() {
match key {
Key::Q | Key::ShiftQ => {
keep_running = false;
}
_ => continue
}
}
}
mgr.terminate();
功能
在您的控制下,使用 mgr
您可以执行各种操作
- 创建一个包含多个帧的
Graphic
,并包含可完全调整的Color
和Glyph
- 查看示例; - 向
Graphic
添加Animation
并运行它 - 查看示例; - 根据 [
Key
] 按键进行操作 - 查看示例; - 更改显示的
Graphic
的帧或Animation
为不同的帧 - 查看示例1 或示例2; - 在所选帧上暂停所选
Animation
- 查看示例; - 在多个
Display
实例之间切换 - 查看示例; - 通过更改层在
Display
上堆叠或置于其他Graphic
之上或之下 - 查看示例; - 在
Display
上向上/向下/向左/向右移动Graphic
- 查看示例; - 更新
Graphic
中现有帧内的所选Glyph
- 查看示例; - 通过更改
Glyph
属性使Graphic
的部分透明 - 查看示例; - 向
Graphic
添加克隆或完全新的帧 - 查看示例1 或示例2; - 还有更多。
示例
请查阅 examples
目录,了解如何使用此库的工作示例。
创建包含多个帧的 Graphic
let cols = 10;
let rows = 5;
let start_frame = 0;
let mut library = HashMap::with_capacity(2);
library.insert(
start_frame,
vec![Glyph::default(); rows * cols]);
library.insert(
start_frame+1,
vec![Glyph::plain(); rows * cols]);
let animations = None;
let mut gr = Graphic::new(cols, rows, start_frame, library, animations);
let layer = 0;
let offset = (15, 5);
let graphic_id = mgr.add_graphic(gr, layer, offset).unwrap();
mgr.set_graphic(graphic_id, start_frame);
向 Graphic 添加动画
// You can define some animations upon creation of a graphic:
let running = false;
let looping = true;
let seconds = 0;
let miliseconds = 500;
let ordering = vec![
(start_frame, Timestamp::new(seconds, miliseconds)),
(start_frame + 1, Timestamp::new(seconds, miliseconds)),
];
let start_time = Timestamp::now();
let animation = Animation::new(running, looping, ordering, start_time);
let mut animations = HashMap::new();
let anim_id = 0;
animations.insert(anim_id, animation);
let mut gr = Graphic::new(cols, rows, start_frame, library, Some(animations));
// Or add a new animation to existing graphic
let option_anim_id = gr.add_animation(Animation::new(running, looping, ordering, start_time));
// You can even create additional animation via a manager
mgr.add_animation(
graphic_id,
Animation::new(running, looping, ordering, Timestamp::now()),
);
let mut var_anim_id = None;
if let Ok(AnimOk::AnimationAdded(anim_id)) = mgr.read_result() {
var_anim_id = Some(anim_id);
}
根据按键进行操作
有关允许用户定义按键绑定的更灵活的解决方案,请查看 studio 如何实现用户输入循环。
let mut keep_running = true;
while keep_running {
if let Some(key) = mgr.read_key() {
match key {
Key::Left => mgr.move_graphic(graphic_id, layer, (-1, 0)),
Key::Right => mgr.move_graphic(graphic_id, layer, (1, 0)),
Key::Up => mgr.move_graphic(graphic_id, layer, (0, -1)),
Key::Down => mgr.move_graphic(graphic_id, layer, (0, 1)),
Key::Q | Key::ShiftQ => {
keep_running = false;
}
_ => continue,
}
}
}
mgr.terminate();
将所选 Graphic 切换到不同的帧
// Use force wisely, since it causes entire screen to be refreshed,
// thus app is becoming less responsive.
let force = true;
mgr.set_graphic(graphic_id, frame_id, force)
将所选 Graphic 切换到不同的动画
mgr.start_animation(graphic_id, anim_id);
暂停所选动画
//You can pause immediately
mgr.pause_animation(graphic_id);
//You can pause on a selected frame
mgr.pause_animation_on_frame(graphic_id, frame_id);
在显示屏之间切换
let keep_existing = true;
let second_display_id = mgr.new_display(keep_existing);
// default display has id = 0
mgr.restore_display(0, keep_existing);
更改图形层
mgr.move_graphic(graphic_id, new_layer, (0, 0));
移动图形
mgr.move_graphic(graphic_id, layer, (offset_cols, offset_rows));
更新所选符号
// Change a Glyph for a selected Frame
a_graphic.set_frame(frame_id);
a_graphic.set_glyph(new_glyph, col, row, graphic_offset);
// Change a Glyph for current Frame of an on Screen Graphic
mgr.set_glyph(graphic_id, new_glyph, col, row);
使图形的一部分透明
// Make a transparent Glyph for a selected Frame
a_graphic.set_frame(frame_id, graphic_offset);
a_graphic.set_glyph(Glyph::transparent(), col, row, graphic_offset);
// Change a Glyph to transparent for current Frame of an on Screen Graphic
mgr.set_glyph(graphic_id, Glyph::transparent(), col, row, graphic_offset));
添加克隆帧
// In both cases empty Frame becomes current for that Graphic
// if source_frame_id is None current Frame will get cloned
let source_frame_id = Some(id);
// Add a new Frame to a graphic directly
let frame_id_option = a_graphic.clone_frame(source_frame_id);
// Or use a Manager to do so
mgr.clone_frame(graphic_id);
if let Ok(AnimOk::FrameAdded(graphic_id, frame_id)) = mgr.read_result() {
let added_frame_id = frame_id;
}
添加新帧
// In both cases empty Frame becomes current for that Graphic
// Add a new Frame to a graphic directly
let frame_id_option = a_graphic.empty_frame();
// Or use a Manager to do so
mgr.empty_frame(graphic_id);
if let Ok(AnimOk::FrameAdded(graphic_id, frame_id)) = mgr.read_result() {
let added_frame_id = frame_id;
}