
BASHUI - это BASH + UI, а не то что вы подумали.
Начиная работать над sshto я решил не переизобретать велосипед, вернее не переизобретать велосипед целиком а только некоторые его части и в качестве "рамы с педалями" использовал dialog. Это значительно ускорило разработку, но идея написать свой UI на баше с блекджеком и всем остальным ни на секунду не покидала мой воспалённый мозг. Звёзды сошлись, и я решил воплотить этот проект в жизнь(в bash).
Какой UI без кнопок? С(т)ранный, поэтому я начал с элемента - кнопка. Идея заключается в том что кнопка(и остальные элементы UI) будет представлена функцией. Функцию можно использовать из коробки. Но удобнее сделать "обёртку"(функцию) с какими-то предустановленными параметрами и уже эту функцию использовать по назначению. Для всех элементов UI я подготовил примеры(demo_*) их можно найти в репе. Вот как выглядит пример для кнопки:
#!/bin/bash source bashui mess="RESULT" name="The Button" title="Push the button, will get a result..." butt(){ # Новая кнопка на основе button из bashui, параметры: #1 координата X(колонка) #2 координата Y(строка) #3 название кнопки #4 выполняемая функция #5 цвет текста #6 цвет рамки #7 цвет подложки local x=$((COLUMNS/2-(${#name}/2+2))) local y=$((LINES/2)) # 1 2 3 4 5 6 7 button $x $y "$name" "result" "$wht" "$ylw" "$bblk" } # кнопка выполнит эту функцию result(){ local x=$((COLUMNS/2-${#mess}/2)) local y=$((LINES/2+5)) XY $x $y "$mess" (sleep 1; XY $x $y "${mess//[[:print:]]/ }") & } # все собрано вместе menu(){ cursor off # отключаем курсор default_button butt # это необходимо для активации кнопки XY $((COLUMNS/2-${#title}/2)) $((LINES/2-2)) "$title" butt # рисуем кнопку # в цикле опрос клавиатуры и логика while true; do read_input case $_input_ in enter ) press_button butt;; # нажат enter, нажимаем кнопку escape) return;; # нажат escape, выход esac done } clear menu
"Цветовая палитра" для выбора цвета текста, рамки и подложки задана в bashui вот так:
... #-------------------------+--------------------------------+---------+ # Text color | Background color | | #-----------+-------------+--------------+-----------------+ | # Base color|Lighter shade| Base color | Lighter shade | | #-----------+-------------+--------------+-----------------+ | BLK='\e[30m'; blk='\e[90m'; BBLK='\e[40m'; bblk='\e[100m' #| Black | RED='\e[31m'; red='\e[91m'; BRED='\e[41m'; bred='\e[101m' #| Red | GRN='\e[32m'; grn='\e[92m'; BGRN='\e[42m'; bgrn='\e[102m' #| Green | YLW='\e[33m'; ylw='\e[93m'; BYLW='\e[43m'; bylw='\e[103m' #| Yellow | BLU='\e[34m'; blu='\e[94m'; BBLU='\e[44m'; bblu='\e[104m' #| Blue | MGN='\e[35m'; mgn='\e[95m'; BMGN='\e[45m'; bmgn='\e[105m' #| Magenta | CYN='\e[36m'; cyn='\e[96m'; BCYN='\e[46m'; bcyn='\e[106m' #| Cyan | WHT='\e[37m'; wht='\e[97m'; BWHT='\e[47m'; bwht='\e[107m' #| White | #-------------------------{ Effects }----------------------+---------+ DEF='\e[0m' #Default color and effects | BLD='\e[1m' #Bold\brighter | DIM='\e[2m' #Dim\darker | CUR='\e[3m' #Italic font | ...
Этот код также доступен в отдельной репе bash_color. Попробуем получить результат:

Иногда необходимо количество кнопок больше чем одна. И надо как-то равномерно расположить их на экране. Чтобы упростить эту задачу я добавил две вспомогательные функции: x_row и y_row, для горизонтального и вертикального рядов кнопок соответственно, пример:
#!/bin/bash source bashui defb(){ # Новая кнопка на основе button из bashui, параметры: #1 координата X(колонка) #2 координата Y(строка) #3 название кнопки #4 выполняемая функция #5 цвет текста #6 цвет рамки #7 цвет подложки # 1 2 3 4 5 6 7 button "$1" "$2" "butt $3" "fun_${3,,}" "$wht" "$ylw" "$bblk" } # зачистка вывода clr(){ for i in {5..9}; do XY 15 $i "%$((COLUMNS-15))s"; done; } but_a(){ defb "$1" "$2" A; } # клонирую but_b(){ defb "$1" "$2" B; } # кнопку but_c(){ defb "$1" "$2" C; } # по but_d(){ defb "$1" "$2" D; } # умолчанию but_e(){ defb "$1" "$2" E; } # шесть but_f(){ defb "$1" "$2" F; } # раз fun_a(){ clr; XY 15 5 "butt A pressed(${_button_[*]})"; } # создаю fun_b(){ clr; XY 15 5 "butt B pressed(${_button_[*]})"; } # функцию fun_c(){ clr; XY 15 5 "butt C pressed(${_button_[*]})"; } # для fun_d(){ clr; XY 15 5 "butt D pressed(${_button_[*]})"; } # выполнения fun_e(){ clr; XY 15 5 "butt E pressed(${_button_[*]})"; } # каждой fun_f(){ clr; XY 15 5 "butt F pressed(${_button_[*]})"; } # кнопкой # ряды кнопок это два массива ybuttons=(but_a but_b but_c) # вертикальные кнопки xbuttons=(but_d but_e but_f) # горизонтальные кнопки # оба обработчика(все кнопки) собраны в одну функцию для удобства butts(){ y_row 2 "${ybuttons[@]}" x_row $LINES "${xbuttons[@]}" } menu(){ cursor off default_button but_b XY 1 1 "$red Esc to exit" while true; do butts read_input case $_input_ in up ) select_prev_butt "${ybuttons[@]}";; down ) select_next_butt "${ybuttons[@]}";; left ) select_prev_butt "${xbuttons[@]}";; right ) select_next_butt "${xbuttons[@]}";; enter ) press_button butts ;; escape ) return ;; esac done } clear menu
Обратите внимание что у кнопок контролируемых через *row функции не установлены явно координаты, значения установлены в "$1" "$2", реальные значения координат передаются в кнопки из *row функций. В качестве аргументов *row функции принимают: позиция на экране(столбец или строка в зависимости от типа функции) и список кнопок. Кнопки(функции) можно просто перечислить так:
x_row 1 but_a but_b but_c
Но тогда придется копипастить список для функций select_(prev|next)_butt что не очень удобно при изменении количества кнопок . Придется редактировать и там тут. Удобней создать массивы для каждого ряда и использовать их в функциях.
Клавиши "вверх", "вниз" выбирают кнопки из вертикального столбика. Клавиши "влево", "вправо" выбирают кнопки из горизонтального ряда. Вместо тысячи слов:

Строка ввода. Сначала я сделал окно ввода информации ограниченное со всех сторон рамками но выяснилось что read с некоторыми ключами при нажатии backspace ведет себя с(т)ранно...

Пришлось отказаться от окна и сделать простую строку во весь экран, пример оформления:
#!/bin/bash source bashui my_reader(){ # Новое окно ввода на основе reader из bashui, параметры: #1 строка на которой появится окно ввода #2 максимальное кол-во символов #3 имя окна ввода #4 имя переменной в которую будет записан текст #5 текст по умолчанию #5 цвет текста #6 цвет рамки #7 цвет подложки # 1 2 3 4 5 6 7 8 reader 10 20 'test data input' td "$td" "$wht" "$ylw" "$bblk" } td='initial text' clear my_reader clear bye "$td"

Ну и самое интересное - список. Это окно с произвольным количеством колонок в котором построчно отображаются какие-то данные. Количество колонок задается в диапазоне от 1 до N, где N = сколько_влезет_в_окно_терминала и определяется методом научного тыка. Выбор элементов осуществляется при помощи клавиш курсора, pgUp/Down и горячих клавиш. Каждому элементу первого столбца в таблице присваивается горячая клавиша соответствующая первому символу. Вот пример создания меню из списка и нескольких кнопок:
#!/bin/bash source bashui defb(){ # Новая кнопка на основе button из bashui, параметры: #1 координата X(колонка) #2 координата Y(строка) #3 название кнопки #4 выполняемая функция #5 цвет текста #6 цвет рамки #7 цвет подложки # 1 2 3 4 5 6 7 button "$1" "$2" "butt $3" "fun_${3,,}" "$wht" "$ylw" "$bblk" } clr(){ for i in {1..4}; do XY 1 $i "%${COLUMNS}s"; done; } # clear output area but_d(){ defb "$1" "$2" D; } but_e(){ defb "$1" "$2" E; } but_f(){ defb "$1" "$2" F; } fun_d(){ clr; XY 1 1 "butt D; target: $_target_"; } fun_e(){ clr; XY 1 1 "butt E; description: ${_target_[1]}"; } fun_f(){ clr; XY 1 1 "butt F; all items of target:" printf -v text -- '%s\n' "${_target_[@]}" XY 1 2 "$text"; } clear w=$((COLUMNS-10)) my_items(){ # Новый список на основе items, параметры: #1 координата X(колонка) #2 координата Y(строка) #3 ширина окна #4 высота окна, мин. 5 #5 кол-во колонок, N или в % от ширины #6 название списка #7 цвет текста #8 цвет рамки #9 цвет подложки local mess="Columns via number(${bred}Esc to continue$bblk)" # 1 2 3 4 5 6 7 8 9 data items 10 5 $w 15 3 "$mess" "$wht" "$ylw" "$bblk" "$@" } my_items2(){ local mess="Columns via percent of Width(${bred}Esc to exit$bblk)" # 1 2 3 4 5 6 7 8 9 data items 10 5 $w 15 '30 55 15' "$mess" "$wht" "$ylw" "$bblk" "$@" } data=( # массив с тестовыми данными #-------------{ first line - column descriptions }-------------------- $red'Item name' $blu'Item description' $grn'Status' #-----------------------{ the data }---------------------------------- 'first' $BLD$ylw'Long description text' 'true' 'second' 'Description 2' 'O_o' 'third' 'description 3' 'false' 'fourth' "${red}Long ${grn}description ${blu}text" 'true' '' '' '' $ylw'fifth' 'Description 2' 'O_o' 'sixth' 'description 3' 'false' 'midle' $grn'Long description text' 'true' 'long name row2' 'Description 2' 'O_o' '' '' '' 'row 3' 'description 3' 'false' 'row1' $blu'Long description text' 'true' 'row1' 'Long description text' 'true' 'last' 'Description 2' 'O_o' ) xbuttons=(but_d but_e but_f) butts (){ x_row $LINES "${xbuttons[@]}"; } ilist (){ my_items "${data[@]}" ; } ilist2(){ my_items2 "${data[@]}" ; } menu (){ cursor off default_button but_d while true; do $1; butts read_input case $_input_ in up ) select_prev_item "${data[@]}" ;; down ) select_next_item "${data[@]}" ;; left ) select_prev_butt "${xbuttons[@]}";; right ) select_next_butt "${xbuttons[@]}";; enter ) press_button butts ;; escape ) return;; pgup ) select_prev_item --fast "${data[@]}";; pgdown ) select_next_item --fast "${data[@]}";; * ) select_by_hot_key $_input_ "${data[@]}";; esac done } menu ilist # Columns via number menu ilist2 # Columns via percent
В примере создается список из трех стобцов, поэтому данные в массиве data оформлены в виде таблицы с тремя столбцами, чтобы визуально сразу представить как данные будут отображаться в списке. Первые 3(по количеству столбцов) элемента из массива данных, будут использованы в качестве названий стобцов и не отображаются в списке. Элементы можно "раскрашивать" разными цветами, добавляя соответствующие цветовые коды в начало элемента данных. Предусмотрено два способа установки ширины столбца:
автоматический, вы указываете только количество столбцов, ширина столбцов в этом случае определяется как
ширина_окна/коли��ество_столбцови одинакова для всех столбцов
менюшка, равномерное распределение
% от ширины окна, в этом случае в 5-й аргумент необходимо передать не цифру количества столбцов а строку, содержащую несколько(по количеству столбцов) значений в %, сумма которых должна составить 100%, например
'30 55 15'
менюшка, распределение в % от ширины
Основным является первый столбец, но можно получить информацию из всех столбцов и строить рабочий процесс соответствующим образом. Полазим по менюшке:

В гифке продемонстрированы все типы перемещения: курсором, pgUp/Down и через горячие кнопки. А вот как реализован опрос клавиатуры:
read_input(){ read -rsN1 _input_ case ${_input_,,} in # One symbol keys support ' '*) _input_=space;; $'\t'*) _input_=tab ;; $'\n'*) _input_=enter;; # escape sequences additional check $'\u1b'*) read -rsN4 -t 0.001 _input_ case ${_input_,,} in # arrows, pgUp/Down and escape support *a*) _input_=up ;; *b*) _input_=down ;; *d*) _input_=left ;; *c*) _input_=right ;; *5*) _input_=pgup ;; *6*) _input_=pgdown;; '') _input_=escape;; esac;; # the rest, return 1 symbol in lowercase *) _input_=${_input_,,} _input_=${_input_:0:1};; esac }
Я опробовал этот bashui'вый интерфейс на одном из своих проектов sshto, посмотрите что из этого получилось demo_sshto


Проект находится на ранней стадии развития, абсолютно всё может поменяться как в лучшую так и в худшую сторону неоднократно, смело тащите в прод!)
Потрогать мой bashui можно тут.
кстати, что касается ...hui(18+)
Разное касается, лично я предпочитаю класическую схему. Но иногда выдаю вот такое вот, для своей bash игры piu-piu я добавил режим penis'а О_о
Активируется он так:
penis=big ./piu-piu
В этом режиме вертолет заменяется на гигантский фалоимитатор с пропеллером. Вместо бонусов таблетки виагры от которых он увеличивается в размерах и стреляет эм, известно чем. Выглядит всё это непотребство как-то так:

Режим кооператива и дуэль тоже оху... там все еще хуже О_о
пссс...
Часто хочется писнуть что-нибудь но для полноценной статьи требуется куча времени которого нет( Решил попробовать формат телеграм канала, заходите!)
Творите, выдумывайте, пробуйте!)
Лайки, пальцы.
Продолжение последовало тут
