diff --git a/example/dub.json b/example/dub.json index 53b9627..bcbb824 100644 --- a/example/dub.json +++ b/example/dub.json @@ -7,5 +7,8 @@ "ncui": { "path": ".." } - } + }, + "libs": [ + "form" + ] } diff --git a/example/source/simple/app.d b/example/source/simple/app.d index 9a25fb5..10bb719 100644 --- a/example/source/simple/app.d +++ b/example/source/simple/app.d @@ -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) diff --git a/source/ncui/core/ncwin.d b/source/ncui/core/ncwin.d index 804b374..4249cca 100644 --- a/source/ncui/core/ncwin.d +++ b/source/ncui/core/ncwin.d @@ -22,4 +22,14 @@ struct NCWin { return _p is null; } + + void opAssign(NCWin rhs) + { + _p = rhs._p; + } + + void opAssign(WINDOW* rhs) + { + _p = rhs; + } } diff --git a/source/ncui/core/session.d b/source/ncui/core/session.d index 1e3d7cc..43ff71a 100644 --- a/source/ncui/core/session.d +++ b/source/ncui/core/session.d @@ -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; diff --git a/source/ncui/engine/basescreen.d b/source/ncui/engine/basescreen.d index b87e603..02100d1 100644 --- a/source/ncui/engine/basescreen.d +++ b/source/ncui/engine/basescreen.d @@ -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(); } } diff --git a/source/ncui/package.d b/source/ncui/package.d index 5aa3ee0..80227fe 100644 --- a/source/ncui/package.d +++ b/source/ncui/package.d @@ -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; diff --git a/source/ncui/widgets/textinput.d b/source/ncui/widgets/textinput.d new file mode 100644 index 0000000..eb36de0 --- /dev/null +++ b/source/ncui/widgets/textinput.d @@ -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(); + } +}