Перейти к содержимому
Doob

Псевдографические интерфейсы в OpenComputers [ч2 кнопки]

Рекомендуемые сообщения

Кнопки, это самый распространенный элемент интерфейса, и не потому, что их полно в каждой графической программе. Функционал кнопки наследует большинство средство ввода, например: ссылки, переключатели, ползунки, менюшки и т. д. Сейчас, огромные возможности для создания других, более подходящих ситуации способов ввода, но традиционно используются кнопки, которые появились в первых электронных устройствах, т. к. они просты и интуитивно понятны.
 
Внешний вид.
Обычная кнопка имеет прямоугольный вид, это позволяет не заморачиваться с алгоритмом взаимодействия, удобно отображать на ней текст/изображения, но если необходимо, ее можно сделать какой угодно формы.
Самое главное в кнопке это ее интерактивность, т. е. она должна не только выполнять функцию, для которой назначена, но и сигнализировать свой статус (иметь обратную связь).
Например, при нажатии на кнопку, она меняет цвет/визуально проваливается/издает звук, все это вместе или по-отдельности дает пользователю знать, что она активирована, а цветом или звуком можно даже передать состояние выполнения задачи, выполняемой этой кнопкой. А вот если пользователь нажимает на кнопку и ничего не происходит, то он может клацнуть по ней еще много раз, пока кнопка будет выполнять задачу. В большинстве случаев это довольно опасно - накликают беду, а винят программиста. Поэтому, если в текущей ситуации кнопка неактивна, надо это подчеркнуть, например, обесцветив ее или частично слив с фоном, можно дополнить специальным звуком.
Все это очевидно, поэтому не стоит об этом забывать.
 
Внутреннее устройство.
В графическом интерфейсе, кнопка это определенно обозначенное место на экране, в которое нужно тыкнуть, чтобы получить определенный результат, обозначенный на кнопке в виде текста/изображения или понимаемый из контекста.
Как это реализовать в OpenComputers?
Нам доступен вывод информации на монитор, посредством компонента GPU. Пока рассмотрим функции назначения цветов и вывода символов.
 
Работа с видеокартой.
Список методов видеокарты можно посмотреть на вики.
Для назначения цвета текста используется метод setForeground.
А чтобы задать цвет фона, есть метод setBackground. Чтобы проверить, как это работает обратимся к компьютеру с установленной OpenOS.
Запустим компьютер. Вводим команду edit gpu_test.lua, так мы запускаем текстовый редактор OpenOS, указывая, что нужно редактировать файл gpu_test.lua. Пока этого файла нет, но когда в редакторе нажмем Ctrl+S он сохранится.
 
Чтобы мы могли обратится к компоненту gpu, нам надо загрузить обертку для компонентов, предоставляемую OpenOS, она немного облегчает работу.

Для этого, в самом начале программы пишем local component = require('component'), так, что это за письмена? Эта строка создает локальную переменную (указываем словом local перед именем переменной), далее указываем имя переменной.
Знак равенства указывает, что в переменной теперь будет хранится то, что дает функция require, в данном случае мы задали этой функции передать переменной component возможности обертки, которая тоже называется component. Так будет удобней понимать, к чему мы обращаемся.

На следующей строке пишем local gpu = component.gpu Это мы назначили переменной gpu все методы видеокарты, т. е. теперь можем обращаться к ней через эту переменную.

При загрузке, OpenOS сделала несколько скрытых операций: определила основную видеокарту, подключила ее к монитору и добавила их к остальным компонентам, чтобы можно было обратиться по имени компонента и получить готовый интерфейс.
 
Итак, видеокарта готова к работе, теперь опишем, что она должна сделать.
Создаем переменные, содержащие информацию о цветах и тексте, с которыми мы будем работать.
local color1, color2, text = 0xff00ff, 0x0000ff, 'Hello, OpenComputers!'
При помощи параллельного присваивания мы задали две переменные с числовыми значениями цветов и переменную с текстом.
Теперь создадим простой цикл, в котором выведем текст на экран, назначая цвет текста и цвет фона.

for i = 1, 10 do
  gpu.setForeground(color1)
  gpu.setBackground(color2)
  gpu.set(10, 5, text)
  color1, color2 = color2, color1
  os.sleep(0.5)
end

Сам код назначает тексту цвет, указанный в color1, а фону color2, затем выводит текст, содержащийся в переменной text на монитор, указывая координаты первого символа (от левого верхнего угла 10 символов в право, 5 символов вниз). Далее, параллельным присваиванием, меняем значения переменных местами, чтобы при следующем выполнении кода, цвета фона и текста поменялись. В конце цикла ставим задержку в пол-секунды, используя системную функцию sleep.

Цикл готов, теперь, в конце программы стоит добавить восстановление цветов текста и фона, т. к. по завершению ее работы, цвета останутся такими, которые были назначены в последний раз.
Готовый алгоритм будет примерно такой:

 

local component = require('component')
local gpu = component.gpu
local color1, color2, text = 0xff00ff, 0x0000ff, 'Hello, OpenComputers!'
for i = 1, 10 do
  gpu.setForeground(color1)
  gpu.setBackground(color2)
  gpu.set(10, 5, text)
  color1, color2 = color2, color1
  os.sleep(0.5)
end
gpu.setForeground(0xffffff)
gpu.setBackground(0)

6aZOVlk.png

 



Нажимаем Ctrl+S, чтобы сохранить файл, затем Ctrl+W, чтобы закрыть редактор. Теперь мы снова попали в шелл. Вызываем только-что созданную программу по имени: gpu_test, и наблюдаем ее выполнение. Так как координаты вывода жестко заданы, мы видим, что текст как-бы мигает. Чтобы увидеть эволюцию работы цикла, можно к координатам вывода добавить значения шага цикла. Для этого откроем файл edit gpu_test.lua, в функции gpu.set к каждому числу прибавим переменную-итератор. Теперь вызов функции gpu.set выглядит вот так: gpu.set(10+i, 5+i, text), сохраним изменения (Ctrl+S), закроем (Ctrl+W), и опять запустим тестовую программу gpu_test
 
Работа с вводом.
Со способом вывода информации более-менее разобрались, теперь разберемся с вводом, т. к. мы хотим создать графический интерфейс, хоть и 'псевдо', мы будем использовать возможность сенсорного ввода, клавиатуру поставим для других задач.
Монитор I уровня это только средство вывода, а мониторы второго и третьего уровня могут работать и как средство ввода.
Когда игрок прикасается к экрану, генерируется сигнал touch, содержащий в себе адрес монитора, координаты касания, код кнопки мыши и ник игрока. Нам остается отследить это событие и соответсвующим образом на него отреагировать.
Для демонстрации работы напишем небольшую тестовую программу, которая будет ловить события касания экрана, отображать место и числовые координаты.
 

Создаем файл edit click_test.lua
Первым делом подключаем управление видеокартой
local gpu = require('component').gpu
Затем, подключим мониторинг событий, который предоставляет OpenOS.
local pull_e = require('event').pull
Общая документация тут, мы воспользуемся только одной функцией.
Теперь позаботимся о пользователе, чтобы при завершении программы цвет фона остался такими же, как и перед запуском, мы его сохраним в переменную:
local b_color = gpu.getBackground() Эта функция сообщает цвет фона, который сейчас используется, для цвета текста есть getForeground, но в данном тесте мы будем управлять только цветом фона.
Так как у мыши две кнопки, в событии touch указывается код кнопки: ЛКМ = 1, ПКМ = 0
Чтобы визуально различать касания, создадим таблицу с цветами для каждой кнопки. В качестве индекса будет использоваться код кнопки.
local color = {[0] = 0x00ff00, 0x0000ff} т. е. ЛКМ будет обозначаться синим, ПКМ - зеленым.
Теперь создадим цикл, в котором будем ловить и обрабатывать события.
while true do
Как следует из описания функции event.pull, можно фильтровать ненужное, передав в функцию название интересующего нас события. Так как возвращается довольно много переменных, будем создавать из них таблицу, для большего удобства.
local tEvent = {pull_e('touch')}
Теперь все события типа touch будут пересоздавать таблицу tEvent, в которой будет заключена информация об этом событии. Обратиться к интересующей информации можно, указав номер, в той же последовательности, как это описано в документации по сигналам
Для начала добавим условие выхода из программы, т. к. цикл бесконечный и прервать его можно только грубо нарушив выполнение программы.
if tEvent[3] == 1 and tEvent[4] == 1 then
Если клик произошел по координатам (1, 1), выполнить следующий код:
gpu.setBackground(b_color) - вернем фону его первоначальное значение, полученное при запуске программы.
os.exit() - вызовем системную функцию выхода из программы.
Закроем условие:
end

А теперь опишем сам процесс отклика на касание.
Присваиваем фону цвет, исходя из полученного кода кнопки мыщи:
gpu.setBackground(color[tEvent[5]])
Устанавливаем по полученным координатам символ пробела, чтобы подсветить фон:
gpu.set(tEvent[3], tEvent[4], ' ')
Затем, по координатм (1, 1) показываем информацию о событии:
gpu.set(1, 1, 'x: '..tEvent[3]..' y: '..tEvent[4]..'\t\t')
В конце добавляется пустое место, чтобы не заморачиваться с затиранием предыдущих значений.
Закрываем цикл
end
 
В итоге получается такой код:

 

local gpu = require('component').gpu
local pull_e = require('event').pull
local b_color = gpu.getBackground()
local color = {[0] = 0x00ff00, 0x0000ff}
while true do
  local tEvent = {pull_e('touch')}
  if tEvent[3] == 1 and tEvent[4] == 1 then
    gpu.setBackground(b_color)
    os.exit()
  end
  gpu.setBackground(color[tEvent[5]])
  gpu.set(tEvent[3], tEvent[4], ' ')
  gpu.set(1, 1, 'x: '..tEvent[3]..' y: '..tEvent[4]..'    ')
end

J816upr.png

 


 
Сохраним и запустим программу. Кликая мышкой по монитору мы видим, как она оставляет следы и обновляется информация в левом верхнем углу.
Кликнув в левый верхний угол мы завершаем работу программы. Значит, мы между делом создали кнопку, хоть и невидимую.
Принцип работы очень прост - пользователь кликнул по экрану, а программа сравнила координаты клика с координатами кнопки, если они совпали, то выполняется код, назначенный для этой кнопки.
 

Теперь создадим нормальные кнопки, с более удобной конфигурацией.
Все кнопки будут находиться в таблице, при каждом клике будем в цикле обходить таблицу, проверяя координаты.
Дополнительно напишем пару функций - одна будет подсвечивать нажатую кнопку, другая переключать видимость.
Каждая кнопка будет содержать следующие параметры: статус отображения (т. е. активна кнопка или нет), координаты, размер, цвет фона кнопки, цвет текста, текст и исполняемую функцию.

{
  visible = boolean,
  X = number,
  Y = number,
  W = number,
  H = number,
  color = number,
  textColor = number,
  text = string,
  action = function
}

Загрузим видеокарту и захват событий.
local gpu = require('component').gpu
local pull_e = require('event').pull
Для того, чтобы очистить экран при запуске и поставить кнопку выхода в угол, нам понадобится узнать и сохранить текущее разрешение.
local W, H = gpu.getResolution()
Сохраняем цвета фона и текста, чтобы вернуть их при завершении программы.
local b_color, f_color = gpu.getBackground(), gpu.getForeground()
 
Создаем таблицу tButtons, в ней мы будем хранить все кнопки.
Добавляем кнопку выхода из программы:
Изначальное состояние кнопки: visible = false,
Устанавливаем кнопку в правый верхний угол, задаем размер в один символ: X = W, Y = 1, W = 1, H = 1,
Устанавливаем цвета: color = 0xff0000, textColor = 0xffffff,
Задаем символ: text = 'X',
Функция завершения программы немного изменилась - устанавливаются первоначальные цвета, очищается экран и вызывается функция выхода.

action = function()
  gpu.setBackground(b_color)
  gpu.setForeground(f_color)
  gpu.fill(1, 1, W, H, ' ')
  os.exit()
end

Опишем функцию рисования кнопки, в которую будет передаваться индекс кнопки.
local function drawButton(n)
задаем цвет кнопки
gpu.setBackground(tButtons[n].color)
задаем цвет текста
gpu.setForeground(tButtons[n].textColor)
заливаем прямоугольник, который занимает кнопка (в данном случае, берем координаты и размер кнопки из ее таблицы)
gpu.fill(tButtons[n].X, tButtons[n].Y, tButtons[n].W, tButtons[n].H, ' ')
для большей красоты отцентрируем текст по границам кнопки, для этого вычислим половину высоты и половину ширины кнопки, прибавим их к координатам кнопки, а у ширины еще отберем половину длинны текста
gpu.set(tButtons[n].X+(tButtons[n].W/2)-(#tButtons[n].text/2), tButtons[n].Y+(tButtons[n].H/2), tButtons[n].text)
закрываем функцию
end

 

Добавим переключение видимости кнопки.
local function toggleVisible(n)
if tButtons[n].visible then
если кнопка видима, то изменим статус tButtons[n].visible = false
установим цвет фона на тот, который был при запуске gpu.setBackground(b_color)
зальем прямоугольник фоновым цветом gpu.fill(tButtons[n].X, tButtons[n].Y, tButtons[n].W, tButtons[n].H, ' ')
else иначе:
изменим статус tButtons[n].visible = true

вызовем отрисовку этой кнопки drawButton(n)

закроем условие и функцию end end

 

Чтобы кнопка подмигивала при активации, создадим функцию меняющую цвет фона и текста, как в первом примере.
Для получения и назначения цветов обратимся к параметрам кнопки по индексу, как и в прошлых функциях.
меняем цвета tButtons[n].color, tButtons[n].textColor = tButtons[n].textColor, tButtons[n].color
рисуем кнопку drawButton(n)
делаем задержку os.sleep(0.09)
возвращаем цвета tButtons[n].color, tButtons[n].textColor = tButtons[n].textColor, tButtons[n].color
заново рисуем кнопку, с номальными цветами drawButton(n)

Функции описаны, теперь напишем саму программу.
При запуске очистим экран gpu.fill(1, 1, W, H, ' ')
Можно вынести ее в отдельную функцию, но в данном примере очистка вызвается только два раза - при запуске и при выходе.

Теперь активируем все нужные кнопки. В данном примере можно запустить все кнопки, поэтому пройдем в цикле по таблице с кнопками и для каждой вызовем переключатель видимости.

for i = 1, #tButtons do
  toggleVisible(i)
end

И опишем главный цикл.
while true do
ловим событие в таблицу tEvent local tEvent = {pull_e('touch')}
перебираем все кнопки for i = 1, #tButtons do
если кнопка видима if tButtons.visible then
сравниваем координаты клика с прямоугольником кнопки
if tEvent[3] >= tButtons.X and tEvent[3] <= tButtons.X+tButtons.W and tEvent[4] >= tButtons.Y and tEvent[4] <= tButtons.Y+tButtons.H then
если клик попал по этой кнопке, заставляем ее мигнуть blink(i)
выполняем функцию, которая назначена для нее tButtons.action()
прерываем цикл проверки кнопок break
закрываем циклы и условия end end end end

На этом все, программа готова, в готовом примере я добавил три кнопки - 'set' выводит строку, а 'del' стирает и бибикает спикером, 'reboot' перезагружает компьютер.

 

 

local gpu = require('component').gpu
local computer = require('computer')
local pull_e = require('event').pull
local W, H = gpu.getResolution()
local b_color, f_color = gpu.getBackground(), gpu.getForeground()

local tButtons = {
  {
    visible = false,
    X = W,
    Y = 1,
    W = 1,
    H = 1,
    color = 0xff0000,
    textColor = 0xffffff,
    text = 'X',
    action = function()
      gpu.setBackground(b_color)
      gpu.setForeground(f_color)
      gpu.fill(1, 1, W, H, ' ')
      os.exit()
  end
  },
  {
    visible = false,
    X = 1,
    Y = 3,
    W = 7,
    H = 1,
    color = 0x008800,
    textColor = 0xffff00,
    text = 'set',
    action = function()
      gpu.setForeground(0xffffff)
      gpu.set(1, 1, 'Hello!')
    end
  },
  {
    visible = false,
    X = 1,
    Y = 5,
    W = 7,
    H = 1,
    color = 0xAA0000,
    textColor = 0xffffff,
    text = 'del',
    action = function()
      computer.beep(1000, 0.1)
      gpu.setBackground(0)
      gpu.fill(1, 1, 6, 1, ' ')
    end
  },
  {
    visible = false,
    X = 1,
    Y = 7,
    W = 7,
    H = 1,
    color = 0xffffff,
    textColor = 0,
    text = 'reboot',
    action = function()
      computer.shutdown(true)
    end
  }
}

local function drawButton(n) -- функция рисования кнопки
  gpu.setBackground(tButtons[n].color) -- задаем цвет кнопки
  gpu.setForeground(tButtons[n].textColor) -- задаем цвет текста
  gpu.fill(tButtons[n].X, tButtons[n].Y, tButtons[n].W, tButtons[n].H, ' ') -- заливаем область
  gpu.set(tButtons[n].X+(tButtons[n].W/2)-(#tButtons[n].text/2), tButtons[n].Y+(tButtons[n].H/2), tButtons[n].text) -- пишем текст по центру
end

local function toggleVisible(n) -- переключение видимости кнопки
  if tButtons[n].visible then -- если кнопка видима
    tButtons[n].visible = false -- отключаем
    gpu.setBackground(b_color) -- берем цвет фона, полученный при старте программы
    gpu.fill(tButtons[n].X, tButtons[n].Y, tButtons[n].W, tButtons[n].H, ' ') -- стираем кнопку
  else -- если кнопка не активна
    tButtons[n].visible = true -- активируем
    drawButton(n) -- запускаем отрисовку
  end
end

local function blink(n) -- мигание кнопки
  tButtons[n].color, tButtons[n].textColor = tButtons[n].textColor, tButtons[n].color -- меняем местами цвета фона и текста
  drawButton(n) -- отрисовываем кнопку
  os.sleep(0.09) -- делаем задержку
  tButtons[n].color, tButtons[n].textColor = tButtons[n].textColor, tButtons[n].color -- меняем цвета обратно
  drawButton(n) -- перерисовываем кнопку
end

gpu.fill(1, 1, W, H, ' ') -- очищаем экран

for i = 1, #tButtons do
  toggleVisible(i) -- активируем каждую кнопку
end

while true do
  local tEvent = {pull_e('touch')} -- ждем клика
  for i = 1, #tButtons do -- перебираем все кнопки
    if tButtons[i].visible then -- если кнопка активна
      if tEvent[3] >= tButtons[i].X and tEvent[3] <= tButtons[i].X+tButtons[i].W and tEvent[4] >= tButtons[i].Y and tEvent[4] <= tButtons[i].Y+tButtons[i].H then -- если клик произведен в пределах кнопки
       blink(i) -- мигнуть кнопкой
       tButtons[i].action() -- выполнить назначенный код
       break
      end
    end
  end
end

WPBFuKa.png

 

 

Изменено пользователем Doob

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Картинки!

Как можно писайть гайд по интерфейсам без единого скриншота?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Тут видео надо, писать видео не могу, ибо негде писать звук.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Видео не обязательно. Тут нужен хотя бы скрин демо-интерфейса с кнопками.

Чтобы видно было, что не кот в мешке, а кнопки.  :)

Ну и можно гифку прилепить, которая покажет смену состояний.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

А можете написать код на кнопки по отдельности?

пожалуйста.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Гость Kartze

@@Barawik_, да ты че, шутишь? там черным по белому написано как запилить кнопку.

я тебе уже сказал, что не твое это. рано тебе в такое лезть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Заюзал данную программу, офигенно сделано. Но есть небольшие правки. Надо подключить юникод, если хочется работать с русскими буковками в названии кнопок.

--весь код нет смысла копировать поэтому куски кода вставлю
local un = require("unicode") --подключить юникод

...

local function drawButton(n) -- функция рисования кнопки
  gpu.setBackground(tButtons[n].color) -- задаем цвет кнопки
  gpu.setForeground(tButtons[n].textColor) -- задаем цвет текста
  gpu.fill(tButtons[n].X, tButtons[n].Y, tButtons[n].W, tButtons[n].H, ' ') -- заливаем область
  gpu.set(tButtons[n].X+(tButtons[n].W/2)-(un.len(tButtons[n].text)/2), tButtons[n].Y+(tButtons[n].H/2), tButtons[n].text) -- пишем текст по центру
end

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...