| @@ -0,0 +1,213 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIMiniWindowContainer = extends(UIWidget, "UIMiniWindowContainer") | |
| 3 | + | ||
| 4 | + | function UIMiniWindowContainer.create() | |
| 5 | + | local container = UIMiniWindowContainer.internalCreate() | |
| 6 | + | container.scheduledWidgets = {} | |
| 7 | + | container:setFocusable(false) | |
| 8 | + | container:setPhantom(true) | |
| 9 | + | return container | |
| 10 | + | end | |
| 11 | + | ||
| 12 | + | -- TODO: connect to window onResize event | |
| 13 | + | -- TODO: try to resize another widget? | |
| 14 | + | -- TODO: try to find another panel? | |
| 15 | + | function UIMiniWindowContainer:fitAll(noRemoveChild) | |
| 16 | + | if not self:isVisible() then | |
| 17 | + | return | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | if not noRemoveChild then | |
| 21 | + | local children = self:getChildren() | |
| 22 | + | if #children > 0 then | |
| 23 | + | noRemoveChild = children[#children] | |
| 24 | + | else | |
| 25 | + | return | |
| 26 | + | end | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | local sumHeight = 0 | |
| 30 | + | local children = self:getChildren() | |
| 31 | + | for i=1,#children do | |
| 32 | + | if children[i]:isVisible() then | |
| 33 | + | sumHeight = sumHeight + children[i]:getHeight() | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | local selfHeight = self:getHeight() - (self:getPaddingTop() + self:getPaddingBottom()) | |
| 38 | + | if sumHeight <= selfHeight then | |
| 39 | + | return | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | local removeChildren = {} | |
| 43 | + | ||
| 44 | + | -- try to resize noRemoveChild | |
| 45 | + | local maximumHeight = selfHeight - (sumHeight - noRemoveChild:getHeight()) | |
| 46 | + | if noRemoveChild:isResizeable() and noRemoveChild:getMinimumHeight() <= maximumHeight then | |
| 47 | + | sumHeight = sumHeight - noRemoveChild:getHeight() + maximumHeight | |
| 48 | + | addEvent(function() noRemoveChild:setHeight(maximumHeight) end) | |
| 49 | + | end | |
| 50 | + | ||
| 51 | + | -- try to remove no-save widget | |
| 52 | + | for i=#children,1,-1 do | |
| 53 | + | if sumHeight <= selfHeight then | |
| 54 | + | break | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | local child = children[i] | |
| 58 | + | if child ~= noRemoveChild and not child.save then | |
| 59 | + | local childHeight = child:getHeight() | |
| 60 | + | sumHeight = sumHeight - childHeight | |
| 61 | + | table.insert(removeChildren, child) | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | -- try to remove save widget, not forceOpen | |
| 66 | + | for i=#children,1,-1 do | |
| 67 | + | if sumHeight <= selfHeight then | |
| 68 | + | break | |
| 69 | + | end | |
| 70 | + | ||
| 71 | + | local child = children[i] | |
| 72 | + | if child ~= noRemoveChild and child:isVisible() and not child.forceOpen then | |
| 73 | + | local childHeight = child:getHeight() | |
| 74 | + | sumHeight = sumHeight - childHeight | |
| 75 | + | table.insert(removeChildren, child) | |
| 76 | + | end | |
| 77 | + | end | |
| 78 | + | ||
| 79 | + | -- try to remove save widget | |
| 80 | + | for i=#children,1,-1 do | |
| 81 | + | if sumHeight <= selfHeight then | |
| 82 | + | break | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | local child = children[i] | |
| 86 | + | if child ~= noRemoveChild and child:isVisible() then | |
| 87 | + | local childHeight = child:getHeight() - 50 | |
| 88 | + | sumHeight = sumHeight - childHeight | |
| 89 | + | table.insert(removeChildren, child) | |
| 90 | + | end | |
| 91 | + | end | |
| 92 | + | ||
| 93 | + | -- close widgets | |
| 94 | + | for i=1,#removeChildren do | |
| 95 | + | if removeChildren[i].forceOpen then | |
| 96 | + | removeChildren[i]:minimize(true) | |
| 97 | + | else | |
| 98 | + | removeChildren[i]:close() | |
| 99 | + | end | |
| 100 | + | end | |
| 101 | + | end | |
| 102 | + | ||
| 103 | + | function UIMiniWindowContainer:onDrop(widget, mousePos) | |
| 104 | + | if widget.UIMiniWindowContainer then | |
| 105 | + | local oldParent = widget:getParent() | |
| 106 | + | if oldParent == self then | |
| 107 | + | return true | |
| 108 | + | end | |
| 109 | + | ||
| 110 | + | if oldParent then | |
| 111 | + | oldParent:removeChild(widget) | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | if widget.movedWidget then | |
| 115 | + | local index = self:getChildIndex(widget.movedWidget) | |
| 116 | + | self:insertChild(index + widget.movedIndex, widget) | |
| 117 | + | else | |
| 118 | + | self:addChild(widget) | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | self:fitAll(widget) | |
| 122 | + | return true | |
| 123 | + | end | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | function UIMiniWindowContainer:moveTo(newPanel) | |
| 127 | + | if not newPanel or newPanel == self then | |
| 128 | + | return | |
| 129 | + | end | |
| 130 | + | local children = self:getChildByIndex(1) | |
| 131 | + | while children do | |
| 132 | + | newPanel:addChild(children) | |
| 133 | + | children = self:getChildByIndex(1) | |
| 134 | + | end | |
| 135 | + | newPanel:fitAll() | |
| 136 | + | end | |
| 137 | + | ||
| 138 | + | function UIMiniWindowContainer:swapInsert(widget, index) | |
| 139 | + | local oldParent = widget:getParent() | |
| 140 | + | local oldIndex = self:getChildIndex(widget) | |
| 141 | + | ||
| 142 | + | if oldParent == self and oldIndex ~= index then | |
| 143 | + | local oldWidget = self:getChildByIndex(index) | |
| 144 | + | if oldWidget then | |
| 145 | + | self:removeChild(oldWidget) | |
| 146 | + | self:insertChild(oldIndex, oldWidget) | |
| 147 | + | end | |
| 148 | + | self:removeChild(widget) | |
| 149 | + | self:insertChild(index, widget) | |
| 150 | + | end | |
| 151 | + | end | |
| 152 | + | ||
| 153 | + | function UIMiniWindowContainer:scheduleInsert(widget, index) | |
| 154 | + | if index - 1 > self:getChildCount() then | |
| 155 | + | if self.scheduledWidgets[index] then | |
| 156 | + | pdebug('replacing scheduled widget id ' .. widget:getId()) | |
| 157 | + | end | |
| 158 | + | self.scheduledWidgets[index] = widget | |
| 159 | + | else | |
| 160 | + | local oldParent = widget:getParent() | |
| 161 | + | if oldParent ~= self then | |
| 162 | + | if oldParent then | |
| 163 | + | oldParent:removeChild(widget) | |
| 164 | + | end | |
| 165 | + | self:insertChild(index, widget) | |
| 166 | + | ||
| 167 | + | while true do | |
| 168 | + | local placed = false | |
| 169 | + | for nIndex,nWidget in pairs(self.scheduledWidgets) do | |
| 170 | + | if nIndex - 1 <= self:getChildCount() then | |
| 171 | + | self:insertChild(nIndex, nWidget) | |
| 172 | + | self.scheduledWidgets[nIndex] = nil | |
| 173 | + | placed = true | |
| 174 | + | break | |
| 175 | + | end | |
| 176 | + | end | |
| 177 | + | if not placed then break end | |
| 178 | + | end | |
| 179 | + | ||
| 180 | + | end | |
| 181 | + | end | |
| 182 | + | end | |
| 183 | + | ||
| 184 | + | function UIMiniWindowContainer:order() | |
| 185 | + | local children = self:getChildren() | |
| 186 | + | for i=1,#children do | |
| 187 | + | if not children[i].miniLoaded then return end | |
| 188 | + | end | |
| 189 | + | ||
| 190 | + | for i=1,#children do | |
| 191 | + | if children[i].miniIndex then | |
| 192 | + | self:swapInsert(children[i], children[i].miniIndex) | |
| 193 | + | elseif children[i].autoOpen then | |
| 194 | + | self:swapInsert(children[i], children[i].autoOpen) | |
| 195 | + | end | |
| 196 | + | end | |
| 197 | + | end | |
| 198 | + | ||
| 199 | + | function UIMiniWindowContainer:saveChildren() | |
| 200 | + | local children = self:getChildren() | |
| 201 | + | local ignoreIndex = 0 | |
| 202 | + | for i=1,#children do | |
| 203 | + | if children[i].save then | |
| 204 | + | children[i]:saveParentIndex(self:getId(), i - ignoreIndex) | |
| 205 | + | else | |
| 206 | + | ignoreIndex = ignoreIndex + 1 | |
| 207 | + | end | |
| 208 | + | end | |
| 209 | + | end | |
| 210 | + | ||
| 211 | + | function UIMiniWindowContainer:onGeometryChange() | |
| 212 | + | self:fitAll() | |
| 213 | + | end |
| @@ -0,0 +1,505 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIMoveableTabBar = extends(UIWidget, "UIMoveableTabBar") | |
| 3 | + | ||
| 4 | + | -- private functions | |
| 5 | + | local function onTabClick(tab) | |
| 6 | + | tab.tabBar:selectTab(tab) | |
| 7 | + | end | |
| 8 | + | ||
| 9 | + | local function updateMargins(tabBar) | |
| 10 | + | if #tabBar.tabs == 0 then return end | |
| 11 | + | ||
| 12 | + | local currentMargin = 0 | |
| 13 | + | for i = 1, #tabBar.tabs do | |
| 14 | + | tabBar.tabs[i]:setMarginLeft(currentMargin) | |
| 15 | + | currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth() | |
| 16 | + | end | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | local function updateNavigation(tabBar) | |
| 20 | + | if tabBar.prevNavigation then | |
| 21 | + | if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then | |
| 22 | + | tabBar.prevNavigation:enable() | |
| 23 | + | else | |
| 24 | + | tabBar.prevNavigation:disable() | |
| 25 | + | end | |
| 26 | + | end | |
| 27 | + | ||
| 28 | + | if tabBar.nextNavigation then | |
| 29 | + | if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then | |
| 30 | + | tabBar.nextNavigation:enable() | |
| 31 | + | else | |
| 32 | + | tabBar.nextNavigation:disable() | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | local function updateIndexes(tabBar, tab, xoff) | |
| 38 | + | local tabs = tabBar.tabs | |
| 39 | + | local currentMargin = 0 | |
| 40 | + | local prevIndex = table.find(tabs, tab) | |
| 41 | + | local newIndex = prevIndex | |
| 42 | + | local xmid = xoff + tab:getWidth()/2 | |
| 43 | + | for i = 1, #tabs do | |
| 44 | + | local nextTab = tabs[i] | |
| 45 | + | if xmid >= currentMargin + nextTab:getWidth()/2 then | |
| 46 | + | newIndex = table.find(tabs, nextTab) | |
| 47 | + | end | |
| 48 | + | currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth() | |
| 49 | + | end | |
| 50 | + | if newIndex ~= prevIndex then | |
| 51 | + | table.remove(tabs, table.find(tabs, tab)) | |
| 52 | + | table.insert(tabs, newIndex, tab) | |
| 53 | + | end | |
| 54 | + | updateNavigation(tabBar) | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | local function getMaxMargin(tabBar, tab) | |
| 58 | + | if #tabBar.tabs == 0 then return 0 end | |
| 59 | + | ||
| 60 | + | local maxMargin = 0 | |
| 61 | + | for i = 1, #tabBar.tabs do | |
| 62 | + | if tabBar.tabs[i] ~= tab then | |
| 63 | + | maxMargin = maxMargin + tabBar.tabs[i]:getWidth() | |
| 64 | + | end | |
| 65 | + | end | |
| 66 | + | return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1) | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | local function updateTabs(tabBar) | |
| 70 | + | if #tabBar.postTabs > 0 then | |
| 71 | + | local i = 1 | |
| 72 | + | while i <= #tabBar.postTabs do | |
| 73 | + | local tab = tabBar.postTabs[i] | |
| 74 | + | if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then | |
| 75 | + | break | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | table.remove(tabBar.postTabs, i) | |
| 79 | + | table.insert(tabBar.tabs, tab) | |
| 80 | + | tab:setVisible(true) | |
| 81 | + | end | |
| 82 | + | end | |
| 83 | + | if #tabBar.preTabs > 0 then | |
| 84 | + | for i = #tabBar.preTabs, 1, -1 do | |
| 85 | + | local tab = tabBar.preTabs[i] | |
| 86 | + | if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then | |
| 87 | + | break | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | table.remove(tabBar.preTabs, i) | |
| 91 | + | table.insert(tabBar.tabs, 1, tab) | |
| 92 | + | tab:setVisible(true) | |
| 93 | + | end | |
| 94 | + | end | |
| 95 | + | updateNavigation(tabBar) | |
| 96 | + | updateMargins(tabBar) | |
| 97 | + | if not tabBar.currentTab and #tabBar.tabs > 0 then | |
| 98 | + | tabBar:selectTab(tabBar.tabs[1]) | |
| 99 | + | end | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | local function hideTabs(tabBar, fromBack, toArray, width) | |
| 103 | + | while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do | |
| 104 | + | local index = fromBack and #tabBar.tabs or 1 | |
| 105 | + | local tab = tabBar.tabs[index] | |
| 106 | + | table.remove(tabBar.tabs, index) | |
| 107 | + | if fromBack then | |
| 108 | + | table.insert(toArray, 1, tab) | |
| 109 | + | else | |
| 110 | + | table.insert(toArray, tab) | |
| 111 | + | end | |
| 112 | + | if tabBar.currentTab == tab then | |
| 113 | + | if #tabBar.tabs > 0 then | |
| 114 | + | tabBar:selectTab(tabBar.tabs[#tabBar.tabs]) | |
| 115 | + | else | |
| 116 | + | tabBar.currentTab:setChecked(false) | |
| 117 | + | tabBar.currentTab = nil | |
| 118 | + | end | |
| 119 | + | end | |
| 120 | + | tab:setVisible(false) | |
| 121 | + | end | |
| 122 | + | end | |
| 123 | + | ||
| 124 | + | local function showPreTab(tabBar) | |
| 125 | + | if #tabBar.preTabs == 0 then | |
| 126 | + | return nil | |
| 127 | + | end | |
| 128 | + | ||
| 129 | + | local tmpTab = tabBar.preTabs[#tabBar.preTabs] | |
| 130 | + | hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth()) | |
| 131 | + | ||
| 132 | + | table.remove(tabBar.preTabs, #tabBar.preTabs) | |
| 133 | + | table.insert(tabBar.tabs, 1, tmpTab) | |
| 134 | + | tmpTab:setVisible(true) | |
| 135 | + | return tmpTab | |
| 136 | + | end | |
| 137 | + | ||
| 138 | + | local function showPostTab(tabBar) | |
| 139 | + | if #tabBar.postTabs == 0 then | |
| 140 | + | return nil | |
| 141 | + | end | |
| 142 | + | ||
| 143 | + | local tmpTab = tabBar.postTabs[1] | |
| 144 | + | hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth()) | |
| 145 | + | ||
| 146 | + | table.remove(tabBar.postTabs, 1) | |
| 147 | + | table.insert(tabBar.tabs, tmpTab) | |
| 148 | + | tmpTab:setVisible(true) | |
| 149 | + | return tmpTab | |
| 150 | + | end | |
| 151 | + | ||
| 152 | + | local function onTabMousePress(tab, mousePos, mouseButton) | |
| 153 | + | if mouseButton == MouseRightButton then | |
| 154 | + | if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end | |
| 155 | + | return true | |
| 156 | + | end | |
| 157 | + | end | |
| 158 | + | ||
| 159 | + | local function onTabDragEnter(tab, mousePos) | |
| 160 | + | tab:raise() | |
| 161 | + | tab.hotSpot = mousePos.x - tab:getMarginLeft() | |
| 162 | + | tab.tabBar.selected = tab | |
| 163 | + | return true | |
| 164 | + | end | |
| 165 | + | ||
| 166 | + | local function onTabDragLeave(tab) | |
| 167 | + | updateMargins(tab.tabBar) | |
| 168 | + | tab.tabBar.selected = nil | |
| 169 | + | return true | |
| 170 | + | end | |
| 171 | + | ||
| 172 | + | local function onTabDragMove(tab, mousePos, mouseMoved) | |
| 173 | + | if tab == tab.tabBar.selected then | |
| 174 | + | local xoff = mousePos.x - tab.hotSpot | |
| 175 | + | ||
| 176 | + | -- update indexes | |
| 177 | + | updateIndexes(tab.tabBar, tab, xoff) | |
| 178 | + | updateIndexes(tab.tabBar, tab, xoff) | |
| 179 | + | ||
| 180 | + | -- update margins | |
| 181 | + | updateMargins(tab.tabBar) | |
| 182 | + | xoff = math.max(xoff, 0) | |
| 183 | + | xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab)) | |
| 184 | + | tab:setMarginLeft(xoff) | |
| 185 | + | end | |
| 186 | + | end | |
| 187 | + | ||
| 188 | + | local function tabBlink(tab, step) | |
| 189 | + | local step = step or 0 | |
| 190 | + | tab:setOn(not tab:isOn()) | |
| 191 | + | ||
| 192 | + | removeEvent(tab.blinkEvent) | |
| 193 | + | if step < 4 then | |
| 194 | + | tab.blinkEvent = scheduleEvent(function() tabBlink(tab, step+1) end, 500) | |
| 195 | + | else | |
| 196 | + | tab:setOn(true) | |
| 197 | + | tab.blinkEvent = nil | |
| 198 | + | end | |
| 199 | + | end | |
| 200 | + | ||
| 201 | + | -- public functions | |
| 202 | + | function UIMoveableTabBar.create() | |
| 203 | + | local tabbar = UIMoveableTabBar.internalCreate() | |
| 204 | + | tabbar:setFocusable(false) | |
| 205 | + | tabbar.tabs = {} | |
| 206 | + | tabbar.selected = nil -- dragged tab | |
| 207 | + | tabbar.tabSpacing = 0 | |
| 208 | + | tabbar.tabsMoveable = false | |
| 209 | + | tabbar.preTabs = {} | |
| 210 | + | tabbar.postTabs = {} | |
| 211 | + | tabbar.prevNavigation = nil | |
| 212 | + | tabbar.nextNavigation = nil | |
| 213 | + | tabbar.onGeometryChange = function() | |
| 214 | + | hideTabs(tabbar, true, tabbar.postTabs, 0) | |
| 215 | + | updateTabs(tabbar) | |
| 216 | + | end | |
| 217 | + | return tabbar | |
| 218 | + | end | |
| 219 | + | ||
| 220 | + | function UIMoveableTabBar:onDestroy() | |
| 221 | + | if self.prevNavigation then | |
| 222 | + | self.prevNavigation:disable() | |
| 223 | + | end | |
| 224 | + | ||
| 225 | + | if self.nextNavigation then | |
| 226 | + | self.nextNavigation:disable() | |
| 227 | + | end | |
| 228 | + | ||
| 229 | + | self.nextNavigation = nil | |
| 230 | + | self.prevNavigation = nil | |
| 231 | + | end | |
| 232 | + | ||
| 233 | + | function UIMoveableTabBar:setContentWidget(widget) | |
| 234 | + | self.contentWidget = widget | |
| 235 | + | if #self.tabs > 0 then | |
| 236 | + | self.contentWidget:addChild(self.tabs[1].tabPanel) | |
| 237 | + | end | |
| 238 | + | end | |
| 239 | + | ||
| 240 | + | function UIMoveableTabBar:setTabSpacing(tabSpacing) | |
| 241 | + | self.tabSpacing = tabSpacing | |
| 242 | + | updateMargins(self) | |
| 243 | + | end | |
| 244 | + | ||
| 245 | + | function UIMoveableTabBar:addTab(text, panel, menuCallback) | |
| 246 | + | if panel == nil then | |
| 247 | + | panel = g_ui.createWidget(self:getStyleName() .. 'Panel') | |
| 248 | + | panel:setId('tabPanel') | |
| 249 | + | end | |
| 250 | + | ||
| 251 | + | local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self) | |
| 252 | + | panel.isTab = true | |
| 253 | + | tab.tabPanel = panel | |
| 254 | + | tab.tabBar = self | |
| 255 | + | tab:setId('tab') | |
| 256 | + | tab:setDraggable(self.tabsMoveable) | |
| 257 | + | tab:setText(text) | |
| 258 | + | tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) | |
| 259 | + | tab.menuCallback = menuCallback or nil | |
| 260 | + | tab.onClick = onTabClick | |
| 261 | + | tab.onMousePress = onTabMousePress | |
| 262 | + | tab.onDragEnter = onTabDragEnter | |
| 263 | + | tab.onDragLeave = onTabDragLeave | |
| 264 | + | tab.onDragMove = onTabDragMove | |
| 265 | + | tab.onDestroy = function() tab.tabPanel:destroy() end | |
| 266 | + | ||
| 267 | + | if #self.tabs == 0 then | |
| 268 | + | self:selectTab(tab) | |
| 269 | + | tab:setMarginLeft(0) | |
| 270 | + | table.insert(self.tabs, tab) | |
| 271 | + | else | |
| 272 | + | local newMargin = self.tabSpacing * #self.tabs | |
| 273 | + | for i = 1, #self.tabs do | |
| 274 | + | newMargin = newMargin + self.tabs[i]:getWidth() | |
| 275 | + | end | |
| 276 | + | tab:setMarginLeft(newMargin) | |
| 277 | + | ||
| 278 | + | hideTabs(self, true, self.postTabs, tab:getWidth()) | |
| 279 | + | table.insert(self.tabs, tab) | |
| 280 | + | if #self.tabs == 1 then | |
| 281 | + | self:selectTab(tab) | |
| 282 | + | end | |
| 283 | + | updateMargins(self) | |
| 284 | + | end | |
| 285 | + | ||
| 286 | + | updateNavigation(self) | |
| 287 | + | return tab | |
| 288 | + | end | |
| 289 | + | ||
| 290 | + | -- Additional function to move the tab by lua | |
| 291 | + | function UIMoveableTabBar:moveTab(tab, units) | |
| 292 | + | local index = table.find(self.tabs, tab) | |
| 293 | + | if index == nil then return end | |
| 294 | + | ||
| 295 | + | local focus = false | |
| 296 | + | if self.currentTab == tab then | |
| 297 | + | self:selectPrevTab() | |
| 298 | + | focus = true | |
| 299 | + | end | |
| 300 | + | ||
| 301 | + | table.remove(self.tabs, index) | |
| 302 | + | ||
| 303 | + | local newIndex = math.min(#self.tabs+1, math.max(index + units, 1)) | |
| 304 | + | table.insert(self.tabs, newIndex, tab) | |
| 305 | + | if focus then self:selectTab(tab) end | |
| 306 | + | updateMargins(self) | |
| 307 | + | return newIndex | |
| 308 | + | end | |
| 309 | + | ||
| 310 | + | function UIMoveableTabBar:onStyleApply(styleName, styleNode) | |
| 311 | + | if styleNode['movable'] then | |
| 312 | + | self.tabsMoveable = styleNode['movable'] | |
| 313 | + | end | |
| 314 | + | if styleNode['tab-spacing'] then | |
| 315 | + | self:setTabSpacing(styleNode['tab-spacing']) | |
| 316 | + | end | |
| 317 | + | end | |
| 318 | + | ||
| 319 | + | function UIMoveableTabBar:clearTabs() | |
| 320 | + | while #self.tabs > 0 do | |
| 321 | + | self:removeTab(self.tabs[#self.tabs]) | |
| 322 | + | end | |
| 323 | + | end | |
| 324 | + | ||
| 325 | + | function UIMoveableTabBar:removeTab(tab) | |
| 326 | + | local tabTables = {self.tabs, self.preTabs, self.postTabs} | |
| 327 | + | local index = nil | |
| 328 | + | local tabTable = nil | |
| 329 | + | for i = 1, #tabTables do | |
| 330 | + | index = table.find(tabTables[i], tab) | |
| 331 | + | if index ~= nil then | |
| 332 | + | tabTable = tabTables[i] | |
| 333 | + | break | |
| 334 | + | end | |
| 335 | + | end | |
| 336 | + | ||
| 337 | + | if tabTable == nil then | |
| 338 | + | return | |
| 339 | + | end | |
| 340 | + | table.remove(tabTable, index) | |
| 341 | + | if self.currentTab == tab then | |
| 342 | + | self:selectPrevTab() | |
| 343 | + | if #self.tabs == 1 then | |
| 344 | + | self.currentTab = nil | |
| 345 | + | end | |
| 346 | + | end | |
| 347 | + | if tab.blinkEvent then | |
| 348 | + | removeEvent(tab.blinkEvent) | |
| 349 | + | end | |
| 350 | + | updateTabs(self) | |
| 351 | + | tab:destroy() | |
| 352 | + | end | |
| 353 | + | ||
| 354 | + | function UIMoveableTabBar:getTab(text) | |
| 355 | + | for k,tab in pairs(self.tabs) do | |
| 356 | + | if tab:getText():lower() == text:lower() then | |
| 357 | + | return tab | |
| 358 | + | end | |
| 359 | + | end | |
| 360 | + | for k,tab in pairs(self.preTabs) do | |
| 361 | + | if tab:getText():lower() == text:lower() then | |
| 362 | + | return tab | |
| 363 | + | end | |
| 364 | + | end | |
| 365 | + | for k,tab in pairs(self.postTabs) do | |
| 366 | + | if tab:getText():lower() == text:lower() then | |
| 367 | + | return tab | |
| 368 | + | end | |
| 369 | + | end | |
| 370 | + | end | |
| 371 | + | ||
| 372 | + | function UIMoveableTabBar:selectTab(tab) | |
| 373 | + | if self.currentTab == tab then return end | |
| 374 | + | if self.contentWidget then | |
| 375 | + | local selectedWidget = self.contentWidget:getLastChild() | |
| 376 | + | if selectedWidget and selectedWidget.isTab then | |
| 377 | + | self.contentWidget:removeChild(selectedWidget) | |
| 378 | + | end | |
| 379 | + | self.contentWidget:addChild(tab.tabPanel) | |
| 380 | + | tab.tabPanel:fill('parent') | |
| 381 | + | end | |
| 382 | + | ||
| 383 | + | if self.currentTab then | |
| 384 | + | self.currentTab:setChecked(false) | |
| 385 | + | end | |
| 386 | + | signalcall(self.onTabChange, self, tab) | |
| 387 | + | self.currentTab = tab | |
| 388 | + | tab:setChecked(true) | |
| 389 | + | tab:setOn(false) | |
| 390 | + | tab.blinking = false | |
| 391 | + | ||
| 392 | + | if tab.blinkEvent then | |
| 393 | + | removeEvent(tab.blinkEvent) | |
| 394 | + | tab.blinkEvent = nil | |
| 395 | + | end | |
| 396 | + | ||
| 397 | + | local parent = tab:getParent() | |
| 398 | + | parent:focusChild(tab, MouseFocusReason) | |
| 399 | + | updateNavigation(self) | |
| 400 | + | end | |
| 401 | + | ||
| 402 | + | function UIMoveableTabBar:selectNextTab() | |
| 403 | + | if self.currentTab == nil then | |
| 404 | + | return | |
| 405 | + | end | |
| 406 | + | ||
| 407 | + | local index = table.find(self.tabs, self.currentTab) | |
| 408 | + | if index == nil then | |
| 409 | + | return | |
| 410 | + | end | |
| 411 | + | ||
| 412 | + | local newIndex = index + 1 | |
| 413 | + | if newIndex > #self.tabs then | |
| 414 | + | if #self.postTabs > 0 then | |
| 415 | + | local widget = showPostTab(self) | |
| 416 | + | self:selectTab(widget) | |
| 417 | + | else | |
| 418 | + | if #self.preTabs > 0 then | |
| 419 | + | for i = 1, #self.preTabs do | |
| 420 | + | showPreTab(self) | |
| 421 | + | end | |
| 422 | + | end | |
| 423 | + | ||
| 424 | + | self:selectTab(self.tabs[1]) | |
| 425 | + | end | |
| 426 | + | updateTabs(self) | |
| 427 | + | return | |
| 428 | + | end | |
| 429 | + | ||
| 430 | + | local nextTab = self.tabs[newIndex] | |
| 431 | + | if not nextTab then | |
| 432 | + | return | |
| 433 | + | end | |
| 434 | + | ||
| 435 | + | self:selectTab(nextTab) | |
| 436 | + | end | |
| 437 | + | ||
| 438 | + | function UIMoveableTabBar:selectPrevTab() | |
| 439 | + | if self.currentTab == nil then | |
| 440 | + | return | |
| 441 | + | end | |
| 442 | + | ||
| 443 | + | local index = table.find(self.tabs, self.currentTab) | |
| 444 | + | if index == nil then | |
| 445 | + | return | |
| 446 | + | end | |
| 447 | + | ||
| 448 | + | local newIndex = index - 1 | |
| 449 | + | if newIndex <= 0 then | |
| 450 | + | if #self.preTabs > 0 then | |
| 451 | + | local widget = showPreTab(self) | |
| 452 | + | self:selectTab(widget) | |
| 453 | + | else | |
| 454 | + | if #self.postTabs > 0 then | |
| 455 | + | for i = 1, #self.postTabs do | |
| 456 | + | showPostTab(self) | |
| 457 | + | end | |
| 458 | + | end | |
| 459 | + | ||
| 460 | + | self:selectTab(self.tabs[#self.tabs]) | |
| 461 | + | end | |
| 462 | + | updateTabs(self) | |
| 463 | + | return | |
| 464 | + | end | |
| 465 | + | ||
| 466 | + | local prevTab = self.tabs[newIndex] | |
| 467 | + | if not prevTab then | |
| 468 | + | return | |
| 469 | + | end | |
| 470 | + | ||
| 471 | + | self:selectTab(prevTab) | |
| 472 | + | end | |
| 473 | + | ||
| 474 | + | function UIMoveableTabBar:blinkTab(tab) | |
| 475 | + | if tab:isChecked() then return end | |
| 476 | + | tab.blinking = true | |
| 477 | + | tabBlink(tab) | |
| 478 | + | end | |
| 479 | + | ||
| 480 | + | function UIMoveableTabBar:getTabPanel(tab) | |
| 481 | + | return tab.tabPanel | |
| 482 | + | end | |
| 483 | + | ||
| 484 | + | function UIMoveableTabBar:getCurrentTabPanel() | |
| 485 | + | if self.currentTab then | |
| 486 | + | return self.currentTab.tabPanel | |
| 487 | + | end | |
| 488 | + | end | |
| 489 | + | ||
| 490 | + | function UIMoveableTabBar:getCurrentTab() | |
| 491 | + | return self.currentTab | |
| 492 | + | end | |
| 493 | + | ||
| 494 | + | function UIMoveableTabBar:setNavigation(prevButton, nextButton) | |
| 495 | + | self.prevNavigation = prevButton | |
| 496 | + | self.nextNavigation = nextButton | |
| 497 | + | ||
| 498 | + | if self.prevNavigation then | |
| 499 | + | self.prevNavigation.onClick = function() self:selectPrevTab() end | |
| 500 | + | end | |
| 501 | + | if self.nextNavigation then | |
| 502 | + | self.nextNavigation.onClick = function() self:selectNextTab() end | |
| 503 | + | end | |
| 504 | + | updateNavigation(self) | |
| 505 | + | end |
| @@ -0,0 +1,122 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIPopupMenu = extends(UIWidget, "UIPopupMenu") | |
| 3 | + | ||
| 4 | + | local currentMenu | |
| 5 | + | ||
| 6 | + | function UIPopupMenu.create() | |
| 7 | + | local menu = UIPopupMenu.internalCreate() | |
| 8 | + | local layout = UIVerticalLayout.create(menu) | |
| 9 | + | layout:setFitChildren(true) | |
| 10 | + | menu:setLayout(layout) | |
| 11 | + | menu.isGameMenu = false | |
| 12 | + | return menu | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function UIPopupMenu:display(pos) | |
| 16 | + | -- don't display if not options was added | |
| 17 | + | if self:getChildCount() == 0 then | |
| 18 | + | self:destroy() | |
| 19 | + | return | |
| 20 | + | end | |
| 21 | + | ||
| 22 | + | if g_ui.isMouseGrabbed() then | |
| 23 | + | self:destroy() | |
| 24 | + | return | |
| 25 | + | end | |
| 26 | + | ||
| 27 | + | if currentMenu then | |
| 28 | + | currentMenu:destroy() | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | if pos == nil then | |
| 32 | + | pos = g_window.getMousePosition() | |
| 33 | + | end | |
| 34 | + | ||
| 35 | + | rootWidget:addChild(self) | |
| 36 | + | self:setPosition(pos) | |
| 37 | + | self:grabMouse() | |
| 38 | + | self:focus() | |
| 39 | + | --self:grabKeyboard() | |
| 40 | + | currentMenu = self | |
| 41 | + | end | |
| 42 | + | ||
| 43 | + | function UIPopupMenu:onGeometryChange(oldRect, newRect) | |
| 44 | + | local parent = self:getParent() | |
| 45 | + | if not parent then return end | |
| 46 | + | local ymax = parent:getY() + parent:getHeight() | |
| 47 | + | local xmax = parent:getX() + parent:getWidth() | |
| 48 | + | if newRect.y + newRect.height > ymax then | |
| 49 | + | local newy = ymax - newRect.height | |
| 50 | + | if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end | |
| 51 | + | end | |
| 52 | + | if newRect.x + newRect.width > xmax then | |
| 53 | + | local newx = xmax - newRect.width | |
| 54 | + | if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end | |
| 55 | + | end | |
| 56 | + | self:bindRectToParent() | |
| 57 | + | end | |
| 58 | + | ||
| 59 | + | function UIPopupMenu:addOption(optionName, optionCallback, shortcut) | |
| 60 | + | local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self) | |
| 61 | + | optionWidget.onClick = function(widget) | |
| 62 | + | self:destroy() | |
| 63 | + | optionCallback() | |
| 64 | + | end | |
| 65 | + | optionWidget:setText(optionName) | |
| 66 | + | local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 | |
| 67 | + | ||
| 68 | + | if shortcut then | |
| 69 | + | local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) | |
| 70 | + | shortcutLabel:setText(shortcut) | |
| 71 | + | width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() | |
| 72 | + | end | |
| 73 | + | ||
| 74 | + | self:setWidth(math.max(self:getWidth(), width)) | |
| 75 | + | end | |
| 76 | + | ||
| 77 | + | function UIPopupMenu:addSeparator() | |
| 78 | + | g_ui.createWidget(self:getStyleName() .. 'Separator', self) | |
| 79 | + | end | |
| 80 | + | ||
| 81 | + | function UIPopupMenu:setGameMenu(state) | |
| 82 | + | self.isGameMenu = state | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | function UIPopupMenu:onDestroy() | |
| 86 | + | if currentMenu == self then | |
| 87 | + | currentMenu = nil | |
| 88 | + | end | |
| 89 | + | self:ungrabMouse() | |
| 90 | + | end | |
| 91 | + | ||
| 92 | + | function UIPopupMenu:onMousePress(mousePos, mouseButton) | |
| 93 | + | -- clicks outside menu area destroys the menu | |
| 94 | + | if not self:containsPoint(mousePos) then | |
| 95 | + | self:destroy() | |
| 96 | + | end | |
| 97 | + | return true | |
| 98 | + | end | |
| 99 | + | ||
| 100 | + | function UIPopupMenu:onKeyPress(keyCode, keyboardModifiers) | |
| 101 | + | if keyCode == KeyEscape then | |
| 102 | + | self:destroy() | |
| 103 | + | return true | |
| 104 | + | end | |
| 105 | + | return false | |
| 106 | + | end | |
| 107 | + | ||
| 108 | + | -- close all menus when the window is resized | |
| 109 | + | local function onRootGeometryUpdate() | |
| 110 | + | if currentMenu then | |
| 111 | + | currentMenu:destroy() | |
| 112 | + | end | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | local function onGameEnd() | |
| 116 | + | if currentMenu and currentMenu.isGameMenu then | |
| 117 | + | currentMenu:destroy() | |
| 118 | + | end | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | connect(rootWidget, { onGeometryChange = onRootGeometryUpdate }) | |
| 122 | + | connect(g_game, { onGameEnd = onGameEnd } ) |
| @@ -0,0 +1,129 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIPopupScrollMenu = extends(UIWidget, "UIPopupScrollMenu") | |
| 3 | + | ||
| 4 | + | local currentMenu | |
| 5 | + | ||
| 6 | + | function UIPopupScrollMenu.create() | |
| 7 | + | local menu = UIPopupScrollMenu.internalCreate() | |
| 8 | + | ||
| 9 | + | local scrollArea = g_ui.createWidget('UIScrollArea', menu) | |
| 10 | + | scrollArea:setLayout(UIVerticalLayout.create(menu)) | |
| 11 | + | scrollArea:setId('scrollArea') | |
| 12 | + | ||
| 13 | + | local scrollBar = g_ui.createWidget('VerticalScrollBar', menu) | |
| 14 | + | scrollBar:setId('scrollBar') | |
| 15 | + | scrollBar.pixelsScroll = false | |
| 16 | + | ||
| 17 | + | scrollBar:addAnchor(AnchorRight, 'parent', AnchorRight) | |
| 18 | + | scrollBar:addAnchor(AnchorTop, 'parent', AnchorTop) | |
| 19 | + | scrollBar:addAnchor(AnchorBottom, 'parent', AnchorBottom) | |
| 20 | + | ||
| 21 | + | scrollArea:addAnchor(AnchorLeft, 'parent', AnchorLeft) | |
| 22 | + | scrollArea:addAnchor(AnchorTop, 'parent', AnchorTop) | |
| 23 | + | scrollArea:addAnchor(AnchorBottom, 'parent', AnchorBottom) | |
| 24 | + | scrollArea:addAnchor(AnchorRight, 'next', AnchorLeft) | |
| 25 | + | scrollArea:setVerticalScrollBar(scrollBar) | |
| 26 | + | ||
| 27 | + | menu.scrollArea = scrollArea | |
| 28 | + | menu.scrollBar = scrollBar | |
| 29 | + | return menu | |
| 30 | + | end | |
| 31 | + | ||
| 32 | + | function UIPopupScrollMenu:setScrollbarStep(step) | |
| 33 | + | self.scrollBar:setStep(step) | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | function UIPopupScrollMenu:display(pos) | |
| 37 | + | -- don't display if not options was added | |
| 38 | + | if self.scrollArea:getChildCount() == 0 then | |
| 39 | + | self:destroy() | |
| 40 | + | return | |
| 41 | + | end | |
| 42 | + | ||
| 43 | + | if g_ui.isMouseGrabbed() then | |
| 44 | + | self:destroy() | |
| 45 | + | return | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | if currentMenu then | |
| 49 | + | currentMenu:destroy() | |
| 50 | + | end | |
| 51 | + | ||
| 52 | + | if pos == nil then | |
| 53 | + | pos = g_window.getMousePosition() | |
| 54 | + | end | |
| 55 | + | ||
| 56 | + | rootWidget:addChild(self) | |
| 57 | + | self:setPosition(pos) | |
| 58 | + | self:grabMouse() | |
| 59 | + | currentMenu = self | |
| 60 | + | end | |
| 61 | + | ||
| 62 | + | function UIPopupScrollMenu:onGeometryChange(oldRect, newRect) | |
| 63 | + | local parent = self:getParent() | |
| 64 | + | if not parent then return end | |
| 65 | + | local ymax = parent:getY() + parent:getHeight() | |
| 66 | + | local xmax = parent:getX() + parent:getWidth() | |
| 67 | + | if newRect.y + newRect.height > ymax then | |
| 68 | + | local newy = newRect.y - newRect.height | |
| 69 | + | if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end | |
| 70 | + | end | |
| 71 | + | if newRect.x + newRect.width > xmax then | |
| 72 | + | local newx = newRect.x - newRect.width | |
| 73 | + | if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end | |
| 74 | + | end | |
| 75 | + | self:bindRectToParent() | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | function UIPopupScrollMenu:addOption(optionName, optionCallback, shortcut) | |
| 79 | + | local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self.scrollArea) | |
| 80 | + | optionWidget.onClick = function(widget) | |
| 81 | + | self:destroy() | |
| 82 | + | optionCallback() | |
| 83 | + | end | |
| 84 | + | optionWidget:setText(optionName) | |
| 85 | + | local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 | |
| 86 | + | ||
| 87 | + | if shortcut then | |
| 88 | + | local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) | |
| 89 | + | shortcutLabel:setText(shortcut) | |
| 90 | + | width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() | |
| 91 | + | end | |
| 92 | + | ||
| 93 | + | self:setWidth(math.max(self:getWidth(), width)) | |
| 94 | + | end | |
| 95 | + | ||
| 96 | + | function UIPopupScrollMenu:addSeparator() | |
| 97 | + | g_ui.createWidget(self:getStyleName() .. 'Separator', self.scrollArea) | |
| 98 | + | end | |
| 99 | + | ||
| 100 | + | function UIPopupScrollMenu:onDestroy() | |
| 101 | + | if currentMenu == self then | |
| 102 | + | currentMenu = nil | |
| 103 | + | end | |
| 104 | + | self:ungrabMouse() | |
| 105 | + | end | |
| 106 | + | ||
| 107 | + | function UIPopupScrollMenu:onMousePress(mousePos, mouseButton) | |
| 108 | + | -- clicks outside menu area destroys the menu | |
| 109 | + | if not self:containsPoint(mousePos) then | |
| 110 | + | self:destroy() | |
| 111 | + | end | |
| 112 | + | return true | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | function UIPopupScrollMenu:onKeyPress(keyCode, keyboardModifiers) | |
| 116 | + | if keyCode == KeyEscape then | |
| 117 | + | self:destroy() | |
| 118 | + | return true | |
| 119 | + | end | |
| 120 | + | return false | |
| 121 | + | end | |
| 122 | + | ||
| 123 | + | -- close all menus when the window is resized | |
| 124 | + | local function onRootGeometryUpdate() | |
| 125 | + | if currentMenu then | |
| 126 | + | currentMenu:destroy() | |
| 127 | + | end | |
| 128 | + | end | |
| 129 | + | connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} ) |
| @@ -0,0 +1,99 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIProgressBar = extends(UIWidget, "UIProgressBar") | |
| 3 | + | ||
| 4 | + | function UIProgressBar.create() | |
| 5 | + | local progressbar = UIProgressBar.internalCreate() | |
| 6 | + | progressbar:setFocusable(false) | |
| 7 | + | progressbar:setOn(true) | |
| 8 | + | progressbar.min = 0 | |
| 9 | + | progressbar.max = 100 | |
| 10 | + | progressbar.value = 0 | |
| 11 | + | progressbar.bgBorderLeft = 0 | |
| 12 | + | progressbar.bgBorderRight = 0 | |
| 13 | + | progressbar.bgBorderTop = 0 | |
| 14 | + | progressbar.bgBorderBottom = 0 | |
| 15 | + | return progressbar | |
| 16 | + | end | |
| 17 | + | ||
| 18 | + | function UIProgressBar:setMinimum(minimum) | |
| 19 | + | self.minimum = minimum | |
| 20 | + | if self.value < minimum then | |
| 21 | + | self:setValue(minimum) | |
| 22 | + | end | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | function UIProgressBar:setMaximum(maximum) | |
| 26 | + | self.maximum = maximum | |
| 27 | + | if self.value > maximum then | |
| 28 | + | self:setValue(maximum) | |
| 29 | + | end | |
| 30 | + | end | |
| 31 | + | ||
| 32 | + | function UIProgressBar:setValue(value, minimum, maximum) | |
| 33 | + | if minimum then | |
| 34 | + | self:setMinimum(minimum) | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | if maximum then | |
| 38 | + | self:setMaximum(maximum) | |
| 39 | + | end | |
| 40 | + | ||
| 41 | + | self.value = math.max(math.min(value, self.maximum), self.minimum) | |
| 42 | + | self:updateBackground() | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | function UIProgressBar:setPercent(percent) | |
| 46 | + | self:setValue(percent, 0, 100) | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | function UIProgressBar:getPercent() | |
| 50 | + | return self.value | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | function UIProgressBar:getPercentPixels() | |
| 54 | + | return (self.maximum - self.minimum) / self:getWidth() | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | function UIProgressBar:getProgress() | |
| 58 | + | if self.minimum == self.maximum then return 1 end | |
| 59 | + | return (self.value - self.minimum) / (self.maximum - self.minimum) | |
| 60 | + | end | |
| 61 | + | ||
| 62 | + | function UIProgressBar:updateBackground() | |
| 63 | + | if self:isOn() then | |
| 64 | + | local width = math.round(math.max((self:getProgress() * (self:getWidth() - self.bgBorderLeft - self.bgBorderRight)), 1)) | |
| 65 | + | local height = self:getHeight() - self.bgBorderTop - self.bgBorderBottom | |
| 66 | + | local rect = { x = self.bgBorderLeft, y = self.bgBorderTop, width = width, height = height } | |
| 67 | + | self:setBackgroundRect(rect) | |
| 68 | + | end | |
| 69 | + | end | |
| 70 | + | ||
| 71 | + | function UIProgressBar:onSetup() | |
| 72 | + | self:updateBackground() | |
| 73 | + | end | |
| 74 | + | ||
| 75 | + | function UIProgressBar:onStyleApply(name, node) | |
| 76 | + | for name,value in pairs(node) do | |
| 77 | + | if name == 'background-border-left' then | |
| 78 | + | self.bgBorderLeft = tonumber(value) | |
| 79 | + | elseif name == 'background-border-right' then | |
| 80 | + | self.bgBorderRight = tonumber(value) | |
| 81 | + | elseif name == 'background-border-top' then | |
| 82 | + | self.bgBorderTop = tonumber(value) | |
| 83 | + | elseif name == 'background-border-bottom' then | |
| 84 | + | self.bgBorderBottom = tonumber(value) | |
| 85 | + | elseif name == 'background-border' then | |
| 86 | + | self.bgBorderLeft = tonumber(value) | |
| 87 | + | self.bgBorderRight = tonumber(value) | |
| 88 | + | self.bgBorderTop = tonumber(value) | |
| 89 | + | self.bgBorderBottom = tonumber(value) | |
| 90 | + | end | |
| 91 | + | end | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | function UIProgressBar:onGeometryChange(oldRect, newRect) | |
| 95 | + | if not self:isOn() then | |
| 96 | + | self:setHeight(0) | |
| 97 | + | end | |
| 98 | + | self:updateBackground() | |
| 99 | + | end |
| @@ -0,0 +1,66 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIRadioGroup = newclass("UIRadioGroup") | |
| 3 | + | ||
| 4 | + | function UIRadioGroup.create() | |
| 5 | + | local radiogroup = UIRadioGroup.internalCreate() | |
| 6 | + | radiogroup.widgets = {} | |
| 7 | + | radiogroup.selectedWidget = nil | |
| 8 | + | return radiogroup | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | function UIRadioGroup:destroy() | |
| 12 | + | for k,widget in pairs(self.widgets) do | |
| 13 | + | widget.onClick = nil | |
| 14 | + | end | |
| 15 | + | self.widgets = {} | |
| 16 | + | end | |
| 17 | + | ||
| 18 | + | function UIRadioGroup:addWidget(widget) | |
| 19 | + | table.insert(self.widgets, widget) | |
| 20 | + | widget.onClick = function(widget) self:selectWidget(widget) end | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function UIRadioGroup:removeWidget(widget) | |
| 24 | + | if self.selectedWidget == widget then | |
| 25 | + | self:selectWidget(nil) | |
| 26 | + | end | |
| 27 | + | widget.onClick = nil | |
| 28 | + | table.removevalue(self.widgets, widget) | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | function UIRadioGroup:selectWidget(selectedWidget, dontSignal) | |
| 32 | + | if selectedWidget == self.selectedWidget then return end | |
| 33 | + | ||
| 34 | + | local previousSelectedWidget = self.selectedWidget | |
| 35 | + | self.selectedWidget = selectedWidget | |
| 36 | + | ||
| 37 | + | if previousSelectedWidget then | |
| 38 | + | previousSelectedWidget:setChecked(false) | |
| 39 | + | end | |
| 40 | + | ||
| 41 | + | if selectedWidget then | |
| 42 | + | selectedWidget:setChecked(true) | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | if not dontSignal then | |
| 46 | + | signalcall(self.onSelectionChange, self, selectedWidget, previousSelectedWidget) | |
| 47 | + | end | |
| 48 | + | end | |
| 49 | + | ||
| 50 | + | function UIRadioGroup:clearSelected() | |
| 51 | + | if not self.selectedWidget then return end | |
| 52 | + | ||
| 53 | + | local previousSelectedWidget = self.selectedWidget | |
| 54 | + | self.selectedWidget:setChecked(false) | |
| 55 | + | self.selectedWidget = nil | |
| 56 | + | ||
| 57 | + | signalcall(self.onSelectionChange, self, nil, previousSelectedWidget) | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | function UIRadioGroup:getSelectedWidget() | |
| 61 | + | return self.selectedWidget | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function UIRadioGroup:getFirstWidget() | |
| 65 | + | return self.widgets[1] | |
| 66 | + | end |
| @@ -0,0 +1,132 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIResizeBorder = extends(UIWidget, "UIResizeBorder") | |
| 3 | + | ||
| 4 | + | function UIResizeBorder.create() | |
| 5 | + | local resizeborder = UIResizeBorder.internalCreate() | |
| 6 | + | resizeborder:setFocusable(false) | |
| 7 | + | resizeborder.minimum = 0 | |
| 8 | + | resizeborder.maximum = 1000 | |
| 9 | + | return resizeborder | |
| 10 | + | end | |
| 11 | + | ||
| 12 | + | function UIResizeBorder:onSetup() | |
| 13 | + | if self:getWidth() > self:getHeight() then | |
| 14 | + | self.vertical = true | |
| 15 | + | else | |
| 16 | + | self.vertical = false | |
| 17 | + | end | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | function UIResizeBorder:onDestroy() | |
| 21 | + | if self.hovering then | |
| 22 | + | g_mouse.popCursor(self.cursortype) | |
| 23 | + | end | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | function UIResizeBorder:onHoverChange(hovered) | |
| 27 | + | if hovered then | |
| 28 | + | if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end | |
| 29 | + | if self:getWidth() > self:getHeight() then | |
| 30 | + | self.vertical = true | |
| 31 | + | self.cursortype = 'vertical' | |
| 32 | + | else | |
| 33 | + | self.vertical = false | |
| 34 | + | self.cursortype = 'horizontal' | |
| 35 | + | end | |
| 36 | + | g_mouse.pushCursor(self.cursortype) | |
| 37 | + | self.hovering = true | |
| 38 | + | if not self:isPressed() then | |
| 39 | + | g_effects.fadeIn(self) | |
| 40 | + | end | |
| 41 | + | else | |
| 42 | + | if not self:isPressed() and self.hovering then | |
| 43 | + | g_mouse.popCursor(self.cursortype) | |
| 44 | + | g_effects.fadeOut(self) | |
| 45 | + | self.hovering = false | |
| 46 | + | end | |
| 47 | + | end | |
| 48 | + | end | |
| 49 | + | ||
| 50 | + | function UIResizeBorder:onMouseMove(mousePos, mouseMoved) | |
| 51 | + | if self:isPressed() then | |
| 52 | + | local parent = self:getParent() | |
| 53 | + | local newSize = 0 | |
| 54 | + | if self.vertical then | |
| 55 | + | local delta = mousePos.y - self:getY() - self:getHeight()/2 | |
| 56 | + | newSize = math.min(math.max(parent:getHeight() + delta, self.minimum), self.maximum) | |
| 57 | + | parent:setHeight(newSize) | |
| 58 | + | else | |
| 59 | + | local delta = mousePos.x - self:getX() - self:getWidth()/2 | |
| 60 | + | newSize = math.min(math.max(parent:getWidth() + delta, self.minimum), self.maximum) | |
| 61 | + | parent:setWidth(newSize) | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | self:checkBoundary(newSize) | |
| 65 | + | return true | |
| 66 | + | end | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | function UIResizeBorder:onMouseRelease(mousePos, mouseButton) | |
| 70 | + | if not self:isHovered() then | |
| 71 | + | g_mouse.popCursor(self.cursortype) | |
| 72 | + | g_effects.fadeOut(self) | |
| 73 | + | self.hovering = false | |
| 74 | + | end | |
| 75 | + | end | |
| 76 | + | ||
| 77 | + | function UIResizeBorder:onStyleApply(styleName, styleNode) | |
| 78 | + | for name,value in pairs(styleNode) do | |
| 79 | + | if name == 'maximum' then | |
| 80 | + | self:setMaximum(tonumber(value)) | |
| 81 | + | elseif name == 'minimum' then | |
| 82 | + | self:setMinimum(tonumber(value)) | |
| 83 | + | end | |
| 84 | + | end | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | function UIResizeBorder:onVisibilityChange(visible) | |
| 88 | + | if visible and self.maximum == self.minimum then | |
| 89 | + | self:hide() | |
| 90 | + | end | |
| 91 | + | end | |
| 92 | + | ||
| 93 | + | function UIResizeBorder:setMaximum(maximum) | |
| 94 | + | self.maximum = maximum | |
| 95 | + | self:checkBoundary() | |
| 96 | + | end | |
| 97 | + | ||
| 98 | + | function UIResizeBorder:setMinimum(minimum) | |
| 99 | + | self.minimum = minimum | |
| 100 | + | self:checkBoundary() | |
| 101 | + | end | |
| 102 | + | ||
| 103 | + | function UIResizeBorder:getMaximum() return self.maximum end | |
| 104 | + | function UIResizeBorder:getMinimum() return self.minimum end | |
| 105 | + | ||
| 106 | + | function UIResizeBorder:setParentSize(size) | |
| 107 | + | local parent = self:getParent() | |
| 108 | + | if self.vertical then | |
| 109 | + | parent:setHeight(size) | |
| 110 | + | else | |
| 111 | + | parent:setWidth(size) | |
| 112 | + | end | |
| 113 | + | self:checkBoundary(size) | |
| 114 | + | end | |
| 115 | + | ||
| 116 | + | function UIResizeBorder:getParentSize() | |
| 117 | + | local parent = self:getParent() | |
| 118 | + | if self.vertical then | |
| 119 | + | return parent:getHeight() | |
| 120 | + | else | |
| 121 | + | return parent:getWidth() | |
| 122 | + | end | |
| 123 | + | end | |
| 124 | + | ||
| 125 | + | function UIResizeBorder:checkBoundary(size) | |
| 126 | + | size = size or self:getParentSize() | |
| 127 | + | if self.maximum == self.minimum and size == self.maximum then | |
| 128 | + | self:hide() | |
| 129 | + | else | |
| 130 | + | self:show() | |
| 131 | + | end | |
| 132 | + | end |
| @@ -0,0 +1,190 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIScrollArea = extends(UIWidget, "UIScrollArea") | |
| 3 | + | ||
| 4 | + | -- public functions | |
| 5 | + | function UIScrollArea.create() | |
| 6 | + | local scrollarea = UIScrollArea.internalCreate() | |
| 7 | + | scrollarea:setClipping(true) | |
| 8 | + | scrollarea.inverted = false | |
| 9 | + | scrollarea.alwaysScrollMaximum = false | |
| 10 | + | return scrollarea | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | function UIScrollArea:onStyleApply(styleName, styleNode) | |
| 14 | + | for name,value in pairs(styleNode) do | |
| 15 | + | if name == 'vertical-scrollbar' then | |
| 16 | + | addEvent(function() | |
| 17 | + | local parent = self:getParent() | |
| 18 | + | if parent then | |
| 19 | + | self:setVerticalScrollBar(parent:getChildById(value)) | |
| 20 | + | end | |
| 21 | + | end) | |
| 22 | + | elseif name == 'horizontal-scrollbar' then | |
| 23 | + | addEvent(function() | |
| 24 | + | local parent = self:getParent() | |
| 25 | + | if parent then | |
| 26 | + | self:setHorizontalScrollBar(self:getParent():getChildById(value)) | |
| 27 | + | end | |
| 28 | + | end) | |
| 29 | + | elseif name == 'inverted-scroll' then | |
| 30 | + | self:setInverted(value) | |
| 31 | + | elseif name == 'always-scroll-maximum' then | |
| 32 | + | self:setAlwaysScrollMaximum(value) | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | function UIScrollArea:updateScrollBars() | |
| 38 | + | local scrollWidth = math.max(self:getChildrenRect().width - self:getPaddingRect().width, 0) | |
| 39 | + | local scrollHeight = math.max(self:getChildrenRect().height - self:getPaddingRect().height, 0) | |
| 40 | + | ||
| 41 | + | local scrollbar = self.verticalScrollBar | |
| 42 | + | if scrollbar then | |
| 43 | + | if self.inverted then | |
| 44 | + | scrollbar:setMinimum(-scrollHeight) | |
| 45 | + | scrollbar:setMaximum(0) | |
| 46 | + | else | |
| 47 | + | scrollbar:setMinimum(0) | |
| 48 | + | scrollbar:setMaximum(scrollHeight) | |
| 49 | + | end | |
| 50 | + | end | |
| 51 | + | ||
| 52 | + | local scrollbar = self.horizontalScrollBar | |
| 53 | + | if scrollbar then | |
| 54 | + | if self.inverted then | |
| 55 | + | scrollbar:setMinimum(-scrollWidth) | |
| 56 | + | scrollbar:setMaximum(0) | |
| 57 | + | else | |
| 58 | + | scrollbar:setMinimum(0) | |
| 59 | + | scrollbar:setMaximum(scrollWidth) | |
| 60 | + | end | |
| 61 | + | end | |
| 62 | + | ||
| 63 | + | if self.lastScrollWidth ~= scrollWidth then | |
| 64 | + | self:onScrollWidthChange() | |
| 65 | + | end | |
| 66 | + | if self.lastScrollHeight ~= scrollHeight then | |
| 67 | + | self:onScrollHeightChange() | |
| 68 | + | end | |
| 69 | + | ||
| 70 | + | self.lastScrollWidth = scrollWidth | |
| 71 | + | self.lastScrollHeight = scrollHeight | |
| 72 | + | end | |
| 73 | + | ||
| 74 | + | function UIScrollArea:setVerticalScrollBar(scrollbar) | |
| 75 | + | self.verticalScrollBar = scrollbar | |
| 76 | + | connect(self.verticalScrollBar, 'onValueChange', function(scrollbar, value) | |
| 77 | + | local virtualOffset = self:getVirtualOffset() | |
| 78 | + | virtualOffset.y = value | |
| 79 | + | self:setVirtualOffset(virtualOffset) | |
| 80 | + | signalcall(self.onScrollChange, self, virtualOffset) | |
| 81 | + | end) | |
| 82 | + | self:updateScrollBars() | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | function UIScrollArea:setHorizontalScrollBar(scrollbar) | |
| 86 | + | self.horizontalScrollBar = scrollbar | |
| 87 | + | connect(self.horizontalScrollBar, 'onValueChange', function(scrollbar, value) | |
| 88 | + | local virtualOffset = self:getVirtualOffset() | |
| 89 | + | virtualOffset.x = value | |
| 90 | + | self:setVirtualOffset(virtualOffset) | |
| 91 | + | signalcall(self.onScrollChange, self, virtualOffset) | |
| 92 | + | end) | |
| 93 | + | self:updateScrollBars() | |
| 94 | + | end | |
| 95 | + | ||
| 96 | + | function UIScrollArea:setInverted(inverted) | |
| 97 | + | self.inverted = inverted | |
| 98 | + | end | |
| 99 | + | ||
| 100 | + | function UIScrollArea:setAlwaysScrollMaximum(value) | |
| 101 | + | self.alwaysScrollMaximum = value | |
| 102 | + | end | |
| 103 | + | ||
| 104 | + | function UIScrollArea:onLayoutUpdate() | |
| 105 | + | self:updateScrollBars() | |
| 106 | + | end | |
| 107 | + | ||
| 108 | + | function UIScrollArea:onMouseWheel(mousePos, mouseWheel) | |
| 109 | + | if self.verticalScrollBar then | |
| 110 | + | if not self.verticalScrollBar:isOn() then | |
| 111 | + | return false | |
| 112 | + | end | |
| 113 | + | if mouseWheel == MouseWheelUp then | |
| 114 | + | local minimum = self.verticalScrollBar:getMinimum() | |
| 115 | + | if self.verticalScrollBar:getValue() <= minimum then | |
| 116 | + | return false | |
| 117 | + | end | |
| 118 | + | self.verticalScrollBar:decrement() | |
| 119 | + | else | |
| 120 | + | local maximum = self.verticalScrollBar:getMaximum() | |
| 121 | + | if self.verticalScrollBar:getValue() >= maximum then | |
| 122 | + | return false | |
| 123 | + | end | |
| 124 | + | self.verticalScrollBar:increment() | |
| 125 | + | end | |
| 126 | + | elseif self.horizontalScrollBar then | |
| 127 | + | if not self.horizontalScrollBar:isOn() then | |
| 128 | + | return false | |
| 129 | + | end | |
| 130 | + | if mouseWheel == MouseWheelUp then | |
| 131 | + | local maximum = self.horizontalScrollBar:getMaximum() | |
| 132 | + | if self.horizontalScrollBar:getValue() >= maximum then | |
| 133 | + | return false | |
| 134 | + | end | |
| 135 | + | self.horizontalScrollBar:increment() | |
| 136 | + | else | |
| 137 | + | local minimum = self.horizontalScrollBar:getMinimum() | |
| 138 | + | if self.horizontalScrollBar:getValue() <= minimum then | |
| 139 | + | return false | |
| 140 | + | end | |
| 141 | + | self.horizontalScrollBar:decrement() | |
| 142 | + | end | |
| 143 | + | end | |
| 144 | + | return true | |
| 145 | + | end | |
| 146 | + | ||
| 147 | + | function UIScrollArea:ensureChildVisible(child) | |
| 148 | + | if child then | |
| 149 | + | local paddingRect = self:getPaddingRect() | |
| 150 | + | if self.verticalScrollBar then | |
| 151 | + | local deltaY = paddingRect.y - child:getY() | |
| 152 | + | if deltaY > 0 then | |
| 153 | + | self.verticalScrollBar:decrement(deltaY) | |
| 154 | + | end | |
| 155 | + | ||
| 156 | + | deltaY = (child:getY() + child:getHeight()) - (paddingRect.y + paddingRect.height) | |
| 157 | + | if deltaY > 0 then | |
| 158 | + | self.verticalScrollBar:increment(deltaY) | |
| 159 | + | end | |
| 160 | + | elseif self.horizontalScrollBar then | |
| 161 | + | local deltaX = paddingRect.x - child:getX() | |
| 162 | + | if deltaX > 0 then | |
| 163 | + | self.horizontalScrollBar:decrement(deltaX) | |
| 164 | + | end | |
| 165 | + | ||
| 166 | + | deltaX = (child:getX() + child:getWidth()) - (paddingRect.x + paddingRect.width) | |
| 167 | + | if deltaX > 0 then | |
| 168 | + | self.horizontalScrollBar:increment(deltaX) | |
| 169 | + | end | |
| 170 | + | end | |
| 171 | + | end | |
| 172 | + | end | |
| 173 | + | ||
| 174 | + | function UIScrollArea:onChildFocusChange(focusedChild, oldFocused, reason) | |
| 175 | + | if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then | |
| 176 | + | self:ensureChildVisible(focusedChild) | |
| 177 | + | end | |
| 178 | + | end | |
| 179 | + | ||
| 180 | + | function UIScrollArea:onScrollWidthChange() | |
| 181 | + | if self.alwaysScrollMaximum and self.horizontalScrollBar then | |
| 182 | + | self.horizontalScrollBar:setValue(self.horizontalScrollBar:getMaximum()) | |
| 183 | + | end | |
| 184 | + | end | |
| 185 | + | ||
| 186 | + | function UIScrollArea:onScrollHeightChange() | |
| 187 | + | if self.alwaysScrollMaximum and self.verticalScrollBar then | |
| 188 | + | self.verticalScrollBar:setValue(self.verticalScrollBar:getMaximum()) | |
| 189 | + | end | |
| 190 | + | end |
| @@ -0,0 +1,290 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIScrollBar = extends(UIWidget, "UIScrollBar") | |
| 3 | + | ||
| 4 | + | -- private functions | |
| 5 | + | local function calcValues(self) | |
| 6 | + | local slider = self:getChildById('sliderButton') | |
| 7 | + | local decrementButton = self:getChildById('decrementButton') | |
| 8 | + | local incrementButton = self:getChildById('incrementButton') | |
| 9 | + | ||
| 10 | + | local pxrange, center | |
| 11 | + | if self.orientation == 'vertical' then | |
| 12 | + | pxrange = (self:getHeight() - decrementButton:getHeight() - decrementButton:getMarginTop() - decrementButton:getMarginBottom() | |
| 13 | + | - incrementButton:getHeight() - incrementButton:getMarginTop() - incrementButton:getMarginBottom()) | |
| 14 | + | center = self:getY() + math.floor(self:getHeight() / 2) | |
| 15 | + | else -- horizontal | |
| 16 | + | pxrange = (self:getWidth() - decrementButton:getWidth() - decrementButton:getMarginLeft() - decrementButton:getMarginRight() | |
| 17 | + | - incrementButton:getWidth() - incrementButton:getMarginLeft() - incrementButton:getMarginRight()) | |
| 18 | + | center = self:getX() + math.floor(self:getWidth() / 2) | |
| 19 | + | end | |
| 20 | + | ||
| 21 | + | local range = self.maximum - self.minimum + 1 | |
| 22 | + | ||
| 23 | + | local proportion | |
| 24 | + | ||
| 25 | + | if self.pixelsScroll then | |
| 26 | + | proportion = pxrange/(range+pxrange) | |
| 27 | + | else | |
| 28 | + | proportion = math.min(math.max(self.step, 1), range)/range | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | local px = math.max(proportion * pxrange, 6) | |
| 32 | + | if g_app.isMobile() then | |
| 33 | + | px = math.max(proportion * pxrange, 24) | |
| 34 | + | end | |
| 35 | + | px = px - px % 2 + 1 | |
| 36 | + | ||
| 37 | + | local offset = 0 | |
| 38 | + | if range == 0 or self.value == self.minimum then | |
| 39 | + | if self.orientation == 'vertical' then | |
| 40 | + | offset = -math.floor((self:getHeight() - px) / 2) + decrementButton:getMarginRect().height | |
| 41 | + | else | |
| 42 | + | offset = -math.floor((self:getWidth() - px) / 2) + decrementButton:getMarginRect().width | |
| 43 | + | end | |
| 44 | + | elseif range > 1 and self.value == self.maximum then | |
| 45 | + | if self.orientation == 'vertical' then | |
| 46 | + | offset = math.ceil((self:getHeight() - px) / 2) - incrementButton:getMarginRect().height | |
| 47 | + | else | |
| 48 | + | offset = math.ceil((self:getWidth() - px) / 2) - incrementButton:getMarginRect().width | |
| 49 | + | end | |
| 50 | + | elseif range > 1 then | |
| 51 | + | offset = (((self.value - self.minimum) / (range - 1)) - 0.5) * (pxrange - px) | |
| 52 | + | end | |
| 53 | + | ||
| 54 | + | return range, pxrange, px, offset, center | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | local function updateValueDisplay(widget) | |
| 58 | + | if widget == nil then return end | |
| 59 | + | ||
| 60 | + | if widget:getShowValue() then | |
| 61 | + | widget:setText(widget:getValue() .. (widget:getSymbol() or '')) | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | local function updateSlider(self) | |
| 66 | + | local slider = self:getChildById('sliderButton') | |
| 67 | + | if slider == nil then return end | |
| 68 | + | ||
| 69 | + | local range, pxrange, px, offset, center = calcValues(self) | |
| 70 | + | if self.orientation == 'vertical' then | |
| 71 | + | slider:setHeight(px) | |
| 72 | + | slider:setMarginTop(offset) | |
| 73 | + | else -- horizontal | |
| 74 | + | slider:setWidth(px) | |
| 75 | + | slider:setMarginLeft(offset) | |
| 76 | + | end | |
| 77 | + | updateValueDisplay(self) | |
| 78 | + | ||
| 79 | + | local status = (self.maximum ~= self.minimum) | |
| 80 | + | ||
| 81 | + | self:setOn(status) | |
| 82 | + | for _i,child in pairs(self:getChildren()) do | |
| 83 | + | child:setEnabled(status) | |
| 84 | + | end | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | local function parseSliderPos(self, slider, pos, move) | |
| 88 | + | local delta, hotDistance | |
| 89 | + | if self.orientation == 'vertical' then | |
| 90 | + | delta = move.y | |
| 91 | + | hotDistance = pos.y - slider:getY() | |
| 92 | + | else | |
| 93 | + | delta = move.x | |
| 94 | + | hotDistance = pos.x - slider:getX() | |
| 95 | + | end | |
| 96 | + | ||
| 97 | + | if (delta > 0 and hotDistance + delta > self.hotDistance) or | |
| 98 | + | (delta < 0 and hotDistance + delta < self.hotDistance) then | |
| 99 | + | local range, pxrange, px, offset, center = calcValues(self) | |
| 100 | + | local newvalue = self.value + delta * (range / (pxrange - px)) | |
| 101 | + | self:setValue(newvalue) | |
| 102 | + | end | |
| 103 | + | end | |
| 104 | + | ||
| 105 | + | local function parseSliderPress(self, slider, pos, button) | |
| 106 | + | if self.orientation == 'vertical' then | |
| 107 | + | self.hotDistance = pos.y - slider:getY() | |
| 108 | + | else | |
| 109 | + | self.hotDistance = pos.x - slider:getX() | |
| 110 | + | end | |
| 111 | + | end | |
| 112 | + | ||
| 113 | + | -- public functions | |
| 114 | + | function UIScrollBar.create() | |
| 115 | + | local scrollbar = UIScrollBar.internalCreate() | |
| 116 | + | scrollbar:setFocusable(false) | |
| 117 | + | scrollbar.value = 0 | |
| 118 | + | scrollbar.minimum = -999999 | |
| 119 | + | scrollbar.maximum = 999999 | |
| 120 | + | scrollbar.step = 1 | |
| 121 | + | scrollbar.orientation = 'vertical' | |
| 122 | + | scrollbar.pixelsScroll = false | |
| 123 | + | scrollbar.showValue = false | |
| 124 | + | scrollbar.symbol = nil | |
| 125 | + | scrollbar.mouseScroll = true | |
| 126 | + | return scrollbar | |
| 127 | + | end | |
| 128 | + | ||
| 129 | + | function UIScrollBar:onSetup() | |
| 130 | + | self.setupDone = true | |
| 131 | + | local sliderButton = self:getChildById('sliderButton') | |
| 132 | + | g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300) | |
| 133 | + | g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:onIncrement() end, 300) | |
| 134 | + | g_mouse.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end) | |
| 135 | + | g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) end) | |
| 136 | + | ||
| 137 | + | updateSlider(self) | |
| 138 | + | end | |
| 139 | + | ||
| 140 | + | function UIScrollBar:onStyleApply(styleName, styleNode) | |
| 141 | + | for name,value in pairs(styleNode) do | |
| 142 | + | if name == 'maximum' then | |
| 143 | + | self:setMaximum(tonumber(value)) | |
| 144 | + | elseif name == 'minimum' then | |
| 145 | + | self:setMinimum(tonumber(value)) | |
| 146 | + | elseif name == 'step' then | |
| 147 | + | self:setStep(tonumber(value)) | |
| 148 | + | elseif name == 'orientation' then | |
| 149 | + | self:setOrientation(value) | |
| 150 | + | elseif name == 'value' then | |
| 151 | + | self:setValue(value) | |
| 152 | + | elseif name == 'pixels-scroll' then | |
| 153 | + | self.pixelsScroll = true | |
| 154 | + | elseif name == 'show-value' then | |
| 155 | + | self.showValue = true | |
| 156 | + | elseif name == 'symbol' then | |
| 157 | + | self.symbol = value | |
| 158 | + | elseif name == 'mouse-scroll' then | |
| 159 | + | self.mouseScroll = value | |
| 160 | + | end | |
| 161 | + | end | |
| 162 | + | end | |
| 163 | + | ||
| 164 | + | function UIScrollBar:onDecrement() | |
| 165 | + | if g_keyboard.isCtrlPressed() then | |
| 166 | + | self:decrement(self.value) | |
| 167 | + | elseif g_keyboard.isShiftPressed() then | |
| 168 | + | self:decrement(10) | |
| 169 | + | else | |
| 170 | + | self:decrement() | |
| 171 | + | end | |
| 172 | + | end | |
| 173 | + | ||
| 174 | + | function UIScrollBar:onIncrement() | |
| 175 | + | if g_keyboard.isCtrlPressed() then | |
| 176 | + | self:increment(self.maximum) | |
| 177 | + | elseif g_keyboard.isShiftPressed() then | |
| 178 | + | self:increment(10) | |
| 179 | + | else | |
| 180 | + | self:increment() | |
| 181 | + | end | |
| 182 | + | end | |
| 183 | + | ||
| 184 | + | function UIScrollBar:decrement(count) | |
| 185 | + | count = count or self.step | |
| 186 | + | self:setValue(self.value - count) | |
| 187 | + | end | |
| 188 | + | ||
| 189 | + | function UIScrollBar:increment(count) | |
| 190 | + | count = count or self.step | |
| 191 | + | self:setValue(self.value + count) | |
| 192 | + | end | |
| 193 | + | ||
| 194 | + | function UIScrollBar:setMaximum(maximum) | |
| 195 | + | if maximum == self.maximum then return end | |
| 196 | + | self.maximum = maximum | |
| 197 | + | if self.minimum > maximum then | |
| 198 | + | self:setMinimum(maximum) | |
| 199 | + | end | |
| 200 | + | if self.value > maximum then | |
| 201 | + | self:setValue(maximum) | |
| 202 | + | else | |
| 203 | + | updateSlider(self) | |
| 204 | + | end | |
| 205 | + | end | |
| 206 | + | ||
| 207 | + | function UIScrollBar:setMinimum(minimum) | |
| 208 | + | if minimum == self.minimum then return end | |
| 209 | + | self.minimum = minimum | |
| 210 | + | if self.maximum < minimum then | |
| 211 | + | self:setMaximum(minimum) | |
| 212 | + | end | |
| 213 | + | if self.value < minimum then | |
| 214 | + | self:setValue(minimum) | |
| 215 | + | else | |
| 216 | + | updateSlider(self) | |
| 217 | + | end | |
| 218 | + | end | |
| 219 | + | ||
| 220 | + | function UIScrollBar:setRange(minimum, maximum) | |
| 221 | + | self:setMinimum(minimum) | |
| 222 | + | self:setMaximum(maximum) | |
| 223 | + | end | |
| 224 | + | ||
| 225 | + | function UIScrollBar:setValue(value) | |
| 226 | + | value = math.max(math.min(value, self.maximum), self.minimum) | |
| 227 | + | if self.value == value then return end | |
| 228 | + | local delta = value - self.value | |
| 229 | + | self.value = value | |
| 230 | + | updateSlider(self) | |
| 231 | + | if self.setupDone then | |
| 232 | + | signalcall(self.onValueChange, self, math.round(value), delta) | |
| 233 | + | end | |
| 234 | + | end | |
| 235 | + | ||
| 236 | + | function UIScrollBar:setMouseScroll(scroll) | |
| 237 | + | self.mouseScroll = scroll | |
| 238 | + | end | |
| 239 | + | ||
| 240 | + | function UIScrollBar:setStep(step) | |
| 241 | + | self.step = step | |
| 242 | + | end | |
| 243 | + | ||
| 244 | + | function UIScrollBar:setOrientation(orientation) | |
| 245 | + | self.orientation = orientation | |
| 246 | + | end | |
| 247 | + | ||
| 248 | + | function UIScrollBar:setText(text) | |
| 249 | + | local valueLabel = self:getChildById('valueLabel') | |
| 250 | + | if valueLabel then | |
| 251 | + | valueLabel:setText(text) | |
| 252 | + | end | |
| 253 | + | end | |
| 254 | + | ||
| 255 | + | function UIScrollBar:onGeometryChange() | |
| 256 | + | updateSlider(self) | |
| 257 | + | end | |
| 258 | + | ||
| 259 | + | function UIScrollBar:onMouseWheel(mousePos, mouseWheel) | |
| 260 | + | if not self.mouseScroll or not self:isOn() or self.disableScroll then | |
| 261 | + | return false | |
| 262 | + | end | |
| 263 | + | if mouseWheel == MouseWheelUp then | |
| 264 | + | if self.orientation == 'vertical' then | |
| 265 | + | if self.value <= self.minimum then return false end | |
| 266 | + | self:decrement() | |
| 267 | + | else | |
| 268 | + | if self.value >= self.maximum then return false end | |
| 269 | + | self:increment() | |
| 270 | + | end | |
| 271 | + | else | |
| 272 | + | if self.orientation == 'vertical' then | |
| 273 | + | if self.value >= self.maximum then return false end | |
| 274 | + | self:increment() | |
| 275 | + | else | |
| 276 | + | if self.value <= self.minimum then return false end | |
| 277 | + | self:decrement() | |
| 278 | + | end | |
| 279 | + | end | |
| 280 | + | return true | |
| 281 | + | end | |
| 282 | + | ||
| 283 | + | function UIScrollBar:getMaximum() return self.maximum end | |
| 284 | + | function UIScrollBar:getMinimum() return self.minimum end | |
| 285 | + | function UIScrollBar:getValue() return math.round(self.value) end | |
| 286 | + | function UIScrollBar:getStep() return self.step end | |
| 287 | + | function UIScrollBar:getOrientation() return self.orientation end | |
| 288 | + | function UIScrollBar:getShowValue() return self.showValue end | |
| 289 | + | function UIScrollBar:getSymbol() return self.symbol end | |
| 290 | + | function UIScrollBar:getMouseScroll() return self.mouseScroll end |
| @@ -0,0 +1,191 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UISpinBox = extends(UITextEdit, "UISpinBox") | |
| 3 | + | ||
| 4 | + | function UISpinBox.create() | |
| 5 | + | local spinbox = UISpinBox.internalCreate() | |
| 6 | + | spinbox:setFocusable(false) | |
| 7 | + | spinbox:setValidCharacters('0123456789') | |
| 8 | + | spinbox.displayButtons = true | |
| 9 | + | spinbox.minimum = 0 | |
| 10 | + | spinbox.maximum = 1 | |
| 11 | + | spinbox.value = 0 | |
| 12 | + | spinbox.step = 1 | |
| 13 | + | spinbox.firstchange = true | |
| 14 | + | spinbox.mouseScroll = true | |
| 15 | + | spinbox:setText("1") | |
| 16 | + | spinbox:setValue(1) | |
| 17 | + | return spinbox | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | function UISpinBox:onSetup() | |
| 21 | + | g_mouse.bindAutoPress(self:getChildById('up'), function() self:up() end, 300) | |
| 22 | + | g_mouse.bindAutoPress(self:getChildById('down'), function() self:down() end, 300) | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | function UISpinBox:onMouseWheel(mousePos, direction) | |
| 26 | + | if not self.mouseScroll or self.disableScroll then | |
| 27 | + | return false | |
| 28 | + | end | |
| 29 | + | if direction == MouseWheelUp then | |
| 30 | + | self:up() | |
| 31 | + | elseif direction == MouseWheelDown then | |
| 32 | + | self:down() | |
| 33 | + | end | |
| 34 | + | return true | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | function UISpinBox:onKeyPress() | |
| 38 | + | if self.firstchange then | |
| 39 | + | self.firstchange = false | |
| 40 | + | self:setText('') | |
| 41 | + | end | |
| 42 | + | return false | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | function UISpinBox:onTextChange(text, oldText) | |
| 46 | + | if text:len() == 0 then | |
| 47 | + | self:setValue(self.minimum) | |
| 48 | + | return | |
| 49 | + | end | |
| 50 | + | ||
| 51 | + | local number = tonumber(text) | |
| 52 | + | if not number then | |
| 53 | + | self:setText(number) | |
| 54 | + | return | |
| 55 | + | else | |
| 56 | + | if number < self.minimum then | |
| 57 | + | self:setText(self.minimum) | |
| 58 | + | return | |
| 59 | + | elseif number > self.maximum then | |
| 60 | + | self:setText(self.maximum) | |
| 61 | + | return | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | self:setValue(number) | |
| 66 | + | end | |
| 67 | + | ||
| 68 | + | function UISpinBox:onValueChange(value) | |
| 69 | + | -- nothing to do | |
| 70 | + | end | |
| 71 | + | ||
| 72 | + | function UISpinBox:onFocusChange(focused) | |
| 73 | + | if not focused then | |
| 74 | + | if self:getText():len() == 0 then | |
| 75 | + | self:setText(self.minimum) | |
| 76 | + | end | |
| 77 | + | end | |
| 78 | + | end | |
| 79 | + | ||
| 80 | + | function UISpinBox:onStyleApply(styleName, styleNode) | |
| 81 | + | for name, value in pairs(styleNode) do | |
| 82 | + | if name == 'maximum' then | |
| 83 | + | self.maximum = value | |
| 84 | + | addEvent(function() self:setMaximum(value) end) | |
| 85 | + | elseif name == 'minimum' then | |
| 86 | + | self.minimum = value | |
| 87 | + | addEvent(function() self:setMinimum(value) end) | |
| 88 | + | elseif name == 'mouse-scroll' then | |
| 89 | + | addEvent(function() self:setMouseScroll(value) end) | |
| 90 | + | elseif name == 'buttons' then | |
| 91 | + | addEvent(function() | |
| 92 | + | if value then | |
| 93 | + | self:showButtons() | |
| 94 | + | else | |
| 95 | + | self:hideButtons() | |
| 96 | + | end | |
| 97 | + | end) | |
| 98 | + | end | |
| 99 | + | end | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | function UISpinBox:showButtons() | |
| 103 | + | self:getChildById('up'):show() | |
| 104 | + | self:getChildById('down'):show() | |
| 105 | + | self.displayButtons = true | |
| 106 | + | end | |
| 107 | + | ||
| 108 | + | function UISpinBox:hideButtons() | |
| 109 | + | self:getChildById('up'):hide() | |
| 110 | + | self:getChildById('down'):hide() | |
| 111 | + | self.displayButtons = false | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | function UISpinBox:up() | |
| 115 | + | self:setValue(self.value + self.step) | |
| 116 | + | end | |
| 117 | + | ||
| 118 | + | function UISpinBox:down() | |
| 119 | + | self:setValue(self.value - self.step) | |
| 120 | + | end | |
| 121 | + | ||
| 122 | + | function UISpinBox:setValue(value, dontSignal) | |
| 123 | + | if type(value) == "string" then | |
| 124 | + | value = tonumber(value) | |
| 125 | + | end | |
| 126 | + | value = value or 0 | |
| 127 | + | value = math.max(math.min(self.maximum, value), self.minimum) | |
| 128 | + | ||
| 129 | + | if value == self.value then return end | |
| 130 | + | ||
| 131 | + | self.value = value | |
| 132 | + | if self:getText():len() > 0 then | |
| 133 | + | self:setText(value) | |
| 134 | + | end | |
| 135 | + | ||
| 136 | + | local upButton = self:getChildById('up') | |
| 137 | + | local downButton = self:getChildById('down') | |
| 138 | + | if upButton then | |
| 139 | + | upButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.maximum) | |
| 140 | + | end | |
| 141 | + | if downButton then | |
| 142 | + | downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum) | |
| 143 | + | end | |
| 144 | + | ||
| 145 | + | if not dontSignal then | |
| 146 | + | signalcall(self.onValueChange, self, value) | |
| 147 | + | end | |
| 148 | + | end | |
| 149 | + | ||
| 150 | + | function UISpinBox:getValue() | |
| 151 | + | return self.value | |
| 152 | + | end | |
| 153 | + | ||
| 154 | + | function UISpinBox:setMinimum(minimum) | |
| 155 | + | minimum = minimum or -9223372036854775808 | |
| 156 | + | self.minimum = minimum | |
| 157 | + | if self.minimum > self.maximum then | |
| 158 | + | self.maximum = self.minimum | |
| 159 | + | end | |
| 160 | + | if self.value < minimum then | |
| 161 | + | self:setValue(minimum) | |
| 162 | + | end | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | function UISpinBox:getMinimum() | |
| 166 | + | return self.minimum | |
| 167 | + | end | |
| 168 | + | ||
| 169 | + | function UISpinBox:setMaximum(maximum) | |
| 170 | + | maximum = maximum or 9223372036854775807 | |
| 171 | + | self.maximum = maximum | |
| 172 | + | if self.value > maximum then | |
| 173 | + | self:setValue(maximum) | |
| 174 | + | end | |
| 175 | + | end | |
| 176 | + | ||
| 177 | + | function UISpinBox:getMaximum() | |
| 178 | + | return self.maximum | |
| 179 | + | end | |
| 180 | + | ||
| 181 | + | function UISpinBox:setStep(step) | |
| 182 | + | self.step = step or 1 | |
| 183 | + | end | |
| 184 | + | ||
| 185 | + | function UISpinBox:setMouseScroll(mouseScroll) | |
| 186 | + | self.mouseScroll = mouseScroll | |
| 187 | + | end | |
| 188 | + | ||
| 189 | + | function UISpinBox:getMouseScroll() | |
| 190 | + | return self.mouseScroll | |
| 191 | + | end |
| @@ -0,0 +1,85 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UISplitter = extends(UIWidget, "UISplitter") | |
| 3 | + | ||
| 4 | + | function UISplitter.create() | |
| 5 | + | local splitter = UISplitter.internalCreate() | |
| 6 | + | splitter:setFocusable(false) | |
| 7 | + | splitter.relativeMargin = 'bottom' | |
| 8 | + | return splitter | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | function UISplitter:onHoverChange(hovered) | |
| 12 | + | -- Check if margin can be changed | |
| 13 | + | local margin = (self.vertical and self:getMarginBottom() or self:getMarginRight()) | |
| 14 | + | if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then | |
| 15 | + | if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end | |
| 16 | + | if self:getWidth() > self:getHeight() then | |
| 17 | + | self.vertical = true | |
| 18 | + | self.cursortype = 'vertical' | |
| 19 | + | else | |
| 20 | + | self.vertical = false | |
| 21 | + | self.cursortype = 'horizontal' | |
| 22 | + | end | |
| 23 | + | self.hovering = true | |
| 24 | + | g_mouse.pushCursor(self.cursortype) | |
| 25 | + | if not self:isPressed() then | |
| 26 | + | g_effects.fadeIn(self) | |
| 27 | + | end | |
| 28 | + | else | |
| 29 | + | if not self:isPressed() and self.hovering then | |
| 30 | + | g_mouse.popCursor(self.cursortype) | |
| 31 | + | g_effects.fadeOut(self) | |
| 32 | + | self.hovering = false | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | function UISplitter:onMouseMove(mousePos, mouseMoved) | |
| 38 | + | if self:isPressed() then | |
| 39 | + | --local currentmargin, newmargin, delta | |
| 40 | + | if self.vertical then | |
| 41 | + | local delta = mousePos.y - self:getY() - self:getHeight()/2 | |
| 42 | + | local newMargin = self:canUpdateMargin(self:getMarginBottom() - delta) | |
| 43 | + | local currentMargin = self:getMarginBottom() | |
| 44 | + | if newMargin ~= currentMargin then | |
| 45 | + | self.newMargin = newMargin | |
| 46 | + | if not self.event or self.event:isExecuted() then | |
| 47 | + | self.event = addEvent(function() | |
| 48 | + | self:setMarginBottom(self.newMargin) | |
| 49 | + | end) | |
| 50 | + | end | |
| 51 | + | end | |
| 52 | + | else | |
| 53 | + | local delta = mousePos.x - self:getX() - self:getWidth()/2 | |
| 54 | + | local newMargin = self:canUpdateMargin(self:getMarginRight() - delta) | |
| 55 | + | local currentMargin = self:getMarginRight() | |
| 56 | + | if newMargin ~= currentMargin then | |
| 57 | + | self.newMargin = newMargin | |
| 58 | + | if not self.event or self.event:isExecuted() then | |
| 59 | + | self.event = addEvent(function() | |
| 60 | + | self:setMarginRight(self.newMargin) | |
| 61 | + | end) | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | end | |
| 65 | + | return true | |
| 66 | + | end | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | function UISplitter:onMouseRelease(mousePos, mouseButton) | |
| 70 | + | if not self:isHovered() then | |
| 71 | + | g_mouse.popCursor(self.cursortype) | |
| 72 | + | g_effects.fadeOut(self) | |
| 73 | + | self.hovering = false | |
| 74 | + | end | |
| 75 | + | end | |
| 76 | + | ||
| 77 | + | function UISplitter:onStyleApply(styleName, styleNode) | |
| 78 | + | if styleNode['relative-margin'] then | |
| 79 | + | self.relativeMargin = styleNode['relative-margin'] | |
| 80 | + | end | |
| 81 | + | end | |
| 82 | + | ||
| 83 | + | function UISplitter:canUpdateMargin(newMargin) | |
| 84 | + | return newMargin | |
| 85 | + | end |
| @@ -0,0 +1,163 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UITabBar = extends(UIWidget, "UITabBar") | |
| 3 | + | ||
| 4 | + | -- private functions | |
| 5 | + | local function onTabClick(tab) | |
| 6 | + | tab.tabBar:selectTab(tab) | |
| 7 | + | end | |
| 8 | + | ||
| 9 | + | local function onTabMouseRelease(tab, mousePos, mouseButton) | |
| 10 | + | if mouseButton == MouseRightButton and tab:containsPoint(mousePos) then | |
| 11 | + | signalcall(tab.tabBar.onTabLeftClick, tab.tabBar, tab) | |
| 12 | + | end | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | -- public functions | |
| 16 | + | function UITabBar.create() | |
| 17 | + | local tabbar = UITabBar.internalCreate() | |
| 18 | + | tabbar:setFocusable(false) | |
| 19 | + | tabbar.tabs = {} | |
| 20 | + | return tabbar | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function UITabBar:onSetup() | |
| 24 | + | self.buttonsPanel = self:getChildById('buttonsPanel') | |
| 25 | + | end | |
| 26 | + | ||
| 27 | + | function UITabBar:setContentWidget(widget) | |
| 28 | + | self.contentWidget = widget | |
| 29 | + | if #self.tabs > 0 then | |
| 30 | + | self.contentWidget:addChild(self.tabs[1].tabPanel) | |
| 31 | + | end | |
| 32 | + | end | |
| 33 | + | ||
| 34 | + | function UITabBar:addTab(text, panel, icon) | |
| 35 | + | if panel == nil then | |
| 36 | + | panel = g_ui.createWidget(self:getStyleName() .. 'Panel') | |
| 37 | + | panel:setId('tabPanel') | |
| 38 | + | end | |
| 39 | + | ||
| 40 | + | local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) | |
| 41 | + | ||
| 42 | + | panel.isTab = true | |
| 43 | + | tab.tabPanel = panel | |
| 44 | + | tab.tabBar = self | |
| 45 | + | tab:setId('tab') | |
| 46 | + | tab:setText(text) | |
| 47 | + | tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) | |
| 48 | + | tab.onClick = onTabClick | |
| 49 | + | tab.onMouseRelease = onTabMouseRelease | |
| 50 | + | tab.onDestroy = function() tab.tabPanel:destroy() end | |
| 51 | + | ||
| 52 | + | table.insert(self.tabs, tab) | |
| 53 | + | if #self.tabs == 1 then | |
| 54 | + | self:selectTab(tab) | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | local tabStyle = {} | |
| 58 | + | tabStyle['icon-source'] = icon | |
| 59 | + | tab:mergeStyle(tabStyle) | |
| 60 | + | ||
| 61 | + | return tab | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function UITabBar:addButton(text, func, icon) | |
| 65 | + | local button = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) | |
| 66 | + | button:setText(text) | |
| 67 | + | ||
| 68 | + | local style = {} | |
| 69 | + | style['icon-source'] = icon | |
| 70 | + | button:mergeStyle(style) | |
| 71 | + | ||
| 72 | + | button.onClick = func | |
| 73 | + | return button | |
| 74 | + | end | |
| 75 | + | ||
| 76 | + | function UITabBar:removeTab(tab) | |
| 77 | + | local index = table.find(self.tabs, tab) | |
| 78 | + | if index == nil then return end | |
| 79 | + | if self.currentTab == tab then | |
| 80 | + | self:selectPrevTab() | |
| 81 | + | end | |
| 82 | + | table.remove(self.tabs, index) | |
| 83 | + | tab:destroy() | |
| 84 | + | end | |
| 85 | + | ||
| 86 | + | function UITabBar:getTab(text) | |
| 87 | + | for k,tab in pairs(self.tabs) do | |
| 88 | + | if tab:getText():lower() == text:lower() then | |
| 89 | + | return tab | |
| 90 | + | end | |
| 91 | + | end | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | function UITabBar:selectTab(tab) | |
| 95 | + | if self.currentTab == tab then return end | |
| 96 | + | if self.contentWidget then | |
| 97 | + | local selectedWidget = self.contentWidget:getLastChild() | |
| 98 | + | if selectedWidget and selectedWidget.isTab then | |
| 99 | + | self.contentWidget:removeChild(selectedWidget) | |
| 100 | + | end | |
| 101 | + | self.contentWidget:addChild(tab.tabPanel) | |
| 102 | + | tab.tabPanel:fill('parent') | |
| 103 | + | end | |
| 104 | + | ||
| 105 | + | if self.currentTab then | |
| 106 | + | self.currentTab:setChecked(false) | |
| 107 | + | end | |
| 108 | + | signalcall(self.onTabChange, self, tab) | |
| 109 | + | self.currentTab = tab | |
| 110 | + | tab:setChecked(true) | |
| 111 | + | tab:setOn(false) | |
| 112 | + | ||
| 113 | + | local parent = tab:getParent() | |
| 114 | + | if parent then | |
| 115 | + | parent:focusChild(tab, MouseFocusReason) | |
| 116 | + | end | |
| 117 | + | end | |
| 118 | + | ||
| 119 | + | function UITabBar:selectNextTab() | |
| 120 | + | if self.currentTab == nil then return end | |
| 121 | + | local index = table.find(self.tabs, self.currentTab) | |
| 122 | + | if index == nil then return end | |
| 123 | + | local nextTab = self.tabs[index + 1] or self.tabs[1] | |
| 124 | + | if not nextTab then return end | |
| 125 | + | self:selectTab(nextTab) | |
| 126 | + | end | |
| 127 | + | ||
| 128 | + | function UITabBar:selectPrevTab() | |
| 129 | + | if self.currentTab == nil then return end | |
| 130 | + | local index = table.find(self.tabs, self.currentTab) | |
| 131 | + | if index == nil then return end | |
| 132 | + | local prevTab = self.tabs[index - 1] or self.tabs[#self.tabs] | |
| 133 | + | if not prevTab then return end | |
| 134 | + | self:selectTab(prevTab) | |
| 135 | + | end | |
| 136 | + | ||
| 137 | + | function UITabBar:getTabPanel(tab) | |
| 138 | + | return tab.tabPanel | |
| 139 | + | end | |
| 140 | + | ||
| 141 | + | function UITabBar:getCurrentTabPanel() | |
| 142 | + | if self.currentTab then | |
| 143 | + | return self.currentTab.tabPanel | |
| 144 | + | end | |
| 145 | + | end | |
| 146 | + | ||
| 147 | + | function UITabBar:getCurrentTab() | |
| 148 | + | return self.currentTab | |
| 149 | + | end | |
| 150 | + | ||
| 151 | + | function UITabBar:getTabs() | |
| 152 | + | return self.tabs | |
| 153 | + | end | |
| 154 | + | ||
| 155 | + | function UITabBar:getTabsPanel() | |
| 156 | + | return table.collect(self.tabs, function(_,tab) return tab.tabPanel end) | |
| 157 | + | end | |
| 158 | + | ||
| 159 | + | function UITabBar:clearTabs() | |
| 160 | + | while #self.tabs > 0 do | |
| 161 | + | self:removeTab(self.tabs[#self.tabs]) | |
| 162 | + | end | |
| 163 | + | end |
| @@ -0,0 +1,432 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | --[[ | |
| 3 | + | TODO: | |
| 4 | + | * Make table headers more robust. | |
| 5 | + | * Get dynamic row heights working with text wrapping. | |
| 6 | + | ]] | |
| 7 | + | ||
| 8 | + | TABLE_SORTING_ASC = 0 | |
| 9 | + | TABLE_SORTING_DESC = 1 | |
| 10 | + | ||
| 11 | + | UITable = extends(UIWidget, "UITable") | |
| 12 | + | ||
| 13 | + | -- Initialize default values | |
| 14 | + | function UITable.create() | |
| 15 | + | local table = UITable.internalCreate() | |
| 16 | + | table.headerRow = nil | |
| 17 | + | table.headerColumns = {} | |
| 18 | + | table.dataSpace = nil | |
| 19 | + | table.rows = {} | |
| 20 | + | table.rowBaseStyle = nil | |
| 21 | + | table.columns = {} | |
| 22 | + | table.columnWidth = {} | |
| 23 | + | table.columBaseStyle = nil | |
| 24 | + | table.headerRowBaseStyle = nil | |
| 25 | + | table.headerColumnBaseStyle = nil | |
| 26 | + | table.selectedRow = nil | |
| 27 | + | table.defaultColumnWidth = 80 | |
| 28 | + | table.sortColumn = -1 | |
| 29 | + | table.sortType = TABLE_SORTING_ASC | |
| 30 | + | table.autoSort = false | |
| 31 | + | ||
| 32 | + | return table | |
| 33 | + | end | |
| 34 | + | ||
| 35 | + | -- Clear table values | |
| 36 | + | function UITable:onDestroy() | |
| 37 | + | for _,row in pairs(self.rows) do | |
| 38 | + | row.onClick = nil | |
| 39 | + | end | |
| 40 | + | self.rows = {} | |
| 41 | + | self.columns = {} | |
| 42 | + | self.headerRow = nil | |
| 43 | + | self.headerColumns = {} | |
| 44 | + | self.columnWidth = {} | |
| 45 | + | self.selectedRow = nil | |
| 46 | + | ||
| 47 | + | if self.dataSpace then | |
| 48 | + | self.dataSpace:destroyChildren() | |
| 49 | + | self.dataSpace = nil | |
| 50 | + | end | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | -- Detect if a header is already defined | |
| 54 | + | function UITable:onSetup() | |
| 55 | + | local header = self:getChildById('header') | |
| 56 | + | if header then | |
| 57 | + | self:setHeader(header) | |
| 58 | + | end | |
| 59 | + | end | |
| 60 | + | ||
| 61 | + | -- Parse table related styles | |
| 62 | + | function UITable:onStyleApply(styleName, styleNode) | |
| 63 | + | for name, value in pairs(styleNode) do | |
| 64 | + | if value ~= false then | |
| 65 | + | if name == 'table-data' then | |
| 66 | + | addEvent(function() | |
| 67 | + | self:setTableData(self:getParent():getChildById(value)) | |
| 68 | + | end) | |
| 69 | + | elseif name == 'column-style' then | |
| 70 | + | addEvent(function() | |
| 71 | + | self:setColumnStyle(value) | |
| 72 | + | end) | |
| 73 | + | elseif name == 'row-style' then | |
| 74 | + | addEvent(function() | |
| 75 | + | self:setRowStyle(value) | |
| 76 | + | end) | |
| 77 | + | elseif name == 'header-column-style' then | |
| 78 | + | addEvent(function() | |
| 79 | + | self:setHeaderColumnStyle(value) | |
| 80 | + | end) | |
| 81 | + | elseif name == 'header-row-style' then | |
| 82 | + | addEvent(function() | |
| 83 | + | self:setHeaderRowStyle(value) | |
| 84 | + | end) | |
| 85 | + | end | |
| 86 | + | end | |
| 87 | + | end | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | function UITable:setColumnWidth(width) | |
| 91 | + | if self:hasHeader() then return end | |
| 92 | + | self.columnWidth = width | |
| 93 | + | end | |
| 94 | + | ||
| 95 | + | function UITable:setDefaultColumnWidth(width) | |
| 96 | + | self.defaultColumnWidth = width | |
| 97 | + | end | |
| 98 | + | ||
| 99 | + | -- Check if the table has a header | |
| 100 | + | function UITable:hasHeader() | |
| 101 | + | return self.headerRow ~= nil | |
| 102 | + | end | |
| 103 | + | ||
| 104 | + | -- Clear all rows | |
| 105 | + | function UITable:clearData() | |
| 106 | + | if not self.dataSpace then | |
| 107 | + | return | |
| 108 | + | end | |
| 109 | + | self.dataSpace:destroyChildren() | |
| 110 | + | self.selectedRow = nil | |
| 111 | + | self.columns = {} | |
| 112 | + | self.rows = {} | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | -- Set existing child as header | |
| 116 | + | function UITable:setHeader(headerWidget) | |
| 117 | + | self:removeHeader() | |
| 118 | + | ||
| 119 | + | if self.dataSpace then | |
| 120 | + | local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() | |
| 121 | + | self.dataSpace:applyStyle({ height = newHeight }) | |
| 122 | + | end | |
| 123 | + | ||
| 124 | + | self.headerColumns = {} | |
| 125 | + | self.columnWidth = {} | |
| 126 | + | for colId, column in pairs(headerWidget:getChildren()) do | |
| 127 | + | column.colId = colId | |
| 128 | + | column.table = self | |
| 129 | + | table.insert(self.columnWidth, column:getWidth()) | |
| 130 | + | table.insert(self.headerColumns, column) | |
| 131 | + | end | |
| 132 | + | ||
| 133 | + | self.headerRow = headerWidget | |
| 134 | + | end | |
| 135 | + | ||
| 136 | + | -- Create and add header from table data | |
| 137 | + | function UITable:addHeader(data) | |
| 138 | + | if not data or type(data) ~= 'table' then | |
| 139 | + | g_logger.error('UITable:addHeaderRow - table columns must be provided in a table') | |
| 140 | + | return | |
| 141 | + | end | |
| 142 | + | ||
| 143 | + | self:removeHeader() | |
| 144 | + | ||
| 145 | + | -- build header columns | |
| 146 | + | local columns = {} | |
| 147 | + | for colId, column in pairs(data) do | |
| 148 | + | local col = g_ui.createWidget(self.headerColumnBaseStyle) | |
| 149 | + | col.colId = colId | |
| 150 | + | col.table = self | |
| 151 | + | for type, value in pairs(column) do | |
| 152 | + | if type == 'width' then | |
| 153 | + | col:setWidth(value) | |
| 154 | + | elseif type == 'height' then | |
| 155 | + | col:setHeight(value) | |
| 156 | + | elseif type == 'text' then | |
| 157 | + | col:setText(value) | |
| 158 | + | elseif type == 'onClick' then | |
| 159 | + | col.onClick = value | |
| 160 | + | end | |
| 161 | + | end | |
| 162 | + | table.insert(columns, col) | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | -- create a new header | |
| 166 | + | local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) | |
| 167 | + | local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() | |
| 168 | + | self.dataSpace:applyStyle({ height = newHeight }) | |
| 169 | + | ||
| 170 | + | headerRow:setId('header') | |
| 171 | + | self.headerColumns = {} | |
| 172 | + | self.columnWidth = {} | |
| 173 | + | for _, column in pairs(columns) do | |
| 174 | + | headerRow:addChild(column) | |
| 175 | + | table.insert(self.columnWidth, column:getWidth()) | |
| 176 | + | table.insert(self.headerColumns, column) | |
| 177 | + | end | |
| 178 | + | ||
| 179 | + | self.headerRow = headerRow | |
| 180 | + | return headerRow | |
| 181 | + | end | |
| 182 | + | ||
| 183 | + | -- Remove header | |
| 184 | + | function UITable:removeHeader() | |
| 185 | + | if self:hasHeader() then | |
| 186 | + | if self.dataSpace then | |
| 187 | + | local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop() | |
| 188 | + | self.dataSpace:applyStyle({ height = newHeight }) | |
| 189 | + | end | |
| 190 | + | self.headerColumns = {} | |
| 191 | + | self.columnWidth = {} | |
| 192 | + | self.headerRow:destroy() | |
| 193 | + | self.headerRow = nil | |
| 194 | + | end | |
| 195 | + | end | |
| 196 | + | ||
| 197 | + | function UITable:addRow(data, height) | |
| 198 | + | if not self.dataSpace then | |
| 199 | + | g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.') | |
| 200 | + | return | |
| 201 | + | end | |
| 202 | + | if not data or type(data) ~= 'table' then | |
| 203 | + | g_logger.error('UITable:addRow - table columns must be provided in a table.') | |
| 204 | + | return | |
| 205 | + | end | |
| 206 | + | ||
| 207 | + | local row = g_ui.createWidget(self.rowBaseStyle) | |
| 208 | + | row.table = self | |
| 209 | + | if height then row:setHeight(height) end | |
| 210 | + | ||
| 211 | + | local rowId = #self.rows + 1 | |
| 212 | + | row.rowId = rowId | |
| 213 | + | row:setId('row'..rowId) | |
| 214 | + | row:updateBackgroundColor() | |
| 215 | + | ||
| 216 | + | self.columns[rowId] = {} | |
| 217 | + | for colId, column in pairs(data) do | |
| 218 | + | local col = g_ui.createWidget(self.columBaseStyle, row) | |
| 219 | + | if column.width then | |
| 220 | + | col:setWidth(column.width) | |
| 221 | + | else | |
| 222 | + | col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth) | |
| 223 | + | end | |
| 224 | + | if column.height then | |
| 225 | + | col:setHeight(column.height) | |
| 226 | + | end | |
| 227 | + | if column.text then | |
| 228 | + | col:setText(column.text) | |
| 229 | + | end | |
| 230 | + | if column.sortvalue then | |
| 231 | + | col.sortvalue = column.sortvalue | |
| 232 | + | else | |
| 233 | + | col.sortvalue = column.text or 0 | |
| 234 | + | end | |
| 235 | + | table.insert(self.columns[rowId], col) | |
| 236 | + | end | |
| 237 | + | ||
| 238 | + | self.dataSpace:addChild(row) | |
| 239 | + | table.insert(self.rows, row) | |
| 240 | + | ||
| 241 | + | if self.autoSort then | |
| 242 | + | self:sort() | |
| 243 | + | end | |
| 244 | + | ||
| 245 | + | return row | |
| 246 | + | end | |
| 247 | + | ||
| 248 | + | -- Update row indices and background color | |
| 249 | + | function UITable:updateRows() | |
| 250 | + | for rowId = 1, #self.rows do | |
| 251 | + | local row = self.rows[rowId] | |
| 252 | + | row.rowId = rowId | |
| 253 | + | row:setId('row'..rowId) | |
| 254 | + | row:updateBackgroundColor() | |
| 255 | + | end | |
| 256 | + | end | |
| 257 | + | ||
| 258 | + | -- Removes the given row widget from the table | |
| 259 | + | function UITable:removeRow(row) | |
| 260 | + | if self.selectedRow == row then | |
| 261 | + | self:selectRow(nil) | |
| 262 | + | end | |
| 263 | + | row.onClick = nil | |
| 264 | + | row.table = nil | |
| 265 | + | table.remove(self.columns, row.rowId) | |
| 266 | + | table.remove(self.rows, row.rowId) | |
| 267 | + | self.dataSpace:removeChild(row) | |
| 268 | + | self:updateRows() | |
| 269 | + | end | |
| 270 | + | ||
| 271 | + | function UITable:toggleSorting(enabled) | |
| 272 | + | self.autoSort = enabled | |
| 273 | + | end | |
| 274 | + | ||
| 275 | + | function UITable:setSorting(colId, sortType) | |
| 276 | + | self.headerColumns[colId]:focus() | |
| 277 | + | ||
| 278 | + | if sortType then | |
| 279 | + | self.sortType = sortType | |
| 280 | + | elseif self.sortColumn == colId then | |
| 281 | + | if self.sortType == TABLE_SORTING_ASC then | |
| 282 | + | self.sortType = TABLE_SORTING_DESC | |
| 283 | + | else | |
| 284 | + | self.sortType = TABLE_SORTING_ASC | |
| 285 | + | end | |
| 286 | + | else | |
| 287 | + | self.sortType = TABLE_SORTING_ASC | |
| 288 | + | end | |
| 289 | + | self.sortColumn = colId | |
| 290 | + | end | |
| 291 | + | ||
| 292 | + | function UITable:sort() | |
| 293 | + | if self.sortColumn <= 0 then | |
| 294 | + | return | |
| 295 | + | end | |
| 296 | + | ||
| 297 | + | if self.sortType == TABLE_SORTING_ASC then | |
| 298 | + | table.sort(self.rows, function(rowA, b) | |
| 299 | + | return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue | |
| 300 | + | end) | |
| 301 | + | else | |
| 302 | + | table.sort(self.rows, function(rowA, b) | |
| 303 | + | return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue | |
| 304 | + | end) | |
| 305 | + | end | |
| 306 | + | ||
| 307 | + | if self.dataSpace then | |
| 308 | + | for _, child in pairs(self.dataSpace:getChildren()) do | |
| 309 | + | self.dataSpace:removeChild(child) | |
| 310 | + | end | |
| 311 | + | end | |
| 312 | + | ||
| 313 | + | self:updateRows() | |
| 314 | + | self.columns = {} | |
| 315 | + | for _, row in pairs(self.rows) do | |
| 316 | + | if self.dataSpace then | |
| 317 | + | self.dataSpace:addChild(row) | |
| 318 | + | end | |
| 319 | + | ||
| 320 | + | self.columns[row.rowId] = {} | |
| 321 | + | for _, column in pairs(row:getChildren()) do | |
| 322 | + | table.insert(self.columns[row.rowId], column) | |
| 323 | + | end | |
| 324 | + | end | |
| 325 | + | end | |
| 326 | + | ||
| 327 | + | function UITable:selectRow(selectedRow) | |
| 328 | + | if selectedRow == self.selectedRow then return end | |
| 329 | + | ||
| 330 | + | local previousSelectedRow = self.selectedRow | |
| 331 | + | self.selectedRow = selectedRow | |
| 332 | + | ||
| 333 | + | if previousSelectedRow then | |
| 334 | + | previousSelectedRow:setChecked(false) | |
| 335 | + | end | |
| 336 | + | ||
| 337 | + | if selectedRow then | |
| 338 | + | selectedRow:setChecked(true) | |
| 339 | + | end | |
| 340 | + | ||
| 341 | + | signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow) | |
| 342 | + | end | |
| 343 | + | ||
| 344 | + | function UITable:setTableData(tableData) | |
| 345 | + | local headerHeight = 0 | |
| 346 | + | if self.headerRow then | |
| 347 | + | headerHeight = self.headerRow:getHeight() | |
| 348 | + | end | |
| 349 | + | ||
| 350 | + | self.dataSpace = tableData | |
| 351 | + | self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() }) | |
| 352 | + | end | |
| 353 | + | ||
| 354 | + | function UITable:setRowStyle(style, dontUpdate) | |
| 355 | + | self.rowBaseStyle = style | |
| 356 | + | ||
| 357 | + | if not dontUpdate then | |
| 358 | + | for _, row in pairs(self.rows) do | |
| 359 | + | row:setStyle(style) | |
| 360 | + | end | |
| 361 | + | end | |
| 362 | + | end | |
| 363 | + | ||
| 364 | + | function UITable:setColumnStyle(style, dontUpdate) | |
| 365 | + | self.columBaseStyle = style | |
| 366 | + | ||
| 367 | + | if not dontUpdate then | |
| 368 | + | for _, columns in pairs(self.columns) do | |
| 369 | + | for _, col in pairs(columns) do | |
| 370 | + | col:setStyle(style) | |
| 371 | + | end | |
| 372 | + | end | |
| 373 | + | end | |
| 374 | + | end | |
| 375 | + | ||
| 376 | + | function UITable:setHeaderRowStyle(style) | |
| 377 | + | self.headerRowBaseStyle = style | |
| 378 | + | if self.headerRow then | |
| 379 | + | self.headerRow:setStyle(style) | |
| 380 | + | end | |
| 381 | + | end | |
| 382 | + | ||
| 383 | + | function UITable:setHeaderColumnStyle(style) | |
| 384 | + | self.headerColumnBaseStyle = style | |
| 385 | + | for _, col in pairs(self.headerColumns) do | |
| 386 | + | col:setStyle(style) | |
| 387 | + | end | |
| 388 | + | end | |
| 389 | + | ||
| 390 | + | ||
| 391 | + | UITableRow = extends(UIWidget, "UITableRow") | |
| 392 | + | ||
| 393 | + | function UITableRow:onFocusChange(focused) | |
| 394 | + | if focused then | |
| 395 | + | if self.table then self.table:selectRow(self) end | |
| 396 | + | end | |
| 397 | + | end | |
| 398 | + | ||
| 399 | + | function UITableRow:onStyleApply(styleName, styleNode) | |
| 400 | + | for name,value in pairs(styleNode) do | |
| 401 | + | if name == 'even-background-color' then | |
| 402 | + | self.evenBackgroundColor = value | |
| 403 | + | elseif name == 'odd-background-color' then | |
| 404 | + | self.oddBackgroundColor = value | |
| 405 | + | end | |
| 406 | + | end | |
| 407 | + | end | |
| 408 | + | ||
| 409 | + | function UITableRow:updateBackgroundColor() | |
| 410 | + | self.backgroundColor = nil | |
| 411 | + | ||
| 412 | + | local isEven = (self.rowId % 2 == 0) | |
| 413 | + | if isEven and self.evenBackgroundColor then | |
| 414 | + | self.backgroundColor = self.evenBackgroundColor | |
| 415 | + | elseif not isEven and self.oddBackgroundColor then | |
| 416 | + | self.backgroundColor = self.oddBackgroundColor | |
| 417 | + | end | |
| 418 | + | ||
| 419 | + | if self.backgroundColor then | |
| 420 | + | self:mergeStyle({ ['background-color'] = self.backgroundColor }) | |
| 421 | + | end | |
| 422 | + | end | |
| 423 | + | ||
| 424 | + | ||
| 425 | + | UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn") | |
| 426 | + | ||
| 427 | + | function UITableHeaderColumn:onClick() | |
| 428 | + | if self.table then | |
| 429 | + | self.table:setSorting(self.colId) | |
| 430 | + | self.table:sort() | |
| 431 | + | end | |
| 432 | + | end |
| @@ -0,0 +1,78 @@ | |||
| 1 | + | function UITextEdit:onStyleApply(styleName, styleNode) | |
| 2 | + | for name,value in pairs(styleNode) do | |
| 3 | + | if name == 'vertical-scrollbar' then | |
| 4 | + | addEvent(function() | |
| 5 | + | self:setVerticalScrollBar(self:getParent():getChildById(value)) | |
| 6 | + | end) | |
| 7 | + | elseif name == 'horizontal-scrollbar' then | |
| 8 | + | addEvent(function() | |
| 9 | + | self:setHorizontalScrollBar(self:getParent():getChildById(value)) | |
| 10 | + | end) | |
| 11 | + | end | |
| 12 | + | end | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function UITextEdit:onMouseWheel(mousePos, mouseWheel) | |
| 16 | + | if self.verticalScrollBar and self:isMultiline() then | |
| 17 | + | if mouseWheel == MouseWheelUp then | |
| 18 | + | self.verticalScrollBar:decrement() | |
| 19 | + | else | |
| 20 | + | self.verticalScrollBar:increment() | |
| 21 | + | end | |
| 22 | + | return true | |
| 23 | + | elseif self.horizontalScrollBar then | |
| 24 | + | if mouseWheel == MouseWheelUp then | |
| 25 | + | self.horizontalScrollBar:increment() | |
| 26 | + | else | |
| 27 | + | self.horizontalScrollBar:decrement() | |
| 28 | + | end | |
| 29 | + | return true | |
| 30 | + | end | |
| 31 | + | end | |
| 32 | + | ||
| 33 | + | function UITextEdit:onTextAreaUpdate(virtualOffset, virtualSize, totalSize) | |
| 34 | + | self:updateScrollBars() | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | function UITextEdit:setVerticalScrollBar(scrollbar) | |
| 38 | + | self.verticalScrollBar = scrollbar | |
| 39 | + | self.verticalScrollBar.onValueChange = function(scrollbar, value) | |
| 40 | + | local virtualOffset = self:getTextVirtualOffset() | |
| 41 | + | virtualOffset.y = value | |
| 42 | + | self:setTextVirtualOffset(virtualOffset) | |
| 43 | + | end | |
| 44 | + | self:updateScrollBars() | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | function UITextEdit:setHorizontalScrollBar(scrollbar) | |
| 48 | + | self.horizontalScrollBar = scrollbar | |
| 49 | + | self.horizontalScrollBar.onValueChange = function(scrollbar, value) | |
| 50 | + | local virtualOffset = self:getTextVirtualOffset() | |
| 51 | + | virtualOffset.x = value | |
| 52 | + | self:setTextVirtualOffset(virtualOffset) | |
| 53 | + | end | |
| 54 | + | self:updateScrollBars() | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | function UITextEdit:updateScrollBars() | |
| 58 | + | local scrollSize = self:getTextTotalSize() | |
| 59 | + | local scrollWidth = math.max(scrollSize.width - self:getTextVirtualSize().width, 0) | |
| 60 | + | local scrollHeight = math.max(scrollSize.height - self:getTextVirtualSize().height, 0) | |
| 61 | + | ||
| 62 | + | local scrollbar = self.verticalScrollBar | |
| 63 | + | if scrollbar then | |
| 64 | + | scrollbar:setMinimum(0) | |
| 65 | + | scrollbar:setMaximum(scrollHeight) | |
| 66 | + | scrollbar:setValue(self:getTextVirtualOffset().y) | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | local scrollbar = self.horizontalScrollBar | |
| 70 | + | if scrollbar then | |
| 71 | + | scrollbar:setMinimum(0) | |
| 72 | + | scrollbar:setMaximum(scrollWidth) | |
| 73 | + | scrollbar:setValue(self:getTextVirtualOffset().x) | |
| 74 | + | end | |
| 75 | + | ||
| 76 | + | end | |
| 77 | + | ||
| 78 | + | -- todo: ontext change, focus to cursor |
| @@ -0,0 +1,21 @@ | |||
| 1 | + | -- @docclass UIWidget | |
| 2 | + | ||
| 3 | + | function UIWidget:setMargin(...) | |
| 4 | + | local params = {...} | |
| 5 | + | if #params == 1 then | |
| 6 | + | self:setMarginTop(params[1]) | |
| 7 | + | self:setMarginRight(params[1]) | |
| 8 | + | self:setMarginBottom(params[1]) | |
| 9 | + | self:setMarginLeft(params[1]) | |
| 10 | + | elseif #params == 2 then | |
| 11 | + | self:setMarginTop(params[1]) | |
| 12 | + | self:setMarginRight(params[2]) | |
| 13 | + | self:setMarginBottom(params[1]) | |
| 14 | + | self:setMarginLeft(params[2]) | |
| 15 | + | elseif #params == 4 then | |
| 16 | + | self:setMarginTop(params[1]) | |
| 17 | + | self:setMarginRight(params[2]) | |
| 18 | + | self:setMarginBottom(params[3]) | |
| 19 | + | self:setMarginLeft(params[4]) | |
| 20 | + | end | |
| 21 | + | end |
| @@ -0,0 +1,46 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIWindow = extends(UIWidget, "UIWindow") | |
| 3 | + | ||
| 4 | + | function UIWindow.create() | |
| 5 | + | local window = UIWindow.internalCreate() | |
| 6 | + | window:setTextAlign(AlignTopCenter) | |
| 7 | + | window:setDraggable(true) | |
| 8 | + | window:setAutoFocusPolicy(AutoFocusFirst) | |
| 9 | + | return window | |
| 10 | + | end | |
| 11 | + | ||
| 12 | + | function UIWindow:onKeyDown(keyCode, keyboardModifiers) | |
| 13 | + | if keyboardModifiers == KeyboardNoModifier then | |
| 14 | + | if keyCode == KeyEnter then | |
| 15 | + | signalcall(self.onEnter, self) | |
| 16 | + | elseif keyCode == KeyEscape then | |
| 17 | + | signalcall(self.onEscape, self) | |
| 18 | + | end | |
| 19 | + | end | |
| 20 | + | end | |
| 21 | + | ||
| 22 | + | function UIWindow:onFocusChange(focused) | |
| 23 | + | if focused then self:raise() end | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | function UIWindow:onDragEnter(mousePos) | |
| 27 | + | if self.static then | |
| 28 | + | return false | |
| 29 | + | end | |
| 30 | + | self:breakAnchors() | |
| 31 | + | self.movingReference = { x = mousePos.x - self:getX(), y = mousePos.y - self:getY() } | |
| 32 | + | return true | |
| 33 | + | end | |
| 34 | + | ||
| 35 | + | function UIWindow:onDragLeave(droppedWidget, mousePos) | |
| 36 | + | -- TODO: auto detect and reconnect anchors | |
| 37 | + | end | |
| 38 | + | ||
| 39 | + | function UIWindow:onDragMove(mousePos, mouseMoved) | |
| 40 | + | if self.static then | |
| 41 | + | return | |
| 42 | + | end | |
| 43 | + | local pos = { x = mousePos.x - self.movingReference.x, y = mousePos.y - self.movingReference.y } | |
| 44 | + | self:setPosition(pos) | |
| 45 | + | self:bindRectToParent() | |
| 46 | + | end |