diff --git a/compcount.lua b/compcount.lua index 027dfea..a68dc22 100644 --- a/compcount.lua +++ b/compcount.lua @@ -1,10 +1,11 @@ local mon = peripheral.find("monitor") assert(mon, "Kein Monitor gefunden") local monName = peripheral.getName(mon) -local SCREEN_WIDTH = 40 mon.setTextScale(0.5) +local SCREEN_WIDTH, SCREEN_HEIGHT = mon.getSize() + local oldTerm = term.redirect(mon) local Pine3D = require("Pine3D") @@ -20,6 +21,8 @@ local itemById local defaultBaseId local fallbackIcon = false local backButton = false +local scrollUpButton = false +local scrollDownButton = false local drawOverview local function ensureChainsLoaded() @@ -233,6 +236,30 @@ local function getBackButton() return backButton end +local function getScrollUpButton() + if scrollUpButton == false then + scrollUpButton = parseNfpImage(table.concat({ + " 00 ", + "0000", + "0 0", + }, "\n")) + end + + return scrollUpButton +end + +local function getScrollDownButton() + if scrollDownButton == false then + scrollDownButton = parseNfpImage(table.concat({ + "0 0", + "0000", + " 00 ", + }, "\n")) + end + + return scrollDownButton +end + local function imageSize(img) local w, h = 0, 0 @@ -296,7 +323,12 @@ local function drawNfpScaled(buffer, img, x, y, scaleX, scaleY) for dy = 0, scaleY - 1 do for dx = 0, scaleX - 1 do - buffer:setPixel(px + dx, py + dy, col) + local drawX = px + dx + local drawY = py + dy + + if drawX >= 1 and drawX <= buffer.width and drawY >= 1 and drawY <= buffer.height then + buffer:setPixel(drawX, drawY, col) + end end end end @@ -304,6 +336,31 @@ local function drawNfpScaled(buffer, img, x, y, scaleX, scaleY) end end +local function fillRect(buffer, x, y, width, height, color) + local startX = math.max(1, x) + local startY = math.max(1, y) + local endX = math.min(buffer.width, x + width - 1) + local endY = math.min(buffer.height, y + height - 1) + + if startX > endX or startY > endY then + return + end + + for py = startY, endY do + for px = startX, endX do + buffer:setPixel(px, py, color) + end + end +end + +local function cellToPixelX(cellX) + return (cellX - 1) * 2 + 1 +end + +local function cellToPixelY(cellY) + return (cellY - 1) * 3 + 1 +end + local function assertBufferValid(frame) local valid = { [colors.white] = true, @@ -335,10 +392,10 @@ local function assertBufferValid(frame) end end -local function drawItem(img, cellX, cellY, count) +local function drawItem(img, cellX, cellY, count, offsetY) -- Normale Monitor-Zellen in Pine3D-Teletext-Pixel umrechnen. - local x = (cellX - 1) * 2 + 1 - local y = (cellY - 1) * 3 + 1 + local x = cellToPixelX(cellX) + local y = cellToPixelY(cellY) + (offsetY or 0) local imgW, imgH = 0, 0 @@ -347,7 +404,9 @@ local function drawItem(img, cellX, cellY, count) if img then imgW, imgH = imageSize(img) - drawNfpScaled(frame.buffer, img, x, y, scaleX, scaleY) + if y <= frame.buffer.height and y + imgH * scaleY - 1 >= 1 then + drawNfpScaled(frame.buffer, img, x, y, scaleX, scaleY) + end end local text = formatCount(count) @@ -366,11 +425,13 @@ local function drawItem(img, cellX, cellY, count) dy = 1, } - -- Schatten - mf.writeOn(frame, text, colors.black, rightX + 1, bottomY + 1, options) + if bottomY >= 1 and y <= frame.buffer.height then + -- Schatten + mf.writeOn(frame, text, colors.black, rightX + 1, bottomY + 1, options) - -- Weißer Count - mf.writeOn(frame, text, colors.white, rightX, bottomY, options) + -- Weißer Count + mf.writeOn(frame, text, colors.white, rightX, bottomY, options) + end end local function drawPage(base_id) @@ -421,10 +482,69 @@ end drawOverview = function() local items = getBaseItemIds() local sortedItemIds = {} + local itemCounts = {} local visibleItemCount = 0 + local totalRows = 0 + local maxScrollRow = 0 + local currentScrollRow = 0 + local targetScrollRow = 0 + local refreshTimer + local animationTimer + + local columns = 3 + local rowsPerView = 3 + local baseCellX = 4 + local baseCellY = 2 + local colStepCells = 12 + local rowStepCells = 6 + local rowStepPixels = rowStepCells * 3 + local scrollbarX = SCREEN_WIDTH - 1 + local scrollbarWidth = 2 + local scrollbarButtonHeight = 3 + local scrollbarTrackY = scrollbarButtonHeight + 1 + local scrollbarTrackHeight = SCREEN_HEIGHT - scrollbarButtonHeight * 2 + local scrollbarPixelX = cellToPixelX(scrollbarX) + local scrollbarPixelWidth = scrollbarWidth * 2 + local scrollbarTrackPixelY = cellToPixelY(scrollbarTrackY) + local scrollbarTrackPixelHeight = math.max(0, scrollbarTrackHeight * 3) + + local function clampScrollRow(row) + return math.max(0, math.min(maxScrollRow, row)) + end + + local function getThumbMetrics() + if scrollbarTrackPixelHeight <= 0 then + return scrollbarTrackPixelY, 0 + end + + if totalRows <= rowsPerView or maxScrollRow == 0 then + return scrollbarTrackPixelY, scrollbarTrackPixelHeight + end + + local thumbHeight = math.max(4, math.floor(scrollbarTrackPixelHeight * rowsPerView / totalRows + 0.5)) + local thumbTravel = scrollbarTrackPixelHeight - thumbHeight + local thumbY = scrollbarTrackPixelY + math.floor((currentScrollRow / maxScrollRow) * thumbTravel + 0.5) + + return thumbY, thumbHeight + end + + local function scheduleAnimation() + if not animationTimer and math.abs(targetScrollRow - currentScrollRow) > 0.001 then + animationTimer = os.startTimer(0.05) + end + end + + local function scrollTo(row) + local clampedRow = clampScrollRow(row) + + if clampedRow ~= targetScrollRow then + targetScrollRow = clampedRow + scheduleAnimation() + end + end local function renderOverview() - local itemCounts = getMeItemCounts(items) + itemCounts = getMeItemCounts(items) for i = 1, #items do sortedItemIds[i] = items[i] @@ -441,23 +561,59 @@ drawOverview = function() return a < b end) - visibleItemCount = math.min(#sortedItemIds, 9) + totalRows = math.ceil(#sortedItemIds / columns) + maxScrollRow = math.max(0, totalRows - rowsPerView) + currentScrollRow = clampScrollRow(currentScrollRow) + targetScrollRow = clampScrollRow(targetScrollRow) + + local firstVisibleRow = math.floor(currentScrollRow) + local rowOffsetPixels = -math.floor((currentScrollRow - firstVisibleRow) * rowStepPixels + 0.5) + local firstVisibleIndex = firstVisibleRow * columns + 1 + local lastVisibleIndex = math.min(#sortedItemIds, (firstVisibleRow + rowsPerView + 1) * columns) frame.buffer:clear() - for i = 1, visibleItemCount do + for i = firstVisibleIndex, lastVisibleIndex do local itemId = sortedItemIds[i] local item = getItemById(itemId) local icon = getItemIcon(item) or getFallbackIcon() - drawItem(icon, 4+(8+4)*((i-1)%3), 2+(5+1)*math.floor((i-1)/3), itemCounts[itemId] or 0) + local relativeIndex = i - firstVisibleIndex + local col = relativeIndex % columns + local row = math.floor(relativeIndex / columns) + drawItem( + icon, + baseCellX + colStepCells * col, + baseCellY + rowStepCells * row, + itemCounts[itemId] or 0, + rowOffsetPixels + ) end + visibleItemCount = math.max(0, math.min(#sortedItemIds - firstVisibleRow * columns, rowsPerView * columns)) + + local upButtonY = cellToPixelY(1) + local downButtonY = cellToPixelY(SCREEN_HEIGHT - scrollbarButtonHeight + 1) + local upColor = targetScrollRow > 0 and colors.gray or colors.lightGray + local downColor = targetScrollRow < maxScrollRow and colors.gray or colors.lightGray + local thumbY, thumbHeight = getThumbMetrics() + + fillRect(frame.buffer, scrollbarPixelX, upButtonY, scrollbarPixelWidth, scrollbarButtonHeight * 3, upColor) + fillRect(frame.buffer, scrollbarPixelX, downButtonY, scrollbarPixelWidth, scrollbarButtonHeight * 3, downColor) + + if scrollbarTrackPixelHeight > 0 then + fillRect(frame.buffer, scrollbarPixelX, scrollbarTrackPixelY, scrollbarPixelWidth, scrollbarTrackPixelHeight, colors.gray) + fillRect(frame.buffer, scrollbarPixelX, thumbY, scrollbarPixelWidth, thumbHeight, colors.white) + end + + drawNfpScaled(frame.buffer, getScrollUpButton(), scrollbarPixelX, upButtonY + 2, 1, 1) + drawNfpScaled(frame.buffer, getScrollDownButton(), scrollbarPixelX, downButtonY + 3, 1, 1) + assertBufferValid(frame) frame:drawBuffer() end renderOverview() - local refreshTimer = os.startTimer(30) + refreshTimer = os.startTimer(30) while true do local event, p1, x, y = os.pullEvent() @@ -465,16 +621,78 @@ drawOverview = function() if event == "timer" and p1 == refreshTimer then renderOverview() refreshTimer = os.startTimer(30) + scheduleAnimation() + elseif event == "timer" and p1 == animationTimer then + local delta = targetScrollRow - currentScrollRow + + if math.abs(delta) <= 0.001 then + currentScrollRow = targetScrollRow + animationTimer = nil + else + local step = delta * 0.35 + + if step > 0 then + step = math.max(0.08, math.min(step, 0.45)) + else + step = math.min(-0.08, math.max(step, -0.45)) + end + + if math.abs(step) >= math.abs(delta) then + currentScrollRow = targetScrollRow + animationTimer = nil + else + currentScrollRow = currentScrollRow + step + animationTimer = os.startTimer(0.05) + end + end + + renderOverview() elseif event == "monitor_touch" and p1 == monName then - local col = math.floor((x - 4) / 12) - local row = math.floor((y - 2) / 6) + if x >= scrollbarX and x < scrollbarX + scrollbarWidth then + if y <= scrollbarButtonHeight then + scrollTo(targetScrollRow - 1) + elseif y > SCREEN_HEIGHT - scrollbarButtonHeight then + scrollTo(targetScrollRow + 1) + elseif scrollbarTrackHeight > 0 then + local touchPixelY = cellToPixelY(y) + 1 + local thumbY, thumbHeight = getThumbMetrics() - if col >= 0 and col < 3 and row >= 0 and row < 3 then - local index = row * 3 + col + 1 + if touchPixelY < thumbY then + if maxScrollRow > 0 then + local thumbTravel = scrollbarTrackPixelHeight - thumbHeight + local targetPixelY = math.max( + scrollbarTrackPixelY, + math.min(scrollbarTrackPixelY + thumbTravel, touchPixelY - math.floor(thumbHeight / 2)) + ) + local progress = (targetPixelY - scrollbarTrackPixelY) / math.max(1, thumbTravel) + scrollTo(progress * maxScrollRow) + end + elseif touchPixelY > thumbY + thumbHeight - 1 then + if maxScrollRow > 0 then + local thumbTravel = scrollbarTrackPixelHeight - thumbHeight + local targetPixelY = math.max( + scrollbarTrackPixelY, + math.min(scrollbarTrackPixelY + thumbTravel, touchPixelY - math.floor(thumbHeight / 2)) + ) + local progress = (targetPixelY - scrollbarTrackPixelY) / math.max(1, thumbTravel) + scrollTo(progress * maxScrollRow) + end + end + end + else + local col = math.floor((x - baseCellX) / colStepCells) + local firstVisibleRow = math.floor(currentScrollRow) + local rowOffsetPixels = (currentScrollRow - firstVisibleRow) * rowStepPixels + local touchPixelY = cellToPixelY(y) + 1 + local row = math.floor((touchPixelY - cellToPixelY(baseCellY) + rowOffsetPixels) / rowStepPixels) - if index <= visibleItemCount then - drawPage(sortedItemIds[index]) - return + if col >= 0 and col < columns and row >= 0 and row < rowsPerView + 1 then + local index = (firstVisibleRow + row) * columns + col + 1 + + if index >= 1 and index <= #sortedItemIds and index <= firstVisibleRow * columns + visibleItemCount + columns then + drawPage(sortedItemIds[index]) + return + end end end end