ncurses 库学习笔记
这次 ENGG1340 课程的 group project 是设计并实现一个 text-base game,作为终端上运行的 text-base game,有一个出彩的 GUI 肯定是一个加分项!
在未老师的介绍下,我知道了有 <ncurses.h>
库这么一个神奇的东西;最重要的是,虽然它不属于 C++ 标准库,但是在学校
server 里居然默认下载好了 (可见其出名的功能强大)。
据说很多热门的终端程序,例如 Vim,SL 都用到了 ncurses。
花了一个下午学习了一下用法,在这里简单的总结一下,并且附上一些简单 GUI 组成的实例。
Installation
官方 release 网址在这里
在终端上输入命令 sudo apt-get install libncurses5
进行安装。
Compilation
所有使用了 <ncurses.h>
库的程序,在编译时需要添加参数 -l ncurses
使编译器链接到
ncurses 库。
例: g++ -o test test.cpp -lncurses
初始化窗口对象
使用 initscr()
初始化标准窗口对象 stdscr (WINDOW
类)。
初始化过后,我们接下来将与该窗口对象进行交互: std::cin/out, scan/printf 等标准输入输出将失效。
返回常规终端模式
当使用完 ncurses,想回到常规终端模式时,使用 endwin()
来关闭窗口。
通常在程序退出前调用 endwin()
。
输入/输出
在调用 endwin()
之前,标准输入输出将失效。
想要与初始化后的窗口对象交互,我们需要使用 ncurses 库提供的输入/输出函数。
1 |
|
由于 gp 中使用了很多 string,我自定义了两个接口用来处理 string 类型的输入输出:
1 |
|
在指定位置输出
在 ncurses 模式中,我们可以通过 mv()
函数轻松控制光标的位置,从而实现在终端的指定位置输出。
mv(x, y)
将光标移动到当前窗口的第 \(x\) 行第 \(y\) 列 (行列均从 \(0\)
开始),且接下来输出的内容都将从该位置开始。
以下实例将在从屏幕中央开始 (注意,是开始而不是位于) 输出 "Hello World!":
1 |
|
移动光标和输出可以在同一个函数 mvprintw()
中完成:
例如上例程序可写成
mvprintw(scrLine / 2 - 1, scrCol / 2 - 1, "Hello World!")
。
在输出完毕后,使用 refresh()
进行刷新,将输出显示到当前屏幕上。
新窗口/子窗口
创建新窗口/子窗口
在未指定的情况下,initscr()
初始化的窗口是标准屏幕
stdscr
。
而有时我们需要多个窗口;这可以通过创建新窗口 (newwin
)
或子窗口 (subwin
) 实现。
创建新窗口时,其会被分配一个新的内存;而子窗口与其父窗口共用内存。
1 | WINDOW* newwin(int line, int col, int x, int y); // 创建新窗口 |
删除新窗口/子窗口
使用 del(win)
删除窗口 win,即,释放其所占用的内存。
删除窗口 win 并不代表 屏幕上 win 输出的内容会消失,因此在删除之前调用
wclear(win)
与wrefresh(win)
进行清屏。不要删除 ncurses 的默认窗口
stdscr
, 结束它使用endwin()
即可。
非标准窗口的交互操作
超级简单易懂,之前我们介绍的所有对 stdscr
的函数,加上前缀
w,再添加对应窗口的指针作为参数就得到了与非标准窗口交互的函数。
例如:printw()
对应 wprintw(win)
,
refresh()
对应 wrefresh(win)
,
mvprinrw()
对应 mvwprintw(win)
(这个位置稍有不同)
1 | WINDOW* win = newwin(30, 30, 0, 0); |
子窗口与父窗口
当我们使用 wmove()
或其他对非标准窗口的交互函数对子窗口进行操作时,父窗口 (或其他子窗口)
输出的内容会暂时消失。
解决方法是:在 touchwin(fascr)
过后,再对父窗口进行刷新。
即 wrefresh(father_scr)
或 refresh()
(当父窗口是标准窗口时)。
1 | WINDOW* upwin = subwin(stdscr, scrLine / 2, scrCol, 0, 0); |
当未加 refresh()
时,upwin
窗口的输出内容
(即 box 绘出的边框) 将不会显示。
只有将对父窗口重新刷新 refresh()
之后,所有的输出内容才会显示。
窗口转储
这在实现 text-base game 中是一个很实用的功能,它可以用来实现场景的切换,与读/存档功能。
例如,"开始" 界面可以进入 "游戏" 界面,但从 "游戏" 界面返回时,由于屏幕已经被 "游戏" 界面所在窗口覆盖,我们需要重新加载 "开始" 界面。
此时,利用窗口的转储,我们可以直接恢复之前的界面。
标准窗口转储
标准窗口 stdscr 可以利用下面的两个函数进行存储与读取:
1 | int scr_dump(const char*); // 参数是当前目录下文件的名称: scr_dump 将会把标准窗口中的内容存储到对应名称的文件中 |
以下是一个简单的例程 (gp 中的界面转换可以以此为基础):
1 | printw("Main Menu\n"); // 标准窗口作为主界面 main menu |
若有时我们需要暂时退出 ncurses 模式,回到行缓冲模式,我们可以储存当前标准窗口的内容后再退出。
(或使用 def_prog_mode()
与
reset_prog_mode()
函数,由于 gp
中可能不会用到,这里不多介绍)
非标准窗口转储
当然,除了标准窗口 stdscr,其他任何我们新创建的窗口都可以进行转储。
使用以下的两个函数:
1 | int putwin(WINDOW*, FILE*); // 存 |
滚屏操作
在行缓冲模式中,若输出的内容超过了终端的 bottom line,将会自动滚屏 (旧的输出将会向上滚动,为新的输出留位置)。
我以为 ncurses 中的窗口默认滚屏,结果当输出超出屏幕时,反而向右溢出了;
我翻了好久的库文档,终于找到了对应的函数 (这里):
1 | int scrollok(WIN*, bool); // 在 WIN 指针指向的窗口中开启 (true)/关闭 (false) 滚屏 |
颜色设置
有时候我们希望改变窗口的背景与文本颜色, ncurses 库对此也提供了支持。
在使用之前,我们先初始化颜色设置:
1 | bool has_color(void); // 返回该环境是否支持颜色设置 |
在 start_color()
成功调用后,一系列的常量将会产生,例
COLOR
(支持的颜色数量),COLOR_BLACK
(黑色),
COLOR_WHITE
...
我们使用 init_pair()
函数来创建
背景-文本 的颜色对,并用 attron()
函数激活。
接下来,直到 attroff()
关闭之前,所有输出的 背景-文本
都将是指定的颜色对。
1 | init_pair(1, COLOR_BLACK, COLOR_WHITE); // [黑]底[白]字为第 1 个颜色对 |
对非标准窗口的操作使用 wattron(WINDOW*, ...)
与
wattroff(WINDOW*, ...)
。
其他输出文本效果
attron()
与 attroff()
不仅可以用来设定颜色,还能够实现许多输出文本效果。
这些效果通过一系列常量来代表: A_BLINK
,
A_DIM
, A_UNDERLINE
...
例如: attron(A_BLINK)
,那么在
attronoff(A_BLINK)
之前输出的文本都将闪烁显示。
这些效果的复合 (甚至与颜色的复合) 可以通过常量间的或 |
运算简单的实现 (这与 windows 对终端文本颜色的操作很相似)。
例如 attron(A_BLINK | A_UNDERLINE)
代表文本闪烁且加下划线显示。
这里附上 ncurses 提供的所有输出文本效果常量:
1 | A_NORMAL // 普通字符输出(不加亮显示) |
Reference
- Debian ncurses manpage
- tiga-Unix/Linux下的Curses库开发指南——第三章curses库窗口 (转载的,但是翻译的很好,也很全)
- KeBlog-ncurses (Kewth NB)
- ztq-ncurses库 常用函数及基本使用 (有一个通过子窗口实现分屏的程序,gp 可以借鉴,修改一下参数)
- ncurses 输出修饰 (包含了 ncurses 提供的一系列 attr 前缀函数,对输出进行修饰,很有用)