Теплый ламповый текстовый интерфейс. Просто о простом

от автора

Периодически просматривая топики на хабре, постоянно лювлю себя на мысли, что ещё чуть-чуть и какой-нибудь нейроинтерфейс в ноутбуке станет реальностью. В работе постоянно натыкаюсь на то, что современные люди не очень понимают и любят простую командную строку. А читать мануалы им тем более лень.
Но в моей практике часто случается так, что нужна небольшая утилита, выполняющая одну или две функции. А где именно она будет выполняться — неизвестно. Это может быть Windows, это может быть исключительно терминальный линукс, загрузочная медия — что угодно. Я не программист, но иногда бывает нужно облегчить жизнь себе или другим. И желательно как можно более наглядно. Сначала я пробовал делать просто консольные утилиты. Собственно, с этого, наверное, начинают все. Но очень быстро оказалось, что средствами printf/sprintf/puts и прочими (а пишу я на С) не очень удобно форматировать текст, выводить какую-то информацию. Скролящиеся окно выглядит не очень симпотично, и если информации много — абсолютно нечитаемо. Тогда я вспомнил про ncurses.


Обычно curses/ncurses ассоциируется с линуксом, хотя на самом деле совместимые реализации есть для многих платформ, в частности и под Windows. Изначально большая часть утилит нужна была под Win, а никаких графических фреймворков я не знал и отчаянно искал способы нормально оформить текст, сделать красиво и наглядно. Вот тогда я и наткнулся на Public Domain Curses. Созданный с целью быть совместимым с ncurses, он позволяет писать кроссплатформенные приложения, используя большую часть функционала оригинального curses/ncurses. Но, к сожалению, без багов и ограничений не обходится. Но это не так страшно, как казалось по началу. Я хочу показать, что создать симпотичное консольное псевдооконное приложение не так сложно; а на выходе мы получаем теплый ламповый TUI. Хочется, чтобы люди не забывали о таких методах работы с пользователем.
В данном посте, я буду описывать работу, совместимую с PDcurses, такчто данные примеры должны без проблем собираться и под Windows и под Linux.

Начало

Так как мы работаем с текстовым интерфейсом, то единицей размерности у нас будет один символ. Работать можно как с обычным ASCII, так и с Wide символами. Следует помнить, что отобразить curses может только то, что поддерживает терминал. К сожалению, лично у меня 80% псевдографики не выводится адекватно. Чуть лучше на линуксе, совсем плохо на Windows. к счастью, простые линии выводятся нормально.
Работать мы можем с окнами, панелями, цветами и текстом (включая скроллинг, копирование и прочее).
Перед началом работы, нам необходимо подготовиться к работе, в(ы)ключить (не)нужные опции.

Вот так выглядит у меня обычное начало работы

initscr(); //инициализируем библиотекц cbreak();  //Не использовать буфер для функции getch() raw(); nonl(); noecho(); //Не печатать на экране то, что набирает пользователь на клавиатуре curs_set(0); //Убрать курсор keypad(stdscr, TRUE); //Активировать специальные клавиши клавиатуры (например, если хотим использовать горячие клавиши) if (has_colors() == FALSE) //На практике столкнулся с линуксом, на котором не было поддержки цвета.  {     endwin();     puts("\nYour terminal does not support color");     return (1); } start_color(); //Активируем поддержку цвета use_default_colors(); //Фон stscr будет "прозрачным" init_pair(1, COLOR_WHITE, COLOR_BLUE); //Все цветовые пары (background-foreground) должны быть заданы прежде,ч ем их используют init_pair(2, COLOR_WHITE, COLOR_RED); ...... 

Сначала было окно

Когда мы запускаем эмулятор/экземпляр терминала, мы оказываемся в stdscr. Это наш базис, начальное окно. Работать мы можем в нем, либо насоздавать своих окон.
Хватит слов, давайте к делу. Создадим окно. Сразу хочу заметить важный нюанс — везде, во всем функциях, сначала идет Y, потом X

WINDOW *win = newwin(height, width, y, x); 

Каждое новое окно имеет свои собственные относительные координаты, которыми вы будете оперировать в дальнейшем. Это важно и удобно.

Окно создано, но в консоли ничего не появилось. Потому что окно унаследовало атрибуты родителя — stdscr в нашем случае.
Сразу покажу, как делаю я. Имеется структура, которая описывает «виртуальное окно», о панелях расскажу попозже

struct cursed_window {     WINDOW *background;     WINDOW *decoration;     WINDOW *overlay;     PANEL *panel; }; typedef struct cursed_window curw; 

Я так делаю для того, чтобы сначала сделать оформление, которое не будет меняться и которое статично. Меняем только рабочие данные, при этом не затирая оформление.
Окно background — прозрачный фон и тень от окна.
decoration — рамка, она рисуется автоматически
overlay — собственно, рабочее поле. Начало координат у неё будет 0,0, так как это новое окно, не нужно вносить поправки на рамку и тень.
про панель — позже.

Создаем наше виртуальное окно

curw *tui_new_win(int sy, int sx, int h, int w, char *label) {     curw *new = malloc (sizeof *new);             new->background = newwin(h, w, sy, sx);//Создаем самую нижнюю часть нашего бутерброда         wattron(new->background, COLOR_PAIR(7));//Черная тень, яркий цвет. Атрибуты можно объединять     //И рисуем тень черным пробелом     for (int i= w*0.1; i<w;i++) 		mvwaddch(new->background, h-1, i, ' ');       for (int i= h*0.2; i<h;i++) 		mvwaddch(new->background, i, w-1, ' ');     wattroff(new->background, COLOR_PAIR(7));         //Создаем окно для рамки, это уже дочернее окно для фона. Поэтому координаты указываются     //Относительно родительского окна     new->decoration = derwin(new->background,  h-2, w-2, 1, 1);     wbkgd(new->decoration, COLOR_PAIR(1));     //Рисуем рамку     box(new->decoration, 0, 0);     int x, y;         getmaxyx(new->decoration, y, x);     new->overlay = derwin(new->decoration,  y-4, x-2, 3, 1);//рабочее дочернее окно     wbkgd(new->overlay, COLOR_PAIR(1));     new->panel = new_panel(new->background);         tui_win_label(new->decoration, label, 0);         //Даем команду обновить все это на экране     update_panels();     doupdate();        return new;     } 

На самом деле, если содать второе окно поверх этого, то наш фон «наедет» на нижнее окно. Это некрасиво. Но устранимо. Но это уже тема отдельного разговора. Уберем тени для простоты и создадим несколько окон

А вот теперь можно сказать про панели. Панель это контейнер-стек, вмещающий в себя окно и все его дочерние окна. С панелью можно проводить множество интересных манипуляций.

Панели

Теперь можно продемонстрировать возможности панелей на практике. Самое верхнее окно в стеке доступно для работы по умолчанию. Мы так же можем обращаться к любым окнам и панелям в стеке снизу, писать в них, при этом никак не влияя на окна в стеке выше. Мы можем сами сортировать окна как угодно, перемещать, изменять их размеры. Уж простите за примитивный код, но старался делать нагляднее.

Создадим, наконец, главный цикл

  int x, y;   getmaxyx(stdscr, y, x);   curw *wins[3];   //Создадим несколько окон   wins[0] = tui_new_win(0, 0, y - 5, x - 5, "-=Hello Habr=-", 1);   wins[1] = tui_new_win(y / 3, x / 2, 15, 30, "-=Data=-", 4);   wins[2] = tui_new_win(5, 5, 10, 20, "-=Memo=-", 5);   PANEL *TOP = wins[0]->panel;   int panel_counter = 0;   do   {     switch ( user_key )     {     case 0x9: //TAB     if(++panel_counter > 2)     { 	panel_counter=0;     }           TOP = wins[panel_counter]->panel;     break;     case KEY_UP:     case KEY_DOWN:     case KEY_LEFT:     case KEY_RIGHT:     tui_move_panel(wins[panel_counter], user_key);     default:     if(isalpha(user_key)) 		waddch(wins[panel_counter]->overlay, user_key);       break;     }    //Ставим текущее выбранное окно на вершину стека и обновляем     top_panel(TOP);     touchwin(panel_window(TOP));     update_panels( );     doupdate( );   }   while (( user_key = getch( )) != KEY_F(12)); 

А вод подпрограмма перемещения окна

void tui_move_panel(curw *win, int ch) {     int begy, begx, maxx, maxy, x, y;     getbegyx(panel_window(win->panel), begy, begx);     getmaxyx(panel_window(win->panel), maxy, maxx);     getmaxyx(stdscr, y, x);     switch (ch)     {     case KEY_UP:         if ((begy - 1) >= 0)             begy--;         break;     case KEY_DOWN:         if (((begy + 1) + maxy) <= y)             begy++;         break;     case KEY_LEFT:         if ((begx - 1) >= 0)             begx--;         break;     case KEY_RIGHT:         if (((begx + 1) + maxx) <= x)             begx++;         break;     }     move_panel(win->panel, begy, begx); } 

Ну и в результате

Думал описать больше, но судя по всему, это был бы слишком большой и скучный пост, я же лишь хотел обратить внимание на эту «древнюю технологию», у которой достаточно возможностей. За кадром остались манипуляции с текстом, с атрибутами и прочим. К примеру, возможно скопировать строку текста из любого окна, узнать его цвет и режим. И многое другое.
Надеюсь, это не было слишком скучным.

ссылка на оригинал статьи http://habrahabr.ru/post/205222/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *