Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- pocket_compact_controller.txt
- -- Compact controller UI for Advanced Wireless Pocket Computer.
- -- Requires server to include server_cmd_patch.txt and to run
- -- mirrorHelloLoop + mirrorStatusLoop.
- local MODE = "ui-mirror-v1"
- -- Open rednet
- local function _openRednetClient()
- local side = nil
- for _, s in ipairs(rs.getSides()) do
- if peripheral.getType(s) == "modem" then side = s break end
- end
- assert(side, "Wireless modem required on client")
- if not rednet.isOpen(side) then rednet.open(side) end
- return side
- end
- _openRednetClient()
- -- State mirrored from server
- local S = {
- now = nil, playing = false, looping = 0, volume = 1.0,
- is_loading = false, is_error = false, queue = {}, last_search = nil,
- search_results = nil,
- }
- local cw, ch = term.getSize()
- local TAB = 1 -- 1=Now, 2=Queue, 3=Find
- local selIdx = 1 -- selection index for lists
- local resIdx = 1 -- selection for search results
- local serverId = nil
- -- Helpers
- local function send(pkt)
- if serverId then rednet.send(serverId, pkt, MODE) else rednet.broadcast(pkt, MODE) end
- end
- local function cmd(name, extra)
- local m = { op="CMD", cmd=name }
- if extra then for k,v in pairs(extra) do m[k]=v end end
- send(m)
- end
- -- UI draw
- local function bar(t) term.setBackgroundColor(colors.gray) term.setTextColor(colors.white) term.setCursorPos(1,1) term.clearLine() term.write(t) end
- local function footer(t) term.setCursorPos(1,ch) term.setBackgroundColor(colors.gray) term.setTextColor(colors.white) term.clearLine() term.write(t) end
- local function clearBody() term.setBackgroundColor(colors.black) for y=2,ch-1 do term.setCursorPos(1,y) term.clearLine() end end
- local function drawNow()
- bar(" Now | Queue | Find ")
- clearBody()
- term.setTextColor(colors.white)
- term.setCursorPos(2,3)
- term.write(S.now and (S.now.name or "Unknown") or "Not playing")
- term.setCursorPos(2,4)
- term.setTextColor(colors.lightGray)
- term.write(S.now and (S.now.artist or "") or "")
- term.setTextColor(colors.white)
- term.setCursorPos(2,6)
- local status = (S.is_error and "Error") or (S.is_loading and "Loading") or (S.playing and "Playing") or "Stopped"
- term.write(("Status: %s"):format(status))
- -- Volume line
- local pct = math.floor((S.volume/3)*100 + 0.5)
- term.setCursorPos(2,8); term.write(("Vol: %3d%%"):format(pct))
- -- Loop state
- local loopTxt = (S.looping==0 and "Off") or (S.looping==1 and "Queue") or "Song"
- term.setCursorPos(2,9); term.write("Loop: "..loopTxt)
- footer("⏯ Play/Pause ⏭ Skip L Loop - Vol+ + Vol- 2 Queue 3 Find")
- end
- local function drawQueue()
- bar(" Now |*Queue*| Find ")
- clearBody()
- term.setTextColor(colors.white)
- term.setCursorPos(2,2) term.write("Up Next:")
- for i=1, math.min(#S.queue, ch-5) do
- local y = 2 + i
- term.setCursorPos(2,y)
- if i==selIdx then term.setBackgroundColor(colors.white) term.setTextColor(colors.black) else term.setBackgroundColor(colors.black) term.setTextColor(colors.white) end
- local name = S.queue[i].name or "Unknown"
- term.write(name:sub(1, cw-2))
- term.setBackgroundColor(colors.black) term.setTextColor(colors.lightGray)
- term.setCursorPos(2,y+1)
- if S.queue[i].artist then term.write(("by %s"):format(S.queue[i].artist):sub(1, cw-2)) end
- y = y + 1
- end
- footer("↑/↓ Select Enter Play D Delete 1 Now 3 Find")
- end
- local function drawFind()
- bar(" Now | Queue |*Find*")
- clearBody()
- term.setCursorPos(2,2) term.setTextColor(colors.white) term.write("Query:")
- term.setCursorPos(2,3) term.setTextColor(colors.yellow)
- term.write(S.last_search or "(press S to set)")
- term.setTextColor(colors.white)
- term.setCursorPos(2,5) term.write("Results:")
- local list = S.search_results or {}
- for i=1, math.min(#list, ch-8) do
- local y = 5 + i
- term.setCursorPos(2,y)
- if i==resIdx then term.setBackgroundColor(colors.white) term.setTextColor(colors.black) else term.setBackgroundColor(colors.black) term.setTextColor(colors.white) end
- local name = list[i].name or "Unknown"
- term.write(name:sub(1, cw-2))
- term.setBackgroundColor(colors.black) term.setTextColor(colors.lightGray)
- term.setCursorPos(2,y+1)
- if list[i].artist then term.write(("by %s"):format(list[i].artist):sub(1, cw-2)) end
- end
- footer("S Search Enter Play N PlayNext A Add 1 Now 2 Queue")
- end
- local function redraw()
- term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
- if TAB==1 then drawNow()
- elseif TAB==2 then drawQueue()
- else drawFind() end
- end
- -- Net receive
- local function netLoop()
- while true do
- local id, msg, proto = rednet.receive(MODE)
- if proto == MODE and type(msg) == "table" then
- if msg.op == "HELLO_ACK" then
- serverId = id
- elseif msg.op == "STATUS" then
- serverId = serverId or id
- S.now = msg.now; S.playing = msg.playing; S.looping = msg.looping
- S.volume = msg.volume; S.is_loading = msg.is_loading; S.is_error = msg.is_error
- S.queue = msg.queue or {}; S.last_search = msg.last_search; S.search_results = msg.search_results
- redraw()
- elseif msg.op == "screen" then
- serverId = serverId or id
- end
- end
- end
- end
- -- Periodic hello + status requests
- local function helloLoop()
- while true do
- if not serverId then rednet.broadcast({ op="HELLO", w=cw, h=ch }, MODE) end
- send({ op="STATUS_REQ" })
- sleep(1.0)
- end
- end
- -- Input
- local function inputLoop()
- redraw()
- while true do
- local e, p1, p2, p3 = os.pullEvent()
- if e == "key" then
- local k = p1
- if k == keys.one then TAB=1; redraw()
- elseif k == keys.two then TAB=2; redraw()
- elseif k == keys.three then TAB=3; redraw()
- elseif TAB == 1 then
- if k == keys.space then cmd("PLAY_TOGGLE")
- elseif k == keys.l then cmd("LOOP_NEXT")
- elseif k == keys.minus then cmd("VOL_SET", {value = math.max(0, (S.volume or 1)-0.15)})
- elseif k == keys.equals or k == keys.plus then cmd("VOL_SET", {value = math.min(3, (S.volume or 1)+0.15)})
- elseif k == keys.right then cmd("SKIP")
- end
- elseif TAB == 2 then
- if k == keys.up then selIdx = math.max(1, selIdx-1); redraw()
- elseif k == keys.down then selIdx = math.min(math.max(#S.queue,1), selIdx+1); redraw()
- elseif k == keys.enter then cmd("PLAY_FROM_QUEUE_IDX", {idx = selIdx})
- elseif k == keys.d then cmd("REMOVE_QUEUE_IDX", {idx = selIdx})
- end
- elseif TAB == 3 then
- if k == keys.up then resIdx = math.max(1, resIdx-1); redraw()
- elseif k == keys.down then resIdx = math.min(math.max(#(S.search_results or {}),1), resIdx+1); redraw()
- elseif k == keys.s then
- term.setCursorBlink(true)
- term.setCursorPos(2,3)
- term.setTextColor(colors.white) term.setBackgroundColor(colors.black)
- term.clearLine() term.setCursorPos(2,3)
- local q = read()
- term.setCursorBlink(false)
- if q and #q>0 then cmd("SEARCH", {q=q}) end
- elseif k == keys.enter then cmd("PLAY_NOW", {idx = resIdx})
- elseif k == keys.n then cmd("PLAY_NEXT", {idx = resIdx})
- elseif k == keys.a then cmd("ADD_QUEUE", {idx = resIdx})
- end
- end
- elseif e == "mouse_click" then
- local x,y = p2,p3
- -- Top bar tabs by rough regions
- if y == 1 then
- if x <= 8 then TAB=1 elseif x <= 16 then TAB=2 else TAB=3 end
- redraw()
- end
- end
- end
- end
- -- Start
- term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
- print("Pocket controller")
- parallel.waitForAny(helloLoop, netLoop, inputLoop)
Advertisement
Add Comment
Please, Sign In to add comment