Добавлен новый модуль виджетов.
Реализована механика работы с виджетами по нажатию 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.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