Skip to content

Модуль клавиатурного ввода

В качестве следующего примера рассмотрим код модуля keyboard. Этот модуль можно скачать с репозитория модулей. Модуль позволяет организовать ввод пользователя с клавиатуры.

Идея модуля состоит в том, что клавиатурный ввод оформлен в виде специальной комнаты, в которой можно выполнить набор текста и вернуться к первоначальной комнате.

Комната, содержащая в себе логику клавиатурного ввода, носит имя @keyboard. Это системный объект, который не уничтожается при рестарте мира.

Для использования клавиатуры используется ссылка на системный объект (см. main3.lua):

lua
p [[Как вас {@keyboard "Имя"|зовут}?]];

Как видим, в качестве параметра передаётся информационная строка, которая будет отображена при вводе.

Объект @keyboard, соответственно, должен реализовать act обработчик, который перенесёт игрока в комнату клавиатурного ввода:

lua
	act = function(s, w, ...)
		s.title = w or "?"
		s.args = { ... }
		walkin(s)
	end;

Кроме того, что здесь мы меняем название комнаты (title) и делаем переход (walkin), мы также запомнили все дополнительные аргументы, если они передавались в ссылке {@keyboard}. Зачем это нужно, будет ясно позднее.

Итак, осталось реализовать клавиатурный ввод. Для этого мы воспользуемся ссылками кнопок (для того, чтобы вводить текст можно было на устройствах без клавиатуры). И одновременно с этим, будем отслеживать нажатия на клавиши с помощью модуля keys (для удобства игроков на компьютерах с клавиатурой).

Формирование ссылок-клавиш осуществляется в decor. При этом, ссылки формируются в виде:

lua
row = row.."{@kbdinput "..vv.."|"..input_esc(a).."}"..fmt.nb "  ";

input_esc() -- функция, реализованная в модуле, которая экранирует некоторые символы, типа { и }.

Как видим, это снова ссылки на системный объект, но теперь это уже @kbdinput. Этот объект специально создан для обработки событий от клавиш. Прежде чем мы перейдём к нему, рассмотрим вопрос использования модуля keys.

Для использования keys мы должны:

  • определить onkey в комнате @keyboard;
  • задать keys:filter()

Мы могли бы просто определить в модуле:

lua
function keys:filter()
-- логика функции
end

Но мы хотим, чтобы наш модуль мог работать совместно с каким-то другим применением модуля keys, поэтому при старте игры мы перехватываем старый обработчик keys:filter, заменяя его на свой. А потом, при деинициализации мира возвращаем старый обработчик обратно (когда он уже не нужен).

lua
local hooked
local orig_filter

std.mod_start(function(load)
	if not hooked then
		hooked = true
		orig_filter = std.rawget(keys, 'filter')
		std.rawset(keys, 'filter', std.hook(keys.filter, function(f, s, press, key)
			if std.here().keyboard_type then
				return hook_keys[key]
			end
			return f(s, press, key)
		end))
	end
end)

std.mod_done(function(load)
	hooked = false
	std.rawset(keys, 'filter', orig_filter)
end)

Здесь мы видим использование нескольких функций:

  • std.rawget(таблица, имя) - получить элемент таблицы без генерации сообщения о несуществующей переменой (работа с переменными на низком уровне);
  • std.rawset(таблица, имя, значение) - установить значение элемента таблицы без генерации сообщения о необъявленной переменной;
  • std.hook(старая функция, функция перехвата) - создать функцию перехвата.

std.rawget/std.rawset -- это способ работы с переменными на самом низком уровне. Например, если бы в mod_start мы просто присвоили keys.filter = std.hook ..., то мы получили бы сообщение о том, что мы меняем объект, который не является переменной.

Итак, если клавиши нажимаются в момент нахождения в комнате @keyboard, то мы используем наш фильтр клавиш, если нет -- используется перехваченный фильтр клавиш (keys:filter).

Массив hook_keys содержит все те клавиши, которые мы перехватываем.

Объект @kbdinput реализует act, который занимается обработкой вводимых символов. Когда текст набран и ввод подтверждён клавишей ввод или соответствующей ссылкой выполняется следующий код:

lua
walkback();
return std.call(std.here(), 'onkbd', _'@keyboard'.text, std.unpack(_'@keyboard'.args))

Как видим, происходит вызов обработчика onkbd в комнате из которой мы пришли. В качестве первого параметра передаётся введённый текст. Затем передаются все те параметры, которые пользователь задал в ссылке @keyboard. Эти параметры помогут идентифицировать поле ввода, если мы хотим использовать несколько полей ввода в одной комнате.

Здесь используются следующие функции:

  • std.unpack(таблица) -- превратить таблицу в набор аргументов;
  • std.call(объект, метод, аргументы) -- вызвать обработчик объекта instead.

Теперь вы можете самостоятельно разобраться в остальных деталях модуля @keyboard.