Добавлена базовая структура движка.

This commit is contained in:
Alexander Zhirov 2026-01-06 10:59:06 +03:00
parent 405a6e7ead
commit 717f9f2b82
Signed by: alexander
GPG key ID: C8D8BE544A27C511
3 changed files with 312 additions and 0 deletions

119
source/ncui/engine/action.d Normal file
View 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
View 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;
}
}

View 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;
}
}