Добавлен новый модуль виджетов.
Реализована механика работы с виджетами по нажатию Tab. Новый модуль кнопки.
This commit is contained in:
parent
f6150c9b5f
commit
59d5650285
4 changed files with 256 additions and 0 deletions
|
|
@ -13,3 +13,7 @@ public import ncui.engine.screen;
|
||||||
|
|
||||||
// Вспомогательная библиотека
|
// Вспомогательная библиотека
|
||||||
public import ncui.lib.logger;
|
public import ncui.lib.logger;
|
||||||
|
|
||||||
|
// Виджеты
|
||||||
|
public import ncui.widgets.container;
|
||||||
|
public import ncui.widgets.button;
|
||||||
|
|
|
||||||
79
source/ncui/widgets/button.d
Normal file
79
source/ncui/widgets/button.d
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
module ncui.widgets.button;
|
||||||
|
|
||||||
|
import ncui.widgets.widget;
|
||||||
|
import ncui.core.window;
|
||||||
|
import ncui.core.event;
|
||||||
|
import ncui.engine.screen;
|
||||||
|
import ncui.engine.action;
|
||||||
|
|
||||||
|
// Опциональное действие, выполняемое при нажатии на кнопку.
|
||||||
|
alias OnClick = ScreenAction delegate();
|
||||||
|
|
||||||
|
final class Button : IWidget
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Кнопка по умолчанию активна.
|
||||||
|
bool _enabled = true;
|
||||||
|
// Опциональная функция обратного вызова, при нажатии на кнопку.
|
||||||
|
OnClick _onClick;
|
||||||
|
// Координаты начала рисования кнопки.
|
||||||
|
int _y;
|
||||||
|
int _x;
|
||||||
|
// Надпись кнопки.
|
||||||
|
string _text;
|
||||||
|
public:
|
||||||
|
this(int y, int x, string text, OnClick onClick = null)
|
||||||
|
{
|
||||||
|
_y = y;
|
||||||
|
_x = x;
|
||||||
|
_text = text;
|
||||||
|
_onClick = onClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
override @property bool focusable()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override @property bool enabled()
|
||||||
|
{
|
||||||
|
return _enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void render(Window window, ScreenContext context, bool focused)
|
||||||
|
{
|
||||||
|
window.put(_y, _x, focused ? "* " : "[ ");
|
||||||
|
window.put(_y, _x + 2, _text);
|
||||||
|
window.put(_y, _x + 2 + cast(int) _text.length, focused ? " *" : " ]");
|
||||||
|
}
|
||||||
|
|
||||||
|
override ScreenAction handle(ScreenContext context, KeyEvent event)
|
||||||
|
{
|
||||||
|
if (!_enabled)
|
||||||
|
return ScreenAction.none();
|
||||||
|
|
||||||
|
if (isEnter(event) || isSpace(event))
|
||||||
|
{
|
||||||
|
if (_onClick !is null)
|
||||||
|
return _onClick();
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClick(OnClick callback)
|
||||||
|
{
|
||||||
|
_onClick = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setText(string text)
|
||||||
|
{
|
||||||
|
_text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
source/ncui/widgets/container.d
Normal file
130
source/ncui/widgets/container.d
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
module ncui.widgets.container;
|
||||||
|
|
||||||
|
import ncui.widgets.widget;
|
||||||
|
import ncui.core.window;
|
||||||
|
import ncui.core.event;
|
||||||
|
import ncui.engine.screen;
|
||||||
|
import ncui.engine.action;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WidgetContainer — контейнер виджетов: хранит список, управляет фокусом и
|
||||||
|
* раздаёт ввод/отрисовку.
|
||||||
|
*
|
||||||
|
* - render(): вызывает render() у каждого виджета.
|
||||||
|
* - handle():
|
||||||
|
* * Tab -> focusNext(+1) и возвращает ScreenAction.none()
|
||||||
|
* * иначе: если фокус валиден -> передаём событие текущему виджету
|
||||||
|
*
|
||||||
|
* Правила фокуса:
|
||||||
|
* - нельзя поставить фокус на виджет, если он:
|
||||||
|
* * не focusable
|
||||||
|
* * или disabled (enabled == false)
|
||||||
|
*/
|
||||||
|
final class WidgetContainer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Список дочерних виджетов.
|
||||||
|
IWidget[] _children;
|
||||||
|
// Индекс сфокусированного виджета или -1, если фокуса нет.
|
||||||
|
int _focus = -1;
|
||||||
|
public:
|
||||||
|
// Добавить виджет в контейнер.
|
||||||
|
void add(IWidget widget)
|
||||||
|
{
|
||||||
|
// Индекс элемента ДО добавления.
|
||||||
|
const int index = cast(int) _children.length;
|
||||||
|
|
||||||
|
_children ~= widget;
|
||||||
|
|
||||||
|
// Если фокуса ещё нет — дать первому фокусируемому/включенному.
|
||||||
|
if (_focus < 0 && widget.focusable && widget.enabled)
|
||||||
|
_focus = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Текущий индекс фокуса (или -1).
|
||||||
|
@property int focusIndex() const
|
||||||
|
{
|
||||||
|
return _focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Установить фокус на виджет по индексу.
|
||||||
|
bool setFocus(int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (cast(size_t) index >= _children.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto widget = _children[index];
|
||||||
|
if (!widget.focusable || !widget.enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_focus = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Переключить фокус на следующий/предыдущий доступный виджет.
|
||||||
|
bool focusNext(int delta = +1)
|
||||||
|
{
|
||||||
|
// Если нет элементов для фокуса.
|
||||||
|
if (_children.length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const int count = cast(int) _children.length;
|
||||||
|
// Точка начала текущего элемента.
|
||||||
|
int index = _focus < 0 ? 0 : _focus;
|
||||||
|
for (int step = 0; step < count; ++step)
|
||||||
|
{
|
||||||
|
index += delta;
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= count)
|
||||||
|
{
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto widget = _children[index];
|
||||||
|
if (!widget.focusable || !widget.enabled)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_focus = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отрисовка виджетов.
|
||||||
|
void render(Window window, ScreenContext context)
|
||||||
|
{
|
||||||
|
foreach (i, child; _children)
|
||||||
|
{
|
||||||
|
child.render(window, context, i == _focus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка ввода для контейнера.
|
||||||
|
ScreenAction handle(ScreenContext context, KeyEvent event)
|
||||||
|
{
|
||||||
|
// Tab — переключение фокуса внутри окна.
|
||||||
|
if (isTab(event))
|
||||||
|
{
|
||||||
|
focusNext(+1);
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если фокуса нет или он вышел за границы.
|
||||||
|
if (_focus < 0 || _focus >= _children.length)
|
||||||
|
return ScreenAction.none();
|
||||||
|
|
||||||
|
// Иначе отдать ввод текущему виджету.
|
||||||
|
return _children[_focus].handle(context, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
source/ncui/widgets/widget.d
Normal file
43
source/ncui/widgets/widget.d
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
module ncui.widgets.widget;
|
||||||
|
|
||||||
|
import ncui.core.window;
|
||||||
|
import ncui.core.event;
|
||||||
|
import ncui.engine.screen;
|
||||||
|
import ncui.engine.action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Базовый интерфейс виджета.
|
||||||
|
*/
|
||||||
|
interface IWidget
|
||||||
|
{
|
||||||
|
// Можно ли на него поставить фокус.
|
||||||
|
@property bool focusable();
|
||||||
|
|
||||||
|
// Активен ли виджет.
|
||||||
|
@property bool enabled();
|
||||||
|
|
||||||
|
// Отрисовка.
|
||||||
|
void render(Window window, ScreenContext context, bool focused);
|
||||||
|
|
||||||
|
// Ввод.
|
||||||
|
ScreenAction handle(ScreenContext context, KeyEvent event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка стандартных нажатий клавиш.
|
||||||
|
|
||||||
|
bool isTab(KeyEvent ev)
|
||||||
|
{
|
||||||
|
return ev.isChar && ev.ch == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnter(KeyEvent ev)
|
||||||
|
{
|
||||||
|
if (ev.isChar && (ev.ch == '\n' || ev.ch == '\r')) return true;
|
||||||
|
if (ev.isKeyCode && cast(int)ev.ch == 343) return true; // KEY_ENTER часто 343
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSpace(KeyEvent ev)
|
||||||
|
{
|
||||||
|
return ev.isChar && ev.ch == ' ';
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue