Добавлена базовая структура движка.
This commit is contained in:
parent
405a6e7ead
commit
717f9f2b82
3 changed files with 312 additions and 0 deletions
119
source/ncui/engine/action.d
Normal file
119
source/ncui/engine/action.d
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* Команды, которые экран возвращает движку.
|
||||||
|
*/
|
||||||
|
module ncui.engine.action;
|
||||||
|
|
||||||
|
import ncui.engine.screen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Тип команды, которую экран возвращает движку.
|
||||||
|
*
|
||||||
|
* `ActionKind` описывает, что именно движок должен сделать после обработки ввода:
|
||||||
|
* изменить стек экранов, сменить тему, завершить приложение и т.д.
|
||||||
|
*/
|
||||||
|
enum ActionKind
|
||||||
|
{
|
||||||
|
// Ничего не делать.
|
||||||
|
None,
|
||||||
|
// Добавить новый экран поверх текущего.
|
||||||
|
Push,
|
||||||
|
// Заменить верхний экран стека новым.
|
||||||
|
Replace,
|
||||||
|
// Удалить один или несколько экранов с вершины стека.
|
||||||
|
Pop,
|
||||||
|
// Завершить выполнение UI-цикла.
|
||||||
|
Quit
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Базовые типы результата экрана.
|
||||||
|
*/
|
||||||
|
enum ScreenKind
|
||||||
|
{
|
||||||
|
// Результат отсутствует или не имеет специальной семантики.
|
||||||
|
None,
|
||||||
|
// Отмена действия.
|
||||||
|
Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Результат работы экрана.
|
||||||
|
*/
|
||||||
|
struct ScreenResult
|
||||||
|
{
|
||||||
|
// Общий тип результата
|
||||||
|
ScreenKind kind;
|
||||||
|
|
||||||
|
static ScreenResult none()
|
||||||
|
{
|
||||||
|
return ScreenResult(ScreenKind.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScreenResult cancel()
|
||||||
|
{
|
||||||
|
return ScreenResult(ScreenKind.Cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Действие, которое возвращает экран.
|
||||||
|
*/
|
||||||
|
struct ScreenAction
|
||||||
|
{
|
||||||
|
// Тип действия.
|
||||||
|
ActionKind kind;
|
||||||
|
// Следующий экран (используется для `Push` и `Replace`).
|
||||||
|
IScreen next;
|
||||||
|
// Результат (используется для `Pop`, `Quit`).
|
||||||
|
ScreenResult result;
|
||||||
|
/**
|
||||||
|
* Ничего не делать.
|
||||||
|
*
|
||||||
|
* Возвращается, если стек и состояние движка менять не требуется.
|
||||||
|
*/
|
||||||
|
static ScreenAction none()
|
||||||
|
{
|
||||||
|
return ScreenAction(ActionKind.None, null, ScreenResult.none());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Добавить новый экран поверх текущего.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* - screen: создаваемый экран.
|
||||||
|
*/
|
||||||
|
static ScreenAction push(IScreen screen)
|
||||||
|
{
|
||||||
|
// assert(isPointer!(typeof(result)), "ncuiNotNull expects a function that returns a pointer.");
|
||||||
|
return ScreenAction(ActionKind.Push, screen, ScreenResult.none());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Заменить верхний экран стека новым.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* - screen: создаваемый экран (не должен быть `null`).
|
||||||
|
*/
|
||||||
|
static ScreenAction replace(IScreen screen)
|
||||||
|
{
|
||||||
|
return ScreenAction(ActionKind.Replace, screen, ScreenResult.none());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Закрыть верхний экран и передать результат родителю.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* - result: результат закрываемого экрана.
|
||||||
|
*/
|
||||||
|
static ScreenAction pop(ScreenResult result)
|
||||||
|
{
|
||||||
|
return ScreenAction(ActionKind.Pop, null, result);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Завершить UI-цикл и вернуть финальный результат наружу.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* - result: финальный результат приложения (возвращается из `NCUI.run()`).
|
||||||
|
*/
|
||||||
|
static ScreenAction quit(ScreenResult result)
|
||||||
|
{
|
||||||
|
return ScreenAction(ActionKind.Quit, null, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
source/ncui/engine/ncui.d
Normal file
107
source/ncui/engine/ncui.d
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* Главный движок: стек экранов + цикл ввода.
|
||||||
|
*/
|
||||||
|
module ncui.engine.ncui;
|
||||||
|
|
||||||
|
import core.stdc.stdlib : EXIT_SUCCESS, EXIT_FAILURE, exit;
|
||||||
|
import std.array : popBack;
|
||||||
|
|
||||||
|
import ncui.lib.logger;
|
||||||
|
import ncui.core.session;
|
||||||
|
import ncui.engine.screen;
|
||||||
|
import ncui.engine.action;
|
||||||
|
|
||||||
|
final class NCUI
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Корневая сессия ncurses.
|
||||||
|
Session _session;
|
||||||
|
// Контекст выполнения действующей сессии.
|
||||||
|
ScreenContext _context;
|
||||||
|
// Стек экранов.
|
||||||
|
IScreen[] _stack;
|
||||||
|
// Флаг активности работы движка.
|
||||||
|
bool _running;
|
||||||
|
// Конечный результат выполнения.
|
||||||
|
ScreenResult _result;
|
||||||
|
|
||||||
|
void apply(ScreenAction action)
|
||||||
|
{
|
||||||
|
while (_running && action.kind != ActionKind.None)
|
||||||
|
{
|
||||||
|
final switch (action.kind)
|
||||||
|
{
|
||||||
|
case ActionKind.Push:
|
||||||
|
_session.clear();
|
||||||
|
_stack ~= action.next;
|
||||||
|
action = action.next.onShow(_context);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionKind.Replace:
|
||||||
|
if (_stack.length != 0)
|
||||||
|
{
|
||||||
|
_stack[$ - 1].close();
|
||||||
|
_stack.popBack();
|
||||||
|
}
|
||||||
|
_session.clear();
|
||||||
|
_stack ~= action.next;
|
||||||
|
action = action.next.onShow(_context);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionKind.Pop:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionKind.Quit:
|
||||||
|
_result = action.result;
|
||||||
|
_running = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case ActionKind.None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
this(const SessionConfig config = SessionConfig.init)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_session = new Session(config);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
error("Failed to initialize the session: ", e.msg);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context = ScreenContext(_session);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenResult run(IScreen screen)
|
||||||
|
{
|
||||||
|
// Пометить работу движка активным.
|
||||||
|
_running = true;
|
||||||
|
// Положить первый экран в стек для начала работы.
|
||||||
|
apply(ScreenAction.push(screen));
|
||||||
|
|
||||||
|
while (_running && _stack.length != 0)
|
||||||
|
{
|
||||||
|
// Взять из стека последний экран.
|
||||||
|
auto currentScreen = _stack[$ - 1];
|
||||||
|
// Ожидать события нажатия клавиш в извлеченном из стека экране.
|
||||||
|
auto event = _session.readKey(currentScreen.inputWindow());
|
||||||
|
// Обработать нажатие клавиши в текущем окне.
|
||||||
|
auto action = currentScreen.handle(_context, event);
|
||||||
|
// Обработать возвращенное действие из окна.
|
||||||
|
apply(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Завершить сессию ncurses.
|
||||||
|
_session.close();
|
||||||
|
|
||||||
|
info("Engine successfully stopped");
|
||||||
|
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
source/ncui/engine/screen.d
Normal file
86
source/ncui/engine/screen.d
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* Контракты экранов (screen) и контекст выполнения.
|
||||||
|
*/
|
||||||
|
module ncui.engine.screen;
|
||||||
|
|
||||||
|
import ncui.core.session;
|
||||||
|
import ncui.core.event;
|
||||||
|
import ncui.core.ncwin;
|
||||||
|
import ncui.core.window;
|
||||||
|
|
||||||
|
import ncui.engine.action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контекст выполнения экрана.
|
||||||
|
*/
|
||||||
|
struct ScreenContext
|
||||||
|
{
|
||||||
|
Session session;
|
||||||
|
|
||||||
|
this(Session s)
|
||||||
|
{
|
||||||
|
session = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Базовый интерфейс экрана.
|
||||||
|
*
|
||||||
|
* Правила:
|
||||||
|
* - `onShow` должен нарисовать экран.
|
||||||
|
* - `handle` обрабатывает ввод.
|
||||||
|
* - `inputWindow` говорит движку, из какого окна читать ввод.
|
||||||
|
* - `close` освобождает ресурс.
|
||||||
|
*/
|
||||||
|
interface IScreen
|
||||||
|
{
|
||||||
|
// Вызывается движком, когда экран становится активным (оказался наверху стека)
|
||||||
|
// или когда движок явно инициирует перерисовку (зависит от реализации).
|
||||||
|
ScreenAction onShow(ScreenContext context);
|
||||||
|
// Вызывается движком после закрытия дочернего экрана (Pop/PopTo),
|
||||||
|
// чтобы передать родителю результат дочернего экрана.
|
||||||
|
ScreenAction onChildResult(ScreenContext context, ScreenResult child);
|
||||||
|
// Обработка события ввода.
|
||||||
|
// Вызывается движком для активного экрана при получении события клавиатуры.
|
||||||
|
ScreenAction handle(ScreenContext context, KeyEvent event);
|
||||||
|
|
||||||
|
// Окно, из которого движок должен читать ввод для этого экрана.
|
||||||
|
NCWin inputWindow();
|
||||||
|
|
||||||
|
// Освобождение ресурсов экрана.
|
||||||
|
void close();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ScreenBase : IScreen
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Window _window;
|
||||||
|
|
||||||
|
public:
|
||||||
|
override NCWin inputWindow()
|
||||||
|
{
|
||||||
|
return _window.handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
override ScreenAction onShow(ScreenContext context)
|
||||||
|
{
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
override ScreenAction onChildResult(ScreenContext context, ScreenResult child)
|
||||||
|
{
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
override ScreenAction handle(ScreenContext context, KeyEvent event)
|
||||||
|
{
|
||||||
|
return ScreenAction.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void close()
|
||||||
|
{
|
||||||
|
if (_window !is null)
|
||||||
|
_window.close();
|
||||||
|
_window = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue