explorer.lua (9179B)
1 -- LuaJIT OS File Explorer 2 -- Browse directories and files with size information 3 4 -- Current directory (start in home) 5 local currentPath = "/home" 6 local scrollOffset = 0 7 8 -- Window dimensions 9 local windowWidth = 600 10 local windowHeight = 500 11 local window = app:newWindow(windowWidth, windowHeight) 12 window.title = "Explorer" 13 14 -- Debug: Check window properties after creation 15 if osprint then 16 osprint("[EXPLORER] Window created\n") 17 osprint("[EXPLORER] window type: " .. type(window) .. "\n") 18 osprint("[EXPLORER] window.visible: " .. tostring(window.visible) .. "\n") 19 osprint("[EXPLORER] window.isBackground: " .. tostring(window.isBackground) .. "\n") 20 end 21 22 -- UI constants 23 local lineHeight = 20 24 local headerHeight = 40 25 local footerHeight = 25 26 27 -- Directory and file listing cache 28 local entries = {} 29 30 -- Helper: Format file size 31 local function formatSize(bytes) 32 if not bytes then return "?" end 33 34 if bytes < 1024 then 35 return tostring(bytes) .. " B" 36 elseif bytes < 1048576 then 37 return string.format("%.1f KB", bytes / 1024) 38 elseif bytes < 1073741824 then 39 return string.format("%.1f MB", bytes / 1048576) 40 else 41 return string.format("%.1f GB", bytes / 1073741824) 42 end 43 end 44 45 -- Helper: Get file/directory size 46 local function getSize(path) 47 if not fs then return 0 end 48 49 local itemType = fs:getType(path) 50 if itemType == "file" then 51 local content = fs:read(path) 52 return content and #content or 0 53 elseif itemType == "directory" then 54 -- For directories, count entries 55 local dirs = fs:dirs(path) or {} 56 local files = fs:files(path) or {} 57 return bit.band(#dirs, 0xFFFFFFFF) + bit.band(#files, 0xFFFFFFFF) 58 end 59 60 return 0 61 end 62 63 -- Load directory contents 64 local function loadDirectory(path) 65 entries = {} 66 67 if osprint then 68 osprint("[EXPLORER] loadDirectory called with path=" .. tostring(path) .. "\n") 69 osprint("[EXPLORER] fs=" .. tostring(fs) .. "\n") 70 end 71 72 if not fs then 73 if osprint then 74 osprint("[EXPLORER] ERROR: fs is nil!\n") 75 end 76 print("Explorer: No filesystem access - fs is nil") 77 return 78 end 79 80 -- Add parent directory entry if not at home 81 if path ~= "/home" then 82 table.insert(entries, { 83 name = "..", 84 type = "directory", 85 size = 0, 86 isParent = true 87 }) 88 end 89 90 -- Get directories using SafeFS 91 local dirs = fs:dirs(path) 92 if dirs then 93 for _, dirName in ipairs(dirs) do 94 local fullPath = fs:join(path, dirName) 95 table.insert(entries, { 96 name = dirName, 97 type = "directory", 98 size = 0, 99 path = fullPath 100 }) 101 end 102 end 103 104 -- Get files using SafeFS 105 local files = fs:files(path) 106 if files then 107 for _, fileName in ipairs(files) do 108 local fullPath = fs:join(path, fileName) 109 table.insert(entries, { 110 name = fileName, 111 type = "file", 112 size = 0, 113 path = fullPath 114 }) 115 end 116 end 117 118 scrollOffset = 0 -- Reset scroll when changing directory 119 end 120 121 -- Navigate to a directory 122 local function navigateTo(path) 123 currentPath = path 124 loadDirectory(currentPath) 125 window.dirty = true 126 127 print("Explorer: Navigated to " .. currentPath) 128 end 129 130 -- Flag to track if directory is loaded 131 local isLoaded = false 132 133 -- Mouse click handling 134 window.onClick = function(mx, my) 135 if osprint then 136 osprint("[EXPLORER] onClick CALLED! Mouse click at (" .. mx .. ", " .. my .. ")\n") 137 end 138 139 -- Check if click is in the file list area (below header and column headers row) 140 local listStartY = headerHeight + lineHeight -- Account for column headers row 141 if my > listStartY and my < windowHeight - footerHeight then 142 local clickedIndex = math.floor((my - listStartY) / lineHeight) + 1 + scrollOffset 143 144 if osprint then 145 osprint("[EXPLORER] Clicked index: " .. clickedIndex .. " (total entries: " .. #entries .. ")\n") 146 end 147 148 if clickedIndex > 0 and clickedIndex <= bit.band(#entries, 0xFFFFFFFF) then 149 local entry = entries[clickedIndex] 150 151 if osprint then 152 osprint("[EXPLORER] Clicked entry: " .. entry.name .. " type=" .. entry.type .. "\n") 153 end 154 155 if entry.type == "directory" then 156 if entry.isParent then 157 -- Go to parent directory 158 local parentPath = fs:parentDir(currentPath) 159 if parentPath then 160 navigateTo(parentPath) 161 end 162 else 163 -- Navigate to subdirectory 164 if osprint then 165 osprint("[EXPLORER] Navigating to: " .. entry.path .. "\n") 166 end 167 navigateTo(entry.path) 168 end 169 end 170 end 171 end 172 173 window:markDirty() 174 end 175 176 -- Debug: Verify onClick is set 177 if osprint then 178 osprint("[EXPLORER] After setting onClick, window.onClick type: " .. type(window.onClick) .. "\n") 179 end 180 181 -- Keyboard input handling 182 window.onInput = function(key_or_event, scancode) 183 if type(key_or_event) == "string" then 184 local key = key_or_event 185 186 -- Up arrow - scroll up 187 if scancode == 0x48 then 188 scrollOffset = math.max(0, scrollOffset - 1) 189 window.dirty = true 190 -- Down arrow - scroll down 191 elseif scancode == 0x50 then 192 local maxScroll = math.max(0, bit.band(#entries, 0xFFFFFFFF) - math.floor((windowHeight - headerHeight - footerHeight) / lineHeight)) 193 scrollOffset = math.min(maxScroll, scrollOffset + 1) 194 window.dirty = true 195 -- Home key - go to home directory 196 elseif key == "h" or key == "H" then 197 navigateTo("/home") 198 -- Backspace or left arrow - go to parent 199 elseif key == "\b" or scancode == 0x4B then 200 local parentPath = fs:parentDir(currentPath) 201 if parentPath then 202 navigateTo(parentPath) 203 end 204 end 205 end 206 end 207 208 -- Flag to track first draw 209 local firstDraw = true 210 211 -- Draw the explorer 212 window.onDraw = function(gfx) 213 -- Load directory on first draw (after fs is available) 214 if firstDraw then 215 firstDraw = false 216 if fs then 217 loadDirectory(currentPath) 218 else 219 if osprint then 220 osprint("[EXPLORER] ERROR: fs is nil on first draw!\n") 221 end 222 end 223 end 224 225 -- Background 226 gfx:fillRect(0, 0, windowWidth, windowHeight, 0x1E1E1E) 227 228 -- Header - current path 229 gfx:fillRect(0, 0, windowWidth, headerHeight, 0x2D2D30) 230 gfx:drawText(10, 12, "Path: " .. currentPath, 0xFFFFFF) 231 232 -- Column headers 233 local headerY = headerHeight 234 gfx:fillRect(0, headerY, windowWidth, lineHeight, 0x3E3E42) 235 gfx:drawText(10, headerY + 5, "Name", 0xCCCCCC) 236 gfx:drawText(windowWidth - 150, headerY + 5, "Type", 0xCCCCCC) 237 gfx:drawText(windowWidth - 80, headerY + 5, "Size", 0xCCCCCC) 238 239 -- File/directory list 240 local listY = headerY + lineHeight 241 local visibleLines = math.floor((windowHeight - headerHeight - lineHeight - footerHeight) / lineHeight) 242 243 for i = 1, visibleLines do 244 local entryIndex = i + scrollOffset 245 if entryIndex <= bit.band(#entries, 0xFFFFFFFF) then 246 local entry = entries[entryIndex] 247 local y = listY + (i - 1) * lineHeight 248 249 -- Alternating row colors 250 if i % 2 == 0 then 251 gfx:fillRect(0, y, windowWidth, lineHeight, 0x252526) 252 end 253 254 -- Icon and name 255 local icon = entry.type == "directory" and "[DIR]" or "[FILE]" 256 local nameColor = entry.type == "directory" and 0x4EC9B0 or 0xCCCCCC 257 258 if entry.isParent then 259 icon = "[UP]" 260 nameColor = 0xDCDCAA 261 end 262 263 gfx:drawText(10, y + 3, icon .. " " .. entry.name, nameColor) 264 265 -- Type 266 local typeStr = entry.type == "directory" and "Folder" or "File" 267 gfx:drawText(windowWidth - 150, y + 3, typeStr, 0x888888) 268 269 -- Size 270 local sizeStr = entry.type == "directory" and tostring(entry.size) .. " items" or formatSize(entry.size) 271 gfx:drawText(windowWidth - 80, y + 3, sizeStr, 0x888888) 272 end 273 end 274 275 -- Footer - help text 276 local footerY = windowHeight - footerHeight 277 gfx:fillRect(0, footerY, windowWidth, footerHeight, 0x2D2D30) 278 gfx:drawText(10, footerY + 5, "Click dir to open | H=Home | Backspace=Parent | Arrows=Scroll", 0x888888) 279 280 -- Scroll indicator 281 if bit.band(#entries, 0xFFFFFFFF) > visibleLines then 282 local scrollText = string.format("%d/%d", scrollOffset + 1, bit.band(#entries, 0xFFFFFFFF)) 283 gfx:drawText(windowWidth - 60, footerY + 5, scrollText, 0xCCCCCC) 284 end 285 end 286 287 print("Explorer started") 288 print("Current directory: " .. currentPath) 289 print("Directory will load on first draw")