-- NoIndex: true --[[ Lokasenna_GUI - Options classes This file provides two separate element classes: Radio A list of options from which the user can only choose one at a time. Checklist A list of options from which the user can choose any, all or none. Both classes take the same parameters on creation, and offer the same parameters afterward - their usage only differs when it comes to their respective :val methods. For documentation, see the class pages on the project wiki: https://github.com/jalovatt/Lokasenna_GUI/wiki/Checklist https://github.com/jalovatt/Lokasenna_GUI/wiki/Radio Creation parameters: name, z, x, y, w, h, caption, opts[, dir, pad] ]]-- if not GUI then reaper.ShowMessageBox("Couldn't access GUI functions.\n\nLokasenna_GUI - Core.lua must be loaded prior to any classes.", "Library Error", 0) missing_lib = true return 0 end local Option = GUI.Element:new() function Option:new(name, z, x, y, w, h, caption, opts, dir, pad) local option = (not x and type(z) == "table") and z or {} option.name = name option.type = "Option" option.z = option.z or z option.x = option.x or x option.y = option.y or y option.w = option.w or w option.h = option.h or h option.caption = option.caption or caption if option.frame == nil then option.frame = true end option.bg = option.bg or "wnd_bg" option.dir = option.dir or dir or "v" option.pad = option.pad or pad or 4 option.col_txt = option.col_txt or "txt" option.col_fill = option.col_fill or "elm_fill" option.font_a = option.font_a or 2 option.font_b = option.font_b or 3 if option.shadow == nil then option.shadow = true end if option.shadow == nil then option.swap = false end -- Size of the option bubbles option.opt_size = option.opt_size or 20 -- Parse the string of options into a table if not option.optarray then option.optarray = {} local opts = option.opts or opts if type(opts) == "table" then for i = 1, #opts do option.optarray[i] = opts[i] end else local tempidx = 1 for word in string.gmatch(opts, '([^,]*)') do option.optarray[tempidx] = word tempidx = tempidx + 1 end end end GUI.redraw_z[option.z] = true setmetatable(option, self) self.__index = self return option end function Option:init() -- Make sure we're not trying to use the base class. if self.type == "Option" then reaper.ShowMessageBox( "'"..self.name.."' was initialized as an Option element,".. "but Option doesn't do anything on its own!", "GUI Error", 0) GUI.quit = true return end self.buff = self.buff or GUI.GetBuffer() gfx.dest = self.buff gfx.setimgdim(self.buff, -1, -1) gfx.setimgdim(self.buff, 2*self.opt_size + 4, 2*self.opt_size + 2) self:initoptions() if self.caption and self.caption ~= "" then GUI.font(self.font_a) local str_w, str_h = gfx.measurestr(self.caption) self.cap_h = 0.5*str_h self.cap_x = self.x + (self.w - str_w) / 2 else self.cap_h = 0 self.cap_x = 0 end end function Option:draw() if self.frame then GUI.color("elm_frame") gfx.rect(self.x, self.y, self.w, self.h, 0) end if self.caption and self.caption ~= "" then self:drawcaption() end self:drawoptions() end ------------------------------------ -------- Input helpers ------------- ------------------------------------ function Option:getmouseopt() local len = #self.optarray -- See which option it's on local mouseopt = self.dir == "h" and (GUI.mouse.x - (self.x + self.pad)) or (GUI.mouse.y - (self.y + self.cap_h + 1.5*self.pad) ) mouseopt = mouseopt / ((self.opt_size + self.pad) * len) mouseopt = GUI.clamp( math.floor(mouseopt * len) + 1 , 1, len ) return self.optarray[mouseopt] ~= "_" and mouseopt or false end ------------------------------------ -------- Drawing methods ----------- ------------------------------------ function Option:drawcaption() GUI.font(self.font_a) gfx.x = self.cap_x gfx.y = self.y - self.cap_h GUI.text_bg(self.caption, self.bg) GUI.shadow(self.caption, self.col_txt, "shadow") end function Option:drawoptions() local x, y, w, h = self.x, self.y, self.w, self.h local horz = self.dir == "h" local pad = self.pad -- Bump everything down for the caption y = y + ((self.caption and self.caption ~= "") and self.cap_h or 0) + 1.5 * pad -- Bump the options down more for horizontal options -- with the text on top if horz and self.caption ~= "" and not self.swap then y = y + self.cap_h + 2*pad end local opt_size = self.opt_size local adj = opt_size + pad local str, opt_x, opt_y for i = 1, #self.optarray do str = self.optarray[i] if str ~= "_" then opt_x = x + (horz and (i - 1) * adj + pad or (self.swap and (w - adj - 1) or pad)) opt_y = y + (i - 1) * (horz and 0 or adj) -- Draw the option bubble self:drawoption(opt_x, opt_y, opt_size, self:isoptselected(i)) self:drawvalue(opt_x,opt_y, opt_size, str) end end end function Option:drawoption(opt_x, opt_y, size, selected) gfx.blit( self.buff, 1, 0, selected and (size + 3) or 1, 1, size + 1, size + 1, opt_x, opt_y) end function Option:drawvalue(opt_x, opt_y, size, str) if not str or str == "" then return end GUI.font(self.font_b) local str_w, str_h = gfx.measurestr(str) if self.dir == "h" then gfx.x = opt_x + (size - str_w) / 2 gfx.y = opt_y + (self.swap and (size + 4) or -size) else gfx.x = opt_x + (self.swap and -(str_w + 8) or 1.5*size) gfx.y = opt_y + (size - str_h) / 2 end GUI.text_bg(str, self.bg) if #self.optarray == 1 or self.shadow then GUI.shadow(str, self.col_txt, "shadow") else GUI.color(self.col_txt) gfx.drawstr(str) end end ------------------------------------ -------- Radio methods ------------- ------------------------------------ GUI.Radio = {} setmetatable(GUI.Radio, {__index = Option}) function GUI.Radio:new(name, z, x, y, w, h, caption, opts, dir, pad) local radio = Option:new(name, z, x, y, w, h, caption, opts, dir, pad) radio.type = "Radio" radio.retval, radio.state = 1, 1 setmetatable(radio, self) self.__index = self return radio end function GUI.Radio:initoptions() local r = self.opt_size / 2 -- Option bubble GUI.color(self.bg) gfx.circle(r + 1, r + 1, r + 2, 1, 0) gfx.circle(3*r + 3, r + 1, r + 2, 1, 0) GUI.color("elm_frame") gfx.circle(r + 1, r + 1, r, 0) gfx.circle(3*r + 3, r + 1, r, 0) GUI.color(self.col_fill) gfx.circle(3*r + 3, r + 1, 0.5*r, 1) end function GUI.Radio:val(newval) if newval then self.retval = newval self.state = newval self:redraw() else return self.retval end end function GUI.Radio:onmousedown() self.state = self:getmouseopt() or self.state if self.onclickdown then self:onclickdown() end self:redraw() end function GUI.Radio:onmouseup() -- Bypass option for GUI Builder if not self.focus then self:redraw() return end -- Set the new option, or revert to the original if the cursor -- isn't inside the list anymore if GUI.IsInside(self, GUI.mouse.x, GUI.mouse.y) then self.retval = self.state else self.state = self.retval end --LKC EDIT if self.onclicked then self:onclicked() end self.focus = false self:redraw() end function GUI.Radio:ondrag() self:onmousedown() self:redraw() end function GUI.Radio:onwheel() --[[ state = GUI.round(self.state + (self.dir == "h" and 1 or -1) * GUI.mouse.inc) ]]-- self.state = self:getnextoption( GUI.xor( GUI.mouse.inc > 0, self.dir == "h" ) and -1 or 1 ) --if self.state < 1 then self.state = 1 end --if self.state > #self.optarray then self.state = #self.optarray end self.retval = self.state self:redraw() end function GUI.Radio:isoptselected(opt) return opt == self.state end function GUI.Radio:getnextoption(dir) local j = dir > 0 and #self.optarray or 1 for i = self.state + dir, j, dir do if self.optarray[i] ~= "_" then return i end end return self.state end ------------------------------------ -------- Checklist methods --------- ------------------------------------ GUI.Checklist = {} setmetatable(GUI.Checklist, {__index = Option}) function GUI.Checklist:new(name, z, x, y, w, h, caption, opts, dir, pad) local checklist = Option:new(name, z, x, y, w, h, caption, opts, dir, pad) checklist.type = "Checklist" checklist.optsel = {} setmetatable(checklist, self) self.__index = self return checklist end function GUI.Checklist:initoptions() local size = self.opt_size -- Option bubble GUI.color("elm_frame") gfx.rect(1, 1, size, size, 0) gfx.rect(size + 3, 1, size, size, 0) GUI.color(self.col_fill) gfx.rect(size + 3 + 0.25*size, 1 + 0.25*size, 0.5*size, 0.5*size, 1) end function GUI.Checklist:val(newval) if newval then if type(newval) == "table" then for k, v in pairs(newval) do self.optsel[tonumber(k)] = v end self:redraw() elseif type(newval) == "boolean" and #self.optarray == 1 then self.optsel[1] = newval self:redraw() end else if #self.optarray == 1 then return self.optsel[1] else local tmp = {} for i = 1, #self.optarray do tmp[i] = not not self.optsel[i] end return tmp end --return #self.optarray > 1 and self.optsel or self.optsel[1] end end function GUI.Checklist:onmouseup() -- Bypass option for GUI Builder if not self.focus then self:redraw() return end local mouseopt = self:getmouseopt() if not mouseopt then return end self.optsel[mouseopt] = not self.optsel[mouseopt] --LKC EDIT if self.onclicked then self:onclicked(mouseopt) end --LKC EDIT END self.focus = false self:redraw() end function GUI.Checklist:isoptselected(opt) return self.optsel[opt] end