Compare commits

..

1 commit

7 changed files with 280 additions and 11 deletions

View file

@ -7,5 +7,8 @@
"ncui": {
"path": ".."
}
}
},
"libs": [
"form"
]
}

View file

@ -22,7 +22,12 @@ final class Simple : ScreenBase
override void build(ScreenContext context, Window window, WidgetContainer ui)
{
auto okBtn = new Button(3, 2, "OK", () => ScreenAction.push(new Simple()));
auto textIntput = new TextInput(5, 2, 10, "Hello");
// auto okBtn = new Button(3, 2, "OK", () => ScreenAction.push(new Simple()));
auto okBtn = new Button(3, 2, "OK", () {
textIntput.close();
return ScreenAction.quit(ScreenResult.none());
});
auto cancelBtn = new Button(3, 9, "Cancel", () => ScreenAction.pop(ScreenResult.none()));
auto disableOk = new Checkbox(4, 2,"Disable OK", false, (checked) {
@ -32,6 +37,7 @@ final class Simple : ScreenBase
_ui.add(okBtn);
_ui.add(cancelBtn);
_ui.add(disableOk);
_ui.add(textIntput);
}
override ScreenAction handleGlobal(ScreenContext context, KeyEvent event)

View file

@ -22,4 +22,14 @@ struct NCWin
{
return _p is null;
}
void opAssign(NCWin rhs)
{
_p = rhs._p;
}
void opAssign(WINDOW* rhs)
{
_p = rhs;
}
}

View file

@ -131,12 +131,13 @@ final class Session
private:
NCWin _root;
bool _ended;
SessionConfig _config;
// Применяет параметры конфигурации к активной ncurses-сессии.
void setup(ref const(SessionConfig) config)
void setup(ref const(SessionConfig) sc)
{
// Настройка режима обработки ввода терминалом.
final switch (config.mode)
final switch (sc.mode)
{
case InputMode.raw:
ncuiNotErr!nocbreak();
@ -154,7 +155,7 @@ private:
break;
}
// Настройка отображения вводимых символов.
final switch (config.echo)
final switch (sc.echo)
{
case Echo.on:
ncuiNotErr!echo();
@ -165,15 +166,15 @@ private:
break;
}
// Настройка видимости курсора.
ncuiNotErr!curs_set(config.cursor);
ncuiNotErr!curs_set(sc.cursor);
// Настройка задержки при нажатии на ESC
ncuiNotErr!set_escdelay(config.escDelay);
ncuiNotErr!set_escdelay(sc.escDelay);
// Настройка обработки специальных клавиш
ncuiNotErr!keypad(_root, config.keypad);
ncuiNotErr!keypad(_root, sc.keypad);
}
public:
this(const SessionConfig config)
this(const SessionConfig sc)
{
// Если на этапе инициализации сработает проблема с конфигурированием сессии
scope (failure)
@ -193,7 +194,9 @@ public:
// Установить флаг инициализации ncurses
gInitialized = true;
// Применение конфигурации
setup(config);
setup(sc);
_config = sc;
}
NCWin root()
@ -201,6 +204,11 @@ public:
return _root;
}
@property SessionConfig config()
{
return _config;
}
KeyEvent readKey(NCWin inputWindow)
{
dchar ch;

View file

@ -14,6 +14,8 @@ protected:
WidgetContainer _ui;
bool _built;
bool _clearNext = true;
void ensureWindow(ScreenContext context);
void build(ScreenContext context, Window window, WidgetContainer ui);
void layout(ScreenContext context, Window window, WidgetContainer ui)
@ -31,7 +33,12 @@ private:
import deimos.ncurses : doupdate;
import ncui.lib.checks;
_window.erase();
if (_clearNext)
{
_window.erase();
_clearNext = false;
}
layout(context, _window, _ui);
_ui.render(_window, context);
_window.noutrefresh();
@ -65,6 +72,8 @@ public:
_built = true;
}
_clearNext = true;
renderAll(context);
return ScreenAction.none();
}
@ -103,6 +112,7 @@ public:
_window = null;
_built = false;
_clearNext = true;
_ui = new WidgetContainer();
}
}

View file

@ -19,3 +19,4 @@ public import ncui.lib.logger;
public import ncui.widgets.container;
public import ncui.widgets.button;
public import ncui.widgets.checkbox;
public import ncui.widgets.textinput;

View file

@ -0,0 +1,231 @@
module ncui.widgets.textinput;
import deimos.form;
import ncui.widgets.widget;
import ncui.core.window;
import ncui.core.event;
import ncui.core.ncwin;
import ncui.engine.screen;
import ncui.engine.action;
import ncui.lib.checks;
import std.string : toStringz;
alias OnChange = void delegate(string text);
final class TextInput : IWidget
{
private:
OnChange _onChange;
int _y;
int _x;
int _width;
bool _enabled = true;
FIELD* _field;
// 0: field, 1: null (терминатор для new_form)
FIELD*[2] _fields;
FORM* _form;
NCWin _window;
NCWin _subWindow;
bool _posted = false;
void ensureCreated()
{
if (_form !is null)
{
return;
}
_field = new_field(1, _width, 0, 0, 0, 0);
set_field_back(_field, A_UNDERLINE);
field_opts_off(_field, O_AUTOSKIP);
field_opts_on(_field, O_EDIT);
_fields[0] = _field;
_fields[1] = null;
_form = new_form(cast(FIELD**) _fields.ptr);
}
void bindTo(Window window)
{
auto parent = window.handle;
if (_window.ptr == parent.ptr && !_subWindow.isNull)
{
return;
}
if (_posted)
{
unpost_form(_form);
_posted = false;
}
if (!_subWindow.isNull)
{
delwin(_subWindow);
_subWindow = NCWin(null);
}
_subWindow = derwin(parent, 1, _width, _y, _x);
_window = parent;
set_form_win(_form, parent);
set_form_sub(_form, _subWindow);
post_form(_form);
_posted = true;
}
void applyStyle(ScreenContext context, bool focused)
{
if (focused && _enabled)
{
set_field_back(_field, A_UNDERLINE | A_REVERSE);
pos_form_cursor(_form);
import ncui.core.session : Cursor;
curs_set(Cursor.high);
}
else
{
set_field_back(_field, A_UNDERLINE);
curs_set(context.session.config.cursor);
}
}
void driveRequest(int request)
{
form_driver_w(_form, KEY_CODE_YES, request);
}
void driveChar(dchar ch)
{
form_driver_w(_form, OK, ch);
}
public:
this(int y, int x, int width, string initial = string.init, OnChange onChange = null)
{
_y = y;
_x = x;
_width = width;
_onChange = onChange;
ensureCreated();
if (initial.length)
{
set_field_buffer(_field, 0, initial.toStringz);
}
}
override @property bool focusable()
{
return true;
}
override @property bool enabled()
{
return _enabled;
}
override void render(Window window, ScreenContext context, bool focused)
{
ensureCreated();
bindTo(window);
applyStyle(context, focused);
}
override ScreenAction handle(ScreenContext context, KeyEvent event)
{
if (!_enabled)
{
return ScreenAction.none();
}
bool changed = false;
if (event.isKeyCode)
{
switch (event.ch)
{
case KEY_LEFT:
driveRequest(REQ_PREV_CHAR);
break;
case KEY_RIGHT:
driveRequest(REQ_NEXT_CHAR);
break;
case KEY_HOME:
driveRequest(REQ_BEG_LINE);
break;
case KEY_END:
driveRequest(REQ_END_LINE);
break;
case KEY_BACKSPACE:
driveRequest(REQ_DEL_PREV);
changed = true;
break;
case KEY_DC:
driveRequest(REQ_DEL_CHAR);
changed = true;
break;
default:
return ScreenAction.none();
}
}
else if (event.isChar)
{
// Backspace иногда приходит как символ (127 или '\b')
if (event.ch == 127 || event.ch == '\b')
{
driveRequest(REQ_DEL_PREV);
changed = true;
}
else
{
driveChar(event.ch);
changed = true;
}
}
return ScreenAction.none();
}
void close()
{
if (_posted && _form !is null)
{
unpost_form(_form);
_posted = false;
}
if (_form !is null)
{
free_form(_form);
_form = null;
}
if (_field !is null)
{
free_field(_field);
_field = null;
}
if (!_subWindow.isNull)
{
delwin(_subWindow);
_subWindow = NCWin(null);
}
_window = NCWin(null);
}
~this()
{
close();
}
}