-- Engage the viewer's UI local leap = require 'leap' local mapargs = require 'mapargs' local result_view = require 'result_view' local Timer = (require 'timers').Timer local util = require 'util' -- Allow lazily accessing UI submodules on demand, e.g. a reference to -- UI.Floater lazily loads the UI/Floater module. local UI = util.submoduledir({}, 'UI') -- *************************************************************************** -- registered menu actions -- *************************************************************************** function UI.call(func, parameter) -- 'call' is fire-and-forget leap.request('UI', {op='call', ['function']=func, parameter=parameter}) end function UI.callables() return leap.request('UI', {op='callables'}).callables end function UI.getValue(path) return leap.request('UI', {op='getValue', path=path})['value'] end -- *************************************************************************** -- UI views -- *************************************************************************** -- Either: -- wreq{op='Something', a=1, b=2, ...} -- or: -- (args should be local, as this wreq() call modifies it) -- local args = {a=1, b=2, ...} -- wreq('Something', args) local function wreq(op_or_data, data_if_op) if data_if_op ~= nil then -- this is the wreq(op, data) form data_if_op.op = op_or_data op_or_data = data_if_op end return leap.request('LLWindow', op_or_data) end -- omit 'parent' to list all view paths function UI.listviews(parent) return wreq{op='getPaths', under=parent} end function UI.viewinfo(path) return wreq{op='getInfo', path=path} end -- *************************************************************************** -- mouse actions -- *************************************************************************** -- pass a table: -- UI.click{path=path -- [, button='LEFT' | 'CENTER' | 'RIGHT'] -- [, x=x, y=y] -- [, hold=duration]} function UI.click(...) local args = mapargs('path,button,x,y,hold', ...) args.button = args.button or 'LEFT' local hold = args.hold or 1.0 wreq('mouseMove', args) wreq('mouseDown', args) Timer(hold, 'wait') wreq('mouseUp', args) end -- pass a table as for UI.click() function UI.doubleclick(...) local args = mapargs('path,button,x,y', ...) args.button = args.button or 'LEFT' wreq('mouseDown', args) wreq('mouseUp', args) wreq('mouseDown', args) wreq('mouseUp', args) end -- UI.drag{path=, xoff=, yoff=} function UI.drag(...) local args = mapargs('path,xoff,yoff', ...) -- query the specified path local rect = UI.viewinfo(args.path).rect local centerx = math.floor(rect.left + (rect.right - rect.left)/2) local centery = math.floor(rect.bottom + (rect.top - rect.bottom)/2) wreq{op='mouseMove', path=args.path, x=centerx, y=centery} wreq{op='mouseDown', path=args.path, button='LEFT'} wreq{op='mouseMove', path=args.path, x=centerx + args.xoff, y=centery + args.yoff} wreq{op='mouseUp', path=args.path, button='LEFT'} end -- *************************************************************************** -- keyboard actions -- *************************************************************************** -- pass a table: -- UI.keypress{ -- [path=path] -- if omitted, default input field -- [, char='x'] -- requires one of char, keycode, keysym -- [, keycode=120] -- keysym per https://github.com/secondlife/viewer/blob/main/indra/llwindow/llkeyboard.cpp#L68-L124 -- [, keysym='Enter'] -- [, mask={'SHIFT', 'CTL', 'ALT', 'MAC_CONTROL'}] -- some subset of these -- } function UI.keypress(...) local args = mapargs('path,char,keycode,keysym,mask', ...) if args.char == '\n' then args.char = nil args.keysym = 'Enter' end return wreq('keyDown', args) end -- UI.type{text=, path=} function UI.type(...) local args = mapargs('text,path', ...) if #args.text > 0 then -- The caller's path may be specified in a way that requires recursively -- searching parts of the LLView tree. No point in doing that more than -- once. Capture the actual path found by that first call and use that for -- subsequent calls. local path = UI.keypress{path=args.path, char=string.sub(args.text, 1, 1)}.path for i = 2, #args.text do UI.keypress{path=path, char=string.sub(args.text, i, i)} end end end -- *************************************************************************** -- Snapshot -- *************************************************************************** -- UI.snapshot{filename=filename -- extension may be specified: bmp, jpeg, png -- [, type='COLOR' | 'DEPTH'] -- [, width=width][, height=height] -- uses current window size if not specified -- [, showui=true][, showhud=true] -- [, rebuild=false]} function UI.snapshot(...) local args = mapargs('filename,width,height,showui,showhud,rebuild,type', ...) args.op = 'saveSnapshot' return leap.request('LLViewerWindow', args).result end -- *************************************************************************** -- Top menu -- *************************************************************************** function UI.getTopMenus() return leap.request('UI', {op='getTopMenus'}).menus end function UI.addMenu(...) local args = mapargs('name,label', ...) args.op = 'addMenu' return leap.request('UI', args) end function UI.setMenuVisible(name, visible) return leap.request('UI', {op='setMenuVisible', name=name, visible=visible}) end function UI.addMenuBranch(...) local args = mapargs('name,label,parent_menu', ...) args.op = 'addMenuBranch' return leap.request('UI', args) end -- see UI.callables() for valid values of 'func' function UI.addMenuItem(...) local args = mapargs('name,label,parent_menu,func,param,pos', ...) args.op = 'addMenuItem' return leap.request('UI', args) end function UI.addMenuSeparator(...) local args = mapargs('parent_menu,pos', ...) args.op = 'addMenuSeparator' return leap.request('UI', args) end -- *************************************************************************** -- Toolbar buttons -- *************************************************************************** -- Clears all buttons off the toolbars function UI.clearAllToolbars() leap.send('UI', {op='clearAllToolbars'}) end function UI.defaultToolbars() leap.send('UI', {op='defaultToolbars'}) end -- UI.addToolbarBtn{btn_name=btn_name -- [, toolbar= bottom] -- left, right, bottom -- default is bottom -- [, rank=1]} -- position on the toolbar, starts at 0 (0 - first position, 1 - second position etc.) function UI.addToolbarBtn(...) local args = mapargs('btn_name,toolbar,rank', ...) args.op = 'addToolbarBtn' return leap.request('UI', args) end -- Returns the rank(position) of the command in the original list function UI.removeToolbarBtn(btn_name) return leap.request('UI', {op = 'removeToolbarBtn', btn_name=btn_name}).rank end function UI.getToolbarBtnNames() return leap.request('UI', {op = 'getToolbarBtnNames'}).cmd_names end -- *************************************************************************** -- Floaters -- *************************************************************************** function UI.showFloater(floater_name) leap.send("LLFloaterReg", {op = "showInstance", name = floater_name}) end function UI.hideFloater(floater_name) leap.send("LLFloaterReg", {op = "hideInstance", name = floater_name}) end function UI.toggleFloater(floater_name) leap.send("LLFloaterReg", {op = "toggleInstance", name = floater_name}) end function UI.isFloaterVisible(floater_name) return leap.request("LLFloaterReg", {op = "instanceVisible", name = floater_name}).visible end function UI.closeAllFloaters() return leap.send("UI", {op = "closeAllFloaters"}) end function UI.getFloaterNames() local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters local view = result_view(key_length) return LL.setdtor('registered floater names', view, view.close) end return UI