Permalink
Cannot retrieve contributors at this time
| --- UI widgets module. | |
| -- Widgets for paging, tabs, lists, dials, sliders, etc. | |
| -- | |
| -- @module lib.UI | |
| -- @release v1.0.2 | |
| -- @author Mark Eats | |
| local UI = {} | |
| UI.__index = UI | |
| -------- Pages -------- | |
| --- Pages | |
| -- @section Pages | |
| UI.Pages = {} | |
| UI.Pages.__index = UI.Pages | |
| --- Create a new Pages object. | |
| -- @tparam number index Selected page, defaults to 1. | |
| -- @tparam number num_pages Total number of pages, defaults to 3. | |
| -- @treturn Pages Instance of Pages. | |
| function UI.Pages.new(index, num_pages) | |
| local pages = { | |
| index = index or 1, | |
| num_pages = num_pages or 3 | |
| } | |
| setmetatable(UI.Pages, {__index = UI}) | |
| setmetatable(pages, UI.Pages) | |
| return pages | |
| end | |
| --- Set selected page. | |
| -- @tparam number index Page number. | |
| function UI.Pages:set_index(index) | |
| self.index = util.clamp(index, 1, self.num_pages) | |
| end | |
| --- Set selected page using delta. | |
| -- @tparam number delta Number to move from selected page. | |
| -- @tparam boolean wrap Boolean, true to wrap pages. | |
| function UI.Pages:set_index_delta(delta, wrap) | |
| local index = self.index + delta | |
| if wrap then | |
| while index > self.num_pages do index = index - self.num_pages end | |
| while index < 1 do index = index + self.num_pages end | |
| end | |
| self:set_index(index) | |
| end | |
| --- Redraw Pages. | |
| -- Call when changed. | |
| function UI.Pages:redraw() | |
| local dot_height = util.clamp(util.round(64 / self.num_pages - 2), 1, 4) | |
| local dot_gap = util.round(util.linlin(1, 4, 1, 2, dot_height)) | |
| local dots_y = util.round((64 - self.num_pages * dot_height - (self.num_pages - 1) * dot_gap) * 0.5) | |
| for i = 1, self.num_pages do | |
| if i == self.index then screen.level(5) | |
| else screen.level(1) end | |
| screen.rect(127, dots_y, 1, dot_height) | |
| screen.fill() | |
| dots_y = dots_y + dot_height + dot_gap | |
| end | |
| end | |
| -------- Tabs -------- | |
| --- Tabs | |
| -- @section Tabs | |
| UI.Tabs = {} | |
| UI.Tabs.__index = UI.Tabs | |
| --- Create a new Tabs object. | |
| -- @tparam number index Selected tab, defaults to 1. | |
| -- @tparam {string,...} titles Table of strings for tab titles. | |
| -- @treturn Tabs Instance of Tabs. | |
| function UI.Tabs.new(index, titles) | |
| local tabs = { | |
| index = index or 1, | |
| titles = titles or {} | |
| } | |
| setmetatable(UI.Tabs, {__index = UI}) | |
| setmetatable(tabs, UI.Tabs) | |
| return tabs | |
| end | |
| --- Set selected tab. | |
| -- @tparam number index Tab number. | |
| function UI.Tabs:set_index(index) | |
| self.index = util.clamp(index, 1, #self.titles) | |
| end | |
| --- Set selected tab using delta. | |
| -- @tparam number delta Number to move from selected tab. | |
| -- @tparam boolean wrap Boolean, true to wrap tabs. | |
| function UI.Tabs:set_index_delta(delta, wrap) | |
| local index = self.index + delta | |
| local count = #self.titles | |
| if wrap then | |
| while index > count do index = index - count end | |
| while index < 1 do index = index + count end | |
| end | |
| self:set_index(index) | |
| end | |
| --- Redraw Tabs. | |
| -- Call when changed. | |
| function UI.Tabs:redraw() | |
| local MARGIN = 8 | |
| local GUTTER = 14 | |
| local col_width = (128 - (MARGIN * 2) - GUTTER * (#self.titles - 1)) / #self.titles | |
| for i = 1, #self.titles do | |
| if i == self.index then screen.level(15) | |
| else screen.level(3) end | |
| screen.move(MARGIN + col_width * 0.5 + ((col_width + GUTTER) * (i - 1)), 6) | |
| screen.text_center(self.titles[i]) | |
| end | |
| screen.fill() | |
| end | |
| -------- List -------- | |
| --- List | |
| -- @section List | |
| UI.List = {} | |
| UI.List.__index = UI.List | |
| --- Create a new List object. | |
| -- @tparam number x X position, defaults to 0. | |
| -- @tparam number y Y position, defaults to 0. | |
| -- @tparam number index Selected entry, defaults to 1. | |
| -- @tparam {string,...} entries Table of strings for list entries. | |
| -- @treturn List Instance of List. | |
| function UI.List.new(x, y, index, entries) | |
| local list = { | |
| x = x or 0, | |
| y = y or 0, | |
| index = index or 1, | |
| entries = entries or {}, | |
| text_align = "left", | |
| active = true | |
| } | |
| setmetatable(UI.List, {__index = UI}) | |
| setmetatable(list, UI.List) | |
| return list | |
| end | |
| --- Set selected entry. | |
| -- @tparam number index Entry number. | |
| function UI.List:set_index(index) | |
| self.index = util.clamp(index, 1, #self.entries) | |
| end | |
| --- Set selected list using delta. | |
| -- @tparam number delta Number to move from selected entry. | |
| -- @tparam boolean wrap Boolean, true to wrap list. | |
| function UI.List:set_index_delta(delta, wrap) | |
| local index = self.index + delta | |
| local count = #self.entries | |
| if wrap then | |
| while index > count do index = index - count end | |
| while index < 1 do index = index + count end | |
| end | |
| self:set_index(index) | |
| end | |
| --- Redraw List. | |
| -- Call when changed. | |
| function UI.List:redraw() | |
| for i = 1, #self.entries do | |
| if self.active and i == self.index then screen.level(15) | |
| else screen.level(3) end | |
| screen.move(self.x, self.y + 5 + (i - 1) * 11) | |
| local entry = self.entries[i] or "" | |
| if self.text_align == "center" then | |
| screen.text_center(entry) | |
| elseif self.text_align == "right" then | |
| screen.text_right(entry) | |
| else | |
| screen.text(entry) | |
| end | |
| end | |
| screen.fill() | |
| end | |
| -------- ScrollingList -------- | |
| --- ScrollingList | |
| -- @section Scrollinglist | |
| UI.ScrollingList = {} | |
| UI.ScrollingList.__index = UI.ScrollingList | |
| --- Create a new ScrollingList object. | |
| -- @tparam number x X position, defaults to 0. | |
| -- @tparam number y Y position, defaults to 0. | |
| -- @tparam number index Selected entry, defaults to 1. | |
| -- @tparam {string,...} entries Table of strings for list entries. | |
| -- @treturn ScrollingList Instance of ScrollingList. | |
| function UI.ScrollingList.new(x, y, index, entries) | |
| local list = { | |
| x = x or 0, | |
| y = y or 0, | |
| index = index or 1, | |
| entries = entries or {}, | |
| num_visible = 5, | |
| num_above_selected = 1, | |
| text_align = "left", | |
| active = true | |
| } | |
| setmetatable(UI.ScrollingList, {__index = UI}) | |
| setmetatable(list, UI.ScrollingList) | |
| return list | |
| end | |
| --- Set selected entry. | |
| -- @tparam number index Entry number. | |
| function UI.ScrollingList:set_index(index) | |
| self.index = util.clamp(index, 1, #self.entries) | |
| end | |
| --- Set selected scrolling list using delta. | |
| -- @tparam number delta Number to move from selected entry. | |
| -- @tparam boolean wrap Boolean, true to wrap list. | |
| function UI.ScrollingList:set_index_delta(delta, wrap) | |
| local index = self.index + delta | |
| local count = #self.entries | |
| if wrap then | |
| while index > count do index = index - count end | |
| while index < 1 do index = index + count end | |
| end | |
| self:set_index(index) | |
| end | |
| --- Redraw ScrollingList. | |
| -- Call when changed. | |
| function UI.ScrollingList:redraw() | |
| local num_entries = #self.entries | |
| local scroll_offset = self.index - 1 - math.max(self.index - (num_entries - 2), 0) | |
| scroll_offset = scroll_offset - util.linlin(num_entries - self.num_above_selected, num_entries, self.num_above_selected, 0, self.index - 1) -- For end of list | |
| for i = 1, self.num_visible do | |
| if self.active and self.index == i + scroll_offset then screen.level(15) | |
| else screen.level(3) end | |
| screen.move(self.x, self.y + 5 + (i - 1) * 11) | |
| local entry = self.entries[i + scroll_offset] or "" | |
| if self.text_align == "center" then | |
| screen.text_center(entry) | |
| elseif self.text_align == "right" then | |
| screen.text_right(entry) | |
| else | |
| screen.text(entry) | |
| end | |
| end | |
| screen.fill() | |
| end | |
| -------- Message -------- | |
| --- Message | |
| -- @section Message | |
| UI.Message = {} | |
| UI.Message.__index = UI.Message | |
| --- Create a new Message object. | |
| -- @tparam [string,...] text_array Array of lines of text. | |
| -- @treturn Message Instance of Message. | |
| function UI.Message.new(text_array) | |
| local message = { | |
| text = text_array or {}, | |
| active = true | |
| } | |
| setmetatable(UI.Message, {__index = UI}) | |
| setmetatable(message, UI.Message) | |
| return message | |
| end | |
| --- Redraw Message. | |
| -- Call when changed. | |
| function UI.Message:redraw() | |
| local LINE_HEIGHT = 11 | |
| local y = util.round(34 - LINE_HEIGHT * (#self.text - 1) * 0.5) | |
| for i = 1, #self.text do | |
| if self.active then screen.level(15) | |
| else screen.level(3) end | |
| screen.move(64, y) | |
| screen.text_center(self.text[i]) | |
| y = y + 11 | |
| end | |
| screen.fill() | |
| end | |
| -------- Slider -------- | |
| --- Slider | |
| -- @section Slider | |
| UI.Slider = {} | |
| UI.Slider.__index = UI.Slider | |
| --- Create a new Slider object. | |
| -- @tparam number x X position, defaults to 0. | |
| -- @tparam number y Y position, defaults to 0. | |
| -- @tparam number width Width of slider, defaults to 3. | |
| -- @tparam number height Height of slider, defaults to 36. | |
| -- @tparam number value Current value, defaults to 0. | |
| -- @tparam number min_value Minimum value, defaults to 0. | |
| -- @tparam number max_value Maximum value, defaults to 1. | |
| -- @tparam table markers Array of marker positions. | |
| -- @tparam string the direction of the slider "up" (defult), down, left, right | |
| -- @treturn Slider Instance of Slider. | |
| function UI.Slider.new(x, y, width, height, value, min_value, max_value, markers, direction) | |
| local slider = { | |
| x = x or 0, | |
| y = y or 0, | |
| width = width or 3, | |
| height = height or 36, | |
| value = value or 0, | |
| min_value = min_value or 0, | |
| max_value = max_value or 1, | |
| markers = markers or {}, | |
| active = true, | |
| direction = direction or "up" | |
| } | |
| local acceptableDirections = {"up","down","left","right"} | |
| if (acceptableDirections[direction] == nil) then direction = acceptableDirections[1] end | |
| setmetatable(UI.Slider, {__index = UI}) | |
| setmetatable(slider, UI.Slider) | |
| return slider | |
| end | |
| --- Set value. | |
| -- @tparam number number Value number. | |
| function UI.Slider:set_value(number) | |
| self.value = util.clamp(number, self.min_value, self.max_value) | |
| end | |
| --- Set value using delta. | |
| -- @tparam number delta Number. | |
| function UI.Slider:set_value_delta(delta) | |
| self:set_value(self.value + delta) | |
| end | |
| --- Set marker position. | |
| -- @tparam number id Marker number. | |
| -- @tparam number position Marker position number. | |
| function UI.Slider:set_marker_position(id, position) | |
| self.markers[id] = util.clamp(position, self.min_value, self.max_value) | |
| end | |
| --- Redraw Slider. | |
| -- Call when changed. | |
| function UI.Slider:redraw() | |
| screen.level(3) | |
| --draws the perimeter | |
| if (self.direction == "up" or self.direction == "down") then | |
| screen.rect(self.x + 0.5, self.y + 0.5, self.width - 1, self.height - 1) | |
| elseif (self.direction == "left" or self.direction == "right") then | |
| screen.rect(self.x + 0.5, self.y + 0.5, self.width - 1, self.height - 1) | |
| end | |
| screen.stroke() | |
| --draws the markers | |
| for _, v in pairs(self.markers) do | |
| if self.direction == "up" then | |
| screen.rect(self.x - 2, util.round(self.y + util.linlin(self.min_value, self.max_value, self.height - 1, 0, v)), self.width + 4, 1) --original | |
| elseif self.direction == "down" then | |
| screen.rect(self.x - 2, util.round(self.y + util.linlin(self.min_value, self.max_value, 0,self.height - 1, v)), self.width + 4, 1) | |
| elseif self.direction == "left" then | |
| screen.rect(util.round(self.x + util.linlin(self.min_value, self.max_value, self.width - 1, 0, v)), self.y - 2, 1, self.height +4) | |
| elseif self.direction == "right" then | |
| screen.rect(util.round(self.x + util.linlin(self.min_value, self.max_value, 0, self.width - 1, v)), self.y - 2, 1, self.height +4) | |
| end | |
| end | |
| screen.fill() | |
| --draws the value | |
| --local filled_height = util.round(util.linlin(self.min_value, self.max_value, 0, self.height, self.value)) | |
| --screen.rect(self.x, self.y + self.height - filled_height, self.width, filled_height) | |
| local filled_amount --sometimes width now | |
| if self.direction == "up" then | |
| filled_amount = util.round(util.linlin(self.min_value, self.max_value, 0, self.height, self.value)) | |
| screen.rect(self.x, self.y + self.height - filled_amount, self.width, filled_amount) | |
| elseif self.direction == "down" then | |
| filled_amount = util.round(util.linlin(self.min_value, self.max_value, 0, self.height, self.value)) --same as up | |
| screen.rect(self.x, self.y, self.width, filled_amount) | |
| elseif self.direction == "left" then | |
| filled_amount = util.round(util.linlin(self.min_value, self.max_value, 0, self.width, self.value)) | |
| screen.rect(self.x + self.width - filled_amount, self.y, filled_amount, self.height) | |
| elseif self.direction == "right" then | |
| filled_amount = util.round(util.linlin(self.min_value, self.max_value, 0, self.width, self.value)) | |
| screen.rect(self.x, self.y, filled_amount, self.height) | |
| end | |
| if self.active then screen.level(15) else screen.level(5) end | |
| screen.fill() | |
| end | |
| -------- Dial -------- | |
| --- Dial | |
| -- @section Dial | |
| UI.Dial = {} | |
| UI.Dial.__index = UI.Dial | |
| --- Create a new Dial object. | |
| -- @tparam number x X position, defaults to 0. | |
| -- @tparam number y Y position, defaults to 0. | |
| -- @tparam number size Diameter of dial, defaults to 22. | |
| -- @tparam number value Current value, defaults to 0. | |
| -- @tparam number min_value Minimum value, defaults to 0. | |
| -- @tparam number max_value Maximum value, defaults to 1. | |
| -- @tparam number rounding Sets precision to round value to, defaults to 0.01. | |
| -- @tparam number start_value Sets where fill line is drawn from, defaults to 0. | |
| -- @tparam table markers Array of marker positions. | |
| -- @tparam string units String to display after value text. | |
| -- @tparam string title String to be displayed instead of value text. | |
| -- @treturn Dial Instance of Dial. | |
| function UI.Dial.new(x, y, size, value, min_value, max_value, rounding, start_value, markers, units, title) | |
| local markers_table = markers or {} | |
| min_value = min_value or 0 | |
| local dial = { | |
| x = x or 0, | |
| y = y or 0, | |
| size = size or 22, | |
| value = value or 0, | |
| min_value = min_value, | |
| max_value = max_value or 1, | |
| rounding = rounding or 0.01, | |
| start_value = start_value or min_value, | |
| units = units, | |
| title = title or nil, | |
| active = true, | |
| _start_angle = math.pi * 0.7, | |
| _end_angle = math.pi * 2.3, | |
| _markers = {}, | |
| _marker_points = {} | |
| } | |
| setmetatable(UI.Dial, {__index = UI}) | |
| setmetatable(dial, UI.Dial) | |
| for k, v in pairs(markers_table) do | |
| dial:set_marker_position(k, v) | |
| end | |
| return dial | |
| end | |
| --- Set value. | |
| -- @tparam number number Value number. | |
| function UI.Dial:set_value(number) | |
| self.value = util.clamp(number, self.min_value, self.max_value) | |
| end | |
| --- Set value using delta. | |
| -- @tparam number delta Number. | |
| function UI.Dial:set_value_delta(delta) | |
| self:set_value(self.value + delta) | |
| end | |
| --- Set marker position. | |
| -- @tparam number id Marker number. | |
| -- @tparam number position Marker position number. | |
| function UI.Dial:set_marker_position(id, position) | |
| self._markers[id] = util.clamp(position, self.min_value, self.max_value) | |
| local radius = self.size * 0.5 | |
| local marker_length = 3 | |
| local marker_in = radius - marker_length | |
| local marker_out = radius + marker_length | |
| local marker_angle = util.linlin(self.min_value, self.max_value, self._start_angle, self._end_angle, self._markers[id]) | |
| local x_center = self.x + self.size / 2 | |
| local y_center = self.y + self.size / 2 | |
| self._marker_points[id] = {} | |
| self._marker_points[id].x1 = x_center + math.cos(marker_angle) * marker_in | |
| self._marker_points[id].y1 = y_center + math.sin(marker_angle) * marker_in | |
| self._marker_points[id].x2 = x_center + math.cos(marker_angle) * marker_out | |
| self._marker_points[id].y2 = y_center + math.sin(marker_angle) * marker_out | |
| end | |
| --- Redraw Dial. | |
| -- Call when changed. | |
| function UI.Dial:redraw() | |
| local radius = self.size * 0.5 | |
| local fill_start_angle = util.linlin(self.min_value, self.max_value, self._start_angle, self._end_angle, self.start_value) | |
| local fill_end_angle = util.linlin(self.min_value, self.max_value, self._start_angle, self._end_angle, self.value) | |
| if fill_end_angle < fill_start_angle then | |
| local temp_angle = fill_start_angle | |
| fill_start_angle = fill_end_angle | |
| fill_end_angle = temp_angle | |
| end | |
| screen.level(5) | |
| screen.arc(self.x + radius, self.y + radius, radius - 0.5, self._start_angle, self._end_angle) | |
| screen.stroke() | |
| for _, v in pairs(self._marker_points) do | |
| screen.move(v.x1, v.y1) | |
| screen.line(v.x2, v.y2) | |
| screen.stroke() | |
| end | |
| screen.level(15) | |
| screen.line_width(2.5) | |
| screen.arc(self.x + radius, self.y + radius, radius - 0.5, fill_start_angle, fill_end_angle) | |
| screen.stroke() | |
| screen.line_width(1) | |
| local title | |
| if self.title then | |
| title = self.title | |
| else | |
| title = util.round(self.value, self.rounding) | |
| if self.units then | |
| title = title .. " " .. self.units | |
| end | |
| end | |
| if self.active then screen.level(15) else screen.level(3) end | |
| screen.move(self.x + radius, self.y + self.size + 6) | |
| screen.text_center(title) | |
| screen.fill() | |
| end | |
| -------- PlaybackIcon -------- | |
| --- PlaybackIcon | |
| -- @section PlaybackIcon | |
| UI.PlaybackIcon = {} | |
| UI.PlaybackIcon.__index = UI.PlaybackIcon | |
| --- Create a new PlaybackIcon object. | |
| -- @tparam number x X position, defaults to 0. | |
| -- @tparam number y Y position, defaults to 0. | |
| -- @tparam number size Icon size, defaults to 6. | |
| -- @tparam number status Status number. 1 = Play, 2 = Reverse Play, 3 = Pause, 4 = Stop. Defaults to 1. | |
| -- @treturn PlaybackIcon Instance of PlaybackIcon. | |
| function UI.PlaybackIcon.new(x, y, size, status) | |
| local playback_icon = { | |
| x = x or 0, | |
| y = y or 0, | |
| size = size or 6, | |
| status = status or 1, | |
| active = true | |
| } | |
| setmetatable(UI.PlaybackIcon, {__index = UI}) | |
| setmetatable(playback_icon, UI.PlaybackIcon) | |
| return playback_icon | |
| end | |
| --- Redraw PlaybackIcon. | |
| -- Call when changed. | |
| function UI.PlaybackIcon:redraw() | |
| if self.active then screen.level(15) | |
| else screen.level(3) end | |
| -- Play | |
| if self.status == 1 then | |
| screen.move(self.x, self.y) | |
| screen.line(self.x + self.size, self.y + self.size * 0.5) | |
| screen.line(self.x, self.y + self.size) | |
| screen.close() | |
| -- Reverse Play | |
| elseif self.status == 2 then | |
| screen.move(self.x + self.size, self.y) | |
| screen.line(self.x, self.y + self.size * 0.5) | |
| screen.line(self.x + self.size, self.y + self.size) | |
| screen.close() | |
| -- Pause | |
| elseif self.status == 3 then | |
| screen.rect(self.x, self.y, util.round(self.size * 0.4), self.size) | |
| screen.rect(self.x + util.round(self.size * 0.6), self.y, util.round(self.size * 0.4), self.size) | |
| -- Stop | |
| else | |
| screen.rect(self.x, self.y, self.size, self.size) | |
| end | |
| screen.fill() | |
| end | |
| return UI |