local term_stack = require("term-stack") local ensure_width = require("cc.strings").ensure_width local ui = require("ui") local main_view = {} local function clamp(x, min, max) return math.min(math.max(x, min), max) end local function rect(x, y, w, h) return { x = x, y = y, w = w, h = h or w } end local function list_items(inventories) local item_registry = {} for _, name in ipairs(inventories) do for slot, item in pairs(peripheral.call(name, "list")) do item_registry[item.name] = item_registry[item.name] or {} table.insert(item_registry[item.name], { count = item.count, slot = slot, peripheral = name }) end end for _, items in pairs(item_registry) do items.count = 0 for _, slot in ipairs(items) do items.count = items.count + slot.count end end return item_registry end local function populate_item_details(item_details, item_registry) for name, items in pairs(item_registry) do local item = items[1] if item and not item_details[name] then local detail = peripheral.call(item.peripheral, "getItemDetail", item.slot) item_details[name] = { display_name = detail.displayName, stack_size = detail.maxCount } end end end local function remove_value(array, value) for i, v in ipairs(array) do if v == value then table.remove(array, i) return end end end local function vsplit(area, x) local left_area = rect(area.x, area.y, x - area.x, area.h) local right_area = rect(area.x + left_area.w+1, area.y, area.w - left_area.w-1, area.h) return left_area, right_area end local function vline(x, y, height) for i=1, height do term.setCursorPos(x, y+i-1) term.write("|") end end local function get_item_counts(items) local item_counts = {} for name, item_collection in pairs(items) do item_counts[name] = item_collection.count end return item_counts end function main_view:prepare(inventories, result_inventory) self.event = {} self.is_left_active = true self.main_area = rect(1, 1, term.getSize()) self.split_x = math.floor(self.main_area.w*0.5) self.left_area, self.right_area = vsplit(self.main_area, self.split_x) self.result_inventory = result_inventory self.inventories = inventories remove_value(self.inventories, result_inventory) self.item_registry = list_items(self.inventories) self.item_details = {} populate_item_details(self.item_details, self.item_registry) local all_item_counts = get_item_counts(self.item_registry) self.left_store = { all_items = all_item_counts, filtered_names = self:list_filtered_names(all_item_counts, ""), search_bar = "", selected_idx = 1, scroll = 0 } self.right_store = { all_items = {}, filtered_names = {}, search_bar = "", selected_idx = 1, scroll = 0 } end function main_view:list_filtered_names(all_items, name_filter) name_filter = name_filter:lower() local names = {} for name, count in pairs(all_items) do local display_name = self.item_details[name].display_name:lower() if display_name:find(name_filter) and count > 0 then table.insert(names, name) end end table.sort(names, function(a, b) if all_items[a] == all_items[b] then return a < b else return all_items[a] > all_items[b] end end) return names end function main_view:display_item_list(store, area, active) term_stack.push_cursor(area.x, area.y) local filtered_names = store.filtered_names local shown_count = #filtered_names local max_count = 0 for i=1, math.min(area.h, shown_count) do local name = filtered_names[i + store.scroll] local count = store.all_items[name] max_count = math.max(max_count, count) end local max_count_width = math.floor(math.log(max_count, 10) + 1) for row=1, math.min(area.h, shown_count) do local i = row + store.scroll local name = filtered_names[i] local count = store.all_items[name] local item_info = self.item_details[name] local count_width = math.floor(math.log(count, 10) + 1) if i == store.selected_idx and active then term.setTextColor(colors.yellow) else term.setTextColor(colors.white) end term.setCursorPos((max_count_width+1 - count_width), row) term.write(count) term.setCursorPos(max_count_width+2, row) term.write(ensure_width(item_info.display_name, area.w-max_count_width-1)) end if shown_count > area.h then local scroll_height = math.max(1, area.h/shown_count*area.h) local y = 1+store.scroll/shown_count*area.h paintutils.drawLine(area.w, y, area.w, y+scroll_height-1, colors.lightGray) end term_stack.pop_cursor() end function main_view:move_item(item_name, amount, destination) local item_collection = self.item_registry[item_name] local moved_amount = 0 while moved_amount < amount and item_collection.count > 0 do local slot_details = item_collection[#item_collection] local needed_amount = amount - moved_amount local transferred_amount = peripheral.call(slot_details.peripheral, "pushItems", destination, slot_details.slot, needed_amount) if transferred_amount == 0 then break end moved_amount = moved_amount + transferred_amount slot_details.count = slot_details.count - transferred_amount item_collection.count = item_collection.count - transferred_amount if slot_details.count == 0 then table.remove(item_collection) end end return moved_amount end function main_view:update_selected_item(store, area, idx) local filtered_count = #store.filtered_names store.selected_idx = clamp(idx, 1, filtered_count) local margin = 2 local item_list_height = area.h-1 if store.selected_idx > store.scroll+item_list_height-margin then store.scroll = math.min(store.selected_idx-item_list_height+margin, filtered_count-item_list_height) elseif store.selected_idx <= store.scroll+margin then store.scroll = math.max(store.selected_idx-1-margin, 0) end end function main_view:select_item(store, area, item_name) for i, name in ipairs(store.filtered_names) do if name == item_name then self:update_selected_item(store, area, i) return end end end function main_view:refresh_filtered_names(store, area) store.filtered_names = main_view:list_filtered_names(store.all_items, store.search_bar or "") self:update_selected_item(store, area, store.selected_idx) end function main_view:display_items_with_search(store, area, active) term_stack.push_cursor(area.x, area.y) local new_search_bar = ui:textbox(area.w, active, "Search", store.search_bar) if new_search_bar ~= store.search_bar then store.search_bar = new_search_bar self:refresh_filtered_names(store, area) end if active then if ui.event[1] == "key" then local key = ui.event[2] if key == keys.down or (main_view.ctrl_down and key == keys.j) then store.selected_idx = math.min(store.selected_idx + 1, #store.filtered_names) elseif key == keys.up or (main_view.ctrl_down and key == keys.k) then store.selected_idx = math.max(store.selected_idx - 1, 1) end local margin = 2 local item_list_height = area.h-1 if store.selected_idx > store.scroll+item_list_height-margin then store.scroll = math.min(store.selected_idx-item_list_height+margin, #store.filtered_names - item_list_height) elseif store.selected_idx <= store.scroll+margin then store.scroll = math.max(store.selected_idx-1-margin, 0) end end end term.setBackgroundColor(colors.black) main_view:display_item_list(store, rect(1, 2, area.w, area.h-1), active) term_stack.pop_cursor() end function main_view:has_selected_items() for _, count in pairs(main_view.right_store.all_items) do if count > 0 then return true end end return false end function main_view:refresh_items() self.item_registry = list_items(self.inventories) populate_item_details(self.item_details, self.item_registry) self.left_store.all_items = get_item_counts(self.item_registry) self.right_store.all_items = {} self:refresh_filtered_names(self.left_store, self.left_area) self:refresh_filtered_names(self.right_store, self.right_area) end function main_view:deposit_items() for slot, item in pairs(peripheral.call(self.result_inventory, "list")) do local total_transferred = 0 for _, inventory in ipairs(self.inventories) do local transferred = peripheral.call(self.result_inventory, "pushItems", inventory, slot) total_transferred = total_transferred + transferred if total_transferred == item.count then break end end end self:refresh_items() end function main_view:draw() term.setBackgroundColor(colors.black) term.clear() local has_selected_items = self:has_selected_items() if has_selected_items then term.setTextColor(colors.lightGray) vline(self.split_x, self.main_area.y, self.main_area.h) main_view:display_items_with_search(self.left_store, self.left_area, self.is_left_active) main_view:display_items_with_search(self.right_store, self.right_area, not self.is_left_active) else main_view:display_items_with_search(self.left_store, self.main_area, true) end end function main_view:on_event(event, ...) local has_selected_items = self:has_selected_items() if not has_selected_items and not self.is_left_active then self.is_left_active = true end if event == "key" then self:on_key(...) elseif event == "key_up" then self:on_key_up(...) end end function main_view:on_key(key) local left_selected = self.left_store.filtered_names[self.left_store.selected_idx] local right_selected = self.right_store.filtered_names[self.right_store.selected_idx] if key == keys.rightCtrl or key == keys.leftCtrl then self.ctrl_down = true elseif key == keys.rightAlt or key == keys.leftAlt then self.alt_down = true elseif key == keys.tab then self.is_left_active = not self.is_left_active elseif key == keys.f5 then self:refresh_items() end if self.ctrl_down then local has_selected_items = self:has_selected_items() if key == keys.space then if has_selected_items then for name, count in pairs(self.right_store.all_items) do local transferred = self:move_item(name, count, self.result_inventory) self.left_store.all_items[name] = self.left_store.all_items[name] - transferred end self.right_store.all_items = {} self:refresh_filtered_names(self.left_store, self.left_area) self:refresh_filtered_names(self.right_store, self.right_area) else if left_selected then local item = left_selected local stack_size = self.item_details[item].stack_size local transferred_count = self:move_item(item, stack_size, self.result_inventory) self.left_store.all_items[item] = self.left_store.all_items[item] - transferred_count self:refresh_filtered_names(self.left_store, self.left_area) self:select_item(self.left_store, self.left_area, item) end end elseif key == keys.d then self:deposit_items() end if self.is_left_active then if key == keys.l and left_selected then local item = left_selected local stack_size = self.item_details[item].stack_size local transferred = math.min(self.left_store.all_items[item], stack_size) self.left_store.all_items[item] = self.left_store.all_items[item] - transferred self.right_store.all_items[item] = (self.right_store.all_items[item] or 0) + transferred self:refresh_filtered_names(self.left_store, self.left_area) self:refresh_filtered_names(self.right_store, self.right_area) self:select_item(self.left_store, self.left_area, item) end else if key == keys.h and right_selected then local item = right_selected local stack_size = self.item_details[item].stack_size local transferred = math.min(self.right_store.all_items[item], stack_size) self.left_store.all_items[item] = self.left_store.all_items[item] + transferred self.right_store.all_items[item] = self.right_store.all_items[item] - transferred self:refresh_filtered_names(self.left_store, self.left_area) self:refresh_filtered_names(self.right_store, self.right_area) self:select_item(self.right_store, self.right_area, item) end end end end function main_view:on_key_up(key) if key == keys.rightCtrl or key == keys.leftCtrl then self.ctrl_down = false elseif key == keys.rightAlt or key == keys.leftAlt then self.alt_down = false end end return main_view