![нажми меня нажми меня](https://habrastorage.org/getpro/habr/upload_files/940/cb2/329/940cb23290e6236234289b9743f72a6d.png)
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. Попробуем получить результат:
![result result](https://habrastorage.org/getpro/habr/upload_files/1a3/29e/f0c/1a329ef0c6761529ed347514fba314a1.gif)
Иногда необходимо количество кнопок больше чем одна. И надо как-то равномерно расположить их на экране. Чтобы упростить эту задачу я добавил две вспомогательные функции: 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
что не очень удобно при изменении количества кнопок . Придется редактировать и там тут. Удобней создать массивы для каждого ряда и использовать их в функциях.
Клавиши "вверх", "вниз" выбирают кнопки из вертикального столбика. Клавиши "влево", "вправо" выбирают кнопки из горизонтального ряда. Вместо тысячи слов:
![кнопки кнопки](https://habrastorage.org/getpro/habr/upload_files/39c/3f9/b61/39c3f9b612b1ac081174024d66fc4c20.gif)
Строка ввода. Сначала я сделал окно ввода информации ограниченное со всех сторон рамками но выяснилось что read
с некоторыми ключами при нажатии backspace ведет себя с(т)ранно...
![read fail read fail](https://habrastorage.org/getpro/habr/upload_files/9c4/dc3/11b/9c4dc311bff407ed7d50578ac8d74263.gif)
Пришлось отказаться от окна и сделать простую строку во весь экран, пример оформления:
#!/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"
![ввод информации ввод информации](https://habrastorage.org/getpro/habr/upload_files/44f/724/add/44f724add09b61a4d65fd1751ebdb0f6.gif)
Ну и самое интересное - список. Это окно с произвольным количеством колонок в котором построчно отображаются какие-то данные. Количество колонок задается в диапазоне от 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'
менюшка, распределение в % от ширины
Основным является первый столбец, но можно получить информацию из всех столбцов и строить рабочий процесс соответствующим образом. Полазим по менюшке:
![меню меню](https://habrastorage.org/getpro/habr/upload_files/280/2e6/ce2/2802e6ce247d3d2d500abf70f8ca9bd8.gif)
В гифке продемонстрированы все типы перемещения: курсором, 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
![bashuisshto список хостов bashuisshto список хостов](https://habrastorage.org/getpro/habr/upload_files/024/c74/38e/024c7438e6429f5e37a8a71d98bf7edb.png)
![bashuisshto список команд bashuisshto список команд](https://habrastorage.org/getpro/habr/upload_files/495/2cc/526/4952cc52616db16acfa7b2fd9e8d366f.png)
Проект находится на ранней стадии развития, абсолютно всё может поменяться как в лучшую так и в худшую сторону неоднократно, смело тащите в прод!)
Потрогать мой bashui можно тут.
кстати, что касается ...hui(18+)
Разное касается, лично я предпочитаю класическую схему. Но иногда выдаю вот такое вот, для своей bash игры piu-piu я добавил режим penis'а О_о
Активируется он так:
penis=big ./piu-piu
В этом режиме вертолет заменяется на гигантский фалоимитатор с пропеллером. Вместо бонусов таблетки виагры от которых он увеличивается в размерах и стреляет эм, известно чем. Выглядит всё это непотребство как-то так:
![shake'em shake'em](https://habrastorage.org/getpro/habr/upload_files/b1d/fef/2fb/b1dfef2fbd63d40679c7ebb961e7eeef.gif)
Режим кооператива и дуэль тоже оху... там все еще хуже О_о
пссс...
Часто хочется писнуть что-нибудь но для полноценной статьи требуется куча времени которого нет( Решил попробовать формат телеграм канала, заходите!)
Творите, выдумывайте, пробуйте!)
Лайки, пальцы.
Продолжение последовало тут