Лидеры
Популярный контент
Показан контент с высокой репутацией 24.03.2019 в Записи блога
-
2 баллаЧтобы было куда кликать, надо на экране разметить места для иконок, еще и иконки нарисовать. Для иконок возьмем формат PPM, а конкретно, цветную бинарную версию P6. Формат ультра-примитивный, иконки можно будет без лишних заморочек рисовать в любом нормальном растровом редакторе, в опенкомпах, а при наличии нужного скрипта - прямо в консоли. Но в этом формате будем хранить иконки на диске. Внутри программы они будут преобразовываться в таблицу, хранящую цвет каждого пикселя. Например, создадим иконку, которая будет рисоваться для всех типов файлов по умолчанию. (Будет отображаться, если подходящая иконка не загружена) local icons = {} -- создать массив с иконками local icons.unknown = {x = 10} -- создать таблицу для иконки, указать ширину в пикселях for i = 1, 100 do -- цикл заполнения таблицы 10x10 if i%3 == 0 then -- если номер пикселя делится на 3 icons.unknown[i] = 3394611 -- сделать пиксель зеленым else -- иначе icons.unknown[i] = 3355443 -- сделать серым end end С внутренним представлением определились, теперь напишем функцию отрисовки. Создадим счетчики для индексов, горизонтальной и вертикальной координаты. Запустим цикл, с условием: пока индекс меньше или равен (количество пикселей - ширина изображения). Установим для символа цвет текущего пикселя, а для фона получим пиксель через текущий индекс + ширина изображения. И выведем полученные пиксели одним символом u+2580. Если счетчик по горизонтали досчитал до ширины изображения - сбросить в начало, к счетчику по вертикали добавить 1, а к индексу прибавить ширину. Получим пропуск строки, т. к. она уже была отрисована в текущей итерации. local function draw_icon(name, X, Y) -- получить название и координаты if not icons[name] then return false end -- прервать, если нет такой иконки local x, y, index = 1, 1, 1 -- создать счетчики while index <= #icons[name]-icons[name].x do -- пройти по индексам gpu.setForeground(icons[name][index]) -- установить цвет верхнего пикселя gpu.setBackground(icons[name][index+icons[name].x]) -- цвет нижнего gpu.set(x+X-1, y+Y-1, quad) -- вывести на экран if x == icons[name].x then -- если достигнута ширина изображения x, y, index = 1, y + 1, index + icons[name].x+1 -- обновить все счетчики else -- простая итерация x, index = x + 1, index + 1 -- обновить счетчик горизонтали и индекса end end end Одна иконка уже сгенерирована, чтобы вывести ее в углу экрана, вызовем ее по имени - draw_icon('unknown', 1, 1) В итоге, на экране получим такое изображение: Чтобы загрузить иконки и конвертировать в удобный вид, создадим такую функцию: Перебрать все файлы в папке, получив их список через filesystem API. Прочитать файл построчно в таблицу, попутно удалив строки с комментариями. Если заголовок файла равен , получить ширину картинки, информацию о пикселях объединить в одну строку. В цикле пройти по пикселям, конвертируя бинарное значение пикселя в число. Тут надо помнить, что значение одного пикселя хранится в трех символах (по одному на канал), поэтому цикл будет скакать через 3. Первый символ конвертируем в число, умножаем на 65536, второй на 256 и складываем. Полученное число добавляем в массив пикселей текущей иконки. Получаем примерно такую реализацию: local function load_icons(path) -- получить путь к папке с иконками local multiplier, path = {65536, 256, 1}, path or '' -- создать таблицу множителей for name in fs.list(path) do -- получить имя файла в папке local file = io.open(path..name, 'r') -- открыть файл if not file then break end -- если файла нет, прервать цикл name = name:gsub('%..+', '') -- обрезать название файла до первой точки local raw_img = {} -- создать массив для сырых данных for line in file:lines() do -- в цикле пройти по строкам if line and line:sub(1,1) ~= '#' then -- если строка не закоментированна table.insert(raw_img, line) -- добавить в таблицу end end file:close() -- закрыть файл if raw_img[1] == 'P6' then -- если заголовок совпадает local _ = raw_img[2]:find(' ') -- проверить наличие пробела на второй строке if _ then -- если размеры на одной строке _, raw_img[2] = raw_img[2]:sub(_+1), raw_img[2]:sub(1,_-1) -- разделить table.insert(raw_img, 3, _) -- перенести высоту на другую строку end raw_img[2] = tonumber(raw_img[2]) -- преобразовать ширину изображения в число icons[name] = {x = raw_img[2]} -- создать пустую таблицу для пикселей local current = '' -- создать переменную с сырой информацией о пикселях for i = 5, #raw_img do -- пройти до конца файла current = current..raw_img[i]..'\n' -- объединить данные в одну строку end local color, n for i = 1, #current-1, 3 do -- пройти по каждому третьему символу, исключая последний перевод строки n, color = 1, 0 -- сбросить счетчик для таблицы множителей и цвет for j = i, i+2 do -- перебрать три символа color = color+current:sub(j,j):byte()*multiplier[n] -- преобразовать символ в число и добавить к значению цвета n = n + 1 -- обновить счетчик end table.insert(icons[name], color) -- добавить цвет пикселя к остальным end end end end Реализация очень примитивная, но главное, что иконки будут загружаться. Можно было бы сотворить свой формат, который быстрей распаковывается и занимает меньше места, но плодить сущностей очень вредно. Для проверки, осталось нарисовать иконки, сложить их в папку /home/icons/, например. И запустить весь код: local fs = require('filesystem') local gpu = require('component').gpu local quad = require('unicode').char(0x2580) local icons = {unknown = {x = 10}} for i = 1, 100 do if i%3 == 0 then icons.unknown[i] = 3394611 else icons.unknown[i] = 3355443 end end local function load_icons(path) local multiplier, path = {65536, 256, 1}, path or '' for name in fs.list(path) do local file = io.open(path..name, 'r') if not file then break end name = name:gsub('%..+', '') local raw_img = {} for line in file:lines() do if line and line:sub(1,1) ~= '#' then table.insert(raw_img, line) end end file:close() if raw_img[1] == 'P6' then local _ = raw_img[2]:find(' ') if _ then _, raw_img[2] = raw_img[2]:sub(_+1), raw_img[2]:sub(1,_-1) table.insert(raw_img, 3, _) end raw_img[2] = tonumber(raw_img[2]) icons[name] = {x = raw_img[2]} local current = '' for i = 5, #raw_img do current = current..raw_img[i]..'\n' end local n, color for i = 1, #current-1, 3 do n, color = 1, 0 for j = i, i+2 do color = color+current:sub(j,j):byte()*multiplier[n] n = n+1 end table.insert(icons[name], color) end end end end local function draw_icon(name, X, Y) if not icons[name] then return false end local x, y, index = 1, 1, 1 while index <= #icons[name]-icons[name].x do gpu.setForeground(icons[name][index]) gpu.setBackground(icons[name][index+icons[name].x]) gpu.set(x+X-1, y+Y-1, quad) if x == icons[name].x then x, y, index = 1, y + 1, index + icons[name].x+1 else x, index = x + 1, index + 1 end end end load_icons('/home/icons/') local n = 1 for name in pairs(icons) do draw_icon(name, (n*11)-10, 1) n = n + 1 end Получаем:
-
1 баллДавным-давно делал модный файловый менеджер с графическим интерфейсом для опенкомпов. Переходы по папкам, запуск файлов, распаковака tarball'ов и просмотр картинок в одной программе, к тому же фичи в виде листания свайпами, экранной клавиатуры и горстки настроек. И все это добро занимало меньше килобайта. Но развивать идею не стал, код удалил и осталась только одна картинка тестовой версии. Недавно решил это дело возродить, без зависимостей и лишних свистоплясок. Для начала напишем функции, которые добавят дополнительные возможности для пользователя. Когда игрок тыкает в экран, создаются два события - touch и drop. Когда зажимает и тащит - touch, потом куча drag и в конце drop. Из имеющихся событий, можно развить дополнительные события - клик, двойной клик и свайп. Можно даже добавить сложные жесты, но пока не понятно, как они могут пригодиться. На все нужные события повесим слушателей и будем сохранять результат в переменную. Слушатель для события touch будет проверять, было ли предыдущее событие drop. Затем сравнит с временем от последнего клика, вычислит расстояние между точками, в которых произошло событие. При совпадении координат и заданным временем между кликами пошлет событие double_click. Для события drop надо проверить, было ли предыдущим touch и по тому же параметру скорости проверять время между событиями, чтобы не захватывать долгие нажатия. Если предыдущим событием было drag, то надо определить расстояние между началом и концом действия, вычислить угол и послать это все в виде события swipe. В итоге получится примерно такой код: local computer = require('computer') -- подгрузить обертку для uptime & pushSignal local event = require('event') -- подгрузить библиотеку событий local lastEvent = nil -- последнее действие local lastTouch = nil -- последнее касание local eventTime = nil -- время от последнего события local clickSpeed = 0.5 -- время, за которое совершается клик и дабл-клик event.listen('drag', function(...) lastEvent = {...} -- просто сохранить событие end) event.listen('touch', function(...) local e = {...} -- сохранить событие в таблицу if e[5] == 0 and lastEvent and lastEvent[1] == 'drop' then -- если нажата ЛКМ и предыдущее было drop if eventTime and computer.uptime()-eventTime < clickSpeed then -- если прошло меньше времени, чем задано if lastTouch and lastTouch[3]-e[3]+lastTouch[4]-e[4] == 0 then -- если координаты событий не отличаются computer.pushSignal('double_click', e[2], e[3], e[4], e[6]) -- послать дабл-клик с координатами end end lastTouch = e -- сохранить последнее касание end eventTime = computer.uptime() -- обновить таймштамп события lastEvent = e -- сохранить событие end) event.listen('drop', function(...) local e = {...} -- сохранить событие в таблицу if e[5] == 0 and lastEvent then -- если нажата ЛКМ if lastEvent[1] == 'touch' then -- если предыдущее событие было касанием if eventTime and computer.uptime()-eventTime < clickSpeed then -- если прошло меньше времени, чем задано computer.pushSignal('click', e[2], e[3], e[4], e[6]) -- послать клик с координатами end elseif lastEvent[1] == 'drag' then -- если предыдущее было тасканием local dx, dy = lastTouch[3]-e[3], lastTouch[4]-e[4] -- найти дельту до координат касания computer.pushSignal('swipe', e[2], dx, dy, math.floor(math.deg(math.atan(dx/dy))), e[6]) -- послать свайп с дельтой и углом end end eventTime = computer.uptime() -- обновить таймштамп события lastEvent = e -- сохранить событие end) Пока он ничего не делает, только создает события, когда будет готов функционал отрисовки и взаимодействия с файловой системой, добавим к этим слушателям управляющие функции.
Эта таблица лидеров рассчитана в Москва/GMT+03:00
